Initial commit for experimental effort to parse 1.32 format

This commit is contained in:
Retera 2021-01-31 23:09:19 -05:00
parent 12ef127d0e
commit 600ec9536b
214 changed files with 20804 additions and 4491 deletions

View File

@ -1,6 +1,4 @@
buildscript {
repositories {
mavenLocal()
flatDir {
@ -9,13 +7,10 @@ buildscript {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://maven.nikr.net/" }
jcenter()
google()
}
dependencies {
}
}
allprojects {
@ -39,6 +34,7 @@ allprojects {
google()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://oss.sonatype.org/content/repositories/releases/" }
maven { url "https://maven.nikr.net/" }
maven { url 'https://jitpack.io' }
}
}
@ -54,6 +50,7 @@ project(":desktop") {
compile "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
compile "com.google.guava:guava:23.5-jre"
compile "net.nikr:dds:1.0.0"
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
compile files(fileTree(dir:'../jars', includes: ['*.jar']))
@ -71,6 +68,7 @@ project(":core") {
compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
compile "com.google.guava:guava:23.5-jre"
compile "net.nikr:dds:1.0.0"
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
compile files(fileTree(dir:'../jars', includes: ['*.jar']))

View File

@ -0,0 +1,41 @@
[DataSources]
Count=5
Type00=CASC
Path00="C:\Program Files\Warcraft III"
Prefixes00=war3.w3mod,war3.w3mod\_deprecated.w3mod,war3.w3mod\_locales\enus.w3mod
Type01=Folder
Path01="..\..\resources"
Type02=Folder
Path02="D:\Backups\Warsmash\Data"
Type03=Folder
Path03="D:\Games\Warcraft III Patch 1.22\Maps"
Type04=Folder
Path04="."
[Map]
//FilePath="CombatUnitTests.w3x"
//FilePath="PitchRoll.w3x"
FilePath="PeonStartingBase.w3x"
//FilePath="MyStromguarde.w3m"
//FilePath="ColdArrows.w3m"
//FilePath="DungeonGoldMine.w3m"
//FilePath="PlayerPeasants.w3m"
//FilePath="FireLord.w3x"
//FilePath="Maps\Campaign\NightElf03.w3m"
//FilePath="PhoenixAttack.w3x"
//FilePath="LightEnvironmentTest.w3x"
//FilePath="TorchLight2.w3x"
//FilePath="OrcAssault.w3x"
//FilePath="FrostyVsFarm.w3m"
//FilePath="ModelTest.w3x"
//FilePath="SpinningSample.w3x"
//FilePath="Maps\Campaign\Prologue02.w3m"
//FilePath="Pathing.w3x"
//FilePath="ItemFacing.w3x"
//FilePath=SomeParticleTests.w3x
//FilePath="PeonMiningMultiHall.w3x"
//FilePath="QuadtreeBugs.w3x"
//FilePath="test2.w3x"
//FilePath="FarseerHoldPositionTest.w3x"
//FilePath="Ramps.w3m"
//FilePath="V1\Farm.w3x"

View File

@ -14,7 +14,6 @@ import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.util.DataSourceFileHandle;
import com.etheller.warsmash.viewer5.Camera;
@ -27,6 +26,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {

View File

@ -6,6 +6,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.badlogic.gdx.ApplicationAdapter;
@ -26,6 +27,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.etheller.warsmash.datasources.CascDataSourceDescriptor;
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
@ -56,7 +58,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
private static final boolean ENABLE_AUDIO = true;
private static final boolean ENABLE_MUSIC = false;
private static final boolean ENABLE_MUSIC = true;
private DataSource codebase;
private War3MapViewer viewer;
private final Rectangle tempRect = new Rectangle();
@ -186,7 +188,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
// this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png"));
this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource,
this.solidGreenTexture = ImageUtils.getAnyExtensionTexture(this.viewer.dataSource,
"ReplaceableTextures\\TeamColor\\TeamColor06.blp");
Gdx.input.setInputProcessor(this);
@ -260,6 +262,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
dataSourcesList.add(new MpqDataSourceDescriptor(path));
break;
}
case "CASC": {
final String prefixes = dataSourcesConfig.getField("Prefixes" + (i < 10 ? "0" : "") + i);
dataSourcesList.add(new CascDataSourceDescriptor(path, Arrays.asList(prefixes.split(","))));
break;
}
default:
throw new RuntimeException("Unknown data source type: " + type);
}

View File

@ -27,7 +27,6 @@ import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.fdf.GameUI;
import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.util.DataSourceFileHandle;
import com.etheller.warsmash.util.ImageUtils;
@ -46,6 +45,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI;
@ -141,7 +141,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva
// this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png"));
this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource,
this.solidGreenTexture = ImageUtils.getAnyExtensionTexture(this.viewer.dataSource,
"ReplaceableTextures\\TeamColor\\TeamColor06.blp");
Gdx.input.setInputProcessor(this);

View File

@ -14,7 +14,6 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.ModelViewer;
@ -26,6 +25,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager;
import com.hiveworkshop.rms.parsers.mdlx.MdlxModel;
import com.hiveworkshop.rms.parsers.mdlx.util.MdxUtils;
public class WarsmashPreviewApplication extends ApplicationAdapter implements CanvasProvider {
private DataSource codebase;
@ -138,7 +139,7 @@ public class WarsmashPreviewApplication extends ApplicationAdapter implements Ca
this.mdxHandler, ".mdx", PathSolver.DEFAULT, filename));
final MdlxModel mdlxModel;
try (FileInputStream stream = new FileInputStream(filename)) {
mdlxModel = new MdlxModel(stream);
mdlxModel = MdxUtils.loadMdlx(stream);
mdx.load(mdlxModel);
mdx.ok = true;
// mdx.lateLoad();

View File

@ -1,7 +1,6 @@
package com.etheller.warsmash;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@ -17,8 +16,8 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.parsers.mdlx.Geoset;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset;
import com.hiveworkshop.rms.parsers.mdlx.MdlxModel;
public class WarsmashTestGameAttributes2 extends ApplicationAdapter {
private int arrayBuffer;
@ -36,8 +35,8 @@ public class WarsmashTestGameAttributes2 extends ApplicationAdapter {
.createDataSource();
final MdlxModel model;
try (InputStream modelStream = this.codebase.getResourceAsStream("Buildings\\Other\\TempArtB\\TempArtB.mdx")) {
model = new MdlxModel(modelStream);
try {
model = new MdlxModel(this.codebase.read("Buildings\\Other\\TempArtB\\TempArtB.mdx"));
}
catch (final IOException e) {
throw new RuntimeException(e);
@ -67,7 +66,7 @@ public class WarsmashTestGameAttributes2 extends ApplicationAdapter {
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
final Geoset geoset0 = model.getGeosets().get(0);
final MdlxGeoset geoset0 = model.getGeosets().get(0);
final float[] vertices = geoset0.getVertices();
final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN);

View File

@ -1,10 +1,7 @@
package com.etheller.warsmash;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
@ -32,13 +29,7 @@ public class WarsmashTestMyTextureGame extends ApplicationAdapter {
this.codebase = new CompoundDataSourceDescriptor(
Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder, currentFolder)).createDataSource();
try {
this.texture = ImageUtils
.getTexture(ImageIO.read(this.codebase.getResourceAsStream("Textures\\Dust3x.blp")));
}
catch (final IOException e) {
e.printStackTrace();
}
this.texture = ImageUtils.getAnyExtensionTexture(this.codebase, "Textures\\Dust3x.blp");
Gdx.gl.glClearColor(0, 0, 0, 1);
this.batch = new SpriteBatch();
this.batch.enableBlending();

View File

@ -0,0 +1,225 @@
package com.etheller.warsmash.datasources;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.hiveworkshop.blizzard.casc.io.WarcraftIIICASC;
import com.hiveworkshop.blizzard.casc.io.WarcraftIIICASC.FileSystem;
import com.hiveworkshop.json.JSONArray;
import com.hiveworkshop.json.JSONObject;
import com.hiveworkshop.json.JSONTokener;
public class CascDataSource implements DataSource {
private final String[] prefixes;
private WarcraftIIICASC warcraftIIICASC;
private FileSystem rootFileSystem;
private List<String> listFile;
private Map<String, String> fileAliases;
public CascDataSource(final String warcraft3InstallPath, final String[] prefixes) {
this.prefixes = prefixes;
for (int i = 0; i < (prefixes.length / 2); i++) {
final String temp = prefixes[i];
prefixes[i] = prefixes[prefixes.length - i - 1];
prefixes[prefixes.length - i - 1] = temp;
}
try {
this.warcraftIIICASC = new WarcraftIIICASC(Paths.get(warcraft3InstallPath), true);
this.rootFileSystem = this.warcraftIIICASC.getRootFileSystem();
this.listFile = this.rootFileSystem.enumerateFiles();
this.fileAliases = new HashMap<>();
if (this.has("filealiases.json")) {
try (InputStream stream = this.getResourceAsStream("filealiases.json")) {
stream.mark(4);
if ('\ufeff' != stream.read()) {
stream.reset(); // not the BOM marker
}
final JSONArray jsonObject = new JSONArray(new JSONTokener(stream));
for (int i = 0; i < jsonObject.length(); i++) {
final JSONObject alias = jsonObject.getJSONObject(i);
final String src = alias.getString("src");
final String dest = alias.getString("dest");
this.fileAliases.put(src.toLowerCase(Locale.US).replace('/', '\\'),
dest.toLowerCase(Locale.US).replace('/', '\\'));
if ((src.toLowerCase(Locale.US).contains(".blp")
|| dest.toLowerCase(Locale.US).contains(".blp"))
&& (!alias.has("assetType") || "Texture".equals(alias.getString("assetType")))) {
// This case: I saw a texture that resolves in game but was failing in our code
// here, because of this entry:
// {"src":"Units/Human/WarWagon/SiegeEngine.blp",
// "dest":"Textures/Steamtank.blp", "assetType": "Texture"},
// Our repo here checks BLP then DDS at a high-up application level thing, and
// the problem is that this entry is written using .BLP but we must be able to
// resolve .DDS when we go to look it up. The actual model is .BLP so maybe
// that's how the game does it, but my alias mapping is happening after the
// .BLP->.DDS dynamic fix, and not before.
this.fileAliases.put(src.toLowerCase(Locale.US).replace('/', '\\').replace(".blp", ".dds"),
dest.toLowerCase(Locale.US).replace('/', '\\').replace(".blp", ".dds"));
}
}
}
}
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public InputStream getResourceAsStream(String filepath) {
filepath = filepath.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\');
final String resolvedAlias = this.fileAliases.get(filepath);
if (resolvedAlias != null) {
filepath = resolvedAlias;
}
for (final String prefix : this.prefixes) {
final String tempFilepath = prefix + "\\" + filepath;
final InputStream stream = internalGetResourceAsStream(tempFilepath);
if (stream != null) {
return stream;
}
}
return internalGetResourceAsStream(filepath);
}
private InputStream internalGetResourceAsStream(final String tempFilepath) {
try {
if (this.rootFileSystem.isFile(tempFilepath) && this.rootFileSystem.isFileAvailable(tempFilepath)) {
final ByteBuffer buffer = this.rootFileSystem.readFileData(tempFilepath);
if (buffer.hasArray()) {
return new ByteArrayInputStream(buffer.array());
}
final byte[] data = new byte[buffer.remaining()];
buffer.clear();
buffer.get(data);
return new ByteArrayInputStream(data);
}
}
catch (final IOException e) {
throw new RuntimeException("CASC parser error for: " + tempFilepath, e);
}
return null;
}
@Override
public ByteBuffer read(String path) {
path = path.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\');
final String resolvedAlias = this.fileAliases.get(path);
if (resolvedAlias != null) {
path = resolvedAlias;
}
for (final String prefix : this.prefixes) {
final String tempFilepath = prefix + "\\" + path;
final ByteBuffer stream = internalRead(tempFilepath);
if (stream != null) {
return stream;
}
}
return internalRead(path);
}
private ByteBuffer internalRead(final String tempFilepath) {
try {
if (this.rootFileSystem.isFile(tempFilepath) && this.rootFileSystem.isFileAvailable(tempFilepath)) {
final ByteBuffer buffer = this.rootFileSystem.readFileData(tempFilepath);
return buffer;
}
}
catch (final IOException e) {
throw new RuntimeException("CASC parser error for: " + tempFilepath, e);
}
return null;
}
@Override
public File getFile(String filepath) {
filepath = filepath.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\');
final String resolvedAlias = this.fileAliases.get(filepath);
if (resolvedAlias != null) {
filepath = resolvedAlias;
}
for (final String prefix : this.prefixes) {
final String tempFilepath = prefix + "\\" + filepath;
final File file = internalGetFile(tempFilepath);
if (file != null) {
return file;
}
}
return internalGetFile(filepath);
}
private File internalGetFile(final String tempFilepath) {
try {
if (this.rootFileSystem.isFile(tempFilepath) && this.rootFileSystem.isFileAvailable(tempFilepath)) {
final ByteBuffer buffer = this.rootFileSystem.readFileData(tempFilepath);
String tmpdir = System.getProperty("java.io.tmpdir");
if (!tmpdir.endsWith(File.separator)) {
tmpdir += File.separator;
}
final String tempDir = tmpdir + "MatrixEaterExtract/";
final File tempProduct = new File(tempDir + tempFilepath.replace('\\', File.separatorChar));
tempProduct.delete();
tempProduct.getParentFile().mkdirs();
try (final FileChannel fileChannel = FileChannel.open(tempProduct.toPath(), StandardOpenOption.CREATE,
StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING)) {
fileChannel.write(buffer);
}
tempProduct.deleteOnExit();
return tempProduct;
}
}
catch (final IOException e) {
throw new RuntimeException("CASC parser error for: " + tempFilepath, e);
}
return null;
}
@Override
public boolean has(String filepath) {
filepath = filepath.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\');
final String resolvedAlias = this.fileAliases.get(filepath);
if (resolvedAlias != null) {
filepath = resolvedAlias;
}
for (final String prefix : this.prefixes) {
final String tempFilepath = prefix + "\\" + filepath;
try {
if (this.rootFileSystem.isFile(tempFilepath)) {
return true;
}
}
catch (final IOException e) {
throw new RuntimeException("CASC parser error for: " + tempFilepath, e);
}
}
try {
return this.rootFileSystem.isFile(filepath);
}
catch (final IOException e) {
throw new RuntimeException("CASC parser error for: " + filepath, e);
}
}
@Override
public Collection<String> getListfile() {
return this.listFile;
}
@Override
public void close() throws IOException {
this.warcraftIIICASC.close();
}
}

View File

@ -0,0 +1,96 @@
package com.etheller.warsmash.datasources;
import java.util.Collections;
import java.util.List;
public class CascDataSourceDescriptor implements DataSourceDescriptor {
/**
* Generated serial id
*/
private static final long serialVersionUID = 832549098549298820L;
private final String gameInstallPath;
private final List<String> prefixes;
public CascDataSourceDescriptor(final String gameInstallPath, final List<String> prefixes) {
this.gameInstallPath = gameInstallPath;
this.prefixes = prefixes;
}
@Override
public DataSource createDataSource() {
return new CascDataSource(this.gameInstallPath, this.prefixes.toArray(new String[this.prefixes.size()]));
}
@Override
public String getDisplayName() {
return "CASC: " + this.gameInstallPath;
}
public void addPrefix(final String prefix) {
this.prefixes.add(prefix);
}
public void deletePrefix(final int index) {
this.prefixes.remove(index);
}
public void movePrefixUp(final int index) {
if (index > 0) {
Collections.swap(this.prefixes, index, index - 1);
}
}
public void movePrefixDown(final int index) {
if (index < (this.prefixes.size() - 1)) {
Collections.swap(this.prefixes, index, index + 1);
}
}
public String getGameInstallPath() {
return this.gameInstallPath;
}
public List<String> getPrefixes() {
return this.prefixes;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + ((this.gameInstallPath == null) ? 0 : this.gameInstallPath.hashCode());
result = (prime * result) + ((this.prefixes == null) ? 0 : this.prefixes.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 CascDataSourceDescriptor other = (CascDataSourceDescriptor) obj;
if (this.gameInstallPath == null) {
if (other.gameInstallPath != null) {
return false;
}
}
else if (!this.gameInstallPath.equals(other.gameInstallPath)) {
return false;
}
if (this.prefixes == null) {
if (other.prefixes != null) {
return false;
}
}
else if (!this.prefixes.equals(other.prefixes)) {
return false;
}
return true;
}
}

View File

@ -3,12 +3,12 @@ package com.etheller.warsmash.datasources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
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;
@ -44,11 +44,23 @@ public class CompoundDataSource implements DataSource {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (filepath.toLowerCase(Locale.US).endsWith(".blp")) {
return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
return null;
}
if (filepath.toLowerCase(Locale.US).endsWith(".tif")) {
return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
@Override
public ByteBuffer read(final String path) throws IOException {
try {
for (int i = this.mpqList.size() - 1; i >= 0; i--) {
final DataSource mpq = this.mpqList.get(i);
final ByteBuffer buffer = mpq.read(path);
if (buffer != null) {
return buffer;
}
}
}
catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
@ -68,12 +80,6 @@ public class CompoundDataSource implements DataSource {
// 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;
}
@ -88,12 +94,6 @@ public class CompoundDataSource implements DataSource {
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;
}

View File

@ -3,6 +3,7 @@ package com.etheller.warsmash.datasources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
public interface DataSource {
@ -27,6 +28,8 @@ public interface DataSource {
*/
File getFile(String filepath) throws IOException;
ByteBuffer read(String path) throws IOException;
/**
* Returns true if the data source contains a valid entry for a particular file.
* Some data sources (MPQs) may contain files for which this returns true, even

View File

@ -3,8 +3,10 @@ package com.etheller.warsmash.datasources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashSet;
@ -34,7 +36,7 @@ public class FolderDataSource implements DataSource {
@Override
public InputStream getResourceAsStream(String filepath) throws IOException {
filepath=fixFilepath(filepath);
filepath = fixFilepath(filepath);
if (!has(filepath)) {
return null;
}
@ -43,16 +45,25 @@ public class FolderDataSource implements DataSource {
@Override
public File getFile(String filepath) throws IOException {
filepath=fixFilepath(filepath);
filepath = fixFilepath(filepath);
if (!has(filepath)) {
return null;
}
return new File(this.folderPath.toString() + File.separatorChar + filepath);
}
@Override
public ByteBuffer read(String path) throws IOException {
path = fixFilepath(path);
if (!has(path)) {
return null;
}
return ByteBuffer.wrap(Files.readAllBytes(Paths.get(path)));
}
@Override
public boolean has(String filepath) {
filepath=fixFilepath(filepath);
filepath = fixFilepath(filepath);
if ("".equals(filepath)) {
return false; // special case for folder data source, dont do this
}
@ -69,7 +80,8 @@ public class FolderDataSource implements DataSource {
public void close() {
}
private static String fixFilepath(String filepath) {
return filepath.replace('\\', File.separatorChar).replace('/', File.separatorChar);
private static String fixFilepath(final String filepath) {
return filepath.replace('\\', File.separatorChar).replace('/', File.separatorChar).replace(':',
File.separatorChar);
}
}

View File

@ -5,6 +5,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
@ -57,6 +58,28 @@ public class MpqDataSource implements DataSource {
return newInputStream;
}
@Override
public ByteBuffer read(final String path) throws IOException {
ArchivedFile file = null;
try {
file = this.archive.lookupHash2(new HashLookup(path));
}
catch (final MPQException exc) {
if (exc.getMessage().equals("lookup not found")) {
return null;
}
else {
throw new IOException(exc);
}
}
try (final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, file)) {
final long size = stream.size();
final ByteBuffer buffer = ByteBuffer.allocate((int) size);
stream.read(buffer);
return buffer;
}
}
@Override
public File getFile(final String filepath) throws IOException {
// TODO Auto-generated method stub

View File

@ -72,4 +72,7 @@ public class MpqDataSourceDescriptor implements DataSourceDescriptor {
return true;
}
public String getMpqFilePath() {
return this.mpqFilePath;
}
}

View File

@ -3,6 +3,7 @@ package com.etheller.warsmash.datasources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -21,6 +22,11 @@ public class SubdirDataSource implements DataSource {
return this.dataSource.getFile(this.subdir + filepath);
}
@Override
public ByteBuffer read(final String path) throws IOException {
return this.dataSource.read(this.subdir + path);
}
@Override
public InputStream getResourceAsStream(final String filepath) throws IOException {
return this.dataSource.getResourceAsStream(this.subdir + filepath);

View File

@ -41,7 +41,6 @@ import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame;
import com.etheller.warsmash.parsers.fdf.frames.StringFrame;
import com.etheller.warsmash.parsers.fdf.frames.TextureFrame;
import com.etheller.warsmash.parsers.fdf.frames.UIFrame;
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.util.ImageUtils;
@ -49,6 +48,7 @@ import com.etheller.warsmash.util.StringBundle;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode;
public final class GameUI extends AbstractUIFrame implements UIFrame {
private final DataSource dataSource;
@ -658,7 +658,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
Texture texture = this.pathToTexture.get(path);
if (texture == null) {
try {
texture = ImageUtils.getBLPTexture(this.dataSource, path);
texture = ImageUtils.getAnyExtensionTexture(this.dataSource, path);
this.pathToTexture.put(path, texture);
}
catch (final Exception exc) {

View File

@ -10,7 +10,8 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.hiveworkshop.rms.parsers.mdlx.MdlxModel;
import com.hiveworkshop.rms.parsers.mdlx.util.MdxUtils;
public class ModelExport {
@ -24,9 +25,9 @@ public class ModelExport {
try (InputStream modelStream = dataSource
.getResourceAsStream("UI\\Glues\\MainMenu\\MainMenu3D\\MainMenu3D.mdx")) {
final MdlxModel model = new MdlxModel(modelStream);
final MdlxModel model = new MdlxModel(dataSource.read("UI\\Glues\\MainMenu\\MainMenu3D\\MainMenu3D.mdx"));
try (FileOutputStream fos = new FileOutputStream(new File("C:\\Temp\\MainMenu3D.mdl"))) {
model.saveMdl(fos);
MdxUtils.saveMdl(model, fos);
}
}
catch (final IOException e) {

View File

@ -4,7 +4,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition;
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode;
public class FilterModeTextureFrame extends TextureFrame {
private int blendSrc;

View File

@ -6,13 +6,14 @@ import com.badlogic.gdx.utils.viewport.Viewport;
import com.etheller.warsmash.parsers.fdf.GameUI;
import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags;
import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition;
import com.etheller.warsmash.parsers.mdlx.Geoset;
import com.etheller.warsmash.parsers.mdlx.Layer;
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
import com.etheller.warsmash.parsers.mdlx.Material;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Texture;
import com.etheller.warsmash.viewer5.Scene;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode;
import com.hiveworkshop.rms.parsers.mdlx.MdlxMaterial;
import com.hiveworkshop.rms.parsers.mdlx.MdlxModel;
import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture;
import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture.WrapMode;
public class SmartBackdropFrame extends SpriteFrame {
private final boolean decorateFileNames;
@ -49,21 +50,21 @@ public class SmartBackdropFrame extends SpriteFrame {
final MdlxModel model = new MdlxModel();
final int edgeFileMaterialId = generateMaterial(model, this.edgeFileString, true);
final int backgroundMaterialId = generateMaterial(model, this.backgroundString, this.tileBackground);
final Geoset edgeGeoset = new Geoset();
final MdlxGeoset edgeGeoset = new MdlxGeoset();
final float[] edgeGeosetVertices = new float[32 * 4];
return model;
}
private int generateMaterial(final MdlxModel model, final String path, final boolean wrap) {
final Texture edgeFileReference = new Texture();
final MdlxTexture edgeFileReference = new MdlxTexture();
if (wrap) {
edgeFileReference.setFlags(0x2 | 0x1);
edgeFileReference.setWrapMode(WrapMode.REPEAT_BOTH);
}
edgeFileReference.setPath(path);
final int textureId = model.getTextures().size();
model.getTextures().add(edgeFileReference);
final Material edgeFileMaterial = new Material();
final Layer edgeFileMaterialLayer = new Layer();
final MdlxMaterial edgeFileMaterial = new MdlxMaterial();
final MdlxLayer edgeFileMaterialLayer = new MdlxLayer();
edgeFileMaterialLayer.setAlpha(1.0f);
edgeFileMaterialLayer.setFilterMode(FilterMode.BLEND);
edgeFileMaterialLayer.setTextureId(textureId);

View File

@ -1,99 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.util.HashMap;
import java.util.Map;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.War3ID;
/**
* A map from MDX animation tags to their equivalent MDL tokens, and the
* implementation objects.
*
* <p>
* Based on the works of Chananya Freiman.
*
*/
public enum AnimationMap {
// Layer
KMTF(MdlUtils.TOKEN_TEXTURE_ID, TimelineDescriptor.UINT32_TIMELINE),
KMTA(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE),
// TextureAnimation
KTAT(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KTAR(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE),
KTAS(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE),
// GeosetAnimation
KGAO(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE),
KGAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
// Light
KLAS(MdlUtils.TOKEN_ATTENUATION_START, TimelineDescriptor.FLOAT_TIMELINE),
KLAE(MdlUtils.TOKEN_ATTENUATION_END, TimelineDescriptor.FLOAT_TIMELINE),
KLAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
KLAI(MdlUtils.TOKEN_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE),
KLBI(MdlUtils.TOKEN_AMB_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE),
KLBC(MdlUtils.TOKEN_AMB_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
KLAV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
// Attachment
KATV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
// ParticleEmitter
KPEE(MdlUtils.TOKEN_EMISSION_RATE, TimelineDescriptor.FLOAT_TIMELINE),
KPEG(MdlUtils.TOKEN_GRAVITY, TimelineDescriptor.FLOAT_TIMELINE),
KPLN(MdlUtils.TOKEN_LONGITUDE, TimelineDescriptor.FLOAT_TIMELINE),
KPLT(MdlUtils.TOKEN_LATITUDE, TimelineDescriptor.FLOAT_TIMELINE),
KPEL(MdlUtils.TOKEN_LIFE_SPAN, TimelineDescriptor.FLOAT_TIMELINE),
KPES(MdlUtils.TOKEN_INIT_VELOCITY, TimelineDescriptor.FLOAT_TIMELINE),
KPEV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
// ParticleEmitter2
KP2S("Speed", TimelineDescriptor.FLOAT_TIMELINE),
KP2R("Variation", TimelineDescriptor.FLOAT_TIMELINE),
KP2L("Latitude", TimelineDescriptor.FLOAT_TIMELINE),
KP2G("Gravity", TimelineDescriptor.FLOAT_TIMELINE),
KP2E("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE),
KP2N("Length", TimelineDescriptor.FLOAT_TIMELINE),
KP2W("Width", TimelineDescriptor.FLOAT_TIMELINE),
KP2V("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
// RibbonEmitter
KRHA("HeightAbove", TimelineDescriptor.FLOAT_TIMELINE),
KRHB("HeightBelow", TimelineDescriptor.FLOAT_TIMELINE),
KRAL("Alpha", TimelineDescriptor.FLOAT_TIMELINE),
KRCO("Color", TimelineDescriptor.VECTOR3_TIMELINE),
KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE),
KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
// Camera
KCTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KTTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KCRL(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.UINT32_TIMELINE),
// GenericObject
KGTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KGRT(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE),
KGSC(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE);
private final String mdlToken;
private final TimelineDescriptor implementation;
private final War3ID war3id;
private AnimationMap(final String mdlToken, final TimelineDescriptor implementation) {
this.mdlToken = mdlToken;
this.implementation = implementation;
this.war3id = War3ID.fromString(this.name());
}
public String getMdlToken() {
return this.mdlToken;
}
public TimelineDescriptor getImplementation() {
return this.implementation;
}
public War3ID getWar3id() {
return this.war3id;
}
public static final Map<War3ID, AnimationMap> ID_TO_TAG = new HashMap<>();
static {
for (final AnimationMap tag : AnimationMap.values()) {
ID_TO_TAG.put(tag.getWar3id(), tag);
}
}
}

View File

@ -1,107 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Attachment extends GenericObject {
private String path = "";
private int attachmentId;
public Attachment() {
super(0x800);
}
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static final byte[] PATH_BYTES_HEAP = new byte[260];
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
super.readMdx(stream);
this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP);
this.attachmentId = stream.readInt();
this.readTimelines(stream, size - this.getByteLength());
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
super.writeMdx(stream);
final byte[] bytes = this.path.getBytes(ParseUtils.UTF8);
stream.write(bytes);
for (int i = 0; i < (260 - bytes.length); i++) {
stream.write((byte) 0);
}
stream.writeInt(this.attachmentId); // Used to be Int32 in JS
this.writeNonGenericAnimationChunks(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : super.readMdlGeneric(stream)) {
if (MdlUtils.TOKEN_ATTACHMENT_ID.equals(token)) {
this.attachmentId = stream.readInt();
}
else if (MdlUtils.TOKEN_PATH.equals(token)) {
this.path = stream.read();
}
else if (MdlUtils.TOKEN_VISIBILITY.equals(token)) {
this.readTimeline(stream, AnimationMap.KATV);
}
else {
throw new IOException("Unknown token in Attachment " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_ATTACHMENT, this.name);
this.writeGenericHeader(stream);
// flowtsohg asks in his JS:
// Is this needed? MDX supplies it, but MdlxConv does not use it.
// Retera:
// I tried to preserve it when it was shown, but omit it when it was omitted
// for MDL in Matrix Eater. Matrix Eater's MDL -> MDX is generating them
// and discarding what was read from the MDL. The Matrix Eater is notably
// buggy from a cursory read through, and would always omit AttachmentID 0
// in MDL output.
stream.writeAttrib(MdlUtils.TOKEN_ATTACHMENT_ID, this.attachmentId);
if ((this.path != null) && (this.path.length() > 0)) {
stream.writeStringAttrib(MdlUtils.TOKEN_PATH, this.path);
}
this.writeTimeline(stream, AnimationMap.KATV);
this.writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 268 + super.getByteLength();
}
public String getPath() {
return this.path;
}
public int getAttachmentId() {
return this.attachmentId;
}
}

View File

@ -1,95 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Bone extends GenericObject {
private int geosetId = -1;
private int geosetAnimationId = -1;
public Bone() {
super(0x100);
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
super.readMdx(stream);
this.geosetId = stream.readInt();
this.geosetAnimationId = stream.readInt();
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
super.writeMdx(stream);
stream.writeInt(this.geosetId);
stream.writeInt(this.geosetAnimationId);
}
@Override
public void readMdl(final MdlTokenInputStream stream) {
for (String token : super.readMdlGeneric(stream)) {
if (MdlUtils.TOKEN_GEOSETID.equals(token)) {
token = stream.read();
if (MdlUtils.TOKEN_MULTIPLE.equals(token)) {
this.geosetId = -1;
}
else {
this.geosetId = Integer.parseInt(token);
}
}
else if (MdlUtils.TOKEN_GEOSETANIMID.equals(token)) {
token = stream.read();
if (MdlUtils.TOKEN_NONE.equals(token)) {
this.geosetAnimationId = -1;
}
else {
this.geosetAnimationId = Integer.parseInt(token);
}
}
else {
throw new RuntimeException("Unknown token in Bone " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_BONE, this.name);
this.writeGenericHeader(stream);
if (this.geosetId == -1) {
stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, MdlUtils.TOKEN_MULTIPLE);
}
else {
stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId);
}
if (this.geosetAnimationId == -1) {
stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, MdlUtils.TOKEN_NONE);
}
else {
stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, this.geosetAnimationId);
}
this.writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 8 + super.getByteLength();
}
public int getGeosetAnimationId() {
return this.geosetAnimationId;
}
public int getGeosetId() {
return this.geosetId;
}
}

View File

@ -1,152 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Camera extends AnimatedObject {
protected String name;
private final float[] position;
private float fieldOfView;
private float farClippingPlane;
private float nearClippingPlane;
private final float[] targetPosition;
public Camera() {
this.position = new float[3];
this.targetPosition = new float[3];
}
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static final byte[] NAME_BYTES_HEAP = new byte[80];
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP);
ParseUtils.readFloatArray(stream, this.position);
this.fieldOfView = stream.readFloat();
this.farClippingPlane = stream.readFloat();
this.nearClippingPlane = stream.readFloat();
ParseUtils.readFloatArray(stream, this.targetPosition);
readTimelines(stream, size - 120);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
final byte[] bytes = this.name.getBytes(ParseUtils.UTF8);
stream.write(bytes);
for (int i = 0; i < (80 - bytes.length); i++) {
stream.write((byte) 0);
}
ParseUtils.writeFloatArray(stream, this.position);
stream.writeFloat(this.fieldOfView);
stream.writeFloat(this.farClippingPlane);
stream.writeFloat(this.nearClippingPlane);
ParseUtils.writeFloatArray(stream, this.targetPosition);
writeTimelines(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
this.name = stream.read();
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_POSITION:
stream.readFloatArray(this.position);
break;
case MdlUtils.TOKEN_TRANSLATION:
readTimeline(stream, AnimationMap.KCTR);
break;
case MdlUtils.TOKEN_ROTATION:
readTimeline(stream, AnimationMap.KCRL);
break;
case MdlUtils.TOKEN_FIELDOFVIEW:
this.fieldOfView = stream.readFloat();
break;
case MdlUtils.TOKEN_FARCLIP:
this.farClippingPlane = stream.readFloat();
break;
case MdlUtils.TOKEN_NEARCLIP:
this.nearClippingPlane = stream.readFloat();
break;
case MdlUtils.TOKEN_TARGET:
for (final String subToken : stream.readBlock()) {
switch (subToken) {
case MdlUtils.TOKEN_POSITION:
stream.readFloatArray(this.targetPosition);
break;
case MdlUtils.TOKEN_TRANSLATION:
readTimeline(stream, AnimationMap.KTTR);
break;
default:
throw new IllegalStateException(
"Unknown token in Camera " + this.name + "'s Target: " + subToken);
}
}
break;
default:
throw new IllegalStateException("Unknown token in Camera " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_CAMERA, this.name);
stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.position);
writeTimeline(stream, AnimationMap.KCTR);
writeTimeline(stream, AnimationMap.KCRL);
stream.writeFloatAttrib(MdlUtils.TOKEN_FIELDOFVIEW, this.fieldOfView);
stream.writeFloatAttrib(MdlUtils.TOKEN_FARCLIP, this.farClippingPlane);
stream.writeFloatAttrib(MdlUtils.TOKEN_NEARCLIP, this.nearClippingPlane);
stream.startBlock(MdlUtils.TOKEN_TARGET);
stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.targetPosition);
writeTimeline(stream, AnimationMap.KTTR);
stream.endBlock();
stream.endBlock();
}
@Override
public long getByteLength() {
return 120 + super.getByteLength();
}
public String getName() {
return this.name;
}
public float[] getPosition() {
return this.position;
}
public float getFieldOfView() {
return this.fieldOfView;
}
public float getFarClippingPlane() {
return this.farClippingPlane;
}
public float getNearClippingPlane() {
return this.nearClippingPlane;
}
public float[] getTargetPosition() {
return this.targetPosition;
}
}

View File

@ -1,5 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
public interface Chunk {
long getByteLength();
}

View File

@ -1,55 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Extent {
protected float boundsRadius = 0;
protected final float[] min = new float[3];
protected final float[] max = new float[3];
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
this.boundsRadius = stream.readFloat();
ParseUtils.readFloatArray(stream, this.min);
ParseUtils.readFloatArray(stream, this.max);
}
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeFloat(this.boundsRadius);
ParseUtils.writeFloatArray(stream, this.min);
ParseUtils.writeFloatArray(stream, this.max);
}
public void writeMdl(final MdlTokenOutputStream stream) {
if ((this.min[0] != 0) || (this.min[1] != 0) || (this.min[2] != 0)) {
stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MINIMUM_EXTENT, this.min);
}
if ((this.max[0] != 0) || (this.max[1] != 0) || (this.max[2] != 0)) {
stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MAXIMUM_EXTENT, this.max);
}
if (this.boundsRadius != 0) {
stream.writeFloatAttrib(MdlUtils.TOKEN_BOUNDSRADIUS, this.boundsRadius);
}
}
public void setBoundsRadius(final float boundsRadius) {
this.boundsRadius = boundsRadius;
}
public float getBoundsRadius() {
return this.boundsRadius;
}
public float[] getMin() {
return this.min;
}
public float[] getMax() {
return this.max;
}
}

View File

@ -1,350 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A generic object.
*
* The parent class for all objects that exist in the world, and may contain
* spatial animations. This includes bones, particle emitters, and many other
* things.
*
* Based on the works of Chananya Freiman.
*/
public abstract class GenericObject extends AnimatedObject implements Chunk {
protected String name;
private int objectId;
private int parentId;
protected int flags;
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static final byte[] NAME_BYTES_HEAP = new byte[80];
public GenericObject(final int flags) {
this.name = "";
this.objectId = -1;
this.parentId = -1;
this.flags = flags;
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP);
this.objectId = stream.readInt();
this.parentId = stream.readInt();
this.flags = stream.readInt(); // Used to be Int32 in JS
readTimelines(stream, size - 96);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getGenericByteLength());
final byte[] bytes = this.name.getBytes(ParseUtils.UTF8);
stream.write(bytes);
for (int i = 0; i < (80 - bytes.length); i++) {
stream.write((byte) 0);
}
stream.writeInt(this.objectId);
stream.writeInt(this.parentId);
stream.writeInt(this.flags); // UInt32 in ghostwolf JS, shouldn't matter for Java
for (final Timeline<?> timeline : eachTimeline(true)) {
timeline.writeMdx(stream);
}
}
public void writeNonGenericAnimationChunks(final LittleEndianDataOutputStream stream) throws IOException {
for (final Timeline<?> timeline : eachTimeline(false)) {
timeline.writeMdx(stream);
}
}
protected final Iterable<String> readMdlGeneric(final MdlTokenInputStream stream) {
this.name = stream.read();
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new WrappedMdlTokenIterator(GenericObject.this.readAnimatedBlock(stream), GenericObject.this,
stream);
}
};
}
public void writeGenericHeader(final MdlTokenOutputStream stream) {
stream.writeAttrib(MdlUtils.TOKEN_OBJECTID, this.objectId);
if (this.parentId != -1) {
stream.writeAttrib("Parent", this.parentId);
}
if ((this.flags & 0x40) != 0) {
stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Z);
}
if ((this.flags & 0x20) != 0) {
stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Y);
}
if ((this.flags & 0x10) != 0) {
stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_X);
}
if ((this.flags & 0x8) != 0) {
stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED);
}
if ((this.flags & 0x80) != 0) {
stream.writeFlag(MdlUtils.TOKEN_CAMERA_ANCHORED);
}
if ((this.flags & 0x2) != 0) {
stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_ROTATION + " }");
}
if ((this.flags & 0x1) != 0) {
stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_TRANSLATION + " }");
}
if ((this.flags & 0x4) != 0) {
stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_SCALING + " }");
}
}
public void writeGenericTimelines(final MdlTokenOutputStream stream) throws IOException {
this.writeTimeline(stream, AnimationMap.KGTR);
this.writeTimeline(stream, AnimationMap.KGRT);
this.writeTimeline(stream, AnimationMap.KGSC);
}
public Iterable<Timeline<?>> eachTimeline(final boolean generic) {
return new TimelineMaskingIterable(generic);
}
public long getGenericByteLength() {
long size = 96;
for (final Chunk animation : eachTimeline(true)) {
size += animation.getByteLength();
}
return size;
}
@Override
public long getByteLength() {
return 96 + super.getByteLength();
}
private final class TimelineMaskingIterable implements Iterable<Timeline<?>> {
private final boolean generic;
private TimelineMaskingIterable(final boolean generic) {
this.generic = generic;
}
@Override
public Iterator<Timeline<?>> iterator() {
return new TimelineMaskingIterator(this.generic, GenericObject.this.timelines);
}
}
private static final class TimelineMaskingIterator implements Iterator<Timeline<?>> {
private final boolean wantGeneric;
private final Iterator<Timeline<?>> delegate;
private boolean hasNext;
private Timeline<?> next;
public TimelineMaskingIterator(final boolean wantGeneric, final List<Timeline<?>> timelines) {
this.wantGeneric = wantGeneric;
this.delegate = timelines.iterator();
scanUntilNext();
}
private boolean isGeneric(final Timeline<?> timeline) {
final War3ID name = timeline.getName();
final boolean generic = AnimationMap.KGTR.getWar3id().equals(name)
|| AnimationMap.KGRT.getWar3id().equals(name) || AnimationMap.KGSC.getWar3id().equals(name);
return generic;
}
private void scanUntilNext() {
boolean hasNext = false;
if (hasNext = this.delegate.hasNext()) {
do {
this.next = this.delegate.next();
}
while ((isGeneric(this.next) != this.wantGeneric) && (hasNext = this.delegate.hasNext()));
}
if (!hasNext) {
this.next = null;
}
}
@Override
public boolean hasNext() {
return this.next != null;
}
@Override
public Timeline<?> next() {
final Timeline<?> last = this.next;
scanUntilNext();
return last;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove is not supported");
}
}
private static final class WrappedMdlTokenIterator implements Iterator<String> {
private final Iterator<String> delegate;
private final GenericObject updatingObject;
private final MdlTokenInputStream stream;
private String next;
private boolean hasLoaded = false;
public WrappedMdlTokenIterator(final Iterator<String> delegate, final GenericObject updatingObject,
final MdlTokenInputStream stream) {
this.delegate = delegate;
this.updatingObject = updatingObject;
this.stream = stream;
}
@Override
public boolean hasNext() {
if (this.delegate.hasNext()) {
this.next = read();
this.hasLoaded = true;
return this.next != null;
}
return false;
}
@Override
public String next() {
if (!this.hasLoaded) {
this.next = read();
}
this.hasLoaded = false;
return this.next;
}
private String read() {
String token;
InteriorParsing: do {
token = this.delegate.next();
if (token == null) {
break;
}
switch (token) {
case MdlUtils.TOKEN_OBJECTID:
this.updatingObject.objectId = Integer.parseInt(this.delegate.next());
token = null;
break;
case MdlUtils.TOKEN_PARENT:
this.updatingObject.parentId = Integer.parseInt(this.delegate.next());
token = null;
break;
case MdlUtils.TOKEN_BILLBOARDED_LOCK_Z:
this.updatingObject.flags |= 0x40;
token = null;
break;
case MdlUtils.TOKEN_BILLBOARDED_LOCK_Y:
this.updatingObject.flags |= 0x20;
token = null;
break;
case MdlUtils.TOKEN_BILLBOARDED_LOCK_X:
this.updatingObject.flags |= 0x10;
token = null;
break;
case MdlUtils.TOKEN_BILLBOARDED:
this.updatingObject.flags |= 0x8;
token = null;
break;
case MdlUtils.TOKEN_CAMERA_ANCHORED:
this.updatingObject.flags |= 0x80;
token = null;
break;
case MdlUtils.TOKEN_DONT_INHERIT:
for (final String subToken : this.stream.readBlock()) {
switch (subToken) {
case MdlUtils.TOKEN_ROTATION:
this.updatingObject.flags |= 0x2;
break;
case MdlUtils.TOKEN_TRANSLATION:
this.updatingObject.flags |= 0x1;
break;
case MdlUtils.TOKEN_SCALING:
this.updatingObject.flags |= 0x0;
break;
}
}
token = null;
break;
case MdlUtils.TOKEN_TRANSLATION:
try {
this.updatingObject.readTimeline(this.stream, AnimationMap.KGTR);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
token = null;
break;
case MdlUtils.TOKEN_ROTATION:
try {
this.updatingObject.readTimeline(this.stream, AnimationMap.KGRT);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
token = null;
break;
case MdlUtils.TOKEN_SCALING:
try {
this.updatingObject.readTimeline(this.stream, AnimationMap.KGSC);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
token = null;
break;
default:
break InteriorParsing;
}
}
while (this.delegate.hasNext());
return token;
}
}
public String getName() {
return this.name;
}
public int getObjectId() {
return this.objectId;
}
public int getParentId() {
return this.parentId;
}
public int getFlags() {
return this.flags;
}
}

View File

@ -1,428 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Geoset implements MdlxBlock, Chunk {
private static final War3ID VRTX = War3ID.fromString("VRTX");
private static final War3ID NRMS = War3ID.fromString("NRMS");
private static final War3ID PTYP = War3ID.fromString("PTYP");
private static final War3ID PCNT = War3ID.fromString("PCNT");
private static final War3ID PVTX = War3ID.fromString("PVTX");
private static final War3ID GNDX = War3ID.fromString("GNDX");
private static final War3ID MTGC = War3ID.fromString("MTGC");
private static final War3ID MATS = War3ID.fromString("MATS");
private static final War3ID UVAS = War3ID.fromString("UVAS");
private static final War3ID UVBS = War3ID.fromString("UVBS");
private float[] vertices;
private float[] normals;
private long[] faceTypeGroups; // unsigned int[]
private long[] faceGroups; // unsigned int[]
private int[] faces; // unsigned short[]
private short[] vertexGroups; // unsigned byte[]
private long[] matrixGroups; // unsigned int[]
private long[] matrixIndices; // unsigned int[]
private long materialId = 0;
private long selectionGroup = 0;
private long selectionFlags = 0;
private final Extent extent = new Extent();
private Extent[] sequenceExtents;
private float[][] uvSets;
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long mySize = ParseUtils.readUInt32(stream);
final int vrtx = stream.readInt(); // skip VRTX
this.vertices = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 3));
final int nrms = stream.readInt(); // skip NRMS
this.normals = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 3));
final int ptyp = stream.readInt(); // skip PTYP
this.faceTypeGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream));
stream.readInt(); // skip PCNT
this.faceGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream));
stream.readInt(); // skip PVTX
this.faces = ParseUtils.readUInt16Array(stream, (int) ParseUtils.readUInt32(stream));
stream.readInt(); // skip GNDX
this.vertexGroups = ParseUtils.readUInt8Array(stream, (int) ParseUtils.readUInt32(stream));
stream.readInt(); // skip MTGC
this.matrixGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream));
stream.readInt(); // skip MATS
this.matrixIndices = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream));
this.materialId = ParseUtils.readUInt32(stream);
this.selectionGroup = ParseUtils.readUInt32(stream);
this.selectionFlags = ParseUtils.readUInt32(stream);
this.extent.readMdx(stream);
final long numExtents = ParseUtils.readUInt32(stream);
this.sequenceExtents = new Extent[(int) numExtents];
for (int i = 0; i < numExtents; i++) {
final Extent extent = new Extent();
extent.readMdx(stream);
this.sequenceExtents[i] = extent;
}
stream.readInt(); // skip UVAS
final long numUVLayers = ParseUtils.readUInt32(stream);
this.uvSets = new float[(int) numUVLayers][];
for (int i = 0; i < numUVLayers; i++) {
stream.readInt(); // skip UVBS
this.uvSets[i] = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 2));
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.getByteLength());
ParseUtils.writeWar3ID(stream, VRTX);
ParseUtils.writeUInt32(stream, this.vertices.length / 3);
ParseUtils.writeFloatArray(stream, this.vertices);
ParseUtils.writeWar3ID(stream, NRMS);
ParseUtils.writeUInt32(stream, this.normals.length / 3);
ParseUtils.writeFloatArray(stream, this.normals);
ParseUtils.writeWar3ID(stream, PTYP);
ParseUtils.writeUInt32(stream, this.faceTypeGroups.length);
ParseUtils.writeUInt32Array(stream, this.faceTypeGroups);
ParseUtils.writeWar3ID(stream, PCNT);
ParseUtils.writeUInt32(stream, this.faceGroups.length);
ParseUtils.writeUInt32Array(stream, this.faceGroups);
ParseUtils.writeWar3ID(stream, PVTX);
ParseUtils.writeUInt32(stream, this.faces.length);
ParseUtils.writeUInt16Array(stream, this.faces);
ParseUtils.writeWar3ID(stream, GNDX);
ParseUtils.writeUInt32(stream, this.vertexGroups.length);
ParseUtils.writeUInt8Array(stream, this.vertexGroups);
ParseUtils.writeWar3ID(stream, MTGC);
ParseUtils.writeUInt32(stream, this.matrixGroups.length);
ParseUtils.writeUInt32Array(stream, this.matrixGroups);
ParseUtils.writeWar3ID(stream, MATS);
ParseUtils.writeUInt32(stream, this.matrixIndices.length);
ParseUtils.writeUInt32Array(stream, this.matrixIndices);
ParseUtils.writeUInt32(stream, this.materialId);
ParseUtils.writeUInt32(stream, this.selectionGroup);
ParseUtils.writeUInt32(stream, this.selectionFlags);
this.extent.writeMdx(stream);
ParseUtils.writeUInt32(stream, this.sequenceExtents.length);
for (final Extent sequenceExtent : this.sequenceExtents) {
sequenceExtent.writeMdx(stream);
}
ParseUtils.writeWar3ID(stream, UVAS);
ParseUtils.writeUInt32(stream, this.uvSets.length);
for (final float[] uvSet : this.uvSets) {
ParseUtils.writeWar3ID(stream, UVBS);
ParseUtils.writeUInt32(stream, uvSet.length / 2);
ParseUtils.writeFloatArray(stream, uvSet);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream) {
this.uvSets = new float[0][];
final List<Extent> sequenceExtents = new ArrayList<>();
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_VERTICES:
this.vertices = stream.readVectorArray(new float[stream.readInt() * 3], 3);
break;
case MdlUtils.TOKEN_NORMALS:
this.normals = stream.readVectorArray(new float[stream.readInt() * 3], 3);
break;
case MdlUtils.TOKEN_TVERTICES:
this.uvSets = Arrays.copyOf(this.uvSets, this.uvSets.length + 1);
this.uvSets[this.uvSets.length - 1] = stream.readVectorArray(new float[stream.readInt() * 2], 2);
break;
case MdlUtils.TOKEN_VERTEX_GROUP: {
// Vertex groups are stored in a block with no count, can't allocate the buffer
// yet.
final List<Short> vertexGroups = new ArrayList<>();
for (final String vertexGroup : stream.readBlock()) {
vertexGroups.add(Short.valueOf(vertexGroup));
}
this.vertexGroups = new short[vertexGroups.size()];
int i = 0;
for (final Short vertexGroup : vertexGroups) {
this.vertexGroups[i++] = vertexGroup.shortValue();
}
}
break;
case MdlUtils.TOKEN_FACES:
// For now hardcoded for triangles, until I see a model with something
// different.
this.faceTypeGroups = new long[] { 4L };
stream.readInt(); // number of groups
final int count = stream.readInt();
stream.read(); // {
stream.read(); // Triangles
stream.read(); // {
this.faces = stream.readUInt16Array(new int[count]);
this.faceGroups = new long[] { count };
stream.read(); // }
stream.read(); // }
break;
case MdlUtils.TOKEN_GROUPS: {
final List<Integer> indices = new ArrayList<>();
final List<Integer> groups = new ArrayList<>();
stream.readInt(); // matrices count
stream.readInt(); // total indices
// eslint-disable-next-line no-unused-vars
for (final String matrix : stream.readBlock()) {
int size = 0;
for (final String index : stream.readBlock()) {
indices.add(Integer.valueOf(index));
size += 1;
}
groups.add(size);
}
this.matrixIndices = new long[indices.size()];
int i = 0;
for (final Integer index : indices) {
this.matrixIndices[i++] = index.intValue();
}
this.matrixGroups = new long[groups.size()];
i = 0;
for (final Integer group : groups) {
this.matrixGroups[i++] = group.intValue();
}
}
break;
case MdlUtils.TOKEN_MINIMUM_EXTENT:
stream.readFloatArray(this.extent.min);
break;
case MdlUtils.TOKEN_MAXIMUM_EXTENT:
stream.readFloatArray(this.extent.max);
break;
case MdlUtils.TOKEN_BOUNDSRADIUS:
this.extent.boundsRadius = stream.readFloat();
break;
case MdlUtils.TOKEN_ANIM:
final Extent extent = new Extent();
for (final String subToken : stream.readBlock()) {
switch (subToken) {
case MdlUtils.TOKEN_MINIMUM_EXTENT:
stream.readFloatArray(extent.min);
break;
case MdlUtils.TOKEN_MAXIMUM_EXTENT:
stream.readFloatArray(extent.max);
break;
case MdlUtils.TOKEN_BOUNDSRADIUS:
extent.boundsRadius = stream.readFloat();
break;
}
}
sequenceExtents.add(extent);
break;
case MdlUtils.TOKEN_MATERIAL_ID:
this.materialId = stream.readInt();
break;
case MdlUtils.TOKEN_SELECTION_GROUP:
this.selectionGroup = stream.readInt();
break;
case MdlUtils.TOKEN_UNSELECTABLE:
this.selectionFlags = 4;
break;
default:
throw new RuntimeException("Unknown token in Geoset: " + token);
}
}
this.sequenceExtents = sequenceExtents.toArray(new Extent[sequenceExtents.size()]);
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) {
stream.startBlock(MdlUtils.TOKEN_GEOSET);
stream.writeVectorArray(MdlUtils.TOKEN_VERTICES, this.vertices, 3);
stream.writeVectorArray(MdlUtils.TOKEN_NORMALS, this.normals, 3);
for (final float[] uvSet : this.uvSets) {
stream.writeVectorArray(MdlUtils.TOKEN_TVERTICES, uvSet, 2);
}
stream.startBlock(MdlUtils.TOKEN_VERTEX_GROUP);
for (int i = 0; i < this.vertexGroups.length; i++) {
stream.writeLine(this.vertexGroups[i] + ",");
}
stream.endBlock();
// For now hardcoded for triangles, until I see a model with something
// different.
stream.startBlock(MdlUtils.TOKEN_FACES, 1, this.faces.length);
stream.startBlock(MdlUtils.TOKEN_TRIANGLES);
final StringBuffer facesBuffer = new StringBuffer();
for (final int faceValue : this.faces) {
if (facesBuffer.length() > 0) {
facesBuffer.append(", ");
}
facesBuffer.append(faceValue);
}
stream.writeLine("{ " + facesBuffer.toString() + " },");
stream.endBlock();
stream.endBlock();
stream.startBlock(MdlUtils.TOKEN_GROUPS, this.matrixGroups.length, this.matrixIndices.length);
int index = 0;
for (final long groupSize : this.matrixGroups) {
stream.writeLongSubArrayAttrib(MdlUtils.TOKEN_MATRICES, this.matrixIndices, index,
(int) (index + groupSize));
index += groupSize;
}
stream.endBlock();
this.extent.writeMdl(stream);
for (final Extent sequenceExtent : this.sequenceExtents) {
stream.startBlock(MdlUtils.TOKEN_ANIM);
sequenceExtent.writeMdl(stream);
stream.endBlock();
}
stream.writeAttribUInt32("MaterialID", this.materialId);
stream.writeAttribUInt32("SelectionGroup", this.selectionGroup);
if (this.selectionFlags == 4) {
stream.writeFlag("Unselectable");
}
stream.endBlock();
}
@Override
public long getByteLength() {
long size = 120 + (this.vertices.length * 4) + (this.normals.length * 4) + (this.faceTypeGroups.length * 4)
+ (this.faceGroups.length * 4) + (this.faces.length * 2) + this.vertexGroups.length
+ (this.matrixGroups.length * 4) + (this.matrixIndices.length * 4) + (this.sequenceExtents.length * 28);
for (final float[] uvSet : this.uvSets) {
size += 8 + (uvSet.length * 4);
}
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;
}
public void setVertices(final float[] vertices) {
this.vertices = vertices;
}
public void setNormals(final float[] normals) {
this.normals = normals;
}
public void setFaceTypeGroups(final long[] faceTypeGroups) {
this.faceTypeGroups = faceTypeGroups;
}
public void setFaceGroups(final long[] faceGroups) {
this.faceGroups = faceGroups;
}
public void setFaces(final int[] faces) {
this.faces = faces;
}
public void setVertexGroups(final short[] vertexGroups) {
this.vertexGroups = vertexGroups;
}
public void setMatrixGroups(final long[] matrixGroups) {
this.matrixGroups = matrixGroups;
}
public void setMatrixIndices(final long[] matrixIndices) {
this.matrixIndices = matrixIndices;
}
public void setMaterialId(final long materialId) {
this.materialId = materialId;
}
public void setSelectionGroup(final long selectionGroup) {
this.selectionGroup = selectionGroup;
}
public void setSelectionFlags(final long selectionFlags) {
this.selectionFlags = selectionFlags;
}
public void setSequenceExtents(final Extent[] sequenceExtents) {
this.sequenceExtents = sequenceExtents;
}
public void setUvSets(final float[][] uvSets) {
this.uvSets = uvSets;
}
}

View File

@ -1,114 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.util.Iterator;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class GeosetAnimation extends AnimatedObject {
private float alpha = 1;
private int flags = 0;
private final float[] color = { 1, 1, 1 };
private int geosetId = -1;
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
this.alpha = stream.readFloat();
this.flags = stream.readInt();// ParseUtils.readUInt32(stream);
ParseUtils.readFloatArray(stream, this.color);
this.geosetId = stream.readInt();
readTimelines(stream, size - 28);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
stream.writeFloat(this.alpha);
stream.writeInt(this.flags);// ParseUtils.writeUInt32(stream, this.flags);
ParseUtils.writeFloatArray(stream, this.color);
stream.writeInt(this.geosetId);
writeTimelines(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
final Iterator<String> blockIterator = readAnimatedBlock(stream);
while (blockIterator.hasNext()) {
final String token = blockIterator.next();
switch (token) {
case MdlUtils.TOKEN_DROP_SHADOW:
this.flags |= 0x1;
break;
case MdlUtils.TOKEN_STATIC_ALPHA:
this.alpha = stream.readFloat();
break;
case MdlUtils.TOKEN_ALPHA:
this.readTimeline(stream, AnimationMap.KGAO);
break;
case MdlUtils.TOKEN_STATIC_COLOR:
this.flags |= 0x2;
stream.readColor(this.color);
break;
case MdlUtils.TOKEN_COLOR:
this.flags |= 0x2;
readTimeline(stream, AnimationMap.KGAC);
break;
case MdlUtils.TOKEN_GEOSETID:
this.geosetId = stream.readInt();
break;
default:
throw new IllegalStateException("Unknown token in GeosetAnimation: " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startBlock(MdlUtils.TOKEN_GEOSETANIM);
if ((this.flags & 0x1) != 0) {
stream.writeFlag(MdlUtils.TOKEN_DROP_SHADOW);
}
if (!this.writeTimeline(stream, AnimationMap.KGAO)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha);
}
if ((this.flags & 0x2) != 0) {
if (!this.writeTimeline(stream, AnimationMap.KGAC)
&& ((this.color[0] != 0) || (this.color[1] != 0) || (this.color[2] != 0))) {
stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR + " ", this.color); // TODO why the space?
}
}
if (this.geosetId != -1) { // TODO Retera added -1 check here, why wasn't it there before in JS???
stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId);
}
stream.endBlock();
}
@Override
public long getByteLength() {
return 28 + super.getByteLength();
}
public float[] getColor() {
return this.color;
}
public float getAlpha() {
return this.alpha;
}
public int getGeosetId() {
return this.geosetId;
}
}

View File

@ -1,29 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
public class Helper extends GenericObject {
public Helper() {
super(0x0); // NOTE: ghostwolf JS didn't pass the 0x1 flag????
// ANOTHER NOTE: setting the 0x1 flag causes other fan programs to spam error
// messages
}
@Override
public void readMdl(final MdlTokenInputStream stream) {
for (final String token : readMdlGeneric(stream)) {
throw new IllegalStateException("Unknown token in Helper: " + token);
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_HELPER, this.name);
writeGenericHeader(stream);
writeGenericTimelines(stream);
stream.endBlock();
}
}

View File

@ -1,11 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
public enum InterpolationType {
DONT_INTERP, LINEAR, BEZIER, HERMITE;
public static final InterpolationType[] VALUES = values();
public boolean tangential() {
return ordinal() > 1;
}
}

View File

@ -1,244 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.util.Iterator;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Layer extends AnimatedObject {
// 0: none
// 1: transparent
// 2: blend
// 3: additive
// 4: add alpha
// 5: modulate
// 6: modulate 2x
public static enum FilterMode {
NONE("None"),
TRANSPARENT("Transparent"),
BLEND("Blend"),
ADDITIVE("Additive"),
ADDALPHA("AddAlpha"),
MODULATE("Modulate"),
MODULATE2X("Modulate2x");
String mdlText;
FilterMode(final String str) {
this.mdlText = str;
}
public String getMdlText() {
return this.mdlText;
}
public static FilterMode fromId(final int id) {
return values()[id];
}
public static int nameToId(final String name) {
for (final FilterMode mode : values()) {
if (mode.getMdlText().equals(name)) {
return mode.ordinal();
}
}
return -1;
}
@Override
public String toString() {
return getMdlText();
}
}
private FilterMode filterMode;
private int flags = 0;
private int textureId = -1;
private int textureAnimationId = -1;
private long coordId = 0;
private float alpha = 1;
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
this.filterMode = FilterMode.fromId((int) ParseUtils.readUInt32(stream));
this.flags = stream.readInt(); // UInt32 in JS
this.textureId = stream.readInt();
this.textureAnimationId = stream.readInt();
this.coordId = ParseUtils.readUInt32(stream);
this.alpha = stream.readFloat();
readTimelines(stream, size - 28);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
ParseUtils.writeUInt32(stream, this.filterMode.ordinal());
ParseUtils.writeUInt32(stream, this.flags);
stream.writeInt(this.textureId);
stream.writeInt(this.textureAnimationId);
ParseUtils.writeUInt32(stream, this.coordId);
stream.writeFloat(this.alpha);
writeTimelines(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
final Iterator<String> iterator = readAnimatedBlock(stream);
while (iterator.hasNext()) {
final String token = iterator.next();
switch (token) {
case MdlUtils.TOKEN_FILTER_MODE:
this.filterMode = FilterMode.fromId(FilterMode.nameToId(stream.read()));
break;
case MdlUtils.TOKEN_UNSHADED:
this.flags |= 0x1;
break;
case MdlUtils.TOKEN_SPHERE_ENV_MAP:
this.flags |= 0x2;
break;
case MdlUtils.TOKEN_TWO_SIDED:
this.flags |= 0x10;
break;
case MdlUtils.TOKEN_UNFOGGED:
this.flags |= 0x20;
break;
case MdlUtils.TOKEN_NO_DEPTH_TEST:
this.flags |= 0x40;
break;
case MdlUtils.TOKEN_NO_DEPTH_SET:
this.flags |= 0x100;
break;
case MdlUtils.TOKEN_STATIC_TEXTURE_ID:
this.textureId = stream.readInt();
break;
case MdlUtils.TOKEN_TEXTURE_ID:
readTimeline(stream, AnimationMap.KMTF);
break;
case MdlUtils.TOKEN_TVERTEX_ANIM_ID:
this.textureAnimationId = stream.readInt();
break;
case MdlUtils.TOKEN_COORD_ID:
this.coordId = stream.readInt();
break;
case MdlUtils.TOKEN_STATIC_ALPHA:
this.alpha = stream.readFloat();
break;
case MdlUtils.TOKEN_ALPHA:
readTimeline(stream, AnimationMap.KMTA);
break;
default:
throw new IllegalStateException("Unknown token in Layer: " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startBlock(MdlUtils.TOKEN_LAYER);
stream.writeAttrib(MdlUtils.TOKEN_FILTER_MODE, this.filterMode.getMdlText());
if ((this.flags & 0x1) != 0) {
stream.writeFlag(MdlUtils.TOKEN_UNSHADED);
}
if ((this.flags & 0x2) != 0) {
stream.writeFlag(MdlUtils.TOKEN_SPHERE_ENV_MAP);
}
if ((this.flags & 0x10) != 0) {
stream.writeFlag(MdlUtils.TOKEN_TWO_SIDED);
}
if ((this.flags & 0x20) != 0) {
stream.writeFlag(MdlUtils.TOKEN_UNFOGGED);
}
if ((this.flags & 0x40) != 0) {
stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_TEST);
}
if ((this.flags & 0x100) != 0) {
stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_SET);
}
if (!writeTimeline(stream, AnimationMap.KMTF)) {
stream.writeAttrib(MdlUtils.TOKEN_STATIC_TEXTURE_ID, this.textureId);
}
if (this.textureAnimationId != -1) {
stream.writeAttrib(MdlUtils.TOKEN_TVERTEX_ANIM_ID, this.textureAnimationId);
}
if (this.coordId != 0) {
stream.writeAttribUInt32(MdlUtils.TOKEN_COORD_ID, this.coordId);
}
if (!writeTimeline(stream, AnimationMap.KMTA) && (this.alpha != 1)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha);
}
stream.endBlock();
}
@Override
public long getByteLength() {
return 28 + super.getByteLength();
}
public FilterMode getFilterMode() {
return this.filterMode;
}
public int getFlags() {
return this.flags;
}
public int getTextureId() {
return this.textureId;
}
public int getTextureAnimationId() {
return this.textureAnimationId;
}
public long getCoordId() {
return this.coordId;
}
public float getAlpha() {
return this.alpha;
}
public void setFilterMode(final FilterMode filterMode) {
this.filterMode = filterMode;
}
public void setFlags(final int flags) {
this.flags = flags;
}
public void setTextureId(final int textureId) {
this.textureId = textureId;
}
public void setTextureAnimationId(final int textureAnimationId) {
this.textureAnimationId = textureAnimationId;
}
public void setCoordId(final long coordId) {
this.coordId = coordId;
}
public void setAlpha(final float alpha) {
this.alpha = alpha;
}
}

View File

@ -1,141 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Material implements MdlxBlock, Chunk {
private static final War3ID LAYS = War3ID.fromString("LAYS");
private int priorityPlane = 0;
private int flags;
private final List<Layer> layers = new ArrayList<>();
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
ParseUtils.readUInt32(stream); // Don't care about the size
this.priorityPlane = stream.readInt();// ParseUtils.readUInt32(stream);
this.flags = stream.readInt();// ParseUtils.readUInt32(stream);
stream.readInt(); // skip LAYS
final long layerCount = ParseUtils.readUInt32(stream);
for (int i = 0; i < layerCount; i++) {
final Layer layer = new Layer();
layer.readMdx(stream);
this.layers.add(layer);
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
stream.writeInt(this.priorityPlane); // was UInt32 in JS, but I *really* thought I used -1 in a past model
stream.writeInt(this.flags); // UInt32 in JS
ParseUtils.writeWar3ID(stream, LAYS);
ParseUtils.writeUInt32(stream, this.layers.size());
for (final Layer layer : this.layers) {
layer.writeMdx(stream);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_CONSTANT_COLOR:
this.flags |= 0x1;
break;
case MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z:
this.flags |= 0x8;
break;
case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z:
this.flags |= 0x10;
break;
case MdlUtils.TOKEN_FULL_RESOLUTION:
this.flags |= 0x20;
break;
case MdlUtils.TOKEN_PRIORITY_PLANE:
this.priorityPlane = stream.readInt();
break;
case MdlUtils.TOKEN_LAYER: {
final Layer layer = new Layer();
layer.readMdl(stream);
this.layers.add(layer);
break;
}
default:
throw new IllegalStateException("Unknown token in Material: " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startBlock(MdlUtils.TOKEN_MATERIAL);
if ((this.flags & 0x1) != 0) {
stream.writeFlag(MdlUtils.TOKEN_CONSTANT_COLOR);
}
if ((this.flags & 0x8) != 0) {
stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z);
}
if ((this.flags & 0x10) != 0) {
stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_FAR_Z);
}
if ((this.flags & 0x20) != 0) {
stream.writeFlag(MdlUtils.TOKEN_FULL_RESOLUTION);
}
if (this.priorityPlane != 0) {
stream.writeAttrib(MdlUtils.TOKEN_PRIORITY_PLANE, this.priorityPlane);
}
for (final Layer layer : this.layers) {
layer.writeMdl(stream);
}
stream.endBlock();
}
@Override
public long getByteLength() {
long size = 20;
for (final Layer layer : this.layers) {
size += layer.getByteLength();
}
return size;
}
public int getPriorityPlane() {
return this.priorityPlane;
}
public void setPriorityPlane(final int priorityPlane) {
this.priorityPlane = priorityPlane;
}
public int getFlags() {
return this.flags;
}
public void setFlags(final int flags) {
this.flags = flags;
}
public List<Layer> getLayers() {
return this.layers;
}
}

View File

@ -1,35 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
public interface MdlTokenInputStream {
String read();
long readUInt32();
int readInt();
float readFloat();
void readIntArray(long[] values);
float[] readFloatArray(float[] values); // is this same as read keyframe???
void readKeyframe(float[] values);
float[] readVectorArray(float[] array, int vectorLength);
int[] readUInt16Array(int[] values);
short[] readUInt8Array(short[] values);
String peek();
// needs crazy generator function behavior that I can call this multiple times
// and it allocates a new iterator that is changing the same underlying
// stream position, and needs nesting of blocks within blocks
// (see crazy transcribed generator in GenericObject, only makes good sense
// in JS)
Iterable<String> readBlock();
void readColor(float[] color);
}

View File

@ -1,59 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
public interface MdlTokenOutputStream {
void writeKeyframe(String prefix, long uInt32Value);
void writeKeyframe(String prefix, float floatValue);
void writeKeyframe(String prefix, float[] floatArrayValues);
void indent();
void unindent();
void startObjectBlock(String name, String objectName);
void startBlock(String name, int blockSize);
void startBlock(String name);
void writeFlag(String token);
void writeFlagUInt32(long flag);
void writeAttrib(String string, int globalSequenceId);
void writeAttribUInt32(String attribName, long uInt);
void writeAttrib(String string, String value);
void writeFloatAttrib(String attribName, float value);
// if this matches writeAttrib(String,String),
// then remove it
void writeStringAttrib(String attribName, String value);
void writeFloatArrayAttrib(String attribName, float[] floatArray);
void writeLongSubArrayAttrib(String attribName, long[] array, int startIndexInclusive, int endIndexExclusive);
void writeFloatArray(float[] floatArray);
void writeVectorArray(String token, float[] vectors, int vectorLength);
void endBlock();
void endBlockComma();
void writeLine(String string);
void startBlock(String tokenFaces, int sizeNumberProbably, int length);
void writeColor(String tokenStaticColor, float[] color);
void writeArrayAttrib(String tokenAlpha, short[] uint8Array);
void writeArrayAttrib(String tokenAlpha, int[] uint16Array);
void writeArrayAttrib(String tokenAlpha, long[] uint32Array);
}

View File

@ -1,16 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public interface MdlxBlock {
void readMdx(final LittleEndianDataInputStream stream) throws IOException;
void writeMdx(final LittleEndianDataOutputStream stream) throws IOException;
void readMdl(final MdlTokenInputStream stream) throws IOException;
void writeMdl(final MdlTokenOutputStream stream) throws IOException;
}

View File

@ -1,125 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import com.etheller.warsmash.util.Descriptor;
public interface MdlxBlockDescriptor<E> extends Descriptor<E> {
public static final MdlxBlockDescriptor<Attachment> ATTACHMENT = new MdlxBlockDescriptor<Attachment>() {
@Override
public Attachment create() {
return new Attachment();
}
};
public static final MdlxBlockDescriptor<Bone> BONE = new MdlxBlockDescriptor<Bone>() {
@Override
public Bone create() {
return new Bone();
}
};
public static final MdlxBlockDescriptor<Camera> CAMERA = new MdlxBlockDescriptor<Camera>() {
@Override
public Camera create() {
return new Camera();
}
};
public static final MdlxBlockDescriptor<CollisionShape> COLLISION_SHAPE = new MdlxBlockDescriptor<CollisionShape>() {
@Override
public CollisionShape create() {
return new CollisionShape();
}
};
public static final MdlxBlockDescriptor<EventObject> EVENT_OBJECT = new MdlxBlockDescriptor<EventObject>() {
@Override
public EventObject create() {
return new EventObject();
}
};
public static final MdlxBlockDescriptor<Geoset> GEOSET = new MdlxBlockDescriptor<Geoset>() {
@Override
public Geoset create() {
return new Geoset();
}
};
public static final MdlxBlockDescriptor<GeosetAnimation> GEOSET_ANIMATION = new MdlxBlockDescriptor<GeosetAnimation>() {
@Override
public GeosetAnimation create() {
return new GeosetAnimation();
}
};
public static final MdlxBlockDescriptor<Helper> HELPER = new MdlxBlockDescriptor<Helper>() {
@Override
public Helper create() {
return new Helper();
}
};
public static final MdlxBlockDescriptor<Light> LIGHT = new MdlxBlockDescriptor<Light>() {
@Override
public Light create() {
return new Light();
}
};
public static final MdlxBlockDescriptor<Layer> LAYER = new MdlxBlockDescriptor<Layer>() {
@Override
public Layer create() {
return new Layer();
}
};
public static final MdlxBlockDescriptor<Material> MATERIAL = new MdlxBlockDescriptor<Material>() {
@Override
public Material create() {
return new Material();
}
};
public static final MdlxBlockDescriptor<ParticleEmitter> PARTICLE_EMITTER = new MdlxBlockDescriptor<ParticleEmitter>() {
@Override
public ParticleEmitter create() {
return new ParticleEmitter();
}
};
public static final MdlxBlockDescriptor<ParticleEmitter2> PARTICLE_EMITTER2 = new MdlxBlockDescriptor<ParticleEmitter2>() {
@Override
public ParticleEmitter2 create() {
return new ParticleEmitter2();
}
};
public static final MdlxBlockDescriptor<RibbonEmitter> RIBBON_EMITTER = new MdlxBlockDescriptor<RibbonEmitter>() {
@Override
public RibbonEmitter create() {
return new RibbonEmitter();
}
};
public static final MdlxBlockDescriptor<Sequence> SEQUENCE = new MdlxBlockDescriptor<Sequence>() {
@Override
public Sequence create() {
return new Sequence();
}
};
public static final MdlxBlockDescriptor<Texture> TEXTURE = new MdlxBlockDescriptor<Texture>() {
@Override
public Texture create() {
return new Texture();
}
};
public static final MdlxBlockDescriptor<TextureAnimation> TEXTURE_ANIMATION = new MdlxBlockDescriptor<TextureAnimation>() {
@Override
public TextureAnimation create() {
return new TextureAnimation();
}
};
}

View File

@ -1,744 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.utils.StreamUtils;
import com.etheller.warsmash.parsers.mdlx.mdl.GhostwolfTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.mdl.GhostwolfTokenOutputStream;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A Warcraft 3 model. Supports loading from and saving to both the binary MDX
* and text MDL file formats.
*/
public class MdlxModel {
// Below, these can't call a function on a string to make their value
// because
// switch/case statements require the value to be compile-time defined in
// order
// to be legal, and it appears to only allow basic binary operators for
// that.
// I would love a clearer way to just type 'MDLX' in a character constant in
// Java for this
private static final int MDLX = ('M' << 24) | ('D' << 16) | ('L' << 8) | ('X');// War3ID.fromString("MDLX").getValue();
private static final int VERS = ('V' << 24) | ('E' << 16) | ('R' << 8) | ('S');// War3ID.fromString("VERS").getValue();
private static final int MODL = ('M' << 24) | ('O' << 16) | ('D' << 8) | ('L');// War3ID.fromString("MODL").getValue();
private static final int SEQS = ('S' << 24) | ('E' << 16) | ('Q' << 8) | ('S');// War3ID.fromString("SEQS").getValue();
private static final int GLBS = ('G' << 24) | ('L' << 16) | ('B' << 8) | ('S');// War3ID.fromString("GLBS").getValue();
private static final int MTLS = ('M' << 24) | ('T' << 16) | ('L' << 8) | ('S');// War3ID.fromString("MTLS").getValue();
private static final int TEXS = ('T' << 24) | ('E' << 16) | ('X' << 8) | ('S');// War3ID.fromString("TEXS").getValue();
private static final int TXAN = ('T' << 24) | ('X' << 16) | ('A' << 8) | ('N');// War3ID.fromString("TXAN").getValue();
private static final int GEOS = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('S');// War3ID.fromString("GEOS").getValue();
private static final int GEOA = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('A');// War3ID.fromString("GEOA").getValue();
private static final int BONE = ('B' << 24) | ('O' << 16) | ('N' << 8) | ('E');// War3ID.fromString("BONE").getValue();
private static final int LITE = ('L' << 24) | ('I' << 16) | ('T' << 8) | ('E');// War3ID.fromString("LITE").getValue();
private static final int HELP = ('H' << 24) | ('E' << 16) | ('L' << 8) | ('P');// War3ID.fromString("HELP").getValue();
private static final int ATCH = ('A' << 24) | ('T' << 16) | ('C' << 8) | ('H');// War3ID.fromString("ATCH").getValue();
private static final int PIVT = ('P' << 24) | ('I' << 16) | ('V' << 8) | ('T');// War3ID.fromString("PIVT").getValue();
private static final int PREM = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('M');// War3ID.fromString("PREM").getValue();
private static final int PRE2 = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('2');// War3ID.fromString("PRE2").getValue();
private static final int RIBB = ('R' << 24) | ('I' << 16) | ('B' << 8) | ('B');// War3ID.fromString("RIBB").getValue();
private static final int CAMS = ('C' << 24) | ('A' << 16) | ('M' << 8) | ('S');// War3ID.fromString("CAMS").getValue();
private static final int EVTS = ('E' << 24) | ('V' << 16) | ('T' << 8) | ('S');// War3ID.fromString("EVTS").getValue();
private static final int CLID = ('C' << 24) | ('L' << 16) | ('I' << 8) | ('D');// War3ID.fromString("CLID").getValue();
private int version = 800;
private String name = "";
/**
* (Comment copied from Ghostwolf JS) To the best of my knowledge, this should
* always be left empty. This is probably a leftover from the Warcraft 3 beta.
* (WS game note: No, I never saw any animation files in the RoC 2001-2002 Beta.
* So it must be from the Alpha)
*
* @member {string}
*/
private String animationFile = "";
private final Extent extent = new Extent();
private long blendTime = 0;
private final List<Sequence> sequences = new ArrayList<Sequence>();
private final List<Long /* UInt32 */> globalSequences = new ArrayList<>();
private final List<Material> materials = new ArrayList<>();
private final List<Texture> textures = new ArrayList<>();
private final List<TextureAnimation> textureAnimations = new ArrayList<>();
private final List<Geoset> geosets = new ArrayList<>();
private final List<GeosetAnimation> geosetAnimations = new ArrayList<>();
private final List<Bone> bones = new ArrayList<>();
private final List<Light> lights = new ArrayList<>();
private final List<Helper> helpers = new ArrayList<>();
private final List<Attachment> attachments = new ArrayList<>();
private final List<float[]> pivotPoints = new ArrayList<>();
private final List<ParticleEmitter> particleEmitters = new ArrayList<>();
private final List<ParticleEmitter2> particleEmitters2 = new ArrayList<>();
private final List<RibbonEmitter> ribbonEmitters = new ArrayList<>();
private final List<Camera> cameras = new ArrayList<>();
private final List<EventObject> eventObjects = new ArrayList<>();
private final List<CollisionShape> collisionShapes = new ArrayList<>();
private final List<UnknownChunk> unknownChunks = new ArrayList<>();
public MdlxModel(final InputStream buffer) throws IOException {
if (buffer != null) {
// In ghostwolf JS, this function called load()
// which decided whether the buffer was an MDL.
loadMdx(buffer);
}
}
public MdlxModel() {
}
public void loadMdx(final InputStream buffer) throws IOException {
final LittleEndianDataInputStream stream = new LittleEndianDataInputStream(buffer);
if (Integer.reverseBytes(stream.readInt()) != MDLX) {
throw new IllegalStateException("WrongMagicNumber");
}
while (stream.available() > 0) {
final int tag = Integer.reverseBytes(stream.readInt());
final long size = ParseUtils.readUInt32(stream);
switch (tag) {
case VERS:
loadVersionChunk(stream);
break;
case MODL:
loadModelChunk(stream);
break;
case SEQS:
loadStaticObjects(this.sequences, MdlxBlockDescriptor.SEQUENCE, stream, size / 132);
break;
case GLBS:
loadGlobalSequenceChunk(stream, size);
break;
case MTLS:
loadDynamicObjects(this.materials, MdlxBlockDescriptor.MATERIAL, stream, size);
break;
case TEXS:
loadStaticObjects(this.textures, MdlxBlockDescriptor.TEXTURE, stream, size / 268);
break;
case TXAN:
loadDynamicObjects(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, stream, size);
break;
case GEOS:
loadDynamicObjects(this.geosets, MdlxBlockDescriptor.GEOSET, stream, size);
break;
case GEOA:
loadDynamicObjects(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream, size);
break;
case BONE:
loadDynamicObjects(this.bones, MdlxBlockDescriptor.BONE, stream, size);
break;
case LITE:
loadDynamicObjects(this.lights, MdlxBlockDescriptor.LIGHT, stream, size);
break;
case HELP:
loadDynamicObjects(this.helpers, MdlxBlockDescriptor.HELPER, stream, size);
break;
case ATCH:
loadDynamicObjects(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream, size);
break;
case PIVT:
loadPivotPointChunk(stream, size);
break;
case PREM:
loadDynamicObjects(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream, size);
break;
case PRE2:
loadDynamicObjects(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream, size);
break;
case RIBB:
loadDynamicObjects(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream, size);
break;
case CAMS:
loadDynamicObjects(this.cameras, MdlxBlockDescriptor.CAMERA, stream, size);
break;
case EVTS:
loadDynamicObjects(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream, size);
break;
case CLID:
loadDynamicObjects(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream, size);
break;
default:
this.unknownChunks.add(new UnknownChunk(stream, size, new War3ID(tag)));
}
}
}
private void loadVersionChunk(final LittleEndianDataInputStream stream) throws IOException {
this.version = (int) ParseUtils.readUInt32(stream);
}
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static final byte[] NAME_BYTES_HEAP = new byte[80];
private static final byte[] ANIMATION_FILE_BYTES_HEAP = new byte[260];
private void loadModelChunk(final LittleEndianDataInputStream stream) throws IOException {
this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP);
this.animationFile = ParseUtils.readString(stream, ANIMATION_FILE_BYTES_HEAP);
this.extent.readMdx(stream);
this.blendTime = ParseUtils.readUInt32(stream);
}
private <E extends MdlxBlock> void loadStaticObjects(final List<E> out, final MdlxBlockDescriptor<E> constructor,
final LittleEndianDataInputStream stream, final long count) throws IOException {
for (int i = 0; i < count; i++) {
final E object = constructor.create();
object.readMdx(stream);
out.add(object);
}
}
private void loadGlobalSequenceChunk(final LittleEndianDataInputStream stream, final long size) throws IOException {
for (long i = 0, l = size / 4; i < l; i++) {
this.globalSequences.add(ParseUtils.readUInt32(stream));
}
}
private <E extends MdlxBlock & Chunk> void loadDynamicObjects(final List<E> out,
final MdlxBlockDescriptor<E> constructor, final LittleEndianDataInputStream stream, final long size)
throws IOException {
long totalSize = 0;
while (totalSize < size) {
final E object = constructor.create();
object.readMdx(stream);
totalSize += object.getByteLength();
out.add(object);
}
}
private void loadPivotPointChunk(final LittleEndianDataInputStream stream, final long size) throws IOException {
for (long i = 0, l = size / 12; i < l; i++) {
this.pivotPoints.add(ParseUtils.readFloatArray(stream, 3));
}
}
public void saveMdx(final OutputStream outputStream) throws IOException {
final LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(outputStream);
stream.writeInt(Integer.reverseBytes(MDLX));
this.saveVersionChunk(stream);
this.saveModelChunk(stream);
this.saveStaticObjectChunk(stream, SEQS, this.sequences, 132);
this.saveGlobalSequenceChunk(stream);
this.saveDynamicObjectChunk(stream, MTLS, this.materials);
this.saveStaticObjectChunk(stream, TEXS, this.textures, 268);
this.saveDynamicObjectChunk(stream, TXAN, this.textureAnimations);
this.saveDynamicObjectChunk(stream, GEOS, this.geosets);
this.saveDynamicObjectChunk(stream, GEOA, this.geosetAnimations);
this.saveDynamicObjectChunk(stream, BONE, this.bones);
this.saveDynamicObjectChunk(stream, LITE, this.lights);
this.saveDynamicObjectChunk(stream, HELP, this.helpers);
this.saveDynamicObjectChunk(stream, ATCH, this.attachments);
this.savePivotPointChunk(stream);
this.saveDynamicObjectChunk(stream, PREM, this.particleEmitters);
this.saveDynamicObjectChunk(stream, PRE2, this.particleEmitters2);
this.saveDynamicObjectChunk(stream, RIBB, this.ribbonEmitters);
this.saveDynamicObjectChunk(stream, CAMS, this.cameras);
this.saveDynamicObjectChunk(stream, EVTS, this.eventObjects);
this.saveDynamicObjectChunk(stream, CLID, this.collisionShapes);
for (final UnknownChunk chunk : this.unknownChunks) {
chunk.writeMdx(stream);
}
}
private void saveVersionChunk(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(Integer.reverseBytes(VERS));
ParseUtils.writeUInt32(stream, 4);
ParseUtils.writeUInt32(stream, this.version);
}
private void saveModelChunk(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(Integer.reverseBytes(MODL));
ParseUtils.writeUInt32(stream, 372);
final byte[] bytes = this.name.getBytes(ParseUtils.UTF8);
stream.write(bytes);
for (int i = 0; i < (NAME_BYTES_HEAP.length - bytes.length); i++) {
stream.write((byte) 0);
}
final byte[] animationFileBytes = this.animationFile.getBytes(ParseUtils.UTF8);
stream.write(animationFileBytes);
for (int i = 0; i < (ANIMATION_FILE_BYTES_HEAP.length - animationFileBytes.length); i++) {
stream.write((byte) 0);
}
this.extent.writeMdx(stream);
ParseUtils.writeUInt32(stream, this.blendTime);
}
private <E extends MdlxBlock> void saveStaticObjectChunk(final LittleEndianDataOutputStream stream, final int name,
final List<E> objects, final long size) throws IOException {
if (!objects.isEmpty()) {
stream.writeInt(Integer.reverseBytes(name));
ParseUtils.writeUInt32(stream, objects.size() * size);
for (final E object : objects) {
object.writeMdx(stream);
}
}
}
private void saveGlobalSequenceChunk(final LittleEndianDataOutputStream stream) throws IOException {
if (!this.globalSequences.isEmpty()) {
stream.writeInt(Integer.reverseBytes(GLBS));
ParseUtils.writeUInt32(stream, this.globalSequences.size() * 4);
for (final Long globalSequence : this.globalSequences) {
ParseUtils.writeUInt32(stream, globalSequence);
}
}
}
private <E extends MdlxBlock & Chunk> void saveDynamicObjectChunk(final LittleEndianDataOutputStream stream,
final int name, final List<E> objects) throws IOException {
if (!objects.isEmpty()) {
stream.writeInt(Integer.reverseBytes(name));
ParseUtils.writeUInt32(stream, getObjectsByteLength(objects));
for (final E object : objects) {
object.writeMdx(stream);
}
}
}
private void savePivotPointChunk(final LittleEndianDataOutputStream stream) throws IOException {
if (this.pivotPoints.size() > 0) {
stream.writeInt(Integer.reverseBytes(PIVT));
ParseUtils.writeUInt32(stream, this.pivotPoints.size() * 12);
for (final float[] pivotPoint : this.pivotPoints) {
ParseUtils.writeFloatArray(stream, pivotPoint);
}
}
}
public void loadMdl(final InputStream inputStream) throws IOException {
final byte[] array = StreamUtils.copyStreamToByteArray(inputStream);
loadMdl(ByteBuffer.wrap(array));
}
public void loadMdl(final ByteBuffer inputStream) throws IOException {
String token;
final MdlTokenInputStream stream = new GhostwolfTokenInputStream(inputStream);
while ((token = stream.read()) != null) {
switch (token) {
case MdlUtils.TOKEN_VERSION:
this.loadVersionBlock(stream);
break;
case MdlUtils.TOKEN_MODEL:
this.loadModelBlock(stream);
break;
case MdlUtils.TOKEN_SEQUENCES:
this.loadNumberedObjectBlock(this.sequences, MdlxBlockDescriptor.SEQUENCE, MdlUtils.TOKEN_ANIM, stream);
break;
case MdlUtils.TOKEN_GLOBAL_SEQUENCES:
this.loadGlobalSequenceBlock(stream);
break;
case MdlUtils.TOKEN_TEXTURES:
this.loadNumberedObjectBlock(this.textures, MdlxBlockDescriptor.TEXTURE, MdlUtils.TOKEN_BITMAP, stream);
break;
case MdlUtils.TOKEN_MATERIALS:
this.loadNumberedObjectBlock(this.materials, MdlxBlockDescriptor.MATERIAL, MdlUtils.TOKEN_MATERIAL,
stream);
break;
case MdlUtils.TOKEN_TEXTURE_ANIMS:
this.loadNumberedObjectBlock(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION,
MdlUtils.TOKEN_TVERTEX_ANIM, stream);
break;
case MdlUtils.TOKEN_GEOSET:
this.loadObject(this.geosets, MdlxBlockDescriptor.GEOSET, stream);
break;
case MdlUtils.TOKEN_GEOSETANIM:
this.loadObject(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream);
break;
case MdlUtils.TOKEN_BONE:
this.loadObject(this.bones, MdlxBlockDescriptor.BONE, stream);
break;
case MdlUtils.TOKEN_LIGHT:
this.loadObject(this.lights, MdlxBlockDescriptor.LIGHT, stream);
break;
case MdlUtils.TOKEN_HELPER:
this.loadObject(this.helpers, MdlxBlockDescriptor.HELPER, stream);
break;
case MdlUtils.TOKEN_ATTACHMENT:
this.loadObject(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream);
break;
case MdlUtils.TOKEN_PIVOT_POINTS:
this.loadPivotPointBlock(stream);
break;
case MdlUtils.TOKEN_PARTICLE_EMITTER:
this.loadObject(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream);
break;
case MdlUtils.TOKEN_PARTICLE_EMITTER2:
this.loadObject(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream);
break;
case MdlUtils.TOKEN_RIBBON_EMITTER:
this.loadObject(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream);
break;
case MdlUtils.TOKEN_CAMERA:
this.loadObject(this.cameras, MdlxBlockDescriptor.CAMERA, stream);
break;
case MdlUtils.TOKEN_EVENT_OBJECT:
this.loadObject(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream);
break;
case MdlUtils.TOKEN_COLLISION_SHAPE:
this.loadObject(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream);
break;
default:
throw new IllegalStateException("Unsupported block: " + token);
}
}
}
private void loadVersionBlock(final MdlTokenInputStream stream) {
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_FORMAT_VERSION:
this.version = stream.readInt();
break;
default:
throw new IllegalStateException("Unknown token in Version: " + token);
}
}
}
private void loadModelBlock(final MdlTokenInputStream stream) {
this.name = stream.read();
for (final String token : stream.readBlock()) {
if (token.startsWith("Num")) {
/*-
* Don't care about the number of things, the arrays will grow as they wish.
* This includes:
* NumGeosets
* NumGeosetAnims
* NumHelpers
* NumLights
* NumBones
* NumAttachments
* NumParticleEmitters
* NumParticleEmitters2
* NumRibbonEmitters
* NumEvents
*/
stream.read();
}
else {
switch (token) {
case MdlUtils.TOKEN_BLEND_TIME:
this.blendTime = stream.readUInt32();
break;
case MdlUtils.TOKEN_MINIMUM_EXTENT:
stream.readFloatArray(this.extent.min);
break;
case MdlUtils.TOKEN_MAXIMUM_EXTENT:
stream.readFloatArray(this.extent.max);
break;
case MdlUtils.TOKEN_BOUNDSRADIUS:
this.extent.boundsRadius = stream.readFloat();
break;
default:
throw new IllegalStateException("Unknown token in Model: " + token);
}
}
}
}
private <E extends MdlxBlock> void loadNumberedObjectBlock(final List<E> out,
final MdlxBlockDescriptor<E> constructor, final String name, final MdlTokenInputStream stream)
throws IOException {
stream.read(); // Don't care about the number, the array will grow
for (final String token : stream.readBlock()) {
if (token.equals(name)) {
final E object = constructor.create();
object.readMdl(stream);
out.add(object);
}
else {
throw new IllegalStateException("Unknown token in " + name + ": " + token);
}
}
}
private void loadGlobalSequenceBlock(final MdlTokenInputStream stream) {
stream.read(); // Don't care about the number, the array will grow
for (final String token : stream.readBlock()) {
if (token.equals(MdlUtils.TOKEN_DURATION)) {
this.globalSequences.add(stream.readUInt32());
}
else {
throw new IllegalStateException("Unknown token in GlobalSequences: " + token);
}
}
}
private <E extends MdlxBlock> void loadObject(final List<E> out, final MdlxBlockDescriptor<E> descriptor,
final MdlTokenInputStream stream) throws IOException {
final E object = descriptor.create();
object.readMdl(stream);
out.add(object);
}
private void loadPivotPointBlock(final MdlTokenInputStream stream) {
final int count = stream.readInt();
stream.read(); // {
for (int i = 0; i < count; i++) {
this.pivotPoints.add(stream.readFloatArray(new float[3]));
}
stream.read(); // }
}
public void saveMdl(final OutputStream outputStream) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
final MdlTokenOutputStream stream = new GhostwolfTokenOutputStream(writer);
this.saveVersionBlock(stream);
this.saveModelBlock(stream);
this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_SEQUENCES, this.sequences);
this.saveGlobalSequenceBlock(stream);
this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURES, this.textures);
this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_MATERIALS, this.materials);
this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURE_ANIMS, this.textureAnimations);
this.saveObjects(stream, this.geosets);
this.saveObjects(stream, this.geosetAnimations);
this.saveObjects(stream, this.bones);
this.saveObjects(stream, this.lights);
this.saveObjects(stream, this.helpers);
this.saveObjects(stream, this.attachments);
this.savePivotPointBlock(stream);
this.saveObjects(stream, this.particleEmitters);
this.saveObjects(stream, this.particleEmitters2);
this.saveObjects(stream, this.ribbonEmitters);
this.saveObjects(stream, this.cameras);
this.saveObjects(stream, this.eventObjects);
this.saveObjects(stream, this.collisionShapes);
}
}
private void saveVersionBlock(final MdlTokenOutputStream stream) {
stream.startBlock(MdlUtils.TOKEN_VERSION);
stream.writeAttrib(MdlUtils.TOKEN_FORMAT_VERSION, this.version);
stream.endBlock();
}
private void saveModelBlock(final MdlTokenOutputStream stream) {
stream.startObjectBlock(MdlUtils.TOKEN_MODEL, this.name);
stream.writeAttribUInt32(MdlUtils.TOKEN_BLEND_TIME, this.blendTime);
this.extent.writeMdl(stream);
stream.endBlock();
}
private void saveStaticObjectsBlock(final MdlTokenOutputStream stream, final String name,
final List<? extends MdlxBlock> objects) throws IOException {
if (!objects.isEmpty()) {
stream.startBlock(name, objects.size());
for (final MdlxBlock object : objects) {
object.writeMdl(stream);
}
stream.endBlock();
}
}
private void saveGlobalSequenceBlock(final MdlTokenOutputStream stream) {
if (!this.globalSequences.isEmpty()) {
stream.startBlock(MdlUtils.TOKEN_GLOBAL_SEQUENCES, this.globalSequences.size());
for (final Long globalSequence : this.globalSequences) {
stream.writeAttribUInt32(MdlUtils.TOKEN_DURATION, globalSequence);
}
stream.endBlock();
}
}
private void saveObjects(final MdlTokenOutputStream stream, final List<? extends MdlxBlock> objects)
throws IOException {
for (final MdlxBlock object : objects) {
object.writeMdl(stream);
}
}
private void savePivotPointBlock(final MdlTokenOutputStream stream) {
if (!this.pivotPoints.isEmpty()) {
stream.startBlock(MdlUtils.TOKEN_PIVOT_POINTS, this.pivotPoints.size());
for (final float[] pivotPoint : this.pivotPoints) {
stream.writeFloatArray(pivotPoint);
}
stream.endBlock();
}
}
private long getByteLength() {
long size = 396;
size += getStaticObjectsChunkByteLength(this.sequences, 132);
size += this.getStaticObjectsChunkByteLength(this.globalSequences, 4);
size += this.getDynamicObjectsChunkByteLength(this.materials);
size += this.getStaticObjectsChunkByteLength(this.textures, 268);
size += this.getDynamicObjectsChunkByteLength(this.textureAnimations);
size += this.getDynamicObjectsChunkByteLength(this.geosets);
size += this.getDynamicObjectsChunkByteLength(this.geosetAnimations);
size += this.getDynamicObjectsChunkByteLength(this.bones);
size += this.getDynamicObjectsChunkByteLength(this.lights);
size += this.getDynamicObjectsChunkByteLength(this.helpers);
size += this.getDynamicObjectsChunkByteLength(this.attachments);
size += this.getStaticObjectsChunkByteLength(this.pivotPoints, 12);
size += this.getDynamicObjectsChunkByteLength(this.particleEmitters);
size += this.getDynamicObjectsChunkByteLength(this.particleEmitters2);
size += this.getDynamicObjectsChunkByteLength(this.ribbonEmitters);
size += this.getDynamicObjectsChunkByteLength(this.cameras);
size += this.getDynamicObjectsChunkByteLength(this.eventObjects);
size += this.getDynamicObjectsChunkByteLength(this.collisionShapes);
size += this.getObjectsByteLength(this.unknownChunks);
return size;
}
private <E extends Chunk> long getObjectsByteLength(final List<E> objects) {
long size = 0;
for (final E object : objects) {
size += object.getByteLength();
}
return size;
}
private <E extends Chunk> long getDynamicObjectsChunkByteLength(final List<E> objects) {
if (!objects.isEmpty()) {
return 8 + this.getObjectsByteLength(objects);
}
return 0;
}
private <E> long getStaticObjectsChunkByteLength(final List<E> objects, final long size) {
if (!objects.isEmpty()) {
return 8 + (objects.size() * size);
}
return 0;
}
public List<Long> getGlobalSequences() {
return this.globalSequences;
}
public List<Sequence> getSequences() {
return this.sequences;
}
public List<float[]> getPivotPoints() {
return this.pivotPoints;
}
public int getVersion() {
return this.version;
}
public String getName() {
return this.name;
}
public String getAnimationFile() {
return this.animationFile;
}
public Extent getExtent() {
return this.extent;
}
public long getBlendTime() {
return this.blendTime;
}
public List<Material> getMaterials() {
return this.materials;
}
public List<Texture> getTextures() {
return this.textures;
}
public List<TextureAnimation> getTextureAnimations() {
return this.textureAnimations;
}
public List<Geoset> getGeosets() {
return this.geosets;
}
public List<GeosetAnimation> getGeosetAnimations() {
return this.geosetAnimations;
}
public List<Bone> getBones() {
return this.bones;
}
public List<Light> getLights() {
return this.lights;
}
public List<Helper> getHelpers() {
return this.helpers;
}
public List<Attachment> getAttachments() {
return this.attachments;
}
public List<ParticleEmitter> getParticleEmitters() {
return this.particleEmitters;
}
public List<ParticleEmitter2> getParticleEmitters2() {
return this.particleEmitters2;
}
public List<RibbonEmitter> getRibbonEmitters() {
return this.ribbonEmitters;
}
public List<Camera> getCameras() {
return this.cameras;
}
public List<EventObject> getEventObjects() {
return this.eventObjects;
}
public List<CollisionShape> getCollisionShapes() {
return this.collisionShapes;
}
public List<UnknownChunk> getUnknownChunks() {
return this.unknownChunks;
}
}

View File

@ -1,64 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class MdlxTest {
public static void main(final String[] args) {
try (FileInputStream stream = new FileInputStream(
new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\ArcaneEpic13.mdx"))) {
final MdlxModel model = new MdlxModel(stream);
try (FileOutputStream mdlStream = new FileOutputStream(new File(
"C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomated.mdl"))) {
model.saveMdl(mdlStream);
}
}
catch (final FileNotFoundException e) {
e.printStackTrace();
}
catch (final IOException e) {
e.printStackTrace();
}
System.out.println("Created MDL, now reparsing to MDX");
try (FileInputStream stream = new FileInputStream(
new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomated.mdl"))) {
final MdlxModel model = new MdlxModel(null);
model.loadMdl(stream);
try (FileOutputStream mdlStream = new FileOutputStream(new File(
"C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDX.mdx"))) {
model.saveMdx(mdlStream);
}
}
catch (final FileNotFoundException e) {
e.printStackTrace();
}
catch (final IOException e) {
e.printStackTrace();
}
try (FileInputStream stream = new FileInputStream(
new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDX.mdx"))) {
final MdlxModel model = new MdlxModel(stream);
try (FileOutputStream mdlStream = new FileOutputStream(new File(
"C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDXBack2MDL.mdl"))) {
model.saveMdl(mdlStream);
}
}
catch (final FileNotFoundException e) {
e.printStackTrace();
}
catch (final IOException e) {
e.printStackTrace();
}
}
}

View File

@ -1,170 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.util.EnumSet;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Sequence implements MdlxBlock {
private String name = "";
private final long[] interval = new long[2];
private float moveSpeed = 0;
private int flags = 0;
private float rarity = 0;
private long syncPoint = 0;
private final Extent extent = new Extent();
private final EnumSet<AnimationTokens.PrimaryTag> primaryTags = EnumSet.noneOf(AnimationTokens.PrimaryTag.class);
private final EnumSet<AnimationTokens.SecondaryTag> secondaryTags = EnumSet
.noneOf(AnimationTokens.SecondaryTag.class);
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static final byte[] NAME_BYTES_HEAP = new byte[80];
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP);
ParseUtils.readUInt32Array(stream, this.interval);
this.moveSpeed = stream.readFloat();
this.flags = (int) ParseUtils.readUInt32(stream);
this.rarity = stream.readFloat();
this.syncPoint = ParseUtils.readUInt32(stream);
this.extent.readMdx(stream);
populateTags();
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
final byte[] bytes = this.name.getBytes(ParseUtils.UTF8);
stream.write(bytes);
for (int i = 0; i < (NAME_BYTES_HEAP.length - bytes.length); i++) {
stream.write((byte) 0);
}
ParseUtils.writeUInt32Array(stream, this.interval);
stream.writeFloat(this.moveSpeed);
ParseUtils.writeUInt32(stream, this.flags);
stream.writeFloat(this.rarity);
ParseUtils.writeUInt32(stream, this.syncPoint);
this.extent.writeMdx(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) {
this.name = stream.read();
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_INTERVAL:
stream.readIntArray(this.interval);
break;
case MdlUtils.TOKEN_NONLOOPING:
this.flags = 1;
break;
case MdlUtils.TOKEN_MOVESPEED:
this.moveSpeed = stream.readFloat();
break;
case MdlUtils.TOKEN_RARITY:
this.rarity = stream.readFloat();
break;
case MdlUtils.TOKEN_MINIMUM_EXTENT:
stream.readFloatArray(this.extent.min);
break;
case MdlUtils.TOKEN_MAXIMUM_EXTENT:
stream.readFloatArray(this.extent.max);
break;
case MdlUtils.TOKEN_BOUNDSRADIUS:
this.extent.boundsRadius = stream.readFloat();
break;
default:
throw new IllegalStateException("Unknown token in Sequence \"" + this.name + "\": " + token);
}
}
populateTags();
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) {
stream.startObjectBlock(MdlUtils.TOKEN_ANIM, this.name);
stream.writeArrayAttrib(MdlUtils.TOKEN_INTERVAL, this.interval);
if (this.flags == 1) {
stream.writeFlag(MdlUtils.TOKEN_NONLOOPING);
}
if (this.moveSpeed != 0) {
stream.writeFloatAttrib(MdlUtils.TOKEN_MOVESPEED, this.moveSpeed);
}
if (this.rarity != 0) {
stream.writeFloatAttrib(MdlUtils.TOKEN_RARITY, this.rarity);
}
this.extent.writeMdl(stream);
stream.endBlock();
}
private void populateTags() {
this.primaryTags.clear();
this.secondaryTags.clear();
TokenLoop: for (final String token : this.name.split("\\s+")) {
final String upperCaseToken = token.toUpperCase();
for (final PrimaryTag primaryTag : PrimaryTag.values()) {
if (upperCaseToken.equals(primaryTag.name())) {
this.primaryTags.add(primaryTag);
continue TokenLoop;
}
}
for (final SecondaryTag secondaryTag : SecondaryTag.values()) {
if (upperCaseToken.equals(secondaryTag.name())) {
this.secondaryTags.add(secondaryTag);
continue TokenLoop;
}
}
break;
}
}
public long[] getInterval() {
return this.interval;
}
public int getFlags() {
return this.flags;
}
public String getName() {
return this.name;
}
public float getRarity() {
return this.rarity;
}
public float getMoveSpeed() {
return this.moveSpeed;
}
public long getSyncPoint() {
return this.syncPoint;
}
public Extent getExtent() {
return this.extent;
}
public EnumSet<AnimationTokens.PrimaryTag> getPrimaryTags() {
return this.primaryTags;
}
public EnumSet<AnimationTokens.SecondaryTag> getSecondaryTags() {
return this.secondaryTags;
}
}

View File

@ -1,105 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Texture implements MdlxBlock {
private int replaceableId = 0;
private String path = "";
private int flags = 0;
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static final byte[] PATH_BYTES_HEAP = new byte[260];
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
this.replaceableId = (int) ParseUtils.readUInt32(stream);
this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP);
this.flags = (int) ParseUtils.readUInt32(stream);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.replaceableId);
final byte[] bytes = this.path.getBytes(ParseUtils.UTF8);
stream.write(bytes);
for (int i = 0; i < (PATH_BYTES_HEAP.length - bytes.length); i++) {
stream.write((byte) 0);
}
ParseUtils.writeUInt32(stream, this.flags);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_IMAGE:
this.path = stream.read();
break;
case MdlUtils.TOKEN_REPLACEABLE_ID:
this.replaceableId = stream.readInt();
break;
case MdlUtils.TOKEN_WRAP_WIDTH:
this.flags |= 0x1;
break;
case MdlUtils.TOKEN_WRAP_HEIGHT:
this.flags |= 0x2;
break;
default:
throw new IllegalStateException("Unknown token in Texture: " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startBlock(MdlUtils.TOKEN_BITMAP);
stream.writeStringAttrib(MdlUtils.TOKEN_IMAGE, this.path);
if (this.replaceableId != 0) {
stream.writeAttrib(MdlUtils.TOKEN_REPLACEABLE_ID, this.replaceableId);
}
if ((this.flags & 0x1) != 0) {
stream.writeFlag(MdlUtils.TOKEN_WRAP_WIDTH);
}
if ((this.flags & 0x2) != 0) {
stream.writeFlag(MdlUtils.TOKEN_WRAP_HEIGHT);
}
stream.endBlock();
}
public int getReplaceableId() {
return this.replaceableId;
}
public String getPath() {
return this.path;
}
public int getFlags() {
return this.flags;
}
public void setReplaceableId(final int replaceableId) {
this.replaceableId = replaceableId;
}
public void setPath(final String path) {
this.path = path;
}
public void setFlags(final int flags) {
this.flags = flags;
}
}

View File

@ -1,57 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.MdlUtils;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class TextureAnimation extends AnimatedObject {
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
this.readTimelines(stream, size - 4);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.getByteLength());
this.writeTimelines(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : stream.readBlock()) {
switch (token) {
case MdlUtils.TOKEN_TRANSLATION:
this.readTimeline(stream, AnimationMap.KTAT);
break;
case MdlUtils.TOKEN_ROTATION:
this.readTimeline(stream, AnimationMap.KTAR);
break;
case MdlUtils.TOKEN_SCALING:
this.readTimeline(stream, AnimationMap.KTAS);
break;
default:
throw new IllegalStateException("Unknown token in TextureAnimation: " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startBlock(MdlUtils.TOKEN_TVERTEX_ANIM_SPACE);
this.writeTimeline(stream, AnimationMap.KTAT);
this.writeTimeline(stream, AnimationMap.KTAR);
this.writeTimeline(stream, AnimationMap.KTAS);
stream.endBlock();
}
@Override
public long getByteLength() {
return 4 + super.getByteLength();
}
}

View File

@ -1,38 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline;
import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline;
public interface TimelineDescriptor {
Timeline createTimeline();
public static final TimelineDescriptor UINT32_TIMELINE = new TimelineDescriptor() {
@Override
public Timeline createTimeline() {
return new UInt32Timeline();
}
};
public static final TimelineDescriptor FLOAT_TIMELINE = new TimelineDescriptor() {
@Override
public Timeline createTimeline() {
return new FloatTimeline();
}
};
public static final TimelineDescriptor VECTOR3_TIMELINE = new TimelineDescriptor() {
@Override
public Timeline createTimeline() {
return new FloatArrayTimeline(3);
}
};
public static final TimelineDescriptor VECTOR4_TIMELINE = new TimelineDescriptor() {
@Override
public Timeline createTimeline() {
return new FloatArrayTimeline(4);
}
};
}

View File

@ -1,35 +0,0 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class UnknownChunk implements Chunk {
private final short[] chunk;
private final War3ID tag;
public UnknownChunk(final LittleEndianDataInputStream stream, final long size, final War3ID tag)
throws IOException {
System.err.println("Loading unknown chunk: " + tag);
this.chunk = ParseUtils.readUInt8Array(stream, (int) size);
this.tag = tag;
}
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.tag);
// Below: Byte.BYTES used because it's mean as a UInt8 array. This is
// not using Short.BYTES, deliberately, despite using a short[] as the
// type for the array. This is a Java problem that did not exist in the original
// JavaScript implementation by Ghostwolf
ParseUtils.writeUInt32(stream, this.chunk.length * Byte.BYTES);
ParseUtils.writeUInt8Array(stream, this.chunk);
}
@Override
public long getByteLength() {
return 8 + (this.chunk.length * Byte.BYTES);
}
}

View File

@ -1,49 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public final class FloatArrayTimeline extends Timeline<float[]> {
private final int arraySize;
public FloatArrayTimeline(final int arraySize) {
this.arraySize = arraySize;
}
@Override
protected int size() {
return this.arraySize;
}
@Override
protected float[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException {
return ParseUtils.readFloatArray(stream, this.arraySize);
}
@Override
protected float[] readMdlValue(final MdlTokenInputStream stream) {
final float[] output = new float[this.arraySize];
stream.readKeyframe(output);
return output;
}
@Override
protected void writeMdxValue(final LittleEndianDataOutputStream stream, final float[] value) throws IOException {
ParseUtils.writeFloatArray(stream, value);
}
@Override
protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) {
stream.writeKeyframe(prefix, value);
}
public int getArraySize() {
return this.arraySize;
}
}

View File

@ -1,37 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public final class FloatTimeline extends Timeline<float[]> {
@Override
protected int size() {
return 1;
}
@Override
protected float[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException {
return new float[] { stream.readFloat() };
}
@Override
protected float[] readMdlValue(final MdlTokenInputStream stream) {
return new float[] { stream.readFloat() };
}
@Override
protected void writeMdxValue(final LittleEndianDataOutputStream stream, final float[] value) throws IOException {
stream.writeFloat(value[0]);
}
@Override
protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) {
stream.writeKeyframe(prefix, value[0]);
}
}

View File

@ -1,38 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public final class UInt32Timeline extends Timeline<long[]> {
@Override
protected int size() {
return 1;
}
@Override
protected long[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException {
return new long[] { ParseUtils.readUInt32(stream) };
}
@Override
protected long[] readMdlValue(final MdlTokenInputStream stream) {
return new long[] { stream.readUInt32() };
}
@Override
protected void writeMdxValue(final LittleEndianDataOutputStream stream, final long[] uint32) throws IOException {
ParseUtils.writeUInt32(stream, uint32[0]);
}
@Override
protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final long[] uint32) {
stream.writeKeyframe(prefix, uint32[0]);
}
}

View File

@ -3,6 +3,7 @@ package com.etheller.warsmash.parsers.w3x;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.util.Arrays;
import java.util.Collection;
@ -119,6 +120,11 @@ public class War3Map implements DataSource {
return this.dataSource.has(filepath);
}
@Override
public ByteBuffer read(final String path) throws IOException {
return this.dataSource.read(path);
}
@Override
public Collection<String> getListfile() {
return this.internalMpqContentsDataSource.getListfile();

View File

@ -101,6 +101,10 @@ public class StandardObjectData {
try {
destructableData.readSLK(this.source.getResourceAsStream("Units\\DestructableData.slk"));
final InputStream unitSkin = this.source.getResourceAsStream("Units\\DestructableSkin.txt");
if (unitSkin != null) {
destructableData.readTXT(unitSkin, true);
}
}
catch (final IOException e) {
throw new RuntimeException(e);
@ -191,6 +195,11 @@ public class StandardObjectData {
profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityStrings.txt"), true);
final InputStream unitSkin = this.source.getResourceAsStream("Units\\AbilitySkin.txt");
if (unitSkin != null) {
profile.readTXT(unitSkin, true);
}
abilityData.readSLK(this.source.getResourceAsStream("Units\\AbilityData.slk"));
}
catch (final IOException e) {

View File

@ -10,7 +10,7 @@ public class DataSourceFileHandle extends FileHandle {
private final DataSource dataSource;
public DataSourceFileHandle(final DataSource dataSource, final String path) {
super(path);
super(fixPath(dataSource, path));
this.dataSource = dataSource;
}
@ -28,4 +28,14 @@ public class DataSourceFileHandle extends FileHandle {
throw new RuntimeException("Failed to load FileHandle from DataSource: " + path());
}
}
private static String fixPath(final DataSource dataSource, String path) {
if (!dataSource.has(path) && (path.toLowerCase().endsWith(".wav") || path.toLowerCase().endsWith(".mp3"))) {
final String otherPossiblePath = path.substring(0, path.lastIndexOf('.')) + ".flac";
if (dataSource.has(otherPossiblePath)) {
path = otherPossiblePath;
}
}
return path;
}
}

View File

@ -29,14 +29,79 @@ public final class ImageUtils {
private static final int BYTES_PER_PIXEL = 4;
public static final String DEFAULT_ICON_PATH = "ReplaceableTextures\\CommandButtons\\BTNTemp.blp";
public static Texture getBLPTexture(final DataSource dataSource, final String path) {
final BufferedImage image = getBLPImage(dataSource, path);
public static Texture getAnyExtensionTexture(final DataSource dataSource, final String path) {
BufferedImage image;
try {
final AnyExtensionImage imageInfo = getAnyExtensionImageFixRGB(dataSource, path, "texture");
image = imageInfo.getImageData();
if (image != null) {
return ImageUtils.getTexture(image);
return ImageUtils.getTexture(image, imageInfo.isNeedsSRGBFix());
}
}
catch (final IOException e) {
return null;
}
return null;
}
public static AnyExtensionImage getAnyExtensionImageFixRGB(final DataSource dataSource, final String path,
final String errorType) throws IOException {
if (path.toLowerCase().endsWith(".blp")) {
try (InputStream stream = dataSource.getResourceAsStream(path)) {
if (stream == null) {
final String tgaPath = path.substring(0, path.length() - 4) + ".tga";
try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) {
if (tgaStream != null) {
final BufferedImage tgaData = TgaFile.readTGA(tgaPath, tgaStream);
return new AnyExtensionImage(false, tgaData);
}
else {
final String ddsPath = path.substring(0, path.length() - 4) + ".dds";
try (final InputStream ddsStream = dataSource.getResourceAsStream(ddsPath)) {
if (ddsStream != null) {
final BufferedImage image = ImageIO.read(ddsStream);
return new AnyExtensionImage(false, image);
}
else {
throw new IllegalStateException("Missing " + errorType + ": " + path);
}
}
}
}
}
else {
final BufferedImage image = ImageIO.read(stream);
return new AnyExtensionImage(true, image);
}
}
}
else {
throw new IllegalStateException("Missing " + errorType + ": " + path);
}
}
public static final class AnyExtensionImage {
private final boolean needsSRGBFix;
private final BufferedImage imageData;
public AnyExtensionImage(final boolean needsSRGBFix, final BufferedImage imageData) {
this.needsSRGBFix = needsSRGBFix;
this.imageData = imageData;
}
public BufferedImage getImageData() {
return this.imageData;
}
public BufferedImage getRGBCorrectImageData() {
return this.needsSRGBFix ? forceBufferedImagesRGB(this.imageData) : this.imageData;
}
public boolean isNeedsSRGBFix() {
return this.needsSRGBFix;
}
}
public static BufferedImage getBLPImage(final DataSource dataSource, final String path) {
try {
try (final InputStream resourceAsStream = dataSource.getResourceAsStream(path)) {
@ -64,7 +129,7 @@ public final class ImageUtils {
}
}
public static Texture getTexture(final BufferedImage image) {
public static Texture getTexture(final BufferedImage image, final boolean sRGBFix) {
final int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
@ -75,12 +140,12 @@ public final class ImageUtils {
// for
// RGB
final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888) {
final Pixmap pixmap = sRGBFix ? new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888) {
@Override
public int getGLInternalFormat() {
return GL30.GL_SRGB8_ALPHA8;
}
};
} : new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
final int pixel = pixels[(y * image.getWidth()) + x];

View File

@ -148,6 +148,15 @@ public abstract class ModelViewer {
final SolvedPath solved = pathSolver.solve(src, solverParams);
finalSrc = solved.getFinalSrc();
if (!this.dataSource.has(finalSrc)) {
final String ddsPath = finalSrc.substring(0, finalSrc.lastIndexOf('.')) + ".dds";
if (this.dataSource.has(ddsPath)) {
finalSrc = ddsPath;
}
else {
System.err.println("Attempting to load non-existant file: " + finalSrc);
}
}
extension = solved.getExtension();
isFetch = solved.isFetch();
@ -185,9 +194,6 @@ public abstract class ModelViewer {
// TODO this is a synchronous hack, skipped some Ghostwolf code
try {
if (!this.dataSource.has(finalSrc)) {
System.err.println("Attempting to load non-existant file: " + finalSrc);
}
resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null);
}
catch (final IOException e) {

View File

@ -91,7 +91,7 @@ public abstract class RawOpenGLTextureResource extends Texture {
final GL20 gl = this.viewer.gl;
}
public void update(final BufferedImage image) {
public void update(final BufferedImage image, final boolean sRGBFix) {
final GL20 gl = this.viewer.gl;
final int imageWidth = image.getWidth();
@ -129,8 +129,8 @@ public abstract class RawOpenGLTextureResource extends Texture {
// GL20.GL_UNSIGNED_BYTE, buffer);
// }
// else {
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_SRGB8_ALPHA8, imageWidth, imageHeight, 0, GL20.GL_RGBA,
GL20.GL_UNSIGNED_BYTE, buffer);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, sRGBFix ? GL30.GL_SRGB8_ALPHA8 : GL30.GL_RGBA8, imageWidth, imageHeight,
0, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, buffer);
this.width = imageWidth;
this.height = imageHeight;

View File

@ -29,7 +29,7 @@ public class BlpGdxTexture extends GdxTextureResource {
BufferedImage img;
try {
img = ImageIO.read(src);
setGdxTexture(ImageUtils.getTexture(img));
setGdxTexture(ImageUtils.getTexture(img, true));
}
catch (final IOException e) {
throw new RuntimeException(e);

View File

@ -2,8 +2,8 @@ package com.etheller.warsmash.viewer5.handlers.blp;
import java.util.ArrayList;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.HandlerResource;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;

View File

@ -28,7 +28,7 @@ public class BlpTexture extends RawOpenGLTextureResource {
BufferedImage img;
try {
img = ImageIO.read(src);
update(img);
update(img, true);
}
catch (final IOException e) {
throw new RuntimeException(e);

View File

@ -0,0 +1,28 @@
package com.etheller.warsmash.viewer5.handlers.blp;
import java.util.ArrayList;
import com.etheller.warsmash.viewer5.HandlerResource;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
public class DdsHandler extends ResourceHandler {
public DdsHandler() {
this.extensions = new ArrayList<>();
this.extensions.add(new String[] { ".dds", "arrayBuffer" });
}
@Override
public boolean load(final ModelViewer modelViewer) {
return true;
}
@Override
public HandlerResource<?> construct(final ResourceHandlerConstructionParams params) {
return new DdsTexture(params.getViewer(), params.getHandler(), params.getExtension(), params.getPathSolver(),
params.getFetchUrl());
}
}

View File

@ -0,0 +1,38 @@
package com.etheller.warsmash.viewer5.handlers.blp;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.RawOpenGLTextureResource;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public class DdsTexture extends RawOpenGLTextureResource {
public DdsTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(viewer, extension, pathSolver, fetchUrl, handler);
}
@Override
protected void lateLoad() {
}
@Override
protected void load(final InputStream src, final Object options) {
BufferedImage img;
try {
img = ImageIO.read(src);
update(img, false);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -3,23 +3,24 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.HashMap;
import java.util.Map;
import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline;
import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline;
import com.etheller.warsmash.util.War3ID;
import com.hiveworkshop.rms.parsers.mdlx.MdlxAnimatedObject;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxFloatArrayTimeline;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxFloatTimeline;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxUInt32Timeline;
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) {
public AnimatedObject(final MdxModel model, final MdlxAnimatedObject object) {
this.model = model;
this.timelines = new HashMap<>();
this.variants = new HashMap<>();
for (final Timeline<?> timeline : object.getTimelines()) {
for (final MdlxTimeline<?> timeline : object.getTimelines()) {
this.timelines.put(timeline.getName(), createTypedSd(model, timeline));
}
}
@ -126,15 +127,15 @@ public class AnimatedObject {
return false;
}
private Sd<?> createTypedSd(final MdxModel model, final Timeline<?> timeline) {
if (timeline instanceof UInt32Timeline) {
return new UInt32Sd(model, (UInt32Timeline) timeline);
private Sd<?> createTypedSd(final MdxModel model, final MdlxTimeline<?> timeline) {
if (timeline instanceof MdlxUInt32Timeline) {
return new UInt32Sd(model, (MdlxUInt32Timeline) timeline);
}
else if (timeline instanceof FloatTimeline) {
return new ScalarSd(model, (FloatTimeline) timeline);
else if (timeline instanceof MdlxFloatTimeline) {
return new ScalarSd(model, (MdlxFloatTimeline) timeline);
}
else if (timeline instanceof FloatArrayTimeline) {
final FloatArrayTimeline faTimeline = (FloatArrayTimeline) timeline;
else if (timeline instanceof MdlxFloatArrayTimeline) {
final MdlxFloatArrayTimeline faTimeline = (MdlxFloatArrayTimeline) timeline;
final int arraySize = faTimeline.getArraySize();
if (arraySize == 3) {
return new VectorSd(model, faTimeline);

View File

@ -1,6 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxAttachment;
public class Attachment extends GenericObject {
protected String name;
@ -8,8 +9,7 @@ public class Attachment extends GenericObject {
protected final int attachmentId;
protected MdxModel internalModel;
public Attachment(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Attachment attachment,
final int index) {
public Attachment(final MdxModel model, final MdlxAttachment attachment, final int index) {
super(model, attachment, index);
final String path = attachment.getPath().toLowerCase().replace(".mdl", ".mdx");

View File

@ -1,10 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.hiveworkshop.rms.parsers.mdlx.MdlxBone;
public class Bone extends GenericObject {
private final GeosetAnimation geosetAnimation;
public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) {
public Bone(final MdxModel model, final MdlxBone bone, final int index) {
super(model, bone, index);
GeosetAnimation geosetAnimation = null;

View File

@ -1,7 +1,8 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxCamera;
public class Camera extends AnimatedObject {
@ -12,7 +13,7 @@ public class Camera extends AnimatedObject {
public final float nearClippingPlane;
public final float[] targetPosition;
public Camera(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Camera camera) {
public Camera(final MdxModel model, final MdlxCamera camera) {
super(model, camera);
this.name = camera.getName();

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.math.collision.Ray;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.GenericNode;
import com.hiveworkshop.rms.parsers.mdlx.MdlxCollisionShape;
public class CollisionShape extends GenericObject {
private static Vector3 intersectHeap = new Vector3();
@ -15,8 +16,7 @@ public class CollisionShape extends GenericObject {
private static Ray intersectRayHeap = new Ray();
private Intersectable intersectable;
public CollisionShape(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.CollisionShape object,
final int index) {
public CollisionShape(final MdxModel model, final MdlxCollisionShape object, final int index) {
super(model, object, index);
final float[][] vertices = object.getVertices();

View File

@ -20,6 +20,8 @@ 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;
import com.hiveworkshop.rms.parsers.mdlx.MdlxEventObject;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2;
public class EventObjectEmitterObject extends GenericObject implements EmitterObject {
private static final class LoadGenericSoundCallback implements LoadGenericCallback {
@ -106,8 +108,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
*/
private boolean ok = false;
public EventObjectEmitterObject(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) {
public EventObjectEmitterObject(final MdxModel model, final MdlxEventObject eventObject, final int index) {
super(model, eventObject, index);
final ModelViewer viewer = model.viewer;
@ -256,8 +257,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
}
final int[] blendModes = FilterMode
.emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode
.fromId(getInt(row, "BlendMode")));
.emitterFilterMode(MdlxParticleEmitter2.FilterMode.fromId(getInt(row, "BlendMode")));
this.blendSrc = blendModes[0];
this.blendDst = blendModes[1];

View File

@ -1,6 +1,8 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.graphics.GL20;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2;
public class FilterMode {
private static final int[] ERROR_DEFAULT = new int[] { 0, 0 };
@ -9,7 +11,7 @@ public class FilterMode {
private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE };
private static final int[] BLEND = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA };
public static int[] layerFilterMode(final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode) {
public static int[] layerFilterMode(final MdlxLayer.FilterMode filterMode) {
switch (filterMode) {
case BLEND:
return BLEND; // Blend
@ -26,8 +28,7 @@ public class FilterMode {
}
}
public static int[] emitterFilterMode(
final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode filterMode) {
public static int[] emitterFilterMode(final MdlxParticleEmitter2.FilterMode filterMode) {
switch (filterMode) {
case BLEND:
return BLEND; // Blend

View File

@ -1,7 +1,8 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGenericObject;
public class GenericObject extends AnimatedObject implements GenericIndexed {
@ -38,8 +39,7 @@ public class GenericObject extends AnimatedObject implements GenericIndexed {
public final boolean hasScaleAnim;
public final boolean hasGenericAnim;
public GenericObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object,
final int index) {
public GenericObject(final MdxModel model, final MdlxGenericObject object, final int index) {
super(model, object);
this.index = index;

View File

@ -5,6 +5,7 @@ import java.util.Arrays;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset;
public class Geoset {
public MdxModel model;
@ -25,12 +26,12 @@ public class Geoset {
private final int skinStride;
private final int boneCountOffsetBytes;
public final boolean unselectable;
public final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset;
public final MdlxGeoset mdlxGeoset;
public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset,
final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements,
final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes, final boolean unselectable,
final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset) {
final MdlxGeoset mdlxGeoset) {
this.model = model;
this.index = index;
this.positionOffset = positionOffset;

View File

@ -1,6 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeosetAnimation;
public class GeosetAnimation extends AnimatedObject {
@ -8,8 +9,7 @@ public class GeosetAnimation extends AnimatedObject {
private final float[] color;
public final int geosetId;
public GeosetAnimation(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation) {
public GeosetAnimation(final MdxModel model, final MdlxGeosetAnimation geosetAnimation) {
super(model, geosetAnimation);
final float[] color = geosetAnimation.getColor();

View File

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

View File

@ -2,7 +2,8 @@ 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.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer;
/**
* An MDX layer.
@ -26,11 +27,10 @@ public class Layer extends AnimatedObject {
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) {
public Layer(final MdxModel model, final MdlxLayer layer, final int layerId, final int priorityPlane) {
super(model, layer);
final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode = layer.getFilterMode();
final MdlxLayer.FilterMode filterMode = layer.getFilterMode();
final int textureAnimationId = layer.getTextureAnimationId();
final GL20 gl = model.viewer.gl;
@ -50,8 +50,8 @@ public class Layer extends AnimatedObject {
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.depthMaskValue = ((filterMode == MdlxLayer.FilterMode.NONE)
|| (filterMode == MdlxLayer.FilterMode.TRANSPARENT));
this.blendSrc = 0;
this.blendDst = 0;

View File

@ -1,6 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLight;
public class Light extends GenericObject {
@ -11,17 +12,17 @@ public class Light extends GenericObject {
private final float[] ambientColor;
private final float ambientIntensity;
public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) {
public Light(final MdxModel model, final MdlxLight light, final int index) {
super(model, light, index);
switch (light.getType()) {
case 0:
case OMNIDIRECTIONAL:
this.type = Type.OMNIDIRECTIONAL;
break;
case 1:
case DIRECTIONAL:
this.type = Type.DIRECTIONAL;
break;
case 2:
case AMBIENT:
this.type = Type.AMBIENT;
break;
default:

View File

@ -10,7 +10,6 @@ import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.GenericNode;
import com.etheller.warsmash.viewer5.ModelInstance;
@ -24,6 +23,7 @@ import com.etheller.warsmash.viewer5.TextureMapper;
import com.etheller.warsmash.viewer5.UpdatableObject;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset;
public class MdxComplexInstance extends ModelInstance {
private static final float[] visibilityHeap = new float[1];
@ -764,7 +764,7 @@ public class MdxComplexInstance extends ModelInstance {
if (!geoset.unselectable) {
geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter);
if (alphaHeap[0] > 0) {
final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset;
final MdlxGeoset mdlxGeoset = geoset.mdlxGeoset;
if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(),
mdlxGeoset.getFaces(), 3, intersection)) {
return true;

View File

@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.handlers.ModelHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler;
import com.etheller.warsmash.viewer5.handlers.blp.DdsHandler;
import com.etheller.warsmash.viewer5.handlers.tga.TgaHandler;
public class MdxHandler extends ModelHandler {
@ -22,6 +23,7 @@ public class MdxHandler extends ModelHandler {
@Override
public boolean load(final ModelViewer viewer) {
viewer.addHandler(new BlpHandler());
viewer.addHandler(new DdsHandler());
viewer.addHandler(new TgaHandler());
Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex);
@ -32,7 +34,8 @@ public class MdxHandler extends ModelHandler {
Shaders.extendedShadowMap = viewer.webGL.createShaderProgram(
"#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap);
Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles);
//Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple);
// Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple,
// MdxShaders.fsSimple);
// Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd);
// TODO HD reforged

View File

@ -2,17 +2,36 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.compress.utils.IOUtils;
import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.parsers.mdlx.Extent;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
import com.hiveworkshop.rms.parsers.mdlx.MdlxAttachment;
import com.hiveworkshop.rms.parsers.mdlx.MdlxBone;
import com.hiveworkshop.rms.parsers.mdlx.MdlxCamera;
import com.hiveworkshop.rms.parsers.mdlx.MdlxCollisionShape;
import com.hiveworkshop.rms.parsers.mdlx.MdlxEventObject;
import com.hiveworkshop.rms.parsers.mdlx.MdlxExtent;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeosetAnimation;
import com.hiveworkshop.rms.parsers.mdlx.MdlxHelper;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLight;
import com.hiveworkshop.rms.parsers.mdlx.MdlxMaterial;
import com.hiveworkshop.rms.parsers.mdlx.MdlxModel;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2;
import com.hiveworkshop.rms.parsers.mdlx.MdlxRibbonEmitter;
import com.hiveworkshop.rms.parsers.mdlx.MdlxSequence;
import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture;
import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture.WrapMode;
import com.hiveworkshop.rms.parsers.mdlx.MdlxTextureAnimation;
public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
public boolean reforged = false;
@ -73,7 +92,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
parser = (MdlxModel) bufferOrParser;
}
else {
parser = new MdlxModel((InputStream) bufferOrParser);
System.err.println("Wasting memory with conversion from InputStream to buffer in MdxModel");
parser = new MdlxModel(ByteBuffer.wrap(IOUtils.toByteArray((InputStream) bufferOrParser)));
}
final ModelViewer viewer = this.viewer;
@ -86,7 +106,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
this.name = parser.getName();
// Initialize the bounds.
final Extent extent = parser.getExtent();
final MdlxExtent extent = parser.getExtent();
final float[] min = extent.getMin();
final float[] max = extent.getMax();
for (int i = 0; i < 3; i++) {
@ -97,23 +117,24 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
this.bounds.fromExtents(min, max);
// Sequences
this.sequences.addAll(parser.getSequences());
for (final MdlxSequence sequence : parser.getSequences()) {
this.sequences.add(new Sequence(sequence));
}
// Global sequences
this.globalSequences.addAll(parser.getGlobalSequences());
// Texture animations
for (final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation : parser
.getTextureAnimations()) {
for (final MdlxTextureAnimation textureAnimation : parser.getTextureAnimations()) {
this.textureAnimations.add(new TextureAnimation(this, textureAnimation));
}
// Materials
int layerId = 0;
for (final com.etheller.warsmash.parsers.mdlx.Material material : parser.getMaterials()) {
for (final MdlxMaterial material : parser.getMaterials()) {
final List<Layer> layers = new ArrayList<>();
for (final com.etheller.warsmash.parsers.mdlx.Layer layer : material.getLayers()) {
for (final MdlxLayer layer : material.getLayers()) {
final Layer vLayer = new Layer(this, layer, layerId++, material.getPriorityPlane());
layers.add(vLayer);
@ -139,10 +160,10 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
final GL20 gl = viewer.gl;
// Textures.
for (final com.etheller.warsmash.parsers.mdlx.Texture texture : parser.getTextures()) {
for (final MdlxTexture texture : parser.getTextures()) {
String path = texture.getPath();
final int replaceableId = texture.getReplaceableId();
final int flags = texture.getFlags();
final WrapMode wrapMode = texture.getWrapMode();
if (replaceableId != 0) {
// TODO This uses dumb, stupid, terrible, no-good hardcoded replaceable IDs
@ -163,11 +184,11 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams);
// When the texture will load, it will apply its wrap modes.
if ((flags & 0x1) != 0) {
if (wrapMode.isWrapWidth()) {
viewerTexture.setWrapS(true);
}
if ((flags & 0x2) != 0) {
if (wrapMode.isWrapHeight()) {
viewerTexture.setWrapT(true);
}
@ -176,7 +197,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
}
// Geoset animations
for (final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation : parser.getGeosetAnimations()) {
for (final MdlxGeosetAnimation geosetAnimation : parser.getGeosetAnimations()) {
this.geosetAnimations.add(new GeosetAnimation(this, geosetAnimation));
}
@ -189,53 +210,52 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
int objectId = 0;
// Bones
for (final com.etheller.warsmash.parsers.mdlx.Bone bone : parser.getBones()) {
for (final MdlxBone bone : parser.getBones()) {
this.bones.add(new Bone(this, bone, objectId++));
}
// Lights
for (final com.etheller.warsmash.parsers.mdlx.Light light : parser.getLights()) {
for (final MdlxLight light : parser.getLights()) {
this.lights.add(new Light(this, light, objectId++));
}
// Helpers
for (final com.etheller.warsmash.parsers.mdlx.Helper helper : parser.getHelpers()) {
for (final MdlxHelper helper : parser.getHelpers()) {
this.helpers.add(new Helper(this, helper, objectId++));
}
// Attachments
for (final com.etheller.warsmash.parsers.mdlx.Attachment attachment : parser.getAttachments()) {
for (final MdlxAttachment attachment : parser.getAttachments()) {
this.attachments.add(new Attachment(this, attachment, objectId++));
}
// Particle Emitters
for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter particleEmitter : parser.getParticleEmitters()) {
for (final MdlxParticleEmitter particleEmitter : parser.getParticleEmitters()) {
this.particleEmitters.add(new ParticleEmitterObject(this, particleEmitter, objectId++));
}
// Particle Emitters 2
for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 particleEmitter2 : parser
.getParticleEmitters2()) {
for (final MdlxParticleEmitter2 particleEmitter2 : parser.getParticleEmitters2()) {
this.particleEmitters2.add(new ParticleEmitter2Object(this, particleEmitter2, objectId++));
}
// Ribbon emitters
for (final com.etheller.warsmash.parsers.mdlx.RibbonEmitter ribbonEmitter : parser.getRibbonEmitters()) {
for (final MdlxRibbonEmitter ribbonEmitter : parser.getRibbonEmitters()) {
this.ribbonEmitters.add(new RibbonEmitterObject(this, ribbonEmitter, objectId++));
}
// Camera
for (final com.etheller.warsmash.parsers.mdlx.Camera camera : parser.getCameras()) {
for (final MdlxCamera camera : parser.getCameras()) {
this.cameras.add(new Camera(this, camera));
}
// Event objects
for (final com.etheller.warsmash.parsers.mdlx.EventObject eventObject : parser.getEventObjects()) {
for (final MdlxEventObject eventObject : parser.getEventObjects()) {
this.eventObjects.add(new EventObjectEmitterObject(this, eventObject, objectId++));
}
// Collision shapes
for (final com.etheller.warsmash.parsers.mdlx.CollisionShape collisionShape : parser.getCollisionShapes()) {
for (final MdlxCollisionShape collisionShape : parser.getCollisionShapes()) {
this.collisionShapes.add(new CollisionShape(this, collisionShape, objectId++));
}

View File

@ -1,9 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2.HeadOrTail;
public class ParticleEmitter2Object extends GenericObject implements EmitterObject {
public float width;
@ -33,8 +35,7 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje
public int blendDst;
public int priorityPlane;
public ParticleEmitter2Object(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 emitter, final int index) {
public ParticleEmitter2Object(final MdxModel model, final MdlxParticleEmitter2 emitter, final int index) {
super(model, emitter, index);
this.width = emitter.getWidth();
@ -68,10 +69,10 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje
this.replaceableId = emitter.getReplaceableId();
final long headOrTail = emitter.getHeadOrTail();
final HeadOrTail headOrTail = emitter.getHeadOrTail();
this.head = ((headOrTail == 0) || (headOrTail == 2));
this.tail = ((headOrTail == 1) || (headOrTail == 2));
this.head = headOrTail.isIncludesHead();
this.tail = headOrTail.isIncludesTail();
this.cellWidth = 1f / emitter.getColumns();
this.cellHeight = 1f / emitter.getRows();

View File

@ -2,8 +2,9 @@ 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;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter;
public class ParticleEmitterObject extends GenericObject implements EmitterObject {
public MdxModel internalModel;
@ -22,8 +23,7 @@ public class ParticleEmitterObject extends GenericObject implements EmitterObjec
*/
public boolean ok = false;
public ParticleEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.ParticleEmitter emitter,
final int index) {
public ParticleEmitterObject(final MdxModel model, final MdlxParticleEmitter emitter, final int index) {
super(model, emitter, index);
this.internalModel = (MdxModel) model.viewer.load(

View File

@ -1,12 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.Interpolator;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
public class QuaternionSd extends Sd<float[]> {
public QuaternionSd(final MdxModel model, final Timeline<float[]> timeline) {
public QuaternionSd(final MdxModel model, final MdlxTimeline<float[]> timeline) {
super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY);
}

View File

@ -1,7 +1,8 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxRibbonEmitter;
public class RibbonEmitterObject extends GenericObject implements EmitterObject {
public Layer layer;
@ -23,8 +24,7 @@ public class RibbonEmitterObject extends GenericObject implements EmitterObject
*/
public boolean ok = true;
public RibbonEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.RibbonEmitter emitter,
final int index) {
public RibbonEmitterObject(final MdxModel model, final MdlxRibbonEmitter emitter, final int index) {
super(model, emitter, index);
this.layer = model.getMaterials().get(emitter.getMaterialId()).layers.get(0);

View File

@ -1,11 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
public class ScalarSd extends Sd<float[]> {
public ScalarSd(final MdxModel model, final Timeline<float[]> timeline) {
public ScalarSd(final MdxModel model, final MdlxTimeline<float[]> timeline) {
super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY);
}

View File

@ -5,9 +5,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.War3ID;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
public abstract class Sd<TYPE> {
public MdxModel model;
@ -85,7 +84,7 @@ public abstract class Sd<TYPE> {
}
public Sd(final MdxModel model, final Timeline<TYPE> timeline, final SdArrayDescriptor<TYPE> arrayDescriptor) {
public Sd(final MdxModel model, final MdlxTimeline<TYPE> timeline, final SdArrayDescriptor<TYPE> arrayDescriptor) {
final List<Long> globalSequences = model.getGlobalSequences();
final int globalSequenceId = timeline.getGlobalSequenceId();
final Integer forcedInterp = forcedInterpMap.get(timeline.getName());

View File

@ -3,9 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.Arrays;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.ParseUtils;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
public final class SdSequence<TYPE> {
private static boolean INJECT_FRAMES_GHOSTWOLF_STYLE = false;
@ -19,7 +19,7 @@ public final class SdSequence<TYPE> {
public TYPE[] outTans;
public boolean constant;
public SdSequence(final Sd<TYPE> sd, final long start, final long end, final Timeline<TYPE> timeline,
public SdSequence(final Sd<TYPE> sd, final long start, final long end, final MdlxTimeline<TYPE> timeline,
final boolean isGlobalSequence, final SdArrayDescriptor<TYPE> arrayDescriptor) {
this.sd = sd;
this.start = start;
@ -136,22 +136,22 @@ public final class SdSequence<TYPE> {
this.outTans = outTansBuilder.toArray(arrayDescriptor.create(outTansBuilder.size()));
}
private TYPE[] getValues(final Timeline<TYPE> timeline) {
private TYPE[] getValues(final MdlxTimeline<TYPE> timeline) {
final TYPE[] values = timeline.getValues();
return fixTimelineArray(timeline, values);
}
private TYPE[] getOutTans(final Timeline<TYPE> timeline) {
private TYPE[] getOutTans(final MdlxTimeline<TYPE> timeline) {
final TYPE[] outTans = timeline.getOutTans();
return fixTimelineArray(timeline, outTans);
}
private TYPE[] getInTans(final Timeline<TYPE> timeline) {
private TYPE[] getInTans(final MdlxTimeline<TYPE> timeline) {
final TYPE[] inTans = timeline.getInTans();
return fixTimelineArray(timeline, inTans);
}
private TYPE[] fixTimelineArray(final Timeline<TYPE> timeline, final TYPE[] values) {
private TYPE[] fixTimelineArray(final MdlxTimeline<TYPE> timeline, final TYPE[] values) {
if (values == null) {
return null;
}

View File

@ -0,0 +1,78 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.EnumSet;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
import com.hiveworkshop.rms.parsers.mdlx.MdlxExtent;
import com.hiveworkshop.rms.parsers.mdlx.MdlxSequence;
public class Sequence {
private final MdlxSequence sequence;
private final EnumSet<AnimationTokens.PrimaryTag> primaryTags = EnumSet.noneOf(AnimationTokens.PrimaryTag.class);
private final EnumSet<AnimationTokens.SecondaryTag> secondaryTags = EnumSet
.noneOf(AnimationTokens.SecondaryTag.class);
public Sequence(final MdlxSequence sequence) {
this.sequence = sequence;
populateTags();
}
private void populateTags() {
this.primaryTags.clear();
this.secondaryTags.clear();
TokenLoop: for (final String token : this.sequence.name.split("\\s+")) {
final String upperCaseToken = token.toUpperCase();
for (final PrimaryTag primaryTag : PrimaryTag.values()) {
if (upperCaseToken.equals(primaryTag.name())) {
this.primaryTags.add(primaryTag);
continue TokenLoop;
}
}
for (final SecondaryTag secondaryTag : SecondaryTag.values()) {
if (upperCaseToken.equals(secondaryTag.name())) {
this.secondaryTags.add(secondaryTag);
continue TokenLoop;
}
}
break;
}
}
public String getName() {
return this.sequence.getName();
}
public long[] getInterval() {
return this.sequence.getInterval();
}
public float getMoveSpeed() {
return this.sequence.getMoveSpeed();
}
public int getFlags() {
return this.sequence.getFlags();
}
public float getRarity() {
return this.sequence.getRarity();
}
public long getSyncPoint() {
return this.sequence.getSyncPoint();
}
public MdlxExtent getExtent() {
return this.sequence.getExtent();
}
public EnumSet<AnimationTokens.PrimaryTag> getPrimaryTags() {
return this.primaryTags;
}
public EnumSet<AnimationTokens.SecondaryTag> getSecondaryTags() {
return this.secondaryTags;
}
}

View File

@ -6,14 +6,14 @@ import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset;
public class SetupGeosets {
private static final int NORMAL_BATCH = 0;
private static final int EXTENDED_BATCH = 1;
private static final int REFORGED_BATCH = 2;
public static void setupGeosets(final MdxModel model, final List<com.etheller.warsmash.parsers.mdlx.Geoset> geosets,
final boolean bigNodeSpace) {
public static void setupGeosets(final MdxModel model, final List<MdlxGeoset> geosets, final boolean bigNodeSpace) {
if (geosets.size() > 0) {
final GL20 gl = model.viewer.gl;
int positionBytes = 0;
@ -30,7 +30,7 @@ public class SetupGeosets {
final int extendedBatchBoneCountOffsetBytes = bigNodeSpace ? 32 : 8;
for (int i = 0, l = geosets.size(); i < l; i++) {
final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i);
final MdlxGeoset geoset = geosets.get(i);
if (true /* geoset.getLod() == 0 */) {
final int vertices = geoset.getVertices().length / 3;
@ -84,7 +84,7 @@ public class SetupGeosets {
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);
final MdlxGeoset geoset = geosets.get(i);
final int batchType = batchTypes[i];
if (true /* geoset.lod == 0 */) {

View File

@ -1,12 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.AnimationMap;
import com.hiveworkshop.rms.parsers.mdlx.MdlxTextureAnimation;
public class TextureAnimation extends AnimatedObject {
public TextureAnimation(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation) {
public TextureAnimation(final MdxModel model, final MdlxTextureAnimation textureAnimation) {
super(model, textureAnimation);
this.addVariants(AnimationMap.KTAT.getWar3id(), "translation");

View File

@ -1,11 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
public class UInt32Sd extends Sd<long[]> {
public UInt32Sd(final MdxModel model, final Timeline<long[]> timeline) {
public UInt32Sd(final MdxModel model, final MdlxTimeline<long[]> timeline) {
super(model, timeline, SdArrayDescriptor.LONG_ARRAY);
}

View File

@ -1,12 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.Interpolator;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline;
public class VectorSd extends Sd<float[]> {
public VectorSd(final MdxModel model, final Timeline<float[]> timeline) {
public VectorSd(final MdxModel model, final MdlxTimeline<float[]> timeline) {
super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY);
}

View File

@ -26,7 +26,7 @@ public class TgaTexture extends RawOpenGLTextureResource {
BufferedImage img;
try {
img = TgaFile.readTGA(this.fetchUrl, src);
update(img);
update(img, false);
}
catch (final IOException e) {
throw new RuntimeException(e);

View File

@ -1,6 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
public class IndexedSequence {
public final Sequence sequence;

View File

@ -5,9 +5,9 @@ import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;

View File

@ -1,113 +0,0 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.utils.TimeUtils;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.util.DataSourceFileHandle;
import com.etheller.warsmash.viewer5.AudioBufferSource;
import com.etheller.warsmash.viewer5.AudioContext;
import com.etheller.warsmash.viewer5.AudioPanner;
import com.etheller.warsmash.viewer5.gl.Extensions;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
public final class UnitAckSound {
private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0);
private final List<Sound> sounds = new ArrayList<>();
private final float volume;
private final float pitch;
private final float pitchVariance;
private final float minDistance;
private final float maxDistance;
private final float distanceCutoff;
private Sound lastPlayedSound;
public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds,
final String soundName, final String soundType) {
final Element row = unitAckSounds.get(soundName + soundType);
if (row == null) {
return SILENT;
}
final String fileNames = row.getField("FileNames");
String directoryBase = row.getField("DirectoryBase");
if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) {
directoryBase += "\\";
}
final float volume = row.getFieldFloatValue("Volume") / 127f;
final float pitch = row.getFieldFloatValue("Pitch");
float pitchVariance = row.getFieldFloatValue("PitchVariance");
if (pitchVariance == 1.0f) {
pitchVariance = 0.0f;
}
final float minDistance = row.getFieldFloatValue("MinDistance");
final float maxDistance = row.getFieldFloatValue("MaxDistance");
final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff");
final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariance, minDistance, maxDistance,
distanceCutoff);
for (final String fileName : fileNames.split(",")) {
String filePath = directoryBase + fileName;
if (!filePath.toLowerCase().endsWith(".wav")) {
filePath += ".wav";
}
if (dataSource.has(filePath)) {
sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath)));
}
}
return sound;
}
public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance,
final float maxDistance, final float distanceCutoff) {
this.volume = volume;
this.pitch = pitch;
this.pitchVariance = pitchVariation;
this.minDistance = minDistance;
this.maxDistance = maxDistance;
this.distanceCutoff = distanceCutoff;
}
public boolean play(final AudioContext audioContext, final RenderUnit unit) {
return play(audioContext, unit, (int) (Math.random() * this.sounds.size()));
}
public boolean play(final AudioContext audioContext, final RenderUnit unit, final int index) {
if (this.sounds.isEmpty()) {
return false;
}
final long millisTime = TimeUtils.millis();
if (millisTime < unit.lastUnitResponseEndTimeMillis) {
return false;
}
final AudioPanner panner = audioContext.createPanner();
final AudioBufferSource source = audioContext.createBufferSource();
// Panner settings
panner.setPosition(unit.location[0], unit.location[1], unit.location[2]);
panner.setDistances(this.distanceCutoff, this.minDistance);
panner.connect(audioContext.destination);
// Source.
source.buffer = this.sounds.get(index);
source.connect(panner);
// Make a sound.
source.start(0, this.volume,
(this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance);
this.lastPlayedSound = source.buffer;
final float duration = Extensions.audio.getDuration(this.lastPlayedSound);
unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration);
return true;
}
public int getSoundCount() {
return this.sounds.size();
}
}

View File

@ -52,11 +52,12 @@ public final class UnitSound {
final UnitSound sound = new UnitSound(volume, pitch, pitchVariance, minDistance, maxDistance, distanceCutoff);
for (final String fileName : fileNames.split(",")) {
String filePath = directoryBase + fileName;
if (!filePath.toLowerCase().endsWith(".wav")) {
filePath += ".wav";
final int lastDotIndex = filePath.lastIndexOf('.');
if (lastDotIndex != -1) {
filePath = filePath.substring(0, lastDotIndex);
}
if (dataSource.has(filePath)) {
sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath)));
if (dataSource.has(filePath + ".wav") || dataSource.has(filePath + ".flac")) {
sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath + ".wav")));
}
}
return sound;

View File

@ -1,7 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x.environment;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@ -10,9 +9,9 @@ import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.mdlx.Geoset;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.util.RenderMathUtils;
import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset;
import com.hiveworkshop.rms.parsers.mdlx.MdlxModel;
public class CliffMesh {
public int vertexBuffer;
@ -28,11 +27,8 @@ public class CliffMesh {
public CliffMesh(final String path, final DataSource dataSource, final GL30 gl) throws IOException {
this.gl = gl;
if (path.endsWith(".mdx") || path.endsWith(".MDX")) {
MdlxModel model;
try (InputStream stream = dataSource.getResourceAsStream(path)) {
model = new MdlxModel(stream);
}
final Geoset geoset = model.getGeosets().get(0);
final MdlxModel model = new MdlxModel(dataSource.read(path));
final MdlxGeoset geoset = model.getGeosets().get(0);
this.vertexBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);

View File

@ -2,15 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import javax.imageio.ImageIO;
import com.badlogic.gdx.graphics.GL30;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
import com.etheller.warsmash.util.ImageUtils.AnyExtensionImage;
public class GroundTexture {
public int id;
@ -18,34 +15,15 @@ public class GroundTexture {
public boolean extended;
public GroundTexture(final String path, final DataSource dataSource, final GL30 gl) throws IOException {
if (path.toLowerCase().endsWith(".blp")) {
try (InputStream stream = dataSource.getResourceAsStream(path)) {
if (stream == null) {
final String tgaPath = path.substring(0, path.length() - 4) + ".tga";
try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) {
if (tgaStream != null) {
final BufferedImage tgaData = TgaFile.readTGA(tgaPath, tgaStream);
loadImage(path, gl, tgaData);
}
else {
throw new IllegalStateException("Missing ground texture: " + path);
}
}
}
else {
final BufferedImage image = ImageIO.read(stream);
loadImage(path, gl, image);
}
}
final AnyExtensionImage imageInfo = ImageUtils.getAnyExtensionImageFixRGB(dataSource, path, "ground texture");
loadImage(path, gl, imageInfo.getImageData(), imageInfo.isNeedsSRGBFix());
}
}
private void loadImage(final String path, final GL30 gl, final BufferedImage image) {
private void loadImage(final String path, final GL30 gl, final BufferedImage image, final boolean sRGBFix) {
if (image == null) {
throw new IllegalStateException("Missing ground texture: " + path);
}
final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image));
final Buffer buffer = ImageUtils.getTextureBuffer(sRGBFix ? ImageUtils.forceBufferedImagesRGB(image) : image);
final int width = image.getWidth();
final int height = image.getHeight();

View File

@ -16,8 +16,6 @@ import java.util.Map;
import java.util.TreeSet;
import java.util.function.Consumer;
import javax.imageio.ImageIO;
import org.apache.commons.compress.utils.IOUtils;
import com.badlogic.gdx.Gdx;
@ -36,6 +34,7 @@ import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.ImageUtils.AnyExtensionImage;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.util.WorldEditStrings;
@ -46,7 +45,6 @@ import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.gl.Extensions;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
@ -257,31 +255,12 @@ public class Terrain {
}
final String texDir = cliffInfo.getField("texDir");
final String texFile = cliffInfo.getField("texFile");
try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) {
final BufferedImage image;
if (imageStream == null) {
final String tgaPath = texDir + "\\" + texFile + ".tga";
try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) {
if (tgaStream != null) {
image = TgaFile.readTGA(tgaPath, tgaStream);
}
else {
throw new IllegalStateException(
"Missing cliff texture: " + texDir + "\\" + texFile + texturesExt);
}
}
}
else {
image = ImageIO.read(imageStream);
if (image == null) {
throw new IllegalStateException(
"Missing cliff texture: " + texDir + "\\" + texFile + texturesExt);
}
}
this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(),
ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)),
final AnyExtensionImage imageInfo = ImageUtils.getAnyExtensionImageFixRGB(dataSource,
texDir + "\\" + texFile + texturesExt, "cliff texture");
final BufferedImage image = imageInfo.getRGBCorrectImageData();
this.cliffTextures
.add(new UnloadedTexture(image.getWidth(), image.getHeight(), ImageUtils.getTextureBuffer(image),
cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir")));
}
this.cliffTexturesSize = Math.max(this.cliffTexturesSize,
this.cliffTextures.get(this.cliffTextures.size() - 1).width);
this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile")));
@ -381,27 +360,33 @@ public class Terrain {
// Water textures
this.waterTextureArray = gl.glGenTexture();
gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray);
gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0,
GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null);
final String fileName = waterInfo.getField("texFile");
final List<BufferedImage> waterTextures = new ArrayList<>();
boolean anyWaterTextureNeedsSRGB = false;
for (int i = 0; i < this.waterTextureCount; i++) {
final AnyExtensionImage imageInfo = ImageUtils.getAnyExtensionImageFixRGB(dataSource,
fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt, "water texture");
final BufferedImage image = imageInfo.getImageData();
if ((image.getWidth() != 128) || (image.getHeight() != 128)) {
System.err
.println("Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight());
}
anyWaterTextureNeedsSRGB |= imageInfo.isNeedsSRGBFix();
waterTextures.add(image);
}
gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, anyWaterTextureNeedsSRGB ? GL30.GL_SRGB8_ALPHA8 : GL30.GL_RGBA8,
128, 128, this.waterTextureCount, 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0);
final String fileName = waterInfo.getField("texFile");
for (int i = 0; i < this.waterTextureCount; i++) {
try (InputStream imageStream = dataSource
.getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) {
final BufferedImage image = ImageIO.read(imageStream);
if ((image.getWidth() != 128) || (image.getHeight() != 128)) {
System.err.println(
"Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight());
}
for (int i = 0; i < waterTextures.size(); i++) {
final BufferedImage image = waterTextures.get(i);
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1,
GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image));
}
}
gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY);
updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1));

View File

@ -2,9 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import java.util.List;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;

View File

@ -3,9 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import java.util.List;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;

View File

@ -6,12 +6,12 @@ import java.util.Queue;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.Sequence;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;

View File

@ -11,8 +11,6 @@ import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
@ -45,7 +43,6 @@ import com.etheller.warsmash.parsers.fdf.frames.StringFrame;
import com.etheller.warsmash.parsers.fdf.frames.TextureFrame;
import com.etheller.warsmash.parsers.fdf.frames.UIFrame;
import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener;
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.util.FastNumberFormat;
@ -133,6 +130,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFram
import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener;
import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener;
import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener;
import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode;
public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener,
QueueIconListener, CommandErrorListener, CPlayerStateListener {
@ -276,7 +274,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
this.cameraManager.target.x = startLocation[0];
this.cameraManager.target.y = startLocation[1];
this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq,
this.activeButtonTexture = ImageUtils.getAnyExtensionTexture(war3MapViewer.mapMpq,
"UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp");
this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter();
this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f;
@ -303,18 +301,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
}
}
else if (war3MapViewer.dataSource.has("war3mapMap.blp")) {
try {
minimapTexture = ImageUtils
.getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp")));
}
catch (final IOException e) {
System.err.println("Could not load minimap BLP file");
e.printStackTrace();
}
minimapTexture = ImageUtils.getAnyExtensionTexture(war3MapViewer.dataSource, "war3mapMap.blp");
}
final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS];
for (int i = 0; i < teamColors.length; i++) {
teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource,
teamColors[i] = ImageUtils.getAnyExtensionTexture(war3MapViewer.dataSource,
"ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp");
}
final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea();

View File

@ -0,0 +1,53 @@
package com.hiveworkshop;
public class ReteraCASCUtils {
public static boolean arraysEquals(final byte[] a, final int aFromIndex, final int aToIndex, final byte[] b,
final int bFromIndex, final int bToIndex) {
if (a == null) {
if (b == null) {
return true;
} else {
return false;
}
}
if (b == null) {
return false;
}
if ((aToIndex - aFromIndex) != (bToIndex - bFromIndex)) {
return false;
}
int j = bFromIndex;
for (int i = aFromIndex; i < aToIndex; i++) {
if (a[i] != b[j++]) {
return false;
}
}
return true;
}
public static int arraysCompareUnsigned(final byte[] a, final int aFromIndex, final int aToIndex, final byte[] b,
final int bFromIndex, final int bToIndex) {
final int i = arraysMismatch(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex);
if ((i >= 0) && (i < Math.min(aToIndex - aFromIndex, bToIndex - bFromIndex))) {
return byteCompareUnsigned(a[aFromIndex + i], b[bFromIndex + i]);
}
return (aToIndex - aFromIndex) - (bToIndex - bFromIndex);
}
private static int byteCompareUnsigned(final byte b, final byte c) {
return Integer.compare(b & 0xFF, c & 0xFF);
}
private static int arraysMismatch(final byte[] a, final int aFromIndex, final int aToIndex, final byte[] b,
final int bFromIndex, final int bToIndex) {
final int aLength = aToIndex - aFromIndex;
final int bLength = bToIndex - bFromIndex;
for (int i = 0; (i < aLength) && (i < bLength); i++) {
if (a[aFromIndex + i] != b[bFromIndex + i]) {
return i;
}
}
return Math.min(aLength, bLength);
}
}

View File

@ -0,0 +1,118 @@
package com.hiveworkshop.blizzard.casc;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException;
import com.hiveworkshop.nio.ByteBufferInputStream;
/**
* File containing CASC configuration information. This is basically a
* collection of keys with their assigned value. What the values mean depends on
* the purpose of the key.
*/
public class ConfigurationFile {
/**
* The name of the data folder containing the configuration files.
*/
public static final String CONFIGURATION_FOLDER_NAME = "config";
/**
* Character encoding used by configuration files.
*/
public static final Charset FILE_ENCODING = Charset.forName("UTF8");
/**
* Length of the configuration bucket folder names.
*/
public static final int BUCKET_NAME_LENGTH = 2;
/**
* Number of configuration bucket folder tiers.
*/
public static final int BUCKET_TIERS = 2;
/**
* Retrieve a configuration file from the data folder by its key.
*
* @param dataFolder Path of the CASC data folder.
* @param keyHex Key for configuration file as a hexadecimal string.
* @return The requested configuration file.
* @throws IOException If an exception occurs when retrieving the file.
*/
public static ConfigurationFile lookupConfigurationFile(final Path dataFolder, final String keyHex)
throws IOException {
Path file = dataFolder.resolve(CONFIGURATION_FOLDER_NAME);
for (int tier = 0; tier < BUCKET_TIERS; tier += 1) {
final int keyOffset = tier * BUCKET_NAME_LENGTH;
final String bucketFolderName = keyHex.substring(keyOffset, keyOffset + BUCKET_NAME_LENGTH);
file = file.resolve(bucketFolderName);
}
file = file.resolve(keyHex);
final ByteBuffer fileBuffer = ByteBuffer.wrap(Files.readAllBytes(file));
return new ConfigurationFile(fileBuffer);
}
/**
* Underlying map holding the configuration data.
*/
private final Map<String, String> configuration = new HashMap<>();
/**
* Construct a configuration file by decoding a file buffer.
*
* @param fileBuffer File buffer to decode from.
* @throws IOException If one or more IO errors occur.
*/
public ConfigurationFile(final ByteBuffer fileBuffer) throws IOException {
try (final ByteBufferInputStream fileStream = new ByteBufferInputStream(fileBuffer);
final Scanner lineScanner = new Scanner(new InputStreamReader(fileStream, FILE_ENCODING))) {
while (lineScanner.hasNextLine()) {
final String line = lineScanner.nextLine().trim();
final int lineLength = line.indexOf('#');
final String record;
if (lineLength != -1) {
record = line.substring(0, lineLength);
} else {
record = line;
}
if (!record.equals("")) {
final int assignmentIndex = record.indexOf('=');
if (assignmentIndex == -1) {
throw new MalformedCASCStructureException(
"configuration file line contains record with no assignment");
}
final String key = record.substring(0, assignmentIndex).trim();
final String value = record.substring(assignmentIndex + 1).trim();
if (configuration.putIfAbsent(key, value) != null) {
throw new MalformedCASCStructureException(
"configuration file contains duplicate key declarations");
}
}
}
}
}
/**
* Get the configuration defined by the file.
*
* @return Configuration map.
*/
public Map<String, String> getConfiguration() {
return configuration;
}
}

Some files were not shown because too many files have changed in this diff Show More