mirror of
https://github.com/Retera/WarsmashModEngine.git
synced 2022-07-31 17:38:59 +02:00
Upgrade code
This commit is contained in:
parent
f381562e9b
commit
f0da595d20
128
.gitignore
vendored
Normal file
128
.gitignore
vendored
Normal 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
|
@ -3,6 +3,9 @@ buildscript {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
flatDir {
|
||||||
|
dirs "$rootProject.projectDir/jars"
|
||||||
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://plugins.gradle.org/m2/" }
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
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.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
|
||||||
compile "com.google.guava:guava:23.5-jre"
|
compile "com.google.guava:guava:23.5-jre"
|
||||||
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
|
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.badlogicgames.gdx:gdx-freetype:$gdxVersion"
|
||||||
compile "com.google.guava:guava:23.5-jre"
|
compile "com.google.guava:guava:23.5-jre"
|
||||||
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
|
implementation 'com.github.inwc3:wc3libs:-SNAPSHOT'
|
||||||
|
compile files(fileTree(dir:'../jars', includes: ['*.jar']))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
apply plugin: "java"
|
apply plugin: "java"
|
||||||
|
|
||||||
sourceCompatibility = 1.7
|
sourceCompatibility = 1.8
|
||||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||||
|
|
||||||
sourceSets.main.java.srcDirs = [ "src/" ]
|
sourceSets.main.java.srcDirs = [ "src/" ]
|
||||||
|
@ -1,27 +1,62 @@
|
|||||||
package com.etheller.warsmash;
|
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.ApplicationAdapter;
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
import com.badlogic.gdx.graphics.GL20;
|
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.etheller.warsmash.util.War3ID;
|
||||||
|
import com.hiveworkshop.wc3.mpq.Codebase;
|
||||||
|
import com.hiveworkshop.wc3.mpq.FileCodebase;
|
||||||
|
|
||||||
public class WarsmashGdxGame extends ApplicationAdapter {
|
public class WarsmashGdxGame extends ApplicationAdapter {
|
||||||
|
private SpriteBatch batch;
|
||||||
|
private BitmapFont font;
|
||||||
|
private Codebase codebase;
|
||||||
|
private Texture texture;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create() {
|
public void create() {
|
||||||
|
this.codebase = new FileCodebase(new File("C:/MPQBuild/War3.mpq/war3.mpq"));
|
||||||
|
|
||||||
final War3ID id = War3ID.fromString("ipea");
|
final War3ID id = War3ID.fromString("ipea");
|
||||||
System.out.println(id.getValue());
|
try {
|
||||||
for (final byte b : "Hello World".getBytes(Charset.forName("utf-8"))) {
|
final String path = "terrainart\\lordaeronsummer\\lords_dirt.blp";
|
||||||
System.out.println(b + " - " + (char) b);
|
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
|
@Override
|
||||||
public void render() {
|
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);
|
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
|
@Override
|
||||||
|
@ -6,6 +6,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
|
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
|
||||||
|
import com.etheller.warsmash.util.MdlUtils;
|
||||||
import com.etheller.warsmash.util.War3ID;
|
import com.etheller.warsmash.util.War3ID;
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
import com.google.common.io.LittleEndianDataInputStream;
|
||||||
import com.google.common.io.LittleEndianDataOutputStream;
|
import com.google.common.io.LittleEndianDataOutputStream;
|
||||||
@ -14,8 +15,8 @@ import com.google.common.io.LittleEndianDataOutputStream;
|
|||||||
* Based on the works of Chananya Freiman.
|
* Based on the works of Chananya Freiman.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class AnimatedObject implements Chunk {
|
public abstract class AnimatedObject implements Chunk, MdlxBlock {
|
||||||
private final List<Timeline> timelines;
|
protected final List<Timeline> timelines;
|
||||||
|
|
||||||
public AnimatedObject() {
|
public AnimatedObject() {
|
||||||
this.timelines = new ArrayList<>();
|
this.timelines = new ArrayList<>();
|
||||||
@ -41,20 +42,20 @@ public class AnimatedObject implements Chunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<String> readAnimatedBlock(final MdlTokenInputStream stream) {
|
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 {
|
public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) throws IOException {
|
||||||
final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline();
|
final Timeline timeline = name.getImplementation().createTimeline();
|
||||||
|
|
||||||
timeline.readMdl(stream, name);
|
timeline.readMdl(stream, name.getWar3id());
|
||||||
|
|
||||||
this.timelines.add(timeline);
|
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) {
|
for (final Timeline timeline : this.timelines) {
|
||||||
if (timeline.getName().equals(name)) {
|
if (timeline.getName().equals(name.getWar3id())) {
|
||||||
timeline.writeMdl(stream);
|
timeline.writeMdl(stream);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -93,8 +94,8 @@ public class AnimatedObject implements Chunk {
|
|||||||
@Override
|
@Override
|
||||||
public String next() {
|
public String next() {
|
||||||
final String token = this.delegate.next();
|
final String token = this.delegate.next();
|
||||||
if (token.equals("static") && hasNext()) {
|
if (token.equals(MdlUtils.TOKEN_STATIC) && hasNext()) {
|
||||||
return "static " + this.delegate.next();
|
return MdlUtils.TOKEN_STATIC + " " + this.delegate.next();
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.etheller.warsmash.parsers.mdlx;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.util.MdlUtils;
|
||||||
import com.etheller.warsmash.util.War3ID;
|
import com.etheller.warsmash.util.War3ID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,33 +16,33 @@ import com.etheller.warsmash.util.War3ID;
|
|||||||
*/
|
*/
|
||||||
public enum AnimationMap {
|
public enum AnimationMap {
|
||||||
// Layer
|
// Layer
|
||||||
KMTF("TextureId", TimelineDescriptor.UINT32_TIMELINE),
|
KMTF(MdlUtils.TOKEN_TEXTURE_ID, TimelineDescriptor.UINT32_TIMELINE),
|
||||||
KMTA("Alpha", TimelineDescriptor.FLOAT_TIMELINE),
|
KMTA(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
// TextureAnimation
|
// TextureAnimation
|
||||||
KTAT("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
|
KTAT(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
KTAR("Rotation", TimelineDescriptor.VECTOR4_TIMELINE),
|
KTAR(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE),
|
||||||
KTAS("Scaling", TimelineDescriptor.VECTOR3_TIMELINE),
|
KTAS(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
// GeosetAnimation
|
// GeosetAnimation
|
||||||
KGAO("Alpha", TimelineDescriptor.FLOAT_TIMELINE),
|
KGAO(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KGAC("Color", TimelineDescriptor.VECTOR3_TIMELINE),
|
KGAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
// Light
|
// Light
|
||||||
KLAS("AttenuationStart", TimelineDescriptor.FLOAT_TIMELINE),
|
KLAS(MdlUtils.TOKEN_ATTENUATION_START, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KLAE("AttenuationEnd", TimelineDescriptor.FLOAT_TIMELINE),
|
KLAE(MdlUtils.TOKEN_ATTENUATION_END, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KLAC("Color", TimelineDescriptor.VECTOR3_TIMELINE),
|
KLAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
KLAI("Intensity", TimelineDescriptor.FLOAT_TIMELINE),
|
KLAI(MdlUtils.TOKEN_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KLBI("AmbientIntensity", TimelineDescriptor.FLOAT_TIMELINE),
|
KLBI(MdlUtils.TOKEN_AMB_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KLBC("AmbientColor", TimelineDescriptor.VECTOR3_TIMELINE),
|
KLBC(MdlUtils.TOKEN_AMB_COLOR, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
KLAV("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
|
KLAV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
// Attachment
|
// Attachment
|
||||||
KATV("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
|
KATV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
// ParticleEmitter
|
// ParticleEmitter
|
||||||
KPEE("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE),
|
KPEE(MdlUtils.TOKEN_EMISSION_RATE, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KPEG("Gravity", TimelineDescriptor.FLOAT_TIMELINE),
|
KPEG(MdlUtils.TOKEN_GRAVITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KPLN("Longitude", TimelineDescriptor.FLOAT_TIMELINE),
|
KPLN(MdlUtils.TOKEN_LONGITUDE, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KPLT("Latitude", TimelineDescriptor.FLOAT_TIMELINE),
|
KPLT(MdlUtils.TOKEN_LATITUDE, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KPEL("LifeSpan", TimelineDescriptor.FLOAT_TIMELINE),
|
KPEL(MdlUtils.TOKEN_LIFE_SPAN, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KPES("Speed", TimelineDescriptor.FLOAT_TIMELINE),
|
KPES(MdlUtils.TOKEN_INIT_VELOCITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KPEV("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
|
KPEV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
// ParticleEmitter2
|
// ParticleEmitter2
|
||||||
KP2S("Speed", TimelineDescriptor.FLOAT_TIMELINE),
|
KP2S("Speed", TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
KP2R("Variation", TimelineDescriptor.FLOAT_TIMELINE),
|
KP2R("Variation", TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
@ -59,19 +60,21 @@ public enum AnimationMap {
|
|||||||
KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE),
|
KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE),
|
||||||
KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
|
KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE),
|
||||||
// Camera
|
// Camera
|
||||||
KCTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
|
KCTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
KTTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
|
KTTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
KCRL("Rotation", TimelineDescriptor.UINT32_TIMELINE),
|
KCRL(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.UINT32_TIMELINE),
|
||||||
// GenericObject
|
// GenericObject
|
||||||
KGTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE),
|
KGTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE),
|
||||||
KGRT("Rotation", TimelineDescriptor.VECTOR4_TIMELINE),
|
KGRT(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE),
|
||||||
KGSC("Scaling", TimelineDescriptor.VECTOR3_TIMELINE);
|
KGSC(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE);
|
||||||
private final String mdlToken;
|
private final String mdlToken;
|
||||||
private final TimelineDescriptor implementation;
|
private final TimelineDescriptor implementation;
|
||||||
|
private final War3ID war3id;
|
||||||
|
|
||||||
private AnimationMap(final String mdlToken, final TimelineDescriptor implementation) {
|
private AnimationMap(final String mdlToken, final TimelineDescriptor implementation) {
|
||||||
this.mdlToken = mdlToken;
|
this.mdlToken = mdlToken;
|
||||||
this.implementation = implementation;
|
this.implementation = implementation;
|
||||||
|
this.war3id = War3ID.fromString(this.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMdlToken() {
|
public String getMdlToken() {
|
||||||
@ -82,11 +85,15 @@ public enum AnimationMap {
|
|||||||
return this.implementation;
|
return this.implementation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public War3ID getWar3id() {
|
||||||
|
return this.war3id;
|
||||||
|
}
|
||||||
|
|
||||||
public static final Map<War3ID, AnimationMap> ID_TO_TAG = new HashMap<>();
|
public static final Map<War3ID, AnimationMap> ID_TO_TAG = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (final AnimationMap tag : AnimationMap.values()) {
|
for (final AnimationMap tag : AnimationMap.values()) {
|
||||||
ID_TO_TAG.put(War3ID.fromString(tag.name()), tag);
|
ID_TO_TAG.put(tag.getWar3id(), tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
99
core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java
Normal file
99
core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
88
core/src/com/etheller/warsmash/parsers/mdlx/Bone.java
Normal file
88
core/src/com/etheller/warsmash/parsers/mdlx/Bone.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
128
core/src/com/etheller/warsmash/parsers/mdlx/Camera.java
Normal file
128
core/src/com/etheller/warsmash/parsers/mdlx/Camera.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
167
core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java
Normal file
167
core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
77
core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java
Normal file
77
core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
47
core/src/com/etheller/warsmash/parsers/mdlx/Extent.java
Normal file
47
core/src/com/etheller/warsmash/parsers/mdlx/Extent.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
package com.etheller.warsmash.parsers.mdlx;
|
package com.etheller.warsmash.parsers.mdlx;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
|
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.ParseUtils;
|
||||||
|
import com.etheller.warsmash.util.War3ID;
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
import com.google.common.io.LittleEndianDataInputStream;
|
||||||
import com.google.common.io.LittleEndianDataOutputStream;
|
import com.google.common.io.LittleEndianDataOutputStream;
|
||||||
|
|
||||||
@ -19,11 +21,10 @@ import com.google.common.io.LittleEndianDataOutputStream;
|
|||||||
* Based on the works of Chananya Freiman.
|
* Based on the works of Chananya Freiman.
|
||||||
*/
|
*/
|
||||||
public abstract class GenericObject extends AnimatedObject implements Chunk {
|
public abstract class GenericObject extends AnimatedObject implements Chunk {
|
||||||
private static final Charset UTF8 = Charset.forName("utf-8");
|
protected String name;
|
||||||
private String name;
|
|
||||||
private int objectId;
|
private int objectId;
|
||||||
private int parentId;
|
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
|
* 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;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
|
public void readMdx(final LittleEndianDataInputStream stream) throws IOException {
|
||||||
final long size = ParseUtils.parseUInt32(stream);
|
final long size = ParseUtils.readUInt32(stream);
|
||||||
stream.read(NAME_BYTES_HEAP);
|
this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP);
|
||||||
this.name = new String(NAME_BYTES_HEAP, UTF8);
|
|
||||||
this.objectId = stream.readInt();
|
this.objectId = stream.readInt();
|
||||||
this.parentId = stream.readInt();
|
this.parentId = stream.readInt();
|
||||||
this.flags = stream.readInt();
|
this.flags = stream.readInt(); // Used to be Int32 in JS
|
||||||
readTimelines(stream, size - 96);
|
readTimelines(stream, size - 96);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
|
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
|
||||||
stream.writeInt((int) getGenericByteLength());
|
ParseUtils.writeUInt32(stream, getGenericByteLength());
|
||||||
final byte[] bytes = this.name.getBytes(UTF8);
|
final byte[] bytes = this.name.getBytes(ParseUtils.UTF8);
|
||||||
|
stream.write(bytes);
|
||||||
for (int i = 0; i < (80 - bytes.length); i++) {
|
for (int i = 0; i < (80 - bytes.length); i++) {
|
||||||
stream.write((byte) 0);
|
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() {
|
public long getGenericByteLength() {
|
||||||
long size = 96;
|
long size = 96;
|
||||||
@ -84,23 +146,188 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
|
|||||||
return 96 + super.getByteLength();
|
return 96 + super.getByteLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class WrappedMdlTokenIterator implements Iterator<String> {
|
private final class TimelineMaskingIterable implements Iterable<Timeline> {
|
||||||
private final Iterator<String> delegate;
|
private final boolean generic;
|
||||||
|
|
||||||
public WrappedMdlTokenIterator(final Iterator<String> delegate) {
|
private TimelineMaskingIterable(final boolean generic) {
|
||||||
this.delegate = delegate;
|
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
|
@Override
|
||||||
public boolean hasNext() {
|
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
|
@Override
|
||||||
public String next() {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
319
core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java
Normal file
319
core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
102
core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java
Normal file
102
core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
29
core/src/com/etheller/warsmash/parsers/mdlx/Helper.java
Normal file
29
core/src/com/etheller/warsmash/parsers/mdlx/Helper.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
195
core/src/com/etheller/warsmash/parsers/mdlx/Layer.java
Normal file
195
core/src/com/etheller/warsmash/parsers/mdlx/Layer.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
166
core/src/com/etheller/warsmash/parsers/mdlx/Light.java
Normal file
166
core/src/com/etheller/warsmash/parsers/mdlx/Light.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
121
core/src/com/etheller/warsmash/parsers/mdlx/Material.java
Normal file
121
core/src/com/etheller/warsmash/parsers/mdlx/Material.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package com.etheller.warsmash.parsers.mdlx;
|
package com.etheller.warsmash.parsers.mdlx;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
public interface MdlTokenInputStream {
|
public interface MdlTokenInputStream {
|
||||||
String read();
|
String read();
|
||||||
|
|
||||||
@ -11,9 +9,27 @@ public interface MdlTokenInputStream {
|
|||||||
|
|
||||||
float readFloat();
|
float readFloat();
|
||||||
|
|
||||||
|
void readIntArray(long[] values);
|
||||||
|
|
||||||
|
float[] readFloatArray(float[] values); // is this same as read keyframe???
|
||||||
|
|
||||||
void readKeyframe(float[] values);
|
void readKeyframe(float[] values);
|
||||||
|
|
||||||
|
float[] readVectorArray(float[] array, int vectorLength);
|
||||||
|
|
||||||
|
int[] readUInt16Array(int[] values);
|
||||||
|
|
||||||
|
short[] readUInt8Array(short[] values);
|
||||||
|
|
||||||
String peek();
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,49 @@ public interface MdlTokenOutputStream {
|
|||||||
|
|
||||||
void unindent();
|
void unindent();
|
||||||
|
|
||||||
|
void startObjectBlock(String name, String objectName);
|
||||||
|
|
||||||
void startBlock(String name, int blockSize);
|
void startBlock(String name, int blockSize);
|
||||||
|
|
||||||
|
void startBlock(String name);
|
||||||
|
|
||||||
void writeFlag(String token);
|
void writeFlag(String token);
|
||||||
|
|
||||||
|
void writeFlagUInt32(long flag);
|
||||||
|
|
||||||
void writeAttrib(String string, int globalSequenceId);
|
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 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);
|
||||||
}
|
}
|
||||||
|
16
core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java
Normal file
16
core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java
Normal 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;
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
642
core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java
Normal file
642
core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
64
core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java
Normal file
64
core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
193
core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java
Normal file
193
core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
176
core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java
Normal file
176
core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
104
core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java
Normal file
104
core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
81
core/src/com/etheller/warsmash/parsers/mdlx/Texture.java
Normal file
81
core/src/com/etheller/warsmash/parsers/mdlx/Texture.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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(); // }
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -39,7 +39,7 @@ public class FloatArrayKeyFrame implements KeyFrame {
|
|||||||
@Override
|
@Override
|
||||||
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
|
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.time = ParseUtils.parseUInt32(stream);
|
this.time = ParseUtils.readUInt32(stream);
|
||||||
ParseUtils.readFloatArray(stream, this.value);
|
ParseUtils.readFloatArray(stream, this.value);
|
||||||
if (interpolationType.tangential()) {
|
if (interpolationType.tangential()) {
|
||||||
ParseUtils.readFloatArray(stream, this.inTan);
|
ParseUtils.readFloatArray(stream, this.inTan);
|
||||||
|
@ -33,7 +33,7 @@ public class FloatKeyFrame implements KeyFrame {
|
|||||||
@Override
|
@Override
|
||||||
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
|
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.time = ParseUtils.parseUInt32(stream);
|
this.time = ParseUtils.readUInt32(stream);
|
||||||
this.value = stream.readFloat();
|
this.value = stream.readFloat();
|
||||||
if (interpolationType.tangential()) {
|
if (interpolationType.tangential()) {
|
||||||
this.inTan = stream.readFloat();
|
this.inTan = stream.readFloat();
|
||||||
|
@ -32,7 +32,7 @@ public abstract class Timeline implements Chunk {
|
|||||||
public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException {
|
public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
final long keyFrameCount = ParseUtils.parseUInt32(stream);
|
final long keyFrameCount = ParseUtils.readUInt32(stream);
|
||||||
|
|
||||||
this.interpolationType = InterpolationType.VALUES[stream.readInt()];
|
this.interpolationType = InterpolationType.VALUES[stream.readInt()];
|
||||||
this.globalSequenceId = stream.readInt();
|
this.globalSequenceId = stream.readInt();
|
||||||
@ -47,7 +47,7 @@ public abstract class Timeline implements Chunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
|
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.keyFrames.size());
|
||||||
stream.writeInt(this.interpolationType.ordinal());
|
stream.writeInt(this.interpolationType.ordinal());
|
||||||
stream.writeInt(this.globalSequenceId);
|
stream.writeInt(this.globalSequenceId);
|
||||||
@ -86,7 +86,7 @@ public abstract class Timeline implements Chunk {
|
|||||||
|
|
||||||
this.interpolationType = interpolationType;
|
this.interpolationType = interpolationType;
|
||||||
|
|
||||||
if (stream.peek().equals("GlobalSeqId")) {
|
if (stream.peek().equals(MdlUtils.TOKEN_GLOBAL_SEQ_ID)) {
|
||||||
stream.read();
|
stream.read();
|
||||||
this.globalSequenceId = stream.readInt();
|
this.globalSequenceId = stream.readInt();
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ public abstract class Timeline implements Chunk {
|
|||||||
stream.writeFlag(token);
|
stream.writeFlag(token);
|
||||||
|
|
||||||
if (this.globalSequenceId != -1) {
|
if (this.globalSequenceId != -1) {
|
||||||
stream.writeAttrib("GlobalSeqId", this.globalSequenceId);
|
stream.writeAttrib(MdlUtils.TOKEN_GLOBAL_SEQ_ID, this.globalSequenceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final KeyFrame keyFrame : this.keyFrames) {
|
for (final KeyFrame keyFrame : this.keyFrames) {
|
||||||
|
@ -33,11 +33,11 @@ public class UInt32KeyFrame implements KeyFrame {
|
|||||||
@Override
|
@Override
|
||||||
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
|
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.time = ParseUtils.parseUInt32(stream);
|
this.time = ParseUtils.readUInt32(stream);
|
||||||
this.value = ParseUtils.parseUInt32(stream);
|
this.value = ParseUtils.readUInt32(stream);
|
||||||
if (interpolationType.tangential()) {
|
if (interpolationType.tangential()) {
|
||||||
this.inTan = ParseUtils.parseUInt32(stream);
|
this.inTan = ParseUtils.readUInt32(stream);
|
||||||
this.outTan = ParseUtils.parseUInt32(stream);
|
this.outTan = ParseUtils.readUInt32(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
core/src/com/etheller/warsmash/parsers/terrain/Corner.java
Normal file
27
core/src/com/etheller/warsmash/parsers/terrain/Corner.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
13
core/src/com/etheller/warsmash/parsers/terrain/Terrain.java
Normal file
13
core/src/com/etheller/warsmash/parsers/terrain/Terrain.java
Normal 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() {
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
6
core/src/com/etheller/warsmash/util/Descriptor.java
Normal file
6
core/src/com/etheller/warsmash/util/Descriptor.java
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package com.etheller.warsmash.util;
|
||||||
|
|
||||||
|
public interface Descriptor<E> {
|
||||||
|
E create();
|
||||||
|
|
||||||
|
}
|
85
core/src/com/etheller/warsmash/util/ImageUtils.java
Normal file
85
core/src/com/etheller/warsmash/util/ImageUtils.java
Normal 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() {
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,198 @@
|
|||||||
package com.etheller.warsmash.util;
|
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 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_DONT_INTERP = "DontInterp";
|
||||||
public static final String TOKEN_LINEAR = "Linear";
|
public static final String TOKEN_LINEAR = "Linear";
|
||||||
public static final String TOKEN_HERMITE = "Hermite";
|
public static final String TOKEN_HERMITE = "Hermite";
|
||||||
public static final String TOKEN_BEZIER = "Bezier";
|
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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
package com.etheller.warsmash.util;
|
package com.etheller.warsmash.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
import com.google.common.io.LittleEndianDataInputStream;
|
||||||
import com.google.common.io.LittleEndianDataOutputStream;
|
import com.google.common.io.LittleEndianDataOutputStream;
|
||||||
|
|
||||||
public class ParseUtils {
|
public class ParseUtils {
|
||||||
public static long parseUInt32(final LittleEndianDataInputStream stream) throws IOException {
|
public static final Charset UTF8 = Charset.forName("utf-8");
|
||||||
return stream.readInt() & 0xFFFFFFFF;
|
|
||||||
|
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)
|
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)
|
public static void writeFloatArray(final LittleEndianDataOutputStream stream, final float[] array)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
for (int i = 0; i < array.length; i++) {
|
for (int i = 0; i < array.length; i++) {
|
||||||
stream.writeFloat(array[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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
290
core/src/com/etheller/warsmash/util/RenderMathUtils.java
Normal file
290
core/src/com/etheller/warsmash/util/RenderMathUtils.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
573
core/src/com/etheller/warsmash/util/Vector4.java
Normal file
573
core/src/com/etheller/warsmash/util/Vector4.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
116
core/src/com/etheller/warsmash/viewer/BoundingShape.java
Normal file
116
core/src/com/etheller/warsmash/viewer/BoundingShape.java
Normal 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 ???
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
core/src/com/etheller/warsmash/viewer/Bucket.java
Normal file
28
core/src/com/etheller/warsmash/viewer/Bucket.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
314
core/src/com/etheller/warsmash/viewer/Camera.java
Normal file
314
core/src/com/etheller/warsmash/viewer/Camera.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
9
core/src/com/etheller/warsmash/viewer/Model.java
Normal file
9
core/src/com/etheller/warsmash/viewer/Model.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package com.etheller.warsmash.viewer;
|
||||||
|
|
||||||
|
public abstract class Model {
|
||||||
|
private ModelView modelView;
|
||||||
|
|
||||||
|
public boolean ok;
|
||||||
|
|
||||||
|
public abstract Viewer getViewer();
|
||||||
|
}
|
5
core/src/com/etheller/warsmash/viewer/ModelInstance.java
Normal file
5
core/src/com/etheller/warsmash/viewer/ModelInstance.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package com.etheller.warsmash.viewer;
|
||||||
|
|
||||||
|
public class ModelInstance {
|
||||||
|
|
||||||
|
}
|
68
core/src/com/etheller/warsmash/viewer/ModelView.java
Normal file
68
core/src/com/etheller/warsmash/viewer/ModelView.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
5
core/src/com/etheller/warsmash/viewer/Scene.java
Normal file
5
core/src/com/etheller/warsmash/viewer/Scene.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package com.etheller.warsmash.viewer;
|
||||||
|
|
||||||
|
public abstract class Scene {
|
||||||
|
public Camera camera;
|
||||||
|
}
|
245
core/src/com/etheller/warsmash/viewer/SceneNode.java
Normal file
245
core/src/com/etheller/warsmash/viewer/SceneNode.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
118
core/src/com/etheller/warsmash/viewer/SkeletalNode.java
Normal file
118
core/src/com/etheller/warsmash/viewer/SkeletalNode.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
core/src/com/etheller/warsmash/viewer/Viewer.java
Normal file
7
core/src/com/etheller/warsmash/viewer/Viewer.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.etheller.warsmash.viewer;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.GL20;
|
||||||
|
|
||||||
|
public abstract class Viewer {
|
||||||
|
public GL20 gl;
|
||||||
|
}
|
65
core/src/com/etheller/warsmash/viewer/ViewerNode.java
Normal file
65
core/src/com/etheller/warsmash/viewer/ViewerNode.java
Normal 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);
|
||||||
|
}
|
12
core/src/com/hiveworkshop/wc3/mpq/Codebase.java
Normal file
12
core/src/com/hiveworkshop/wc3/mpq/Codebase.java
Normal 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);
|
||||||
|
}
|
35
core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java
Normal file
35
core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java
Normal 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
BIN
jars/blp-iio-plugin.jar
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user