Upgrade code

This commit is contained in:
Retera 2019-09-08 16:39:20 -05:00
parent f381562e9b
commit f0da595d20
61 changed files with 6825 additions and 78 deletions

128
.gitignore vendored Normal file
View File

@ -0,0 +1,128 @@
## Java
*.class
*.war
*.ear
hs_err_pid*
## Robovm
/ios/robovm-build/
## GWT
/html/war/
/html/gwt-unitCache/
.apt_generated/
.gwt/
gwt-unitCache/
www-test/
.gwt-tmp/
## Android Studio and Intellij and Android in general
/android/libs/armeabi/
/android/libs/armeabi-v7a/
/android/libs/arm64-v8a/
/android/libs/x86/
/android/libs/x86_64/
/android/gen/
.idea/
*.ipr
*.iws
*.iml
/android/out/
com_crashlytics_export_strings.xml
## Eclipse
.classpath
.project
.metadata/
/android/bin/
/core/bin/
/desktop/bin/
/html/bin/
/ios/bin/
/ios-moe/bin/
*.tmp
*.bak
*.swp
*~.nib
.settings/
.loadpath
.externalToolBuilders/
*.launch
## NetBeans
/nbproject/private/
/android/nbproject/private/
/core/nbproject/private/
/desktop/nbproject/private/
/html/nbproject/private/
/ios/nbproject/private/
/ios-moe/nbproject/private/
/build/
/android/build/
/core/build/
/desktop/build/
/html/build/
/ios/build/
/ios-moe/build/
/nbbuild/
/android/nbbuild/
/core/nbbuild/
/desktop/nbbuild/
/html/nbbuild/
/ios/nbbuild/
/ios-moe/nbbuild/
/dist/
/android/dist/
/core/dist/
/desktop/dist/
/html/dist/
/ios/dist/
/ios-moe/dist/
/nbdist/
/android/nbdist/
/core/nbdist/
/desktop/nbdist/
/html/nbdist/
/ios/nbdist/
/ios-moe/nbdist/
nbactions.xml
nb-configuration.xml
## Gradle
/local.properties
.gradle/
gradle-app.setting
/build/
/android/build/
/core/build/
/desktop/build/
/html/build/
/ios/build/
/ios-moe/build/
## OS Specific
.DS_Store
Thumbs.db
## iOS
/ios/xcode/*.xcodeproj/*
!/ios/xcode/*.xcodeproj/xcshareddata
!/ios/xcode/*.xcodeproj/project.pbxproj
/ios/xcode/native/
/ios-moe/xcode/*.xcodeproj/*
!/ios-moe/xcode/*.xcodeproj/xcshareddata
!/ios-moe/xcode/*.xcodeproj/project.pbxproj
/ios-moe/xcode/native/
## data
/core/data

View File

@ -3,6 +3,9 @@ buildscript {
repositories {
mavenLocal()
flatDir {
dirs "$rootProject.projectDir/jars"
}
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
@ -51,6 +54,7 @@ project(":desktop") {
compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
compile "com.google.guava:guava:23.5-jre"
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
compile files(fileTree(dir:'../jars', includes: ['*.jar']))
}
}
@ -65,6 +69,7 @@ project(":core") {
compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
compile "com.google.guava:guava:23.5-jre"
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
compile files(fileTree(dir:'../jars', includes: ['*.jar']))
}
}

View File

@ -1,6 +1,6 @@
apply plugin: "java"
sourceCompatibility = 1.7
sourceCompatibility = 1.8
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
sourceSets.main.java.srcDirs = [ "src/" ]

View File

@ -1,27 +1,62 @@
package com.etheller.warsmash;
import java.nio.charset.Charset;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.War3ID;
import com.hiveworkshop.wc3.mpq.Codebase;
import com.hiveworkshop.wc3.mpq.FileCodebase;
public class WarsmashGdxGame extends ApplicationAdapter {
private SpriteBatch batch;
private BitmapFont font;
private Codebase codebase;
private Texture texture;
@Override
public void create() {
this.codebase = new FileCodebase(new File("C:/MPQBuild/War3.mpq/war3.mpq"));
final War3ID id = War3ID.fromString("ipea");
System.out.println(id.getValue());
for (final byte b : "Hello World".getBytes(Charset.forName("utf-8"))) {
System.out.println(b + " - " + (char) b);
try {
final String path = "terrainart\\lordaeronsummer\\lords_dirt.blp";
final boolean has = this.codebase.has(path);
final BufferedImage img = ImageIO.read(this.codebase.getResourceAsStream(path));
this.texture = ImageUtils.getTexture(ImageUtils.forceBufferedImagesRGB(img));
this.texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
this.batch = new SpriteBatch();
this.font = new BitmapFont();
}
@Override
public void render() {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClearColor(0, 1, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
final int srcFunc = this.batch.getBlendSrcFunc();
final int dstFunc = this.batch.getBlendDstFunc();
this.batch.enableBlending();
this.batch.begin();
// this.font.draw(this.batch, "Hello World", 100, 100);
this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
this.batch.draw(this.texture, 0, 0);
this.batch.end();
this.batch.setBlendFunction(srcFunc, dstFunc);
}
@Override

View File

@ -6,6 +6,7 @@ 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.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
@ -14,8 +15,8 @@ import com.google.common.io.LittleEndianDataOutputStream;
* Based on the works of Chananya Freiman.
*
*/
public class AnimatedObject implements Chunk {
private final List<Timeline> timelines;
public abstract class AnimatedObject implements Chunk, MdlxBlock {
protected final List<Timeline> timelines;
public AnimatedObject() {
this.timelines = new ArrayList<>();
@ -41,20 +42,20 @@ public class AnimatedObject implements Chunk {
}
public Iterator<String> readAnimatedBlock(final MdlTokenInputStream stream) {
return new TransformedAnimatedBlockIterator(stream.readBlock());
return new TransformedAnimatedBlockIterator(stream.readBlock().iterator());
}
public void readTimeline(final MdlTokenInputStream stream, final War3ID name) throws IOException {
final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline();
public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) throws IOException {
final Timeline timeline = name.getImplementation().createTimeline();
timeline.readMdl(stream, name);
timeline.readMdl(stream, name.getWar3id());
this.timelines.add(timeline);
}
public boolean writeTimeline(final MdlTokenOutputStream stream, final War3ID name) throws IOException {
public boolean writeTimeline(final MdlTokenOutputStream stream, final AnimationMap name) throws IOException {
for (final Timeline timeline : this.timelines) {
if (timeline.getName().equals(name)) {
if (timeline.getName().equals(name.getWar3id())) {
timeline.writeMdl(stream);
return true;
}
@ -93,8 +94,8 @@ public class AnimatedObject implements Chunk {
@Override
public String next() {
final String token = this.delegate.next();
if (token.equals("static") && hasNext()) {
return "static " + this.delegate.next();
if (token.equals(MdlUtils.TOKEN_STATIC) && hasNext()) {
return MdlUtils.TOKEN_STATIC + " " + this.delegate.next();
}
return token;
}

View File

@ -3,6 +3,7 @@ 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;
/**
@ -15,33 +16,33 @@ import com.etheller.warsmash.util.War3ID;
*/
public enum AnimationMap {
// Layer
KMTF("TextureId", TimelineDescriptor.UINT32_TIMELINE),
KMTA("Alpha", TimelineDescriptor.FLOAT_TIMELINE),
KMTF(MdlUtils.TOKEN_TEXTURE_ID, TimelineDescriptor.UINT32_TIMELINE),
KMTA(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE),
// TextureAnimation
KTAT("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
KTAR("Rotation", TimelineDescriptor.VECTOR4_TIMELINE),
KTAS("Scaling", TimelineDescriptor.VECTOR3_TIMELINE),
KTAT(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KTAR(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE),
KTAS(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE),
// GeosetAnimation
KGAO("Alpha", TimelineDescriptor.FLOAT_TIMELINE),
KGAC("Color", TimelineDescriptor.VECTOR3_TIMELINE),
KGAO(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE),
KGAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
// Light
KLAS("AttenuationStart", TimelineDescriptor.FLOAT_TIMELINE),
KLAE("AttenuationEnd", TimelineDescriptor.FLOAT_TIMELINE),
KLAC("Color", TimelineDescriptor.VECTOR3_TIMELINE),
KLAI("Intensity", TimelineDescriptor.FLOAT_TIMELINE),
KLBI("AmbientIntensity", TimelineDescriptor.FLOAT_TIMELINE),
KLBC("AmbientColor", TimelineDescriptor.VECTOR3_TIMELINE),
KLAV("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
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("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
KATV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
// ParticleEmitter
KPEE("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE),
KPEG("Gravity", TimelineDescriptor.FLOAT_TIMELINE),
KPLN("Longitude", TimelineDescriptor.FLOAT_TIMELINE),
KPLT("Latitude", TimelineDescriptor.FLOAT_TIMELINE),
KPEL("LifeSpan", TimelineDescriptor.FLOAT_TIMELINE),
KPES("Speed", TimelineDescriptor.FLOAT_TIMELINE),
KPEV("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
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),
@ -59,19 +60,21 @@ public enum AnimationMap {
KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE),
KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
// Camera
KCTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
KTTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
KCRL("Rotation", TimelineDescriptor.UINT32_TIMELINE),
KCTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KTTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
KCRL(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.UINT32_TIMELINE),
// GenericObject
KGTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
KGRT("Rotation", TimelineDescriptor.VECTOR4_TIMELINE),
KGSC("Scaling", TimelineDescriptor.VECTOR3_TIMELINE);
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() {
@ -82,11 +85,15 @@ public enum AnimationMap {
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(War3ID.fromString(tag.name()), tag);
ID_TO_TAG.put(tag.getWar3id(), tag);
}
}
}

View File

@ -0,0 +1,99 @@
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();
}
}

View File

@ -0,0 +1,88 @@
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();
}
}

View File

@ -0,0 +1,128 @@
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();
}
}

View File

@ -0,0 +1,167 @@
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 CollisionShape extends GenericObject {
public static enum Type {
PLANE,
BOX,
SPHERE,
CYLINDER;
private static final Type[] VALUES = values();
private final boolean boundsRadius;
private Type() {
this.boundsRadius = false;
}
private Type(final boolean boundsRadius) {
this.boundsRadius = boundsRadius;
}
public boolean isBoundsRadius() {
return this.boundsRadius;
}
public static Type from(final int index) {
return VALUES[index];
}
}
private CollisionShape.Type type;
private final float[][] vertices = { new float[3], new float[3] };
private float boundsRadius;
public CollisionShape() {
super(0x2000);
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
super.readMdx(stream);
final long typeIndex = ParseUtils.readUInt32(stream);
this.type = CollisionShape.Type.from((int) typeIndex);
ParseUtils.readFloatArray(stream, this.vertices[0]);
if (this.type != Type.SPHERE) {
ParseUtils.readFloatArray(stream, this.vertices[1]);
}
if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) {
this.boundsRadius = stream.readFloat();
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
super.writeMdx(stream);
ParseUtils.writeUInt32(stream, this.type == null ? -1 : this.type.ordinal());
ParseUtils.writeFloatArray(stream, this.vertices[0]);
if (this.type != CollisionShape.Type.SPHERE) {
ParseUtils.writeFloatArray(stream, this.vertices[1]);
}
if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) {
stream.writeFloat(this.boundsRadius);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream) {
for (final String token : super.readMdlGeneric(stream)) {
switch (token) {
case MdlUtils.TOKEN_PLANE:
this.type = Type.PLANE;
break;
case MdlUtils.TOKEN_BOX:
this.type = Type.BOX;
break;
case MdlUtils.TOKEN_SPHERE:
this.type = Type.SPHERE;
break;
case MdlUtils.TOKEN_CYLINDER:
this.type = Type.CYLINDER;
break;
case MdlUtils.TOKEN_VERTICES:
final int count = stream.readInt();
stream.read(); // {
stream.readFloatArray(this.vertices[0]);
if (count == 2) {
stream.readFloatArray(this.vertices[1]);
}
stream.read(); // }
break;
case MdlUtils.TOKEN_BOUNDSRADIUS:
this.boundsRadius = stream.readFloat();
break;
default:
throw new RuntimeException("Unknown token in CollisionShape " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_COLLISION_SHAPE, this.name);
writeGenericHeader(stream);
String type;
int vertices = 2;
switch (this.type) {
case PLANE:
type = MdlUtils.TOKEN_PLANE;
break;
case BOX:
type = MdlUtils.TOKEN_BOX;
break;
case SPHERE:
type = MdlUtils.TOKEN_SPHERE;
vertices = 1;
break;
case CYLINDER:
type = MdlUtils.TOKEN_CYLINDER;
break;
default:
throw new IllegalStateException("Invalid type in CollisionShape " + this.name + ": " + this.type);
}
stream.writeFlag(type);
stream.startBlock(MdlUtils.TOKEN_VERTICES, vertices);
stream.writeFloatArray(this.vertices[0]);
if (vertices == 2) {
stream.writeFloatArray(this.vertices[1]);
}
stream.endBlock();
if (this.type.boundsRadius) {
stream.writeFloatAttrib(MdlUtils.TOKEN_BOUNDSRADIUS, this.boundsRadius);
}
writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
long size = 16 + super.getByteLength();
if (this.type != Type.SPHERE) {
size += 12;
}
if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) {
size += 4;
}
return size;
}
}

View File

@ -0,0 +1,77 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
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 EventObject extends GenericObject {
private static final War3ID KEVT = War3ID.fromString("KEVT");
private int globalSequenceId = -1;
private long[] keyFrames = { 1 };
public EventObject() {
super(0x400);
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
super.readMdx(stream);
stream.readInt(); // KEVT skipped
final long count = ParseUtils.readUInt32(stream);
this.globalSequenceId = stream.readInt();
this.keyFrames = new long[(int) count];
for (int i = 0; i < count; i++) {
this.keyFrames[i] = stream.readInt();
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
super.writeMdx(stream);
ParseUtils.writeWar3ID(stream, KEVT);
ParseUtils.writeUInt32(stream, this.keyFrames.length);
stream.writeInt(this.globalSequenceId);
for (int i = 0; i < this.keyFrames.length; i++) {
ParseUtils.writeUInt32(stream, this.keyFrames[i]);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream) {
for (final String token : super.readMdlGeneric(stream)) {
if (MdlUtils.TOKEN_EVENT_TRACK.equals(token)) {
this.keyFrames = new long[stream.readInt()];
stream.readIntArray(this.keyFrames);
}
else {
throw new RuntimeException("Unknown token in EventObject " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_EVENT_OBJECT, this.name);
writeGenericHeader(stream);
stream.startBlock(MdlUtils.TOKEN_EVENT_TRACK, this.keyFrames.length);
for (final long keyFrame : this.keyFrames) {
stream.writeFlagUInt32(keyFrame);
}
stream.endBlock();
writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 12 + (this.keyFrames.length * 4) + super.getByteLength();
}
}

View File

@ -0,0 +1,47 @@
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;
}
}

View File

@ -1,11 +1,13 @@
package com.etheller.warsmash.parsers.mdlx;
import java.io.IOException;
import java.nio.charset.Charset;
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;
@ -19,11 +21,10 @@ import com.google.common.io.LittleEndianDataOutputStream;
* Based on the works of Chananya Freiman.
*/
public abstract class GenericObject extends AnimatedObject implements Chunk {
private static final Charset UTF8 = Charset.forName("utf-8");
private String name;
protected String name;
private int objectId;
private int parentId;
private int flags;
protected int flags;
/**
* Restricts us to only be able to parse models on one thread at a time, in
@ -38,19 +39,21 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
this.flags = flags;
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.parseUInt32(stream);
stream.read(NAME_BYTES_HEAP);
this.name = new String(NAME_BYTES_HEAP, UTF8);
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();
this.flags = stream.readInt(); // Used to be Int32 in JS
readTimelines(stream, size - 96);
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt((int) getGenericByteLength());
final byte[] bytes = this.name.getBytes(UTF8);
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);
}
@ -69,7 +72,66 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
}
}
protected abstract Iterable<Timeline> eachTimeline(boolean generic);
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;
@ -84,23 +146,188 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
return 96 + super.getByteLength();
}
private static final class WrappedMdlTokenIterator implements Iterator<String> {
private final Iterator<String> delegate;
private final class TimelineMaskingIterable implements Iterable<Timeline> {
private final boolean generic;
public WrappedMdlTokenIterator(final Iterator<String> delegate) {
this.delegate = delegate;
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.delegate.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() {
final String token = this.delegate.next();
if (!this.hasLoaded) {
this.next = read();
}
this.hasLoaded = false;
return this.next;
}
return null;
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;
}
}

View File

@ -0,0 +1,319 @@
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;
}
}

View File

@ -0,0 +1,102 @@
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();
}
}

View File

@ -0,0 +1,29 @@
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

@ -0,0 +1,195 @@
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();
}
}

View File

@ -0,0 +1,166 @@
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 Light extends GenericObject {
private int type = -1;
private final float[] attenuation = new float[2];
private final float[] color = new float[3];
private float intensity = 0;
private final float[] ambientColor = new float[3];
private float ambientIntensity = 0;
public Light() {
super(0x200);
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
super.readMdx(stream);
this.type = stream.readInt(); // UInt32 in JS
ParseUtils.readFloatArray(stream, this.attenuation);
ParseUtils.readFloatArray(stream, this.color);
this.intensity = stream.readFloat();
ParseUtils.readFloatArray(stream, this.ambientColor);
this.ambientIntensity = stream.readFloat();
readTimelines(stream, size - this.getByteLength());
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
super.writeMdx(stream);
ParseUtils.writeUInt32(stream, this.type);
ParseUtils.writeFloatArray(stream, this.attenuation);
ParseUtils.writeFloatArray(stream, this.color);
stream.writeFloat(this.intensity);
ParseUtils.writeFloatArray(stream, this.ambientColor);
stream.writeFloat(this.ambientIntensity);
writeNonGenericAnimationChunks(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : super.readMdlGeneric(stream)) {
switch (token) {
case MdlUtils.TOKEN_OMNIDIRECTIONAL:
this.type = 0;
break;
case MdlUtils.TOKEN_DIRECTIONAL:
this.type = 1;
break;
case MdlUtils.TOKEN_AMBIENT:
this.type = 2;
break;
case MdlUtils.TOKEN_STATIC_ATTENUATION_START:
this.attenuation[0] = stream.readFloat();
break;
case MdlUtils.TOKEN_ATTENUATION_START:
readTimeline(stream, AnimationMap.KLAS);
break;
case MdlUtils.TOKEN_STATIC_ATTENUATION_END:
this.attenuation[1] = stream.readFloat();
break;
case MdlUtils.TOKEN_ATTENUATION_END:
readTimeline(stream, AnimationMap.KLAE);
break;
case MdlUtils.TOKEN_STATIC_INTENSITY:
this.intensity = stream.readFloat();
break;
case MdlUtils.TOKEN_INTENSITY:
readTimeline(stream, AnimationMap.KLAI);
break;
case MdlUtils.TOKEN_STATIC_COLOR:
stream.readColor(this.color);
break;
case MdlUtils.TOKEN_COLOR:
readTimeline(stream, AnimationMap.KLAC);
break;
case MdlUtils.TOKEN_STATIC_AMB_INTENSITY:
this.ambientIntensity = stream.readFloat();
break;
case MdlUtils.TOKEN_AMB_INTENSITY:
readTimeline(stream, AnimationMap.KLBI);
break;
case MdlUtils.TOKEN_STATIC_AMB_COLOR:
stream.readColor(this.ambientColor);
break;
case MdlUtils.TOKEN_AMB_COLOR:
readTimeline(stream, AnimationMap.KLBC);
break;
case MdlUtils.TOKEN_VISIBILITY:
readTimeline(stream, AnimationMap.KLAV);
break;
default:
throw new IllegalStateException("Unknown token in Light: " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_LIGHT, this.name);
writeGenericHeader(stream);
switch (this.type) {
case 0:
stream.writeFlag(MdlUtils.TOKEN_OMNIDIRECTIONAL);
break;
case 1:
stream.writeFlag(MdlUtils.TOKEN_DIRECTIONAL);
break;
case 2:
stream.writeFlag(MdlUtils.TOKEN_AMBIENT);
break;
default:
throw new IllegalStateException("Unable to save Light of type: " + this.type);
}
if (!writeTimeline(stream, AnimationMap.KLAS)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ATTENUATION_START, this.attenuation[0]);
}
if (!writeTimeline(stream, AnimationMap.KLAE)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ATTENUATION_END, this.attenuation[1]);
}
if (!writeTimeline(stream, AnimationMap.KLAI)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_INTENSITY, this.intensity);
}
if (!writeTimeline(stream, AnimationMap.KLAC)) {
stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR, this.color);
}
if (!writeTimeline(stream, AnimationMap.KLBI)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_AMB_INTENSITY, this.ambientIntensity);
}
if (!writeTimeline(stream, AnimationMap.KLBC)) {
stream.writeColor(MdlUtils.TOKEN_STATIC_AMB_COLOR, this.ambientColor);
}
writeTimeline(stream, AnimationMap.KLAV);
writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 48 + super.getByteLength();
}
}

View File

@ -0,0 +1,121 @@
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;
}
}

View File

@ -1,7 +1,5 @@
package com.etheller.warsmash.parsers.mdlx;
import java.util.Iterator;
public interface MdlTokenInputStream {
String read();
@ -11,9 +9,27 @@ public interface MdlTokenInputStream {
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();
Iterator<String> readBlock();
// 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

@ -11,11 +11,49 @@ public interface MdlTokenOutputStream {
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,125 @@
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

@ -0,0 +1,642 @@
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 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_TEXTURE_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;
}
}

View File

@ -0,0 +1,64 @@
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

@ -0,0 +1,193 @@
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 ParticleEmitter extends GenericObject {
private float emissionRate = 0;
private float gravity = 0;
private float longitude = 0;
private float latitude = 0;
private String path = "";
private float lifeSpan = 0;
private float speed = 0;
public ParticleEmitter() {
super(0x1000);
}
/**
* 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.emissionRate = stream.readFloat();
this.gravity = stream.readFloat();
this.longitude = stream.readFloat();
this.latitude = stream.readFloat();
this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP);
this.lifeSpan = stream.readFloat();
this.speed = stream.readFloat();
readTimelines(stream, size - this.getByteLength());
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
super.writeMdx(stream);
stream.writeFloat(this.emissionRate);
stream.writeFloat(this.gravity);
stream.writeFloat(this.longitude);
stream.writeFloat(this.latitude);
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);
}
stream.writeFloat(this.lifeSpan);
stream.writeFloat(this.speed);
writeNonGenericAnimationChunks(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : super.readMdlGeneric(stream)) {
switch (token) {
case MdlUtils.TOKEN_EMITTER_USES_MDL:
this.flags |= 0x8000;
break;
case MdlUtils.TOKEN_EMITTER_USES_TGA:
this.flags |= 0x10000;
break;
case MdlUtils.TOKEN_STATIC_EMISSION_RATE:
this.emissionRate = stream.readFloat();
break;
case MdlUtils.TOKEN_EMISSION_RATE:
readTimeline(stream, AnimationMap.KPEE);
break;
case MdlUtils.TOKEN_STATIC_GRAVITY:
this.gravity = stream.readFloat();
break;
case MdlUtils.TOKEN_GRAVITY:
readTimeline(stream, AnimationMap.KPEG);
break;
case MdlUtils.TOKEN_STATIC_LONGITUDE:
this.longitude = stream.readFloat();
break;
case MdlUtils.TOKEN_LONGITUDE:
readTimeline(stream, AnimationMap.KPLN);
break;
case MdlUtils.TOKEN_STATIC_LATITUDE:
this.latitude = stream.readFloat();
break;
case MdlUtils.TOKEN_LATITUDE:
readTimeline(stream, AnimationMap.KPLT);
break;
case MdlUtils.TOKEN_VISIBILITY:
readTimeline(stream, AnimationMap.KPEV);
break;
case MdlUtils.TOKEN_PARTICLE:
final Iterator<String> iterator = readAnimatedBlock(stream);
while (iterator.hasNext()) {
final String subToken = iterator.next();
switch (subToken) {
case MdlUtils.TOKEN_STATIC_LIFE_SPAN:
this.lifeSpan = stream.readFloat();
break;
case MdlUtils.TOKEN_LIFE_SPAN:
readTimeline(stream, AnimationMap.KPEL);
break;
case MdlUtils.TOKEN_STATIC_INIT_VELOCITY:
this.speed = stream.readFloat();
break;
case MdlUtils.TOKEN_INIT_VELOCITY:
readTimeline(stream, AnimationMap.KPES);
break;
case MdlUtils.TOKEN_PATH:
this.path = stream.read();
break;
default:
throw new IllegalStateException(
"Unknown token in ParticleEmitter " + this.name + "'s Particle: " + subToken);
}
}
break;
default:
throw new IllegalStateException("Unknown token in ParticleEmitter " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER, this.name);
writeGenericHeader(stream);
if ((this.flags & 0x8000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_EMITTER_USES_MDL);
}
if ((this.flags & 0x10000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_EMITTER_USES_TGA);
}
if (!this.writeTimeline(stream, AnimationMap.KPEE)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_EMISSION_RATE, this.emissionRate);
}
if (!this.writeTimeline(stream, AnimationMap.KPEG)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_GRAVITY, this.gravity);
}
if (!this.writeTimeline(stream, AnimationMap.KPLN)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LONGITUDE, this.longitude);
}
if (!this.writeTimeline(stream, AnimationMap.KPLT)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LATITUDE, this.latitude);
}
this.writeTimeline(stream, AnimationMap.KPEV);
stream.startBlock(MdlUtils.TOKEN_PARTICLE);
if (!this.writeTimeline(stream, AnimationMap.KPEL)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LIFE_SPAN, this.lifeSpan);
}
if (!this.writeTimeline(stream, AnimationMap.KPES)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_INIT_VELOCITY, this.speed);
}
if (((this.flags & 0x8000) != 0) || ((this.flags & 0x10000) != 0)) {
stream.writeAttrib(MdlUtils.TOKEN_PATH, this.path);
}
stream.endBlock();
writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 288 + super.getByteLength();
}
}

View File

@ -0,0 +1,425 @@
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 ParticleEmitter2 extends GenericObject {
// 0: blend
// 1: additive
// 2: modulate
// 3: modulate 2x
// 4: alphakey
public static enum FilterMode {
BLEND("Blend"),
ADDITIVE("Additive"),
MODULATE("Modulate"),
MODULATE2X("Modulate2x"),
ALPHAKEY("AlphaKey");
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 float speed = 0;
private float variation = 0;
private float latitude = 0;
private float gravity = 0;
private float lifeSpan = 0;
private float emissionRate = 0;
private float length;
private float width;
private FilterMode filterMode = FilterMode.BLEND;
private long rows = 0;
private long columns = 0;
private long headOrTail = 0;
private float tailLength = 0;
private float timeMiddle = 0;
private final float[][] segmentColors = new float[3][3];
private final short[] segmentAlphas = new short[3]; // unsigned byte[]
private final float[] segmentScaling = new float[3];
private final long[][] headIntervals = new long[2][3];
private final long[][] tailIntervals = new long[2][3];
private int textureId = -1;
private long squirt = 0;
private int priorityPlane = 0;
private long replaceableId = 0;
public ParticleEmitter2() {
super(0x1000);
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
super.readMdx(stream);
this.speed = stream.readFloat();
this.variation = stream.readFloat();
this.latitude = stream.readFloat();
this.gravity = stream.readFloat();
this.lifeSpan = stream.readFloat();
this.emissionRate = stream.readFloat();
this.length = stream.readFloat();
this.width = stream.readFloat();
this.filterMode = FilterMode.fromId((int) (ParseUtils.readUInt32(stream)));
this.rows = ParseUtils.readUInt32(stream);
this.columns = ParseUtils.readUInt32(stream);
this.headOrTail = ParseUtils.readUInt32(stream);
this.tailLength = stream.readFloat();
this.timeMiddle = stream.readFloat();
ParseUtils.readFloatArray(stream, this.segmentColors[0]);
ParseUtils.readFloatArray(stream, this.segmentColors[1]);
ParseUtils.readFloatArray(stream, this.segmentColors[2]);
ParseUtils.readUInt8Array(stream, this.segmentAlphas);
ParseUtils.readFloatArray(stream, this.segmentScaling);
ParseUtils.readUInt32Array(stream, this.headIntervals[0]);
ParseUtils.readUInt32Array(stream, this.headIntervals[1]);
ParseUtils.readUInt32Array(stream, this.tailIntervals[0]);
ParseUtils.readUInt32Array(stream, this.tailIntervals[1]);
this.textureId = stream.readInt();
this.squirt = ParseUtils.readUInt32(stream);
this.priorityPlane = stream.readInt();
this.replaceableId = ParseUtils.readUInt32(stream);
readTimelines(stream, size - this.getByteLength());
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
super.writeMdx(stream);
stream.writeFloat(this.speed);
stream.writeFloat(this.variation);
stream.writeFloat(this.latitude);
stream.writeFloat(this.gravity);
stream.writeFloat(this.lifeSpan);
stream.writeFloat(this.emissionRate);
stream.writeFloat(this.length);
stream.writeFloat(this.width);
ParseUtils.writeUInt32(stream, this.filterMode.ordinal());
ParseUtils.writeUInt32(stream, this.rows);
ParseUtils.writeUInt32(stream, this.columns);
ParseUtils.writeUInt32(stream, this.headOrTail);
stream.writeFloat(this.tailLength);
stream.writeFloat(this.timeMiddle);
ParseUtils.writeFloatArray(stream, this.segmentColors[0]);
ParseUtils.writeFloatArray(stream, this.segmentColors[1]);
ParseUtils.writeFloatArray(stream, this.segmentColors[2]);
ParseUtils.writeUInt8Array(stream, this.segmentAlphas);
ParseUtils.writeFloatArray(stream, this.segmentScaling);
ParseUtils.writeUInt32Array(stream, this.headIntervals[0]);
ParseUtils.writeUInt32Array(stream, this.headIntervals[1]);
ParseUtils.writeUInt32Array(stream, this.tailIntervals[0]);
ParseUtils.writeUInt32Array(stream, this.tailIntervals[1]);
stream.writeInt(this.textureId);
ParseUtils.writeUInt32(stream, this.squirt);
stream.writeInt(this.priorityPlane);
ParseUtils.writeUInt32(stream, this.replaceableId);
writeNonGenericAnimationChunks(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : super.readMdlGeneric(stream)) {
switch (token) {
case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z:
this.flags |= 0x10000;
break;
case MdlUtils.TOKEN_UNSHADED:
this.flags |= 0x8000;
break;
case MdlUtils.TOKEN_LINE_EMITTER:
this.flags |= 0x20000;
break;
case MdlUtils.TOKEN_UNFOGGED:
this.flags |= 0x40000;
break;
case MdlUtils.TOKEN_MODEL_SPACE:
this.flags |= 0x80000;
break;
case MdlUtils.TOKEN_XY_QUAD:
this.flags |= 0x100000;
break;
case MdlUtils.TOKEN_STATIC_SPEED:
this.speed = stream.readFloat();
break;
case MdlUtils.TOKEN_SPEED:
readTimeline(stream, AnimationMap.KP2S);
break;
case MdlUtils.TOKEN_STATIC_VARIATION:
this.variation = stream.readFloat();
break;
case MdlUtils.TOKEN_VARIATION:
readTimeline(stream, AnimationMap.KP2R);
break;
case MdlUtils.TOKEN_STATIC_LATITUDE:
this.latitude = stream.readFloat();
break;
case MdlUtils.TOKEN_LATITUDE:
readTimeline(stream, AnimationMap.KP2L);
break;
case MdlUtils.TOKEN_STATIC_GRAVITY:
this.gravity = stream.readFloat();
break;
case MdlUtils.TOKEN_GRAVITY:
readTimeline(stream, AnimationMap.KP2G);
break;
case MdlUtils.TOKEN_VISIBILITY:
readTimeline(stream, AnimationMap.KP2V);
break;
case MdlUtils.TOKEN_SQUIRT:
this.squirt = 1;
break;
case MdlUtils.TOKEN_LIFE_SPAN:
this.lifeSpan = stream.readFloat();
break;
case MdlUtils.TOKEN_STATIC_EMISSION_RATE:
this.emissionRate = stream.readFloat();
break;
case MdlUtils.TOKEN_EMISSION_RATE:
readTimeline(stream, AnimationMap.KP2E);
break;
case MdlUtils.TOKEN_STATIC_WIDTH:
this.width = stream.readFloat();
break;
case MdlUtils.TOKEN_WIDTH:
readTimeline(stream, AnimationMap.KP2W);
break;
case MdlUtils.TOKEN_STATIC_LENGTH:
this.length = stream.readFloat();
break;
case MdlUtils.TOKEN_LENGTH:
readTimeline(stream, AnimationMap.KP2N);
break;
case MdlUtils.TOKEN_BLEND:
this.filterMode = FilterMode.BLEND;
break;
case MdlUtils.TOKEN_ADDITIVE:
this.filterMode = FilterMode.ADDITIVE;
break;
case MdlUtils.TOKEN_MODULATE:
this.filterMode = FilterMode.MODULATE;
break;
case MdlUtils.TOKEN_MODULATE2X:
this.filterMode = FilterMode.MODULATE2X;
break;
case MdlUtils.TOKEN_ALPHAKEY:
this.filterMode = FilterMode.ALPHAKEY;
break;
case MdlUtils.TOKEN_ROWS:
this.rows = stream.readUInt32();
break;
case MdlUtils.TOKEN_COLUMNS:
this.columns = stream.readUInt32();
break;
case MdlUtils.TOKEN_HEAD:
this.headOrTail = 0;
break;
case MdlUtils.TOKEN_TAIL:
this.headOrTail = 1;
break;
case MdlUtils.TOKEN_BOTH:
this.headOrTail = 2;
break;
case MdlUtils.TOKEN_TAIL_LENGTH:
this.tailLength = stream.readFloat();
break;
case MdlUtils.TOKEN_TIME:
this.timeMiddle = stream.readFloat();
break;
case MdlUtils.TOKEN_SEGMENT_COLOR:
stream.read(); // {
for (int i = 0; i < 3; i++) {
stream.read(); // Color
stream.readColor(this.segmentColors[i]);
}
stream.read(); // }
break;
case MdlUtils.TOKEN_ALPHA:
stream.readUInt8Array(this.segmentAlphas);
break;
case MdlUtils.TOKEN_PARTICLE_SCALING:
stream.readFloatArray(this.segmentScaling);
break;
case MdlUtils.TOKEN_LIFE_SPAN_UV_ANIM:
stream.readIntArray(this.headIntervals[0]);
break;
case MdlUtils.TOKEN_DECAY_UV_ANIM:
stream.readIntArray(this.headIntervals[1]);
break;
case MdlUtils.TOKEN_TAIL_UV_ANIM:
stream.readIntArray(this.tailIntervals[0]);
break;
case MdlUtils.TOKEN_TAIL_DECAY_UV_ANIM:
stream.readIntArray(this.tailIntervals[1]);
break;
case MdlUtils.TOKEN_TEXTURE_ID:
this.textureId = stream.readInt();
break;
case MdlUtils.TOKEN_REPLACEABLE_ID:
this.replaceableId = stream.readUInt32();
break;
case MdlUtils.TOKEN_PRIORITY_PLANE:
this.priorityPlane = stream.readInt();
break;
default:
throw new IllegalStateException("Unknown token in ParticleEmitter2 " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER2, this.name);
writeGenericHeader(stream);
if ((this.flags & 0x10000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_FAR_Z);
}
if ((this.flags & 0x8000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_UNSHADED);
}
if ((this.flags & 0x20000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_LINE_EMITTER);
}
if ((this.flags & 0x40000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_UNFOGGED);
}
if ((this.flags & 0x80000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_MODEL_SPACE);
}
if ((this.flags & 0x100000) != 0) {
stream.writeFlag(MdlUtils.TOKEN_XY_QUAD);
}
if (!this.writeTimeline(stream, AnimationMap.KP2S)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_SPEED, this.speed);
}
if (!this.writeTimeline(stream, AnimationMap.KP2R)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_VARIATION, this.variation);
}
if (!this.writeTimeline(stream, AnimationMap.KP2L)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LATITUDE, this.latitude);
}
if (!this.writeTimeline(stream, AnimationMap.KP2G)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_GRAVITY, this.gravity);
}
writeTimeline(stream, AnimationMap.KP2V);
if (this.squirt != 0) {
stream.writeFlag(MdlUtils.TOKEN_SQUIRT);
}
stream.writeFloatAttrib(MdlUtils.TOKEN_LIFE_SPAN, this.lifeSpan);
if (!this.writeTimeline(stream, AnimationMap.KP2E)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_EMISSION_RATE, this.emissionRate);
}
if (!this.writeTimeline(stream, AnimationMap.KP2W)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_WIDTH, this.width);
}
if (!this.writeTimeline(stream, AnimationMap.KP2N)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LENGTH, this.length);
}
stream.writeFlag(this.filterMode.getMdlText());
stream.writeAttribUInt32(MdlUtils.TOKEN_ROWS, this.rows);
stream.writeAttribUInt32(MdlUtils.TOKEN_COLUMNS, this.columns);
switch ((int) this.headOrTail) {
case 0:
stream.writeFlag(MdlUtils.TOKEN_HEAD);
break;
case 1:
stream.writeFlag(MdlUtils.TOKEN_TAIL);
break;
case 2:
stream.writeFlag(MdlUtils.TOKEN_BOTH);
break;
default:
throw new IllegalStateException("Bad headOrTail value when saving MDL: " + this.headOrTail);
}
stream.writeFloatAttrib(MdlUtils.TOKEN_TAIL_LENGTH, this.tailLength);
stream.writeFloatAttrib(MdlUtils.TOKEN_TIME, this.timeMiddle);
stream.startBlock(MdlUtils.TOKEN_SEGMENT_COLOR);
stream.writeColor(MdlUtils.TOKEN_COLOR, this.segmentColors[0]);
stream.writeColor(MdlUtils.TOKEN_COLOR, this.segmentColors[1]);
stream.writeColor(MdlUtils.TOKEN_COLOR, this.segmentColors[2]);
stream.endBlockComma();
stream.writeArrayAttrib(MdlUtils.TOKEN_ALPHA, this.segmentAlphas);
stream.writeFloatArrayAttrib(MdlUtils.TOKEN_PARTICLE_SCALING, this.segmentScaling);
stream.writeArrayAttrib(MdlUtils.TOKEN_LIFE_SPAN_UV_ANIM, this.headIntervals[0]);
stream.writeArrayAttrib(MdlUtils.TOKEN_DECAY_UV_ANIM, this.headIntervals[1]);
stream.writeArrayAttrib(MdlUtils.TOKEN_TAIL_UV_ANIM, this.tailIntervals[0]);
stream.writeArrayAttrib(MdlUtils.TOKEN_TAIL_DECAY_UV_ANIM, this.tailIntervals[1]);
stream.writeAttrib(MdlUtils.TOKEN_TEXTURE_ID, this.textureId);
if (this.replaceableId != 0) {
stream.writeAttribUInt32(MdlUtils.TOKEN_REPLACEABLE_ID, this.replaceableId);
}
if (this.priorityPlane != 0) {
stream.writeAttrib(MdlUtils.TOKEN_PRIORITY_PLANE, this.priorityPlane);
}
writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 175 + super.getByteLength();
}
}

View File

@ -0,0 +1,176 @@
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 RibbonEmitter extends GenericObject {
private float heightAbove = 0;
private float heightBelow = 0;
private float alpha = 0;
private final float[] color = new float[3];
private float lifeSpan = 0;
private long textureSlot = 0;
private long emissionRate = 0;
private long rows = 0;
private long columns = 0;
private int materialId = 0;
private float gravity = 0;
public RibbonEmitter() {
super(0x4000);
}
@Override
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
final long size = ParseUtils.readUInt32(stream);
super.readMdx(stream);
this.heightAbove = stream.readFloat();
this.heightBelow = stream.readFloat();
this.alpha = stream.readFloat();
ParseUtils.readFloatArray(stream, this.color);
this.lifeSpan = stream.readFloat();
this.textureSlot = ParseUtils.readUInt32(stream);
this.emissionRate = ParseUtils.readUInt32(stream);
this.rows = ParseUtils.readUInt32(stream);
this.columns = ParseUtils.readUInt32(stream);
this.materialId = stream.readInt();
this.gravity = stream.readFloat();
readTimelines(stream, size - getByteLength());
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, getByteLength());
super.writeMdx(stream);
stream.writeFloat(this.heightAbove);
stream.writeFloat(this.heightBelow);
stream.writeFloat(this.alpha);
ParseUtils.writeFloatArray(stream, this.color);
stream.writeFloat(this.lifeSpan);
ParseUtils.writeUInt32(stream, this.textureSlot);
ParseUtils.writeUInt32(stream, this.emissionRate);
ParseUtils.writeUInt32(stream, this.rows);
ParseUtils.writeUInt32(stream, this.columns);
stream.writeInt(this.materialId);
stream.writeFloat(this.gravity);
writeNonGenericAnimationChunks(stream);
}
@Override
public void readMdl(final MdlTokenInputStream stream) throws IOException {
for (final String token : super.readMdlGeneric(stream)) {
switch (token) {
case MdlUtils.TOKEN_STATIC_HEIGHT_ABOVE:
this.heightAbove = stream.readFloat();
break;
case MdlUtils.TOKEN_HEIGHT_ABOVE:
readTimeline(stream, AnimationMap.KRHA);
break;
case MdlUtils.TOKEN_STATIC_HEIGHT_BELOW:
this.heightBelow = stream.readFloat();
break;
case MdlUtils.TOKEN_HEIGHT_BELOW:
readTimeline(stream, AnimationMap.KRHB);
break;
case MdlUtils.TOKEN_STATIC_ALPHA:
this.alpha = stream.readFloat();
break;
case MdlUtils.TOKEN_ALPHA:
readTimeline(stream, AnimationMap.KRAL);
break;
case MdlUtils.TOKEN_STATIC_COLOR:
stream.readColor(this.color);
break;
case MdlUtils.TOKEN_COLOR:
readTimeline(stream, AnimationMap.KRCO);
break;
case MdlUtils.TOKEN_STATIC_TEXTURE_SLOT:
this.textureSlot = stream.readUInt32();
break;
case MdlUtils.TOKEN_TEXTURE_SLOT:
readTimeline(stream, AnimationMap.KRTX);
break;
case MdlUtils.TOKEN_VISIBILITY:
readTimeline(stream, AnimationMap.KRVS);
break;
case MdlUtils.TOKEN_EMISSION_RATE:
this.emissionRate = stream.readUInt32();
break;
case MdlUtils.TOKEN_LIFE_SPAN:
this.lifeSpan = stream.readFloat();
break;
case MdlUtils.TOKEN_GRAVITY:
this.gravity = stream.readFloat();
break;
case MdlUtils.TOKEN_ROWS:
this.rows = stream.readUInt32();
break;
case MdlUtils.TOKEN_COLUMNS:
this.columns = stream.readUInt32();
break;
case MdlUtils.TOKEN_MATERIAL_ID:
this.materialId = stream.readInt();
break;
default:
throw new IllegalStateException("Unknown token in RibbonEmitter " + this.name + ": " + token);
}
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startObjectBlock(MdlUtils.TOKEN_RIBBON_EMITTER, this.name);
writeGenericHeader(stream);
if (!writeTimeline(stream, AnimationMap.KRHA)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_HEIGHT_ABOVE, this.heightAbove);
}
if (!writeTimeline(stream, AnimationMap.KRHB)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_HEIGHT_BELOW, this.heightBelow);
}
if (!writeTimeline(stream, AnimationMap.KRAL)) {
stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha);
}
if (!writeTimeline(stream, AnimationMap.KRCO)) {
stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR, this.color);
}
if (!writeTimeline(stream, AnimationMap.KRTX)) {
stream.writeAttribUInt32(MdlUtils.TOKEN_STATIC_TEXTURE_SLOT, this.textureSlot);
}
writeTimeline(stream, AnimationMap.KRVS);
stream.writeAttribUInt32(MdlUtils.TOKEN_EMISSION_RATE, this.emissionRate);
stream.writeFloatAttrib(MdlUtils.TOKEN_LIFE_SPAN, this.lifeSpan);
if (this.gravity != 0) {
stream.writeFloatAttrib(MdlUtils.TOKEN_GRAVITY, this.gravity);
}
stream.writeAttribUInt32(MdlUtils.TOKEN_ROWS, this.rows);
stream.writeAttribUInt32(MdlUtils.TOKEN_COLUMNS, this.columns);
stream.writeAttrib(MdlUtils.TOKEN_MATERIAL_ID, this.materialId);
writeGenericTimelines(stream);
stream.endBlock();
}
@Override
public long getByteLength() {
return 56 + super.getByteLength();
}
}

View File

@ -0,0 +1,104 @@
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 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();
/**
* 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);
}
@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);
}
}
}
@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();
}
}

View File

@ -0,0 +1,81 @@
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();
}
}

View File

@ -0,0 +1,57 @@
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

@ -0,0 +1,35 @@
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

@ -0,0 +1,232 @@
package com.etheller.warsmash.parsers.mdlx.mdl;
import java.nio.ByteBuffer;
import java.util.Iterator;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
public class GhostwolfTokenInputStream implements MdlTokenInputStream {
private final ByteBuffer buffer;
private int index;
private final int ident;
private final int fractionDigits;
public GhostwolfTokenInputStream(final ByteBuffer buffer) {
this.buffer = buffer;
this.index = 0;
this.ident = 0; // Used for writing blocks nicely.
this.fractionDigits = 6; // The number of fraction digits when writing floats.
}
@Override
public String read() {
boolean inComment = false;
boolean inString = false;
final StringBuilder token = new StringBuilder();
final int length = this.buffer.remaining();
while (this.index < length) {
// Note: cast from 'byte' to 'char' will cause Java incompatibility with Chinese
// and Russian/Cyrillic and others
final char c = (char) this.buffer.get(this.buffer.position() + this.index++);
if (inComment) {
if (c == '\n') {
inComment = false;
}
}
else if (inString) {
if (c == '"') {
return token.toString();
}
else {
token.append(c);
}
}
else if ((c == ' ') || (c == ',') || (c == '\t') || (c == '\n') || (c == ':') || (c == '\r')) {
if (token.length() > 0) {
return token.toString();
}
}
else if ((c == '{') || (c == '}')) {
if (token.length() > 0) {
this.index--;
return token.toString();
}
else {
return Character.toString(c);
}
}
else if ((c == '/') && (this.buffer.get(this.buffer.position() + this.index) == '/')) {
if (token.length() > 0) {
this.index--;
return token.toString();
}
else {
inComment = true;
}
}
else if (c == '"') {
if (token.length() > 0) {
this.index--;
return token.toString();
}
else {
inString = true;
}
}
else {
token.append(c);
}
}
return null;
}
@Override
public String peek() {
final int index = this.index;
final String value = this.read();
this.index = index;
return value;
}
@Override
public long readUInt32() {
return Long.parseLong(this.read());
}
@Override
public int readInt() {
return Integer.parseInt(this.read());
}
@Override
public float readFloat() {
return Float.parseFloat(this.read());
}
@Override
public void readIntArray(final long[] values) {
this.read(); // {
for (int i = 0, l = values.length; i < l; i++) {
values[i] = this.readInt();
}
this.read(); // }
}
@Override
public float[] readFloatArray(final float[] values) {
this.read(); // {
for (int i = 0, l = values.length; i < l; i++) {
values[i] = this.readFloat();
}
this.read(); // }
return values;
}
/**
* Read an MDL keyframe value. If the value is a scalar, it is just the number.
* If the value is a vector, it is enclosed with curly braces.
*
* @param {Float32Array|Uint32Array} value
*/
@Override
public void readKeyframe(final float[] values) {
if (values.length == 1) {
values[0] = this.readFloat();
}
else {
this.readFloatArray(values);
}
}
@Override
public float[] readVectorArray(final float[] array, final int vectorLength) {
this.read(); // {
for (int i = 0, l = array.length / vectorLength; i < l; i++) {
this.read(); // {
for (int j = 0; j < vectorLength; j++) {
array[(i * vectorLength) + j] = this.readFloat();
}
this.read(); // }
}
this.read(); // }
return array;
}
@Override
public Iterable<String> readBlock() {
this.read(); // {
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
String current;
private boolean hasLoaded = false;
@Override
public String next() {
if (!this.hasLoaded) {
hasNext();
}
this.hasLoaded = false;
return this.current;
}
@Override
public boolean hasNext() {
this.current = read();
this.hasLoaded = true;
return (this.current != null) && !this.current.equals("}");
}
};
}
};
}
@Override
public int[] readUInt16Array(final int[] values) {
this.read(); // {
for (int i = 0, l = values.length; i < l; i++) {
values[i] = this.readInt();
}
this.read(); // }
return values;
}
@Override
public short[] readUInt8Array(final short[] values) {
this.read(); // {
for (int i = 0, l = values.length; i < l; i++) {
values[i] = Short.parseShort(this.read());
}
this.read(); // }
return values;
}
@Override
public void readColor(final float[] color) {
this.read(); // {
color[2] = this.readFloat();
color[1] = this.readFloat();
color[0] = this.readFloat();
this.read(); // }
}
}

View File

@ -0,0 +1,261 @@
package com.etheller.warsmash.parsers.mdlx.mdl;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
public class GhostwolfTokenOutputStream implements MdlTokenOutputStream {
private final Appendable buffer;
private final int index;
private int ident;
private final int fractionDigits;
public GhostwolfTokenOutputStream(final Appendable appendable) {
this.buffer = appendable;
this.index = 0;
this.ident = 0; // Used for writing blocks nicely.
this.fractionDigits = 6; // The number of fraction digits when writing floats.
}
@Override
public void writeKeyframe(final String prefix, final long uInt32Value) {
writeAttribUInt32(prefix, uInt32Value);
}
@Override
public void writeKeyframe(final String prefix, final float floatValue) {
writeFloatAttrib(prefix, floatValue);
}
@Override
public void writeKeyframe(final String prefix, final float[] floatArrayValues) {
writeFloatArrayAttrib(prefix, floatArrayValues);
}
@Override
public void indent() {
this.ident += 1;
}
@Override
public void unindent() {
this.ident -= 1;
}
@Override
public void startObjectBlock(final String name, final String objectName) {
this.writeLine(name + " \"" + objectName + "\" {");
this.ident += 1;
}
@Override
public void startBlock(final String name, final int blockSize) {
this.writeLine(name + " " + blockSize + " {" + "");
this.ident += 1;
}
@Override
public void startBlock(final String name) {
this.writeLine(name + " {" + "");
this.ident += 1;
}
@Override
public void writeFlag(final String token) {
this.writeLine(token + ",");
}
@Override
public void writeFlagUInt32(final long flag) {
this.writeLine(flag + ",");
}
@Override
public void writeAttrib(final String string, final int globalSequenceId) {
writeLine(string + " " + globalSequenceId + ",");
}
@Override
public void writeAttribUInt32(final String attribName, final long uInt) {
writeLine(attribName + " " + uInt + ",");
}
@Override
public void writeAttrib(final String string, final String value) {
writeLine(string + " " + value + ",");
}
@Override
public void writeFloatAttrib(final String attribName, final float value) {
writeLine(attribName + " " + value + ",");
}
@Override
public void writeStringAttrib(final String attribName, final String value) {
writeLine(attribName + " \"" + value + "\",");
}
@Override
public void writeFloatArrayAttrib(final String attribName, final float[] floatArray) {
this.writeLine(attribName + " { " + formatFloatArray(floatArray) + " },");
}
@Override
public void writeLongSubArrayAttrib(final String attribName, final long[] array, final int startIndexInclusive,
final int endIndexExclusive) {
this.writeLine(attribName + " { " + formatLongSubArray(array, startIndexInclusive, endIndexExclusive) + " },");
}
@Override
public void writeFloatArray(final float[] floatArray) {
this.writeLine("{ " + formatFloatArray(floatArray) + " },");
}
public void writeFloatSubArray(final float[] floatArray, final int startIndexInclusive,
final int endIndexExclusive) {
this.writeLine("{ " + formatFloatSubArray(floatArray, startIndexInclusive, endIndexExclusive) + " },");
}
@Override
public void writeVectorArray(final String token, final float[] vectors, final int vectorLength) {
this.startBlock(token, vectors.length / vectorLength);
for (int i = 0, l = vectors.length; i < l; i += vectorLength) {
this.writeFloatSubArray(vectors, i, i + vectorLength);
}
this.endBlock();
}
@Override
public void endBlock() {
this.ident -= 1;
this.writeLine("}");
}
@Override
public void endBlockComma() {
this.ident -= 1;
this.writeLine("},");
}
@Override
public void writeLine(final String string) {
try {
for (int i = 0; i < this.ident; i++) {
this.buffer.append('\t');
}
this.buffer.append(string);
this.buffer.append('\n');
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void startBlock(final String tokenFaces, final int sizeNumberProbably, final int length) {
this.writeLine(tokenFaces + " " + sizeNumberProbably + " " + length + " {" + "");
this.ident += 1;
}
@Override
public void writeColor(final String tokenStaticColor, final float[] color) {
this.writeLine(tokenStaticColor + " { " + color[2] + ", " + color[1] + ", " + color[0] + " },");
}
@Override
public void writeArrayAttrib(final String tokenAlpha, final short[] uint8Array) {
this.writeLine(tokenAlpha + " { " + formatShortArray(uint8Array) + " },");
}
@Override
public void writeArrayAttrib(final String tokenAlpha, final int[] uint16Array) {
this.writeLine(tokenAlpha + " { " + formatIntArray(uint16Array) + " },");
}
@Override
public void writeArrayAttrib(final String tokenAlpha, final long[] uint32Array) {
this.writeLine(tokenAlpha + " { " + formatLongArray(uint32Array) + " },");
}
private String formatFloat(final float value) {
final String s = Float.toString(value);
final String f = String.format("%." + this.fractionDigits + "f", value);
if (s.length() > f.length()) {
return f;
}
else {
return s;
}
}
private String formatFloatArray(final float[] value) {
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, l = value.length; i < l; i++) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(formatFloat(value[i]));
}
return stringBuilder.toString();
}
private String formatLongArray(final long[] value) {
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, l = value.length; i < l; i++) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(value[i]);
}
return stringBuilder.toString();
}
private String formatShortArray(final short[] value) {
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, l = value.length; i < l; i++) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(value[i]);
}
return stringBuilder.toString();
}
private String formatIntArray(final int[] value) {
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, l = value.length; i < l; i++) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(value[i]);
}
return stringBuilder.toString();
}
private String formatLongSubArray(final long[] value, final int startIndexInclusive, final int endIndexExclusive) {
final StringBuilder stringBuilder = new StringBuilder();
for (int i = startIndexInclusive; i < endIndexExclusive; i++) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(value[i]);
}
return stringBuilder.toString();
}
private String formatFloatSubArray(final float[] value, final int startIndexInclusive,
final int endIndexExclusive) {
final StringBuilder stringBuilder = new StringBuilder();
for (int i = startIndexInclusive; i < endIndexExclusive; i++) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(formatFloat(value[i]));
}
return stringBuilder.toString();
}
}

View File

@ -39,7 +39,7 @@ public class FloatArrayKeyFrame implements KeyFrame {
@Override
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = ParseUtils.parseUInt32(stream);
this.time = ParseUtils.readUInt32(stream);
ParseUtils.readFloatArray(stream, this.value);
if (interpolationType.tangential()) {
ParseUtils.readFloatArray(stream, this.inTan);

View File

@ -33,7 +33,7 @@ public class FloatKeyFrame implements KeyFrame {
@Override
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = ParseUtils.parseUInt32(stream);
this.time = ParseUtils.readUInt32(stream);
this.value = stream.readFloat();
if (interpolationType.tangential()) {
this.inTan = stream.readFloat();

View File

@ -32,7 +32,7 @@ public abstract class Timeline implements Chunk {
public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException {
this.name = name;
final long keyFrameCount = ParseUtils.parseUInt32(stream);
final long keyFrameCount = ParseUtils.readUInt32(stream);
this.interpolationType = InterpolationType.VALUES[stream.readInt()];
this.globalSequenceId = stream.readInt();
@ -47,7 +47,7 @@ public abstract class Timeline implements Chunk {
}
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(this.name.getValue());
stream.writeInt(Integer.reverseBytes(this.name.getValue()));
stream.writeInt(this.keyFrames.size());
stream.writeInt(this.interpolationType.ordinal());
stream.writeInt(this.globalSequenceId);
@ -86,7 +86,7 @@ public abstract class Timeline implements Chunk {
this.interpolationType = interpolationType;
if (stream.peek().equals("GlobalSeqId")) {
if (stream.peek().equals(MdlUtils.TOKEN_GLOBAL_SEQ_ID)) {
stream.read();
this.globalSequenceId = stream.readInt();
}
@ -130,7 +130,7 @@ public abstract class Timeline implements Chunk {
stream.writeFlag(token);
if (this.globalSequenceId != -1) {
stream.writeAttrib("GlobalSeqId", this.globalSequenceId);
stream.writeAttrib(MdlUtils.TOKEN_GLOBAL_SEQ_ID, this.globalSequenceId);
}
for (final KeyFrame keyFrame : this.keyFrames) {

View File

@ -33,11 +33,11 @@ public class UInt32KeyFrame implements KeyFrame {
@Override
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = ParseUtils.parseUInt32(stream);
this.value = ParseUtils.parseUInt32(stream);
this.time = ParseUtils.readUInt32(stream);
this.value = ParseUtils.readUInt32(stream);
if (interpolationType.tangential()) {
this.inTan = ParseUtils.parseUInt32(stream);
this.outTan = ParseUtils.parseUInt32(stream);
this.inTan = ParseUtils.readUInt32(stream);
this.outTan = ParseUtils.readUInt32(stream);
}
}

View File

@ -0,0 +1,27 @@
package com.etheller.warsmash.parsers.terrain;
public class Corner {
boolean mapEdge;
int groundTexture;
float height;
float waterHeight;
boolean ramp;
boolean blight;
boolean water;
boolean boundary;
boolean cliff;
boolean romp;
int groundVariation;
int cliffVariation;
int cliffTexture;
int layerHeight;
public float finalGroundHeight() {
return 0;
}
public float finalWaterHeight() {
return 0;
}
}

View File

@ -0,0 +1,13 @@
package com.etheller.warsmash.parsers.terrain;
import com.badlogic.gdx.utils.FloatArray;
public class Terrain {
private FloatArray groundHeights;
private FloatArray groundCornerHeights;
private FloatArray waterHeights;
public Terrain() {
}
}

View File

@ -0,0 +1,15 @@
package com.etheller.warsmash.parsers.terrain;
public class TilePathing {
boolean unwalkable = false;
boolean unflyable = false;
boolean unbuildable = false;
public byte mask() {
byte mask = 0;
mask |= this.unwalkable ? 0b00000010 : 0;
mask |= this.unflyable ? 0b00000100 : 0;
mask |= this.unbuildable ? 0b00001000 : 0;
return mask;
}
}

View File

@ -0,0 +1,6 @@
package com.etheller.warsmash.util;
public interface Descriptor<E> {
E create();
}

View File

@ -0,0 +1,85 @@
package com.etheller.warsmash.util;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
/**
* Uses AWT stuff
*
*/
public final class ImageUtils {
private static final int BYTES_PER_PIXEL = 4;
public static Texture getTexture(final BufferedImage image) {
final int[] pixels = new int[image.getWidth() * image.getHeight()];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
// 4
// for
// RGBA,
// 3
// for
// RGB
final Pixmap pixmap = 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];
pixmap.drawPixel(x, y, (pixel << 8) | (pixel >>> 24));
}
}
return new Texture(pixmap);
}
/**
* Convert an input buffered image into sRGB color space using component values
* directly instead of performing a color space conversion.
*
* @param in Input image to be converted.
* @return Resulting sRGB image.
*/
public static BufferedImage forceBufferedImagesRGB(final BufferedImage in) {
// Resolve input ColorSpace.
final ColorSpace inCS = in.getColorModel().getColorSpace();
final ColorSpace sRGBCS = ColorSpace.getInstance(ColorSpace.CS_sRGB);
if (inCS == sRGBCS) {
// Already is sRGB.
return in;
}
if (inCS.getNumComponents() != sRGBCS.getNumComponents()) {
throw new IllegalArgumentException("Input color space has different number of components from sRGB.");
}
// Draw input.
final ColorModel lRGBModel = new ComponentColorModel(inCS, true, false, Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
final ColorModel sRGBModel = new ComponentColorModel(sRGBCS, true, false, Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
final BufferedImage lRGB = new BufferedImage(lRGBModel,
lRGBModel.createCompatibleWritableRaster(in.getWidth(), in.getHeight()), false, null);
final Graphics2D graphic = lRGB.createGraphics();
try {
graphic.drawImage(in, 0, 0, null);
}
finally {
graphic.dispose();
}
// Convert to sRGB.
final BufferedImage sRGB = new BufferedImage(sRGBModel, lRGB.getRaster(), false, null);
return sRGB;
}
private ImageUtils() {
}
}

View File

@ -1,8 +1,198 @@
package com.etheller.warsmash.util;
/**
* Constants for the tokens were used to prevent typos in token literals. It
* would be very easy for me to type "Interval" in one place and "Intreval" in
* another by mistake. With this paradigm, that mistake causes a compile error,
* since TOKEN_INTREVAL does not exist.
*/
public class MdlUtils {
public static final String TOKEN_VERSION = "Version";
public static final String TOKEN_MODEL = "Model";
public static final String TOKEN_SEQUENCES = "Sequences";
public static final String TOKEN_GLOBAL_SEQUENCES = "GlobalSequences";
public static final String TOKEN_INTERVAL = "Interval";
public static final String TOKEN_NONLOOPING = "NonLooping";
public static final String TOKEN_MOVESPEED = "MoveSpeed";
public static final String TOKEN_RARITY = "Rarity";
public static final String TOKEN_FORMAT_VERSION = "FormatVersion";
public static final String TOKEN_BLEND_TIME = "BlendTime";
public static final String TOKEN_DURATION = "Duration";
public static final String TOKEN_IMAGE = "Image";
public static final String TOKEN_WRAP_WIDTH = "WrapWidth";
public static final String TOKEN_WRAP_HEIGHT = "WrapHeight";
public static final String TOKEN_BITMAP = "Bitmap";
public static final String TOKEN_TVERTEX_ANIM_SPACE = "TVertexAnim ";
public static final String TOKEN_DONT_INTERP = "DontInterp";
public static final String TOKEN_LINEAR = "Linear";
public static final String TOKEN_HERMITE = "Hermite";
public static final String TOKEN_BEZIER = "Bezier";
public static final String TOKEN_GLOBAL_SEQ_ID = "GlobalSeqId";
public static final String TOKEN_PLANE = "Plane";
public static final String TOKEN_BOX = "Box";
public static final String TOKEN_SPHERE = "Sphere";
public static final String TOKEN_CYLINDER = "Cylinder";
public static final String TOKEN_GEOSETID = "GeosetId";
public static final String TOKEN_MULTIPLE = "Multiple";
public static final String TOKEN_GEOSETANIMID = "GeosetAnimId";
public static final String TOKEN_NONE = "None";
public static final String TOKEN_OBJECTID = "ObjectId";
public static final String TOKEN_PARENT = "Parent";
public static final String TOKEN_BILLBOARDED_LOCK_Z = "BillboardedLockZ";
public static final String TOKEN_BILLBOARDED_LOCK_Y = "BillboardedLockY";
public static final String TOKEN_BILLBOARDED_LOCK_X = "BillboardedLockX";
public static final String TOKEN_BILLBOARDED = "Billboarded";
public static final String TOKEN_CAMERA_ANCHORED = "CameraAnchored";
public static final String TOKEN_DONT_INHERIT = "DontInherit";
public static final String TOKEN_ROTATION = "Rotation";
public static final String TOKEN_TRANSLATION = "Translation";
public static final String TOKEN_SCALING = "Scaling";
public static final String TOKEN_STATIC = "static";
public static final String TOKEN_ATTACHMENT_ID = "AttachmentID";
public static final String TOKEN_PATH = "Path";
public static final String TOKEN_VISIBILITY = "Visibility";
public static final String TOKEN_POSITION = "Position";
public static final String TOKEN_FIELDOFVIEW = "FieldOfView";
public static final String TOKEN_FARCLIP = "FarClip";
public static final String TOKEN_NEARCLIP = "NearClip";
public static final String TOKEN_TARGET = "Target";
public static final String TOKEN_VERTICES = "Vertices";
public static final String TOKEN_BOUNDSRADIUS = "BoundsRadius";
public static final String TOKEN_EVENT_TRACK = "EventTrack";
public static final String TOKEN_MAXIMUM_EXTENT = "MaximumExtent";
public static final String TOKEN_MINIMUM_EXTENT = "MinimumExtent";
public static final String TOKEN_NORMALS = "Normals";
public static final String TOKEN_TVERTICES = "TVertices";
public static final String TOKEN_VERTEX_GROUP = "VertexGroup";
public static final String TOKEN_FACES = "Faces";
public static final String TOKEN_GROUPS = "Groups";
public static final String TOKEN_ANIM = "Anim";
public static final String TOKEN_MATERIAL_ID = "MaterialID";
public static final String TOKEN_SELECTION_GROUP = "SelectionGroup";
public static final String TOKEN_UNSELECTABLE = "Unselectable";
public static final String TOKEN_TRIANGLES = "Triangles";
public static final String TOKEN_MATRICES = "Matrices";
public static final String TOKEN_DROP_SHADOW = "DropShadow";
public static final String TOKEN_ALPHA = "Alpha";
public static final String TOKEN_COLOR = "Color";
public static final String TOKEN_STATIC_ALPHA = TOKEN_STATIC + " " + TOKEN_ALPHA;
public static final String TOKEN_STATIC_COLOR = TOKEN_STATIC + " " + TOKEN_COLOR;
public static final String TOKEN_FILTER_MODE = "FilterMode";
public static final String TOKEN_UNSHADED = "Unshaded";
public static final String TOKEN_SPHERE_ENV_MAP = "SphereEnvMap";
public static final String TOKEN_TWO_SIDED = "TwoSided";
public static final String TOKEN_UNFOGGED = "Unfogged";
public static final String TOKEN_NO_DEPTH_TEST = "NoDepthTest";
public static final String TOKEN_NO_DEPTH_SET = "NoDepthSet";
public static final String TOKEN_TEXTURE_ID = "TextureID";
public static final String TOKEN_STATIC_TEXTURE_ID = TOKEN_STATIC + " " + TOKEN_TEXTURE_ID;
public static final String TOKEN_TVERTEX_ANIM_ID = "TVertexAnimId";
public static final String TOKEN_COORD_ID = "CoordId";
public static final String TOKEN_OMNIDIRECTIONAL = "Omnidirectional";
public static final String TOKEN_DIRECTIONAL = "Directional";
public static final String TOKEN_AMBIENT = "Ambient";
public static final String TOKEN_ATTENUATION_START = "AttenuationStart";
public static final String TOKEN_STATIC_ATTENUATION_START = TOKEN_STATIC + " " + TOKEN_ATTENUATION_START;
public static final String TOKEN_ATTENUATION_END = "AttenuationEnd";
public static final String TOKEN_STATIC_ATTENUATION_END = TOKEN_STATIC + " " + TOKEN_ATTENUATION_END;
public static final String TOKEN_INTENSITY = "Intensity";
public static final String TOKEN_STATIC_INTENSITY = TOKEN_STATIC + " " + TOKEN_INTENSITY;
public static final String TOKEN_AMB_INTENSITY = "AmbIntensity";
public static final String TOKEN_STATIC_AMB_INTENSITY = TOKEN_STATIC + " " + TOKEN_AMB_INTENSITY;
public static final String TOKEN_AMB_COLOR = "AmbColor";
public static final String TOKEN_STATIC_AMB_COLOR = TOKEN_STATIC + " " + TOKEN_AMB_COLOR;
public static final String TOKEN_CONSTANT_COLOR = "ConstantColor";
public static final String TOKEN_SORT_PRIMS_NEAR_Z = "SortPrimsNearZ";
public static final String TOKEN_SORT_PRIMS_FAR_Z = "SortPrimsFarZ";
public static final String TOKEN_FULL_RESOLUTION = "FullResolution";
public static final String TOKEN_PRIORITY_PLANE = "PriorityPlane";
public static final String TOKEN_EMITTER_USES_MDL = "EmitterUsesMDL";
public static final String TOKEN_EMITTER_USES_TGA = "EmitterUsesTGA";
public static final String TOKEN_EMISSION_RATE = "EmissionRate";
public static final String TOKEN_STATIC_EMISSION_RATE = TOKEN_STATIC + " " + TOKEN_EMISSION_RATE;
public static final String TOKEN_GRAVITY = "Gravity";
public static final String TOKEN_STATIC_GRAVITY = TOKEN_STATIC + " " + TOKEN_GRAVITY;
public static final String TOKEN_LONGITUDE = "Longitude";
public static final String TOKEN_STATIC_LONGITUDE = TOKEN_STATIC + " " + TOKEN_LONGITUDE;
public static final String TOKEN_LATITUDE = "Latitude";
public static final String TOKEN_STATIC_LATITUDE = TOKEN_STATIC + " " + TOKEN_LATITUDE;
public static final String TOKEN_PARTICLE = "Particle";
public static final String TOKEN_LIFE_SPAN = "LifeSpan";
public static final String TOKEN_STATIC_LIFE_SPAN = TOKEN_STATIC + " " + TOKEN_LIFE_SPAN;
public static final String TOKEN_INIT_VELOCITY = "InitVelocity";
public static final String TOKEN_STATIC_INIT_VELOCITY = TOKEN_STATIC + " " + TOKEN_INIT_VELOCITY;
public static final String TOKEN_LINE_EMITTER = "LineEmitter";
public static final String TOKEN_MODEL_SPACE = "ModelSpace";
public static final String TOKEN_XY_QUAD = "XYQuad";
public static final String TOKEN_SPEED = "Speed";
public static final String TOKEN_STATIC_SPEED = TOKEN_STATIC + " " + TOKEN_SPEED;
public static final String TOKEN_VARIATION = "Variation";
public static final String TOKEN_STATIC_VARIATION = TOKEN_STATIC + " " + TOKEN_VARIATION;
public static final String TOKEN_SQUIRT = "Squirt";
public static final String TOKEN_WIDTH = "Width";
public static final String TOKEN_STATIC_WIDTH = TOKEN_STATIC + " " + TOKEN_WIDTH;
public static final String TOKEN_LENGTH = "Length";
public static final String TOKEN_STATIC_LENGTH = TOKEN_STATIC + " " + TOKEN_LENGTH;
public static final String TOKEN_ROWS = "Rows";
public static final String TOKEN_COLUMNS = "Columns";
public static final String TOKEN_HEAD = "Head";
public static final String TOKEN_TAIL = "Tail";
public static final String TOKEN_BOTH = "Both";
public static final String TOKEN_TAIL_LENGTH = "TailLength";
public static final String TOKEN_TIME = "Time";
public static final String TOKEN_SEGMENT_COLOR = "SegmentColor";
public static final String TOKEN_PARTICLE_SCALING = "ParticleScaling";
public static final String TOKEN_LIFE_SPAN_UV_ANIM = "LifeSpanUVAnim";
public static final String TOKEN_DECAY_UV_ANIM = "DecayUVAnim";
public static final String TOKEN_TAIL_UV_ANIM = "TailUVAnim";
public static final String TOKEN_TAIL_DECAY_UV_ANIM = "TailDecayUVAnim";
public static final String TOKEN_REPLACEABLE_ID = "ReplaceableId";
public static final String TOKEN_BLEND = "Blend";// ParticleEmitter2.FilterMode.BLEND.getMdlText();
public static final String TOKEN_ADDITIVE = "Additive";// ParticleEmitter2.FilterMode.ADDITIVE.getMdlText();
public static final String TOKEN_MODULATE = "Modulate";// ParticleEmitter2.FilterMode.MODULATE.getMdlText();
public static final String TOKEN_MODULATE2X = "Modulate2x";// ParticleEmitter2.FilterMode.MODULATE2X.getMdlText();
public static final String TOKEN_ALPHAKEY = "AlphaKey";// ParticleEmitter2.FilterMode.ALPHAKEY.getMdlText();
public static final String TOKEN_HEIGHT_ABOVE = "HeightAbove";
public static final String TOKEN_STATIC_HEIGHT_ABOVE = TOKEN_STATIC + " " + TOKEN_HEIGHT_ABOVE;
public static final String TOKEN_HEIGHT_BELOW = "HeightBelow";
public static final String TOKEN_STATIC_HEIGHT_BELOW = TOKEN_STATIC + " " + TOKEN_HEIGHT_BELOW;
public static final String TOKEN_TEXTURE_SLOT = "TextureSlot";
public static final String TOKEN_STATIC_TEXTURE_SLOT = TOKEN_STATIC + " " + TOKEN_TEXTURE_SLOT;
public static final String TOKEN_TEXTURES = "Textures";
public static final String TOKEN_MATERIALS = "Materials";
public static final String TOKEN_TEXTURE_ANIMS = "TextureAnims";
public static final String TOKEN_TEXTURE_ANIM = "TextureAnim";
public static final String TOKEN_PIVOT_POINTS = "PivotPoints";
public static final String TOKEN_ATTACHMENT = "Attachment";
public static final String TOKEN_BONE = "Bone";
public static final String TOKEN_CAMERA = "Camera";
public static final String TOKEN_COLLISION_SHAPE = "CollisionShape";
public static final String TOKEN_EVENT_OBJECT = "EventObject";
public static final String TOKEN_GEOSET = "Geoset";
public static final String TOKEN_GEOSETANIM = "GeosetAnim";
public static final String TOKEN_HELPER = "Helper";
public static final String TOKEN_LAYER = "Layer";
public static final String TOKEN_LIGHT = "Light";
public static final String TOKEN_MATERIAL = "Material";
public static final String TOKEN_PARTICLE_EMITTER = "ParticleEmitter";
public static final String TOKEN_PARTICLE_EMITTER2 = "ParticleEmitter2";
public static final String TOKEN_RIBBON_EMITTER = "RibbonEmitter";
}

View File

@ -1,13 +1,24 @@
package com.etheller.warsmash.util;
import java.io.IOException;
import java.nio.charset.Charset;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class ParseUtils {
public static long parseUInt32(final LittleEndianDataInputStream stream) throws IOException {
return stream.readInt() & 0xFFFFFFFF;
public static final Charset UTF8 = Charset.forName("utf-8");
public static long readUInt32(final LittleEndianDataInputStream stream) throws IOException {
return stream.readInt() & 0xFFFFFFFFL;
}
public static int readUInt16(final LittleEndianDataInputStream stream) throws IOException {
return stream.readShort() & 0xFFFF;
}
public static short readUInt8(final LittleEndianDataInputStream stream) throws IOException {
return (short) (stream.readByte() & (short) 0xFF);
}
public static void readFloatArray(final LittleEndianDataInputStream stream, final float[] array)
@ -17,10 +28,114 @@ public class ParseUtils {
}
}
public static float[] readFloatArray(final LittleEndianDataInputStream stream, final int length)
throws IOException {
final float[] array = new float[length];
readFloatArray(stream, array);
return array;
}
public static void readUInt32Array(final LittleEndianDataInputStream stream, final long[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
array[i] = readUInt32(stream);
}
}
public static long[] readUInt32Array(final LittleEndianDataInputStream stream, final int length)
throws IOException {
final long[] array = new long[length];
readUInt32Array(stream, array);
return array;
}
public static void readUInt16Array(final LittleEndianDataInputStream stream, final int[] array) throws IOException {
for (int i = 0; i < array.length; i++) {
array[i] = readUInt16(stream);
}
}
public static int[] readUInt16Array(final LittleEndianDataInputStream stream, final int length) throws IOException {
final int[] array = new int[length];
readUInt16Array(stream, array);
return array;
}
public static void readUInt8Array(final LittleEndianDataInputStream stream, final short[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
array[i] = readUInt8(stream);
}
}
public static short[] readUInt8Array(final LittleEndianDataInputStream stream, final int length)
throws IOException {
final short[] array = new short[length];
readUInt8Array(stream, array);
return array;
}
public static War3ID readWar3ID(final LittleEndianDataInputStream stream) throws IOException {
// final int value = stream.readInt();
// return new War3ID(((value & 0xFF000000) >>> 24) | ((value & 0x00FF0000) >>> 8) | ((value & 0x0000FF00) << 8)
// | ((value & 0x000000FF) << 24));
return new War3ID(Integer.reverseBytes(stream.readInt()));
}
public static void writeWar3ID(final LittleEndianDataOutputStream stream, final War3ID id) throws IOException {
// final int value = id.getValue();
// stream.writeInt(((value & 0xFF000000) >>> 24) | ((value & 0x00FF0000) >>> 8) | ((value & 0x0000FF00) << 8)
// | ((value & 0x000000FF) << 24));
stream.writeInt(Integer.reverseBytes(id.getValue()));
}
public static void writeFloatArray(final LittleEndianDataOutputStream stream, final float[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
stream.writeFloat(array[i]);
}
}
public static void writeUInt32(final LittleEndianDataOutputStream stream, final long uInt) throws IOException {
stream.writeInt((int) (uInt & 0xFFFFFFFF));
}
public static void writeUInt16(final LittleEndianDataOutputStream stream, final int uInt) throws IOException {
stream.writeShort((short) (uInt & 0xFFFF));
}
public static void writeUInt8(final LittleEndianDataOutputStream stream, final short uInt) throws IOException {
stream.writeByte((byte) (uInt & 0xFF));
}
public static void writeUInt32Array(final LittleEndianDataOutputStream stream, final long[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
writeUInt32(stream, array[i]);
}
}
public static void writeUInt16Array(final LittleEndianDataOutputStream stream, final int[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
writeUInt16(stream, array[i]);
}
}
public static void writeUInt8Array(final LittleEndianDataOutputStream stream, final short[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
writeUInt8(stream, array[i]);
}
}
public static String readString(final LittleEndianDataInputStream stream, final byte[] recycleByteArray)
throws IOException {
stream.read(recycleByteArray);
int i;
for (i = 0; (i < recycleByteArray.length) && (recycleByteArray[i] != 0); i++) {
}
final String name = new String(recycleByteArray, 0, i, ParseUtils.UTF8);
return name;
}
}

View File

@ -0,0 +1,290 @@
package com.etheller.warsmash.util;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
public enum RenderMathUtils {
;
public static final Quaternion QUAT_DEFAULT = new Quaternion(0, 0, 0, 1);
public static final Vector3 VEC3_ONE = new Vector3(1, 1, 1);
public static final Vector3 VEC3_UNIT_X = new Vector3(1, 0, 0);
public static final Vector3 VEC3_UNIT_Y = new Vector3(0, 1, 0);
public static final Vector3 VEC3_UNIT_Z = new Vector3(0, 0, 1);
// copied from ghostwolf and
// https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html
public static void fromRotationTranslationScaleOrigin(final Quaternion q, final Vector3 v, final Vector3 s,
final Matrix4 out, final Vector3 pivot) {
final float x = q.x;
final float y = q.y;
final float z = q.z;
final float w = q.w;
final float x2 = x + x;
final float y2 = y + y;
final float z2 = z + z;
final float xx = x * x2;
final float xy = x * y2;
final float xz = x * z2;
final float yy = y * y2;
final float yz = y * z2;
final float zz = z * z2;
final float wx = w * x2;
final float wy = w * y2;
final float wz = w * z2;
final float sx = s.x;
final float sy = s.y;
final float sz = s.z;
out.val[Matrix4.M00] = (1 - (yy + zz)) * sx;
out.val[Matrix4.M01] = (xy + wz) * sx;
out.val[Matrix4.M02] = (xz - wy) * sx;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = (xy - wz) * sy;
out.val[Matrix4.M11] = (1 - (xx + zz)) * sy;
out.val[Matrix4.M12] = (yz + wx) * sy;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = (xz + wy) * sz;
out.val[Matrix4.M21] = (yz - wx) * sz;
out.val[Matrix4.M22] = (1 - (xx + yy)) * sz;
out.val[Matrix4.M23] = 0;
out.val[Matrix4.M30] = (v.x + pivot.x) - ((out.val[Matrix4.M00] * pivot.x) + (out.val[Matrix4.M10] * pivot.y)
+ (out.val[Matrix4.M20] * pivot.z));
out.val[Matrix4.M31] = (v.y + pivot.y) - ((out.val[Matrix4.M01] * pivot.x) + (out.val[Matrix4.M11] * pivot.y)
+ (out.val[Matrix4.M21] * pivot.z));
out.val[Matrix4.M32] = (v.z + pivot.z) - ((out.val[Matrix4.M02] * pivot.x) + (out.val[Matrix4.M12] * pivot.y)
+ (out.val[Matrix4.M22] * pivot.z));
out.val[Matrix4.M33] = 1;
}
// copied from
// https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html
public static void fromRotationTranslationScale(final Quaternion q, final Vector3 v, final Vector3 s,
final Matrix4 out) {
final float x = q.x;
final float y = q.y;
final float z = q.z;
final float w = q.w;
final float x2 = x + x;
final float y2 = y + y;
final float z2 = z + z;
final float xx = x * x2;
final float xy = x * y2;
final float xz = x * z2;
final float yy = y * y2;
final float yz = y * z2;
final float zz = z * z2;
final float wx = w * x2;
final float wy = w * y2;
final float wz = w * z2;
final float sx = s.x;
final float sy = s.y;
final float sz = s.z;
out.val[Matrix4.M00] = (1 - (yy + zz)) * sx;
out.val[Matrix4.M01] = (xy + wz) * sx;
out.val[Matrix4.M02] = (xz - wy) * sx;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = (xy - wz) * sy;
out.val[Matrix4.M11] = (1 - (xx + zz)) * sy;
out.val[Matrix4.M12] = (yz + wx) * sy;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = (xz + wy) * sz;
out.val[Matrix4.M21] = (yz - wx) * sz;
out.val[Matrix4.M22] = (1 - (xx + yy)) * sz;
out.val[Matrix4.M23] = 0;
out.val[Matrix4.M30] = v.x;
out.val[Matrix4.M31] = v.y;
out.val[Matrix4.M32] = v.z;
out.val[Matrix4.M33] = 1;
}
public static void mul(final Matrix4 dest, final Matrix4 left, final Matrix4 right) {
dest.set(left); // TODO better performance here, remove the extra copying
dest.mul(right);
}
public static void mul(final Quaternion dest, final Quaternion left, final Quaternion right) {
dest.set(left); // TODO better performance here, remove the extra copying
dest.mul(right);
}
public static Quaternion rotateX(final Quaternion out, final Quaternion a, float rad) {
rad *= 0.5;
final float ax = a.x, ay = a.y, az = a.z, aw = a.w;
final float bx = (float) Math.sin(rad), bw = (float) Math.cos(rad);
out.x = (ax * bw) + (aw * bx);
out.y = (ay * bw) + (az * bx);
out.z = (az * bw) - (ay * bx);
out.w = (aw * bw) - (ax * bx);
return out;
}
public static Quaternion rotateY(final Quaternion out, final Quaternion a, float rad) {
rad *= 0.5;
final float ax = a.x, ay = a.y, az = a.z, aw = a.w;
final float by = (float) Math.sin(rad), bw = (float) Math.cos(rad);
out.x = (ax * bw) - (az * by);
out.y = (ay * bw) + (aw * by);
out.z = (az * bw) + (ax * by);
out.w = (aw * bw) - (ay * by);
return out;
}
public static Quaternion rotateZ(final Quaternion out, final Quaternion a, float rad) {
rad *= 0.5;
final float ax = a.x, ay = a.y, az = a.z, aw = a.w;
final float bz = (float) Math.sin(rad), bw = (float) Math.cos(rad);
out.x = (ax * bw) + (ay * bz);
out.y = (ay * bw) - (ax * bz);
out.z = (az * bw) + (aw * bz);
out.w = (aw * bw) - (az * bz);
return out;
}
public static Matrix4 perspective(final Matrix4 out, final float fovy, final float aspect, final float near,
final float far) {
final float f = 1.0f / (float) Math.tan(fovy / 2), nf;
out.val[Matrix4.M00] = f / aspect;
out.val[Matrix4.M01] = 0;
out.val[Matrix4.M02] = 0;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = 0;
out.val[Matrix4.M11] = f;
out.val[Matrix4.M12] = 0;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = 0;
out.val[Matrix4.M21] = 0;
out.val[Matrix4.M23] = -1;
out.val[Matrix4.M30] = 0;
out.val[Matrix4.M31] = 0;
out.val[Matrix4.M33] = 0;
if (!Double.isNaN(far) && !Double.isInfinite(far)) {
nf = 1 / (near - far);
out.val[Matrix4.M22] = (far + near) * nf;
out.val[Matrix4.M32] = (2 * far * near) * nf;
}
else {
out.val[Matrix4.M22] = -1;
out.val[Matrix4.M32] = -2 * near;
}
return out;
}
public static Matrix4 ortho(final Matrix4 out, final float left, final float right, final float bottom,
final float top, final float near, final float far) {
final float lr = 1 / (left - right);
final float bt = 1 / (bottom - top);
final float nf = 1 / (near - far);
out.val[Matrix4.M00] = -2 * lr;
out.val[Matrix4.M01] = 0;
out.val[Matrix4.M02] = 0;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = 0;
out.val[Matrix4.M11] = -2 * bt;
out.val[Matrix4.M12] = 0;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = 0;
out.val[Matrix4.M21] = 0;
out.val[Matrix4.M22] = 2 * nf;
out.val[Matrix4.M23] = 0;
out.val[Matrix4.M30] = (left + right) * lr;
out.val[Matrix4.M31] = (top + bottom) * bt;
out.val[Matrix4.M32] = (far + near) * nf;
out.val[Matrix4.M33] = 1;
return out;
}
public static void unpackPlanes(final Vector4[] planes, final Matrix4 m) {
final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M01], a02 = m.val[Matrix4.M02],
a03 = m.val[Matrix4.M03], a10 = m.val[Matrix4.M10], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M12],
a13 = m.val[Matrix4.M13], a20 = m.val[Matrix4.M20], a21 = m.val[Matrix4.M21], a22 = m.val[Matrix4.M22],
a23 = m.val[Matrix4.M23], a30 = m.val[Matrix4.M30], a31 = m.val[Matrix4.M31], a32 = m.val[Matrix4.M32],
a33 = m.val[Matrix4.M33];
// Left clipping plane
Vector4 plane = planes[0];
plane.x = a30 + a00;
plane.y = a31 + a01;
plane.z = a32 + a02;
plane.w = a33 + a03;
// Right clipping plane
plane = planes[1];
plane.x = a30 - a00;
plane.y = a31 - a01;
plane.z = a32 - a02;
plane.w = a33 - a03;
// Top clipping plane
plane = planes[2];
plane.x = a30 - a10;
plane.y = a31 - a11;
plane.z = a32 - a12;
plane.w = a33 - a13;
// Bottom clipping plane
plane = planes[3];
plane.x = a30 + a10;
plane.y = a31 + a11;
plane.z = a32 + a12;
plane.w = a33 + a13;
// Near clipping plane
plane = planes[4];
plane.x = a30 + a20;
plane.y = a31 + a21;
plane.z = a32 + a22;
plane.w = a33 + a23;
// Far clipping plane
plane = planes[5];
plane.x = a30 - a20;
plane.y = a31 - a21;
plane.z = a32 - a22;
plane.w = a33 - a23;
normalizePlane(planes[0], planes[0]);
normalizePlane(planes[1], planes[1]);
normalizePlane(planes[2], planes[2]);
normalizePlane(planes[3], planes[3]);
normalizePlane(planes[4], planes[4]);
normalizePlane(planes[5], planes[5]);
}
public static void normalizePlane(final Vector4 out, final Vector4 plane) {
final float len = Vector3.len(plane.x, plane.y, plane.z);
out.x = plane.x / len;
out.y = plane.y / len;
out.z = plane.z / len;
out.w = plane.w / len;
}
public static float distanceToPlane(final Vector4 plane, final Vector3 point) {
return (plane.x * point.x) + (plane.y * point.y) + (plane.z * point.z) + plane.w;
}
private static final Vector4 heap = new Vector4();
public static Vector3 unproject(final Vector3 out, final Vector3 v, final Matrix4 inverseMatrix,
final Rectangle viewport) {
final float x = ((2 * (v.x - viewport.x)) / viewport.width) - 1;
final float y = 1 - ((2 * (v.y - viewport.y)) / viewport.height);
final float z = (2 * v.z) - 1;
heap.set(x, y, z, 1);
Vector4.transformMat4(heap, heap, inverseMatrix);
out.set(heap.x / heap.w, heap.y / heap.w, heap.z / heap.w);
return out;
}
}

View File

@ -0,0 +1,573 @@
package com.etheller.warsmash.util;
import java.io.Serializable;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector;
import com.badlogic.gdx.utils.NumberUtils;
/**
* Encapsulates a 4D vector. Allows chaining operations by returning a reference
* to itself in all modification methods.
*
* @author intrigus
*/
public class Vector4 implements Serializable, Vector<Vector4> {
/** the x-component of this vector **/
public float x;
/** the y-component of this vector **/
public float y;
/** the z-component of this vector **/
public float z;
/** the w-component of this vector **/
public float w;
public final static Vector4 X = new Vector4(1, 0, 0, 0);
public final static Vector4 Y = new Vector4(0, 1, 0, 0);
public final static Vector4 Z = new Vector4(0, 0, 1, 0);
public final static Vector4 W = new Vector4(0, 0, 0, 1);
public final static Vector4 Zero = new Vector4(0, 0, 0, 0);
private final static Matrix4 tmpMat = new Matrix4();
/** Constructs a vector at (0,0,0,0) */
public Vector4() {
}
/**
* Creates a vector with the given components
*
* @param x The x-component
* @param y The y-component
* @param z The z-component
* @param w The w-component
*/
public Vector4(final float x, final float y, final float z, final float w) {
this.set(x, y, z, w);
}
/**
* Creates a vector from the given vector
*
* @param vector The vector
*/
public Vector4(final Vector4 vector) {
this.set(vector);
}
/**
* Creates a vector from the given array. The array must have at least 4
* elements.
*
* @param values The array
*/
public Vector4(final float[] values) {
this.set(values[0], values[1], values[2], values[3]);
}
/**
* Sets the vector to the given components
*
* @param x The x-component
* @param y The y-component
* @param z The z-component
* @param w The w-component
* @return this vector for chaining
*/
public Vector4 set(final float x, final float y, final float z, final float w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return this;
}
@Override
public Vector4 set(final Vector4 vector) {
return this.set(vector.x, vector.y, vector.z, vector.w);
}
/**
* Sets the components from the array. The array must have at least 4 elements
*
* @param values The array
* @return this vector for chaining
*/
public Vector4 set(final float[] values) {
return this.set(values[0], values[1], values[2], values[3]);
}
@Override
public Vector4 cpy() {
return new Vector4(this);
}
@Override
public Vector4 add(final Vector4 vector) {
return this.add(vector.x, vector.y, vector.z, vector.w);
}
/**
* Adds the given vector to this component
*
* @param x The x-component of the other vector
* @param y The y-component of the other vector
* @param z The z-component of the other vector
* @param w The w-component of the other vector
* @return This vector for chaining.
*/
public Vector4 add(final float x, final float y, final float z, final float w) {
return this.set(this.x + x, this.y + y, this.z + z, this.w + w);
}
/**
* Adds the given value to all four components of the vector.
*
* @param values The value
* @return This vector for chaining
*/
public Vector4 add(final float values) {
return this.set(this.x + values, this.y + values, this.z + values, this.w + values);
}
@Override
public Vector4 sub(final Vector4 a_vec) {
return this.sub(a_vec.x, a_vec.y, a_vec.z, a_vec.w);
}
/**
* Subtracts the other vector from this vector.
*
* @param x The x-component of the other vector
* @param y The y-component of the other vector
* @param z The z-component of the other vector
* @param w The w-component of the other vector
* @return This vector for chaining
*/
public Vector4 sub(final float x, final float y, final float z, final float w) {
return this.set(this.x - x, this.y - y, this.z - z, this.w - w);
}
/**
* Subtracts the given value from all components of this vector
*
* @param value The value
* @return This vector for chaining
*/
public Vector4 sub(final float value) {
return this.set(this.x - value, this.y - value, this.z - value, this.w - value);
}
@Override
public Vector4 scl(final float scalar) {
return this.set(this.x * scalar, this.y * scalar, this.z * scalar, this.w * scalar);
}
@Override
public Vector4 scl(final Vector4 other) {
return this.set(this.x * other.x, this.y * other.y, this.z * other.z, this.w * other.w);
}
/**
* Scales this vector by the given values
*
* @param vx X value
* @param vy Y value
* @param vz Z value
* @param vw W value
* @return This vector for chaining
*/
public Vector4 scl(final float vx, final float vy, final float vz, final float vw) {
return this.set(this.x * vx, this.y * vy, this.z * vz, this.z * vw);
}
@Override
public Vector4 mulAdd(final Vector4 vec, final float scalar) {
this.x += vec.x * scalar;
this.y += vec.y * scalar;
this.z += vec.z * scalar;
this.w += vec.w * scalar;
return this;
}
@Override
public Vector4 mulAdd(final Vector4 vec, final Vector4 mulVec) {
this.x += vec.x * mulVec.x;
this.y += vec.y * mulVec.y;
this.z += vec.z * mulVec.z;
this.w += vec.w * mulVec.w;
return this;
}
/** @return The euclidian length */
public static float len(final float x, final float y, final float z, final float w) {
return (float) Math.sqrt((x * x) + (y * y) + (z * z) + (w * w));
}
@Override
public float len() {
return (float) Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z) + (this.w * this.w));
}
/** @return The squared euclidian length */
public static float len2(final float x, final float y, final float z, final float w) {
return (x * x) + (y * y) + (z * z) + (w * w);
}
@Override
public float len2() {
return (this.x * this.x) + (this.y * this.y) + (this.z * this.z) + (this.w * this.w);
}
/**
* @param vector The other vector
* @return Whether this and the other vector are equal
*/
public boolean idt(final Vector4 vector) {
return (this.x == vector.x) && (this.y == vector.y) && (this.z == vector.z) && (this.w == vector.w);
}
/** @return The euclidian distance between the two specified vectors */
public static float dst(final float x1, final float y1, final float z1, final float w1, final float x2,
final float y2, final float z2, final float w2) {
final float a = x2 - x1;
final float b = y2 - y1;
final float c = z2 - z1;
final float d = w2 - w1;
return (float) Math.sqrt((a * a) + (b * b) + (c * c) + (d * d));
}
@Override
public float dst(final Vector4 vector) {
final float a = vector.x - this.x;
final float b = vector.y - this.y;
final float c = vector.z - this.z;
final float d = vector.w - this.w;
return (float) Math.sqrt((a * a) + (b * b) + (c * c) + (d * d));
}
/** @return the distance between this point and the given point */
public float dst(final float x, final float y, final float z, final float w) {
final float a = x - this.x;
final float b = y - this.y;
final float c = z - this.z;
final float d = w - this.w;
return (float) Math.sqrt((a * a) + (b * b) + (c * c) + (d * d));
}
/** @return the squared distance between the given points */
public static float dst2(final float x1, final float y1, final float z1, final float w1, final float x2,
final float y2, final float z2, final float w2) {
final float a = x2 - x1;
final float b = y2 - y1;
final float c = z2 - z1;
final float d = w2 - w1;
return (a * a) + (b * b) + (c * c) + (d * d);
}
@Override
public float dst2(final Vector4 point) {
final float a = point.x - this.x;
final float b = point.y - this.y;
final float c = point.z - this.z;
final float d = point.w - this.w;
return (a * a) + (b * b) + (c * c) + (d * d);
}
/**
* Returns the squared distance between this point and the given point
*
* @param x The x-component of the other point
* @param y The y-component of the other point
* @param z The z-component of the other point
* @param w The w-component of the other point
* @return The squared distance
*/
public float dst2(final float x, final float y, final float z, final float w) {
final float a = x - this.x;
final float b = y - this.y;
final float c = z - this.z;
final float d = w - this.w;
return (a * a) + (b * b) + (c * c) + (d * d);
}
@Override
public Vector4 nor() {
final float len2 = this.len2();
if ((len2 == 0f) || (len2 == 1f)) {
return this;
}
return this.scl(1f / (float) Math.sqrt(len2));
}
/** @return The dot product between the two vectors */
public static float dot(final float x1, final float y1, final float z1, final float w1, final float x2,
final float y2, final float z2, final float w2) {
return (x1 * x2) + (y1 * y2) + (z1 * z2) + (w1 * w2);
}
@Override
public float dot(final Vector4 vector) {
return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z) + (this.w * vector.w);
}
/**
* Returns the dot product between this and the given vector.
*
* @param x The x-component of the other vector
* @param y The y-component of the other vector
* @param z The z-component of the other vector
* @param w The w-component of the other vector
* @return The dot product
*/
public float dot(final float x, final float y, final float z, final float w) {
return (this.x * x) + (this.y * y) + (this.z * z) + (this.w * w);
}
@Override
public boolean isUnit() {
return isUnit(0.000000001f);
}
@Override
public boolean isUnit(final float margin) {
return Math.abs(len2() - 1f) < margin;
}
@Override
public boolean isZero() {
return (this.x == 0) && (this.y == 0) && (this.z == 0);
}
@Override
public boolean isZero(final float margin) {
return len2() < margin;
}
@Override
public boolean isOnLine(final Vector4 other, final float epsilon) {
throw new UnsupportedOperationException();
// TODO
// return len2((this.y * other.z) - (this.z * other.y), (this.z * other.x) - (this.x * other.z),
// (this.x * other.y) - (this.y * other.x)) <= epsilon;
}
@Override
public boolean isOnLine(final Vector4 other) {
throw new UnsupportedOperationException();
// TODO
// return len2((this.y * other.z) - (this.z * other.y), (this.z * other.x) - (this.x * other.z),
// (this.x * other.y) - (this.y * other.x)) <= MathUtils.FLOAT_ROUNDING_ERROR;
}
@Override
public boolean isCollinear(final Vector4 other, final float epsilon) {
return isOnLine(other, epsilon) && hasSameDirection(other);
}
@Override
public boolean isCollinear(final Vector4 other) {
return isOnLine(other) && hasSameDirection(other);
}
@Override
public boolean isCollinearOpposite(final Vector4 other, final float epsilon) {
return isOnLine(other, epsilon) && hasOppositeDirection(other);
}
@Override
public boolean isCollinearOpposite(final Vector4 other) {
return isOnLine(other) && hasOppositeDirection(other);
}
@Override
public boolean isPerpendicular(final Vector4 vector) {
return MathUtils.isZero(dot(vector));
}
@Override
public boolean isPerpendicular(final Vector4 vector, final float epsilon) {
return MathUtils.isZero(dot(vector), epsilon);
}
@Override
public boolean hasSameDirection(final Vector4 vector) {
return dot(vector) > 0;
}
@Override
public boolean hasOppositeDirection(final Vector4 vector) {
return dot(vector) < 0;
}
@Override
public Vector4 lerp(final Vector4 target, final float alpha) {
// TODO
this.x += alpha * (target.x - this.x);
this.y += alpha * (target.y - this.y);
this.z += alpha * (target.z - this.z);
return this;
}
@Override
public Vector4 interpolate(final Vector4 target, final float alpha, final Interpolation interpolator) {
// TODO
return lerp(target, interpolator.apply(0f, 1f, alpha));
}
@Override
public String toString() {
return "[" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + "]";
}
@Override
public Vector4 limit(final float limit) {
// TODO
return limit2(limit * limit);
}
@Override
public Vector4 limit2(final float limit2) {
// TODO
final float len2 = len2();
if (len2 > limit2) {
scl((float) Math.sqrt(limit2 / len2));
}
return this;
}
@Override
public Vector4 setLength(final float len) {
return setLength2(len * len);
}
@Override
public Vector4 setLength2(final float len2) {
final float oldLen2 = len2();
return ((oldLen2 == 0) || (oldLen2 == len2)) ? this : scl((float) Math.sqrt(len2 / oldLen2));
}
@Override
public Vector4 clamp(final float min, final float max) {
final float len2 = len2();
if (len2 == 0f) {
return this;
}
final float max2 = max * max;
if (len2 > max2) {
return scl((float) Math.sqrt(max2 / len2));
}
final float min2 = min * min;
if (len2 < min2) {
return scl((float) Math.sqrt(min2 / len2));
}
return this;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + NumberUtils.floatToIntBits(this.x);
result = (prime * result) + NumberUtils.floatToIntBits(this.y);
result = (prime * result) + NumberUtils.floatToIntBits(this.z);
result = (prime * result) + NumberUtils.floatToIntBits(this.w);
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 Vector4 other = (Vector4) obj;
if (NumberUtils.floatToIntBits(this.x) != NumberUtils.floatToIntBits(other.x)) {
return false;
}
if (NumberUtils.floatToIntBits(this.y) != NumberUtils.floatToIntBits(other.y)) {
return false;
}
if (NumberUtils.floatToIntBits(this.z) != NumberUtils.floatToIntBits(other.z)) {
return false;
}
if (NumberUtils.floatToIntBits(this.w) != NumberUtils.floatToIntBits(other.w)) {
return false;
}
return true;
}
@Override
public boolean epsilonEquals(final Vector4 other, final float epsilon) {
if (other == null) {
return false;
}
if (Math.abs(other.x - this.x) > epsilon) {
return false;
}
if (Math.abs(other.y - this.y) > epsilon) {
return false;
}
if (Math.abs(other.z - this.z) > epsilon) {
return false;
}
if (Math.abs(other.w - this.w) > epsilon) {
return false;
}
return true;
}
/**
* Compares this vector with the other vector, using the supplied epsilon for
* fuzzy equality testing.
*
* @return whether the vectors are the same.
*/
public boolean epsilonEquals(final float x, final float y, final float z, final float w, final float epsilon) {
if (Math.abs(x - this.x) > epsilon) {
return false;
}
if (Math.abs(y - this.y) > epsilon) {
return false;
}
if (Math.abs(z - this.z) > epsilon) {
return false;
}
if (Math.abs(w - this.w) > epsilon) {
return false;
}
return true;
}
@Override
public Vector4 setZero() {
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 0;
return this;
}
@Override
public Vector4 setToRandomDirection() {
throw new UnsupportedOperationException();
}
public static Vector4 transformMat4(final Vector4 out, final Vector4 a, final Matrix4 matrix) {
final float x = a.x, y = a.y, z = a.z, w = a.w;
final float[] m = matrix.val;
out.x = (m[Matrix4.M00] * x) + (m[Matrix4.M10] * y) + (m[Matrix4.M20] * z) + (m[Matrix4.M30] * w);
out.y = (m[Matrix4.M01] * x) + (m[Matrix4.M11] * y) + (m[Matrix4.M21] * z) + (m[Matrix4.M31] * w);
out.z = (m[Matrix4.M02] * x) + (m[Matrix4.M12] * y) + (m[Matrix4.M22] * z) + (m[Matrix4.M32] * w);
out.w = (m[Matrix4.M03] * x) + (m[Matrix4.M13] * y) + (m[Matrix4.M23] * z) + (m[Matrix4.M33] * w);
return out;
}
}

View File

@ -0,0 +1,116 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
public class BoundingShape extends SceneNode {
private final float[] min = new float[] { -1, -1, -1 };
private final float[] max = new float[] { 1, 1, 1 };
private float radius = (float) Math.sqrt(2);
public void fromBounds(final float[] min, final float[] max) {
System.arraycopy(min, 0, this.min, 0, this.min.length);
System.arraycopy(max, 0, this.max, 0, this.max.length);
final float dX = max[0] - min[0];
final float dY = max[1] - min[1];
final float dZ = max[2] - min[2];
this.radius = (float) Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) / 2;
}
public void fromRadius(final float radius) {
final float s = (float) (radius * Math.cos(radius));
this.min[0] = this.min[1] = this.min[2] = s;
this.max[0] = this.max[1] = this.max[2] = s;
this.radius = radius;
}
public void fromVertices(final float[] vertices) {
final float[] min = new float[] { 1E9f, 1E9f, 1E9f };
final float[] max = new float[] { -1E9f, -1E9f, -1E9f };
for (int i = 0, l = vertices.length; i < l; i += 3) {
final float x = vertices[i];
final float y = vertices[i + 1];
final float z = vertices[i + 2];
if (x > max[0]) {
max[0] = x;
}
if (x < min[0]) {
min[0] = x;
}
if (y > max[1]) {
max[1] = y;
}
if (y < min[1]) {
min[1] = y;
}
if (z > max[2]) {
max[2] = z;
}
if (z < min[2]) {
min[2] = z;
}
}
fromBounds(min, max);
}
public Vector3 getPositiveVertex(final Vector3 out, final Vector3 normal) {
if (normal.x >= 0) {
out.x = this.max[0];
}
else {
out.x = this.min[0];
}
if (normal.y >= 0) {
out.y = this.max[1];
}
else {
out.y = this.min[1];
}
if (normal.z >= 0) {
out.z = this.max[2];
}
else {
out.z = this.min[2];
}
return out;
}
public Vector3 getNegativeVertex(final Vector3 out, final Vector3 normal) {
if (normal.x >= 0) {
out.x = this.min[0];
}
else {
out.x = this.max[0];
}
if (normal.y >= 0) {
out.y = this.min[1];
}
else {
out.y = this.max[1];
}
if (normal.z >= 0) {
out.z = this.min[2];
}
else {
out.z = this.max[2];
}
return out;
}
@Override
protected void updateObject(final Scene scene) {
}
@Override
protected void convertBasis(final Quaternion computedRotation) {
// TODO ???
}
}

View File

@ -0,0 +1,28 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.viewer.ModelView.SceneData;
public class Bucket {
private final ModelView modelView;
private final Model model;
private final int count;
public Bucket(final ModelView modelView) {
final Model model = modelView.model;
final GL20 gl = model.getViewer().gl;
this.modelView = modelView;
this.model = model;
this.count = 0;
// this.instanceIdBuffer =
}
public int fill(final SceneData data, final int baseInstance, final Scene scene) {
// Make believe the bucket is now filled with data for all instances.
// This is because if a non-specific bucket implementation is supplied,
// instancing isn't used, so batching is irrelevant.
return data.instances.size();
}
}

View File

@ -0,0 +1,314 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.Vector4;
public class Camera {
private static final Vector3 vectorHeap = new Vector3();
private static final Vector3 vectorHeap2 = new Vector3();
private static final Vector3 vectorHeap3 = new Vector3();
private static final Quaternion quatHeap = new Quaternion();
private static final Matrix4 matHeap = new Matrix4();
private final Rectangle rect;
private boolean isPerspective;
private float fov;
private float aspect;
private boolean isOrtho;
private float leftClipPlane;
private float rightClipPlane;
private float bottomClipPlane;
private float topClipPlane;
private float nearClipPlane;
private float farClipPlane;
private final Vector3 location;
private final Quaternion rotation;
public Quaternion inverseRotation;
private final Matrix4 worldMatrix;
private final Matrix4 projectionMatrix;
private final Matrix4 worldProjectionMatrix;
private final Matrix4 inverseWorldMatrix;
private final Matrix4 inverseRotationMatrix;
private final Matrix4 inverseWorldProjectionMatrix;
private final Vector3 directionX;
private final Vector3 directionY;
private final Vector3 directionZ;
private final Vector3[] vectors;
private final Vector3[] billboardedVectors;
private final Vector4[] planes;
private boolean dirty;
public Camera() {
// rencered viewport
this.rect = new Rectangle();
// perspective values
this.isPerspective = true;
this.fov = 0;
this.aspect = 0;
// Orthogonal values
this.isOrtho = false;
this.leftClipPlane = 0f;
this.rightClipPlane = 0f;
this.bottomClipPlane = 0f;
this.topClipPlane = 0f;
// Shared values
this.nearClipPlane = 0f;
this.farClipPlane = 0f;
// World values
this.location = new Vector3();
this.rotation = new Quaternion();
// Derived values.
this.inverseRotation = new Quaternion();
this.worldMatrix = new Matrix4();
this.projectionMatrix = new Matrix4();
this.worldProjectionMatrix = new Matrix4();
this.inverseWorldMatrix = new Matrix4();
this.inverseRotationMatrix = new Matrix4();
this.inverseWorldProjectionMatrix = new Matrix4();
this.directionX = new Vector3();
this.directionY = new Vector3();
this.directionZ = new Vector3();
// First four vectors are the corners of a 2x2 rectangle, the last three vectors
// are the unit axes
this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0),
new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) };
// First four vectors are the corners of a 2x2 rectangle billboarded to the
// camera, the last three vectors are the unit axes billboarded
this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(),
new Vector3(), new Vector3(), new Vector3() };
// Left, right, top, bottom, near, far
this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(),
new Vector4() };
this.dirty = true;
}
public void perspective(final float fov, final float aspect, final float near, final float far) {
this.isPerspective = true;
this.isOrtho = false;
this.fov = fov;
this.aspect = aspect;
this.nearClipPlane = near;
this.farClipPlane = far;
this.dirty = true;
}
public void ortho(final float left, final float right, final float bottom, final float top, final float near,
final float far) {
this.isPerspective = false;
this.isOrtho = true;
this.leftClipPlane = left;
this.rightClipPlane = right;
this.bottomClipPlane = bottom;
this.topClipPlane = top;
this.nearClipPlane = near;
this.farClipPlane = far;
}
public void viewport(final Rectangle viewport) {
this.rect.set(viewport);
this.aspect = viewport.width / viewport.height;
this.dirty = true;
}
public void setLocation(final Vector3 location) {
this.location.set(location);
this.dirty = true;
}
public void move(final Vector3 offset) {
this.location.add(offset);
this.dirty = true;
}
public void setRotation(final Quaternion rotation) {
this.rotation.set(rotation);
this.dirty = true;
}
public void rotate(final Quaternion rotation) {
this.rotation.mul(rotation);
this.dirty = true;
}
public void setRotationAngles(final float horizontalAngle, final float verticalAngle) {
this.rotation.idt();
// this.rotateAngles(horizontalAngle, verticalAngle);
throw new UnsupportedOperationException(
"Ghostwolf called a function that does not exist, so I did not know what to do here");
}
public void rotateAround(final Quaternion rotation, final Vector3 point) {
this.rotate(rotation);
quatHeap.conjugate(); // TODO ?????????
vectorHeap.set(this.location);
vectorHeap.sub(point);
rotation.transform(vectorHeap);
vectorHeap.add(point);
this.location.set(vectorHeap);
}
public void setRotationAround(final Quaternion rotation, final Vector3 point) {
this.setRotation(rotation);
;
final float length = vectorHeap.set(this.location).sub(point).len();
quatHeap.conjugate(); // TODO ?????????
vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z);
quatHeap.transform(vectorHeap);
vectorHeap.scl(length);
this.location.set(vectorHeap.add(point));
}
public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) {
quatHeap.idt();
RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle);
RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle);
this.setRotationAround(quatHeap, point);
}
public void face(final Vector3 point, final Vector3 worldUp) {
matHeap.setToLookAt(this.location, point, worldUp);
matHeap.getRotation(this.rotation);
this.dirty = true;
}
public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) {
this.location.set(location);
this.face(target, worldUp);
}
public void reset() {
this.location.set(0, 0, 0);
this.rotation.idt();
this.dirty = true;
}
public void update() {
if (this.dirty) {
this.dirty = true;
final Vector3 location = this.location;
final Quaternion rotation = this.rotation;
final Quaternion inverseRotation = this.inverseRotation;
final Matrix4 worldMatrix = this.worldMatrix;
final Matrix4 projectionMatrix = this.projectionMatrix;
final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix;
final Vector3[] vectors = this.vectors;
final Vector3[] billboardedVectors = this.billboardedVectors;
if (this.isPerspective) {
RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane,
this.farClipPlane);
}
else {
RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane,
this.topClipPlane, this.nearClipPlane, this.farClipPlane);
}
rotation.toMatrix(projectionMatrix.val);
worldMatrix.translate(vectorHeap.set(location).scl(-1));
inverseRotation.set(rotation).conjugate();
// World projection matrix
// World space -> NDC space
worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix);
// Recalculate the camera's frustum planes
RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix);
// Inverse world matrix
// Camera space -> world space
this.inverseWorldMatrix.set(worldMatrix).inv();
this.directionX.set(RenderMathUtils.VEC3_UNIT_X);
inverseRotation.transform(this.directionX);
this.directionY.set(RenderMathUtils.VEC3_UNIT_Y);
inverseRotation.transform(this.directionY);
this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z);
inverseRotation.transform(this.directionZ);
// Inverse world projection matrix
// NDC space -> World space
this.inverseWorldProjectionMatrix.set(worldProjectionMatrix);
this.inverseWorldProjectionMatrix.inv();
for (int i = 0; i < 7; i++) {
billboardedVectors[i].set(vectors[i]);
inverseRotation.transform(billboardedVectors[i]);
}
}
}
public boolean testSphere(final Vector3 center, final float radius) {
for (final Vector4 plane : this.planes) {
if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) {
return false;
}
}
return true;
}
public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) {
return out.set(v).prj(this.inverseWorldMatrix);
}
public Vector3 worldToCamera(final Vector3 out, final Vector3 v) {
return out.set(v).prj(this.worldMatrix);
}
public float[] screenToWorldRay(final float[] out, final Vector2 v) {
final Vector3 a = vectorHeap;
final Vector3 b = vectorHeap2;
final Vector3 c = vectorHeap3;
final float x = v.x;
final float y = v.y;
final Rectangle viewport = this.rect;
// Intersection on the near-plane
RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport);
// Intersection on the far-plane
RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport);
out[0] = a.x;
out[1] = a.y;
out[2] = a.z;
out[3] = b.x;
out[4] = b.y;
out[5] = b.z;
return out;
}
}

View File

@ -0,0 +1,9 @@
package com.etheller.warsmash.viewer;
public abstract class Model {
private ModelView modelView;
public boolean ok;
public abstract Viewer getViewer();
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer;
public class ModelInstance {
}

View File

@ -0,0 +1,68 @@
package com.etheller.warsmash.viewer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
public abstract class ModelView {
protected final Model model;
protected final HashSet<Object> instanceSet;
protected final HashMap<Scene, SceneData> sceneData;
protected final int renderedInstances;
protected final int renderedParticles;
protected final int renderedBuckets;
protected final int renderedCalls;
public ModelView(final Model model) {
this.model = model;
this.instanceSet = new HashSet<>();
this.sceneData = new HashMap<>();
this.renderedInstances = 0;
this.renderedParticles = 0;
this.renderedBuckets = 0;
this.renderedCalls = 0;
}
public abstract Object getShallowCopy();
public abstract void applyShallowCopy(final Object view);
@Override
public abstract boolean equals(Object view);
// public boo
public void addSceneData(final ModelInstance instance, final Scene scene) {
if (this.model.ok && (scene != null)) {
SceneData data = this.sceneData.get(scene);
if (data == null) {
data = this.createSceneData(scene);
this.sceneData.put(scene, data);
}
}
}
private SceneData createSceneData(final Scene scene) {
return new SceneData(scene, this);
}
public static final class SceneData {
public final Scene scene;
public final ModelView modelView;
public final int baseIndex = 0;
public final List<ModelInstance> instances = new ArrayList<>();
public final List<Bucket> buckets = new ArrayList<>();
public final int usedBuckets = 0;
public SceneData(final Scene scene, final ModelView modelView) {
this.scene = scene;
this.modelView = modelView;
}
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer;
public abstract class Scene {
public Camera camera;
}

View File

@ -0,0 +1,245 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SceneNode extends ViewerNode {
public SceneNode() {
}
public SceneNode setPivot(final float[] pivot) {
this.pivot.set(pivot);
this.dirty = true;
return this;
}
public SceneNode setLocation(final float[] location) {
this.localLocation.set(location);
this.dirty = true;
return this;
}
public SceneNode setRotation(final float[] rotation) {
this.localRotation.set(rotation[0], rotation[1], rotation[2], rotation[3]);
this.dirty = true;
return this;
}
public SceneNode setScale(final float[] varying) {
this.localScale.set(varying);
this.dirty = true;
return this;
}
public SceneNode setUniformScale(final float uniform) {
this.localScale.set(uniform, uniform, uniform);
this.dirty = true;
return this;
}
public SceneNode setTransformation(final Vector3 location, final Quaternion rotation, final Vector3 scale) {
// TODO for performance, Ghostwolf did a direct field write on everything here.
// I'm hoping we can get Java's JIT to just figure it out and do it on its own
this.localLocation.set(location);
this.localRotation.set(rotation);
this.localScale.set(scale);
this.dirty = true;
return this;
}
public SceneNode resetTransformation() {
this.pivot.set(Vector3.Zero);
this.localLocation.set(Vector3.Zero);
this.localRotation.set(RenderMathUtils.QUAT_DEFAULT);
this.localScale.set(RenderMathUtils.VEC3_ONE);
this.dirty = true;
return this;
}
public SceneNode movePivot(final float[] offset) {
this.pivot.add(offset[0], offset[1], offset[2]);
this.dirty = true;
return this;
}
public SceneNode move(final float[] offset) {
this.localLocation.add(offset[0], offset[1], offset[2]);
this.dirty = true;
return this;
}
public SceneNode rotate(final Quaternion rotation) {
RenderMathUtils.mul(this.localRotation, this.localRotation, rotation);
this.dirty = true;
return this;
}
public SceneNode rotateLocal(final Quaternion rotation) {
RenderMathUtils.mul(this.localRotation, rotation, this.localRotation);
this.dirty = true;
return this;
}
public SceneNode scale(final float[] scale) {
this.localScale.x *= scale[0];
this.localScale.y *= scale[1];
this.localScale.z *= scale[2];
this.dirty = true;
return this;
}
public SceneNode uniformScale(final float scale) {
this.localScale.x *= scale;
this.localScale.y *= scale;
this.localScale.z *= scale;
this.dirty = true;
return this;
}
public SceneNode setParent(final ViewerNode parent) {
if (this.parent != null) {
this.parent.children.remove(this);
}
this.parent = parent;
if (parent != null) {
parent.children.add(this);
}
this.dirty = true;
return this;
}
public void recalculateTransformation() {
boolean dirty = this.dirty;
final ViewerNode parent = this.parent;
this.wasDirty = this.dirty;
if (parent != null) {
dirty = dirty || parent.wasDirty;
}
this.wasDirty = dirty;
if (dirty) {
this.dirty = false;
if (parent != null) {
Vector3 computedLocation;
Vector3 computedScaling;
final Vector3 parentPivot = parent.pivot;
computedLocation = locationHeap;
computedLocation.x = this.localLocation.x + parentPivot.x;
computedLocation.y = this.localLocation.y + parentPivot.y;
computedLocation.z = this.localLocation.z + parentPivot.z;
if (this.dontInheritScaling) {
computedScaling = scalingHeap;
final Vector3 parentInverseScale = parent.inverseWorldScale;
computedScaling.x = parentInverseScale.x * this.localScale.x;
computedScaling.y = parentInverseScale.y * this.localScale.y;
computedScaling.z = parentInverseScale.z * this.localScale.z;
this.worldScale.x = this.localScale.x;
this.worldScale.y = this.localScale.y;
this.worldScale.z = this.localScale.z;
}
else {
computedScaling = this.localScale;
final Vector3 parentScale = parent.worldScale;
this.worldScale.x = parentScale.x * this.localScale.x;
this.worldScale.y = parentScale.y * this.localScale.y;
this.worldScale.z = parentScale.z * this.localScale.z;
}
RenderMathUtils.fromRotationTranslationScale(this.localRotation, computedLocation, computedScaling,
this.localMatrix);
RenderMathUtils.mul(this.worldMatrix, parent.worldMatrix, this.localMatrix);
RenderMathUtils.mul(this.worldRotation, parent.worldRotation, this.localRotation);
}
else {
RenderMathUtils.fromRotationTranslationScale(this.localRotation, this.localLocation, this.localScale,
this.localMatrix);
this.worldMatrix.set(this.localMatrix);
this.worldRotation.set(this.localRotation);
this.worldScale.set(this.localScale);
}
}
// Inverse world rotation
this.inverseWorldRotation.x = -this.worldRotation.x;
this.inverseWorldRotation.y = -this.worldRotation.y;
this.inverseWorldRotation.z = -this.worldRotation.z;
this.inverseWorldRotation.w = this.worldRotation.w;
// Inverse world scale
this.inverseWorldScale.x = 1 / this.worldScale.x;
this.inverseWorldScale.y = 1 / this.worldScale.y;
this.inverseWorldScale.z = 1 / this.worldScale.z;
// World location
this.worldLocation.x = this.worldMatrix.val[Matrix4.M30];
this.worldLocation.y = this.worldMatrix.val[Matrix4.M31];
this.worldLocation.z = this.worldMatrix.val[Matrix4.M32];
// Inverse world location
this.inverseWorldLocation.x = -this.worldLocation.x;
this.inverseWorldLocation.y = -this.worldLocation.y;
this.inverseWorldLocation.z = -this.worldLocation.z;
}
@Override
public void update(final Scene scene) {
if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) {
this.dirty = true;
this.wasDirty = true;
this.recalculateTransformation();
}
else {
this.wasDirty = false;
}
this.updateObject(scene);
this.updateChildren(scene);
}
protected abstract void updateObject(Scene scene);
protected void updateChildren(final Scene scene) {
for (int i = 0, l = this.children.size(); i < l; i++) {
this.children.get(i).update(scene);
}
}
protected abstract void convertBasis(Quaternion computedRotation);
}

View File

@ -0,0 +1,118 @@
package com.etheller.warsmash.viewer;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.Descriptor;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SkeletalNode extends ViewerNode {
private final Object object;
private final boolean billboarded = false;
private final boolean billboardedX = false;
private final boolean billboardedY = false;
private final boolean billboardedZ = false;
public SkeletalNode() {
this.object = null;
}
public void recalculateTransformation(final Scene scene) {
final Quaternion computedRotation;
Vector3 computedScaling;
if (this.dontInheritScaling) {
computedScaling = scalingHeap;
final Vector3 parentInverseScale = this.parent.inverseWorldScale;
computedScaling.x = parentInverseScale.x * this.localScale.x;
computedScaling.y = parentInverseScale.y * this.localScale.y;
computedScaling.z = parentInverseScale.z * this.localScale.z;
this.worldScale.x = this.localScale.x;
this.worldScale.y = this.localScale.y;
this.worldScale.z = this.localScale.z;
}
else {
computedScaling = this.localScale;
final Vector3 parentScale = this.parent.worldScale;
this.worldScale.x = parentScale.x * this.worldScale.x;
this.worldScale.y = parentScale.y * this.worldScale.y;
this.worldScale.z = parentScale.z * this.worldScale.z;
}
if (this.billboarded) {
computedRotation = rotationHeap;
computedRotation.set(this.parent.inverseWorldRotation);
computedRotation.mul(scene.camera.inverseRotation);
this.convertBasis(computedRotation);
}
else {
computedRotation = this.localRotation;
}
RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling,
this.localMatrix, this.pivot);
RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix);
RenderMathUtils.mul(this.worldRotation, this.parent.worldRotation, computedRotation);
// Inverse world rotation
this.inverseWorldRotation.x = -this.worldRotation.x;
this.inverseWorldRotation.y = -this.worldRotation.y;
this.inverseWorldRotation.z = -this.worldRotation.z;
this.inverseWorldRotation.w = this.worldRotation.w;
// Inverse world scale
this.inverseWorldScale.x = 1 / this.worldScale.x;
this.inverseWorldScale.y = 1 / this.worldScale.y;
this.inverseWorldScale.z = 1 / this.worldScale.z;
// World location
final float x = this.pivot.x;
final float y = this.pivot.y;
final float z = this.pivot.z;
this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y)
+ (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30];
this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y)
+ (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31];
this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y)
+ (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32];
// Inverse world location
this.inverseWorldLocation.x = -this.worldLocation.x;
this.inverseWorldLocation.y = -this.worldLocation.y;
this.inverseWorldLocation.z = -this.worldLocation.z;
}
protected void updateChildren(final Scene scene) {
for (int i = 0, l = this.children.size(); i < l; i++) {
this.children.get(i).update(scene);
}
}
protected abstract void convertBasis(Quaternion computedRotation);
public static <NODE extends SkeletalNode> Object[] createSkeletalNodes(final int count,
final Descriptor<NODE> nodeDescriptor) {
final List<NODE> nodes = new ArrayList<>();
final List<Matrix4> worldMatrices = new ArrayList<>();
for (int i = 0; i < count; i++) {
final NODE node = nodeDescriptor.create();
nodes.add(node);
worldMatrices.add(node.worldMatrix);
}
final Object[] data = { nodes, worldMatrices };
return data;
}
}

View File

@ -0,0 +1,7 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.graphics.GL20;
public abstract class Viewer {
public GL20 gl;
}

View File

@ -0,0 +1,65 @@
package com.etheller.warsmash.viewer;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
public abstract class ViewerNode {
protected static final Vector3 locationHeap = new Vector3();
protected static final Quaternion rotationHeap = new Quaternion();
protected static final Vector3 scalingHeap = new Vector3();
protected final Vector3 pivot;
protected final Vector3 localLocation;
protected final Quaternion localRotation;
protected final Vector3 localScale;
protected final Vector3 worldLocation;
protected final Quaternion worldRotation;
protected final Vector3 worldScale;
protected final Vector3 inverseWorldLocation;
protected final Quaternion inverseWorldRotation;
protected final Vector3 inverseWorldScale;
protected final Matrix4 localMatrix;
protected final Matrix4 worldMatrix;
protected final boolean dontInheritTranslation;
protected final boolean dontInheritRotation;
protected final boolean dontInheritScaling;
protected boolean visible;
protected boolean wasDirty;
protected boolean dirty;
protected ViewerNode parent;
protected final List<ViewerNode> children;
public ViewerNode() {
this.pivot = new Vector3();
this.localLocation = new Vector3();
this.localRotation = new Quaternion(0, 0, 0, 1);
this.localScale = new Vector3(1, 1, 1);
this.worldLocation = new Vector3();
this.worldRotation = new Quaternion();
this.worldScale = new Vector3();
this.inverseWorldLocation = new Vector3();
this.inverseWorldRotation = new Quaternion();
this.inverseWorldScale = new Vector3();
this.localMatrix = new Matrix4();
this.localMatrix.val[0] = 1;
this.localMatrix.val[5] = 1;
this.localMatrix.val[10] = 1;
this.localMatrix.val[15] = 1;
this.worldMatrix = new Matrix4();
this.dontInheritTranslation = false;
this.dontInheritRotation = false;
this.dontInheritScaling = false;
this.visible = true;
this.wasDirty = false;
this.dirty = true;
this.children = new ArrayList<>();
}
public abstract void update(Scene scene);
}

View File

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

View File

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

BIN
jars/blp-iio-plugin.jar Normal file

Binary file not shown.