More updates for handlers mdx

This commit is contained in:
Retera 2019-12-17 02:00:19 -06:00
parent dca8289d14
commit 005dd375ad
83 changed files with 3692 additions and 681 deletions

View File

@ -16,7 +16,7 @@ import com.google.common.io.LittleEndianDataOutputStream;
*
*/
public abstract class AnimatedObject implements Chunk, MdlxBlock {
protected final List<Timeline> timelines;
protected final List<Timeline<?>> timelines;
public AnimatedObject() {
this.timelines = new ArrayList<>();
@ -25,7 +25,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock {
public void readTimelines(final LittleEndianDataInputStream stream, long size) throws IOException {
while (size > 0) {
final War3ID name = new War3ID(Integer.reverseBytes(stream.readInt()));
final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline();
final Timeline<?> timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline();
timeline.readMdx(stream, name);
@ -36,7 +36,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock {
}
public void writeTimelines(final LittleEndianDataOutputStream stream) throws IOException {
for (final Timeline timeline : this.timelines) {
for (final Timeline<?> timeline : this.timelines) {
timeline.writeMdx(stream);
}
}
@ -46,7 +46,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock {
}
public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) throws IOException {
final Timeline timeline = name.getImplementation().createTimeline();
final Timeline<?> timeline = name.getImplementation().createTimeline();
timeline.readMdl(stream, name.getWar3id());
@ -54,7 +54,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock {
}
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.getWar3id())) {
timeline.writeMdl(stream);
return true;
@ -66,7 +66,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock {
@Override
public long getByteLength() {
long size = 0;
for (final Timeline timeline : this.timelines) {
for (final Timeline<?> timeline : this.timelines) {
size += timeline.getByteLength();
}
return size;
@ -101,4 +101,8 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock {
}
}
public List<Timeline<?>> getTimelines() {
return this.timelines;
}
}

View File

@ -96,4 +96,12 @@ public class Attachment extends GenericObject {
public long getByteLength() {
return 268 + super.getByteLength();
}
public String getPath() {
return this.path;
}
public int getAttachmentId() {
return this.attachmentId;
}
}

View File

@ -85,4 +85,7 @@ public class Bone extends GenericObject {
return 8 + super.getByteLength();
}
public int getGeosetAnimationId() {
return this.geosetAnimationId;
}
}

View File

@ -125,4 +125,28 @@ public class Camera extends AnimatedObject {
public long getByteLength() {
return 120 + super.getByteLength();
}
public String getName() {
return this.name;
}
public float[] getPosition() {
return this.position;
}
public float getFieldOfView() {
return this.fieldOfView;
}
public float getFarClippingPlane() {
return this.farClippingPlane;
}
public float getNearClippingPlane() {
return this.nearClippingPlane;
}
public float[] getTargetPosition() {
return this.targetPosition;
}
}

View File

@ -74,4 +74,21 @@ public class EventObject extends GenericObject {
public long getByteLength() {
return 12 + (this.keyFrames.length * 4) + super.getByteLength();
}
public int getGlobalSequenceId() {
return this.globalSequenceId;
}
public void setGlobalSequenceId(final int globalSequenceId) {
this.globalSequenceId = globalSequenceId;
}
public long[] getKeyFrames() {
return this.keyFrames;
}
public void setKeyFrames(final long[] keyFrames) {
this.keyFrames = keyFrames;
}
}

View File

@ -61,13 +61,13 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
stream.writeInt(this.parentId);
stream.writeInt(this.flags); // UInt32 in ghostwolf JS, shouldn't matter for Java
for (final Timeline timeline : eachTimeline(true)) {
for (final Timeline<?> timeline : eachTimeline(true)) {
timeline.writeMdx(stream);
}
}
public void writeNonGenericAnimationChunks(final LittleEndianDataOutputStream stream) throws IOException {
for (final Timeline timeline : eachTimeline(false)) {
for (final Timeline<?> timeline : eachTimeline(false)) {
timeline.writeMdx(stream);
}
}
@ -129,7 +129,7 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
this.writeTimeline(stream, AnimationMap.KGSC);
}
public Iterable<Timeline> eachTimeline(final boolean generic) {
public Iterable<Timeline<?>> eachTimeline(final boolean generic) {
return new TimelineMaskingIterable(generic);
}
@ -146,7 +146,7 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
return 96 + super.getByteLength();
}
private final class TimelineMaskingIterable implements Iterable<Timeline> {
private final class TimelineMaskingIterable implements Iterable<Timeline<?>> {
private final boolean generic;
private TimelineMaskingIterable(final boolean generic) {
@ -154,24 +154,24 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
}
@Override
public Iterator<Timeline> iterator() {
public Iterator<Timeline<?>> iterator() {
return new TimelineMaskingIterator(this.generic, GenericObject.this.timelines);
}
}
private static final class TimelineMaskingIterator implements Iterator<Timeline> {
private static final class TimelineMaskingIterator implements Iterator<Timeline<?>> {
private final boolean wantGeneric;
private final Iterator<Timeline> delegate;
private final Iterator<Timeline<?>> delegate;
private boolean hasNext;
private Timeline next;
private Timeline<?> next;
public TimelineMaskingIterator(final boolean wantGeneric, final List<Timeline> timelines) {
public TimelineMaskingIterator(final boolean wantGeneric, final List<Timeline<?>> timelines) {
this.wantGeneric = wantGeneric;
this.delegate = timelines.iterator();
scanUntilNext();
}
private boolean isGeneric(final Timeline timeline) {
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);
@ -197,8 +197,8 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
}
@Override
public Timeline next() {
final Timeline last = this.next;
public Timeline<?> next() {
final Timeline<?> last = this.next;
scanUntilNext();
return last;
}
@ -331,4 +331,20 @@ public abstract class GenericObject extends AnimatedObject implements Chunk {
}
}
public String getName() {
return this.name;
}
public int getObjectId() {
return this.objectId;
}
public int getParentId() {
return this.parentId;
}
public int getFlags() {
return this.flags;
}
}

View File

@ -99,4 +99,16 @@ public class GeosetAnimation extends AnimatedObject {
public long getByteLength() {
return 28 + super.getByteLength();
}
public float[] getColor() {
return this.color;
}
public float getAlpha() {
return this.alpha;
}
public int getGeosetId() {
return this.geosetId;
}
}

View File

@ -55,10 +55,10 @@ public class MdlxModel {
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)
* (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}
*/
@ -208,7 +208,7 @@ public class MdlxModel {
private <E extends MdlxBlock & Chunk> void loadDynamicObjects(final List<E> out,
final MdlxBlockDescriptor<E> constructor, final LittleEndianDataInputStream stream, final long size)
throws IOException {
throws IOException {
long totalSize = 0;
while (totalSize < size) {
final E object = constructor.create();
@ -459,7 +459,7 @@ public class MdlxModel {
private <E extends MdlxBlock> void loadNumberedObjectBlock(final List<E> out,
final MdlxBlockDescriptor<E> constructor, final String name, final MdlTokenInputStream stream)
throws IOException {
throws IOException {
stream.read(); // Don't care about the number, the array will grow
for (final String token : stream.readBlock()) {
@ -644,10 +644,14 @@ public class MdlxModel {
}
public List<Long> getGlobalSequences() {
return globalSequences;
return this.globalSequences;
}
public List<Sequence> getSequences() {
return sequences;
return this.sequences;
}
public List<float[]> getPivotPoints() {
return this.pivotPoints;
}
}

View File

@ -422,4 +422,97 @@ public class ParticleEmitter2 extends GenericObject {
public long getByteLength() {
return 175 + super.getByteLength();
}
public float getSpeed() {
return this.speed;
}
public float getVariation() {
return this.variation;
}
public float getLatitude() {
return this.latitude;
}
public float getGravity() {
return this.gravity;
}
public float getLifeSpan() {
return this.lifeSpan;
}
public float getEmissionRate() {
return this.emissionRate;
}
public float getLength() {
return this.length;
}
public float getWidth() {
return this.width;
}
public FilterMode getFilterMode() {
return this.filterMode;
}
public long getRows() {
return this.rows;
}
public long getColumns() {
return this.columns;
}
public long getHeadOrTail() {
return this.headOrTail;
}
public float getTailLength() {
return this.tailLength;
}
public float getTimeMiddle() {
return this.timeMiddle;
}
public float[][] getSegmentColors() {
return this.segmentColors;
}
public short[] getSegmentAlphas() {
return this.segmentAlphas;
}
public float[] getSegmentScaling() {
return this.segmentScaling;
}
public long[][] getHeadIntervals() {
return this.headIntervals;
}
public long[][] getTailIntervals() {
return this.tailIntervals;
}
public int getTextureId() {
return this.textureId;
}
public long getSquirt() {
return this.squirt;
}
public int getPriorityPlane() {
return this.priorityPlane;
}
public long getReplaceableId() {
return this.replaceableId;
}
}

View File

@ -173,4 +173,49 @@ public class RibbonEmitter extends GenericObject {
public long getByteLength() {
return 56 + super.getByteLength();
}
public float getHeightAbove() {
return this.heightAbove;
}
public float getHeightBelow() {
return this.heightBelow;
}
public float getAlpha() {
return this.alpha;
}
public float[] getColor() {
return this.color;
}
public float getLifeSpan() {
return this.lifeSpan;
}
public long getTextureSlot() {
return this.textureSlot;
}
public long getEmissionRate() {
return this.emissionRate;
}
public long getRows() {
return this.rows;
}
public long getColumns() {
return this.columns;
}
public int getMaterialId() {
return this.materialId;
}
public float getGravity() {
return this.gravity;
}
}

View File

@ -1,124 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import java.util.Arrays;
import com.etheller.warsmash.parsers.mdlx.InterpolationType;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A UInt32 animation keyframe in a time track.
*
* Based on the works of Chananya Freiman. I changed the name of Track to
* KeyFrame. A possible future optimization would be to make 2 subclasses, one
* with inTan and one without, so that non-Hermite/non-Bezier animation data
* would use less memory.
*
*/
public class FloatArrayKeyFrame implements KeyFrame {
private long time;
private final float[] value;
private final float[] inTan;
private final float[] outTan;
public FloatArrayKeyFrame(final int arraySize) {
this.value = new float[arraySize];
this.inTan = new float[arraySize];
this.outTan = new float[arraySize];
}
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer();
@Override
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = ParseUtils.readUInt32(stream);
ParseUtils.readFloatArray(stream, this.value);
if (interpolationType.tangential()) {
ParseUtils.readFloatArray(stream, this.inTan);
ParseUtils.readFloatArray(stream, this.outTan);
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType)
throws IOException {
stream.writeInt((int) this.time);
ParseUtils.writeFloatArray(stream, this.value);
if (interpolationType.tangential()) {
ParseUtils.writeFloatArray(stream, this.inTan);
ParseUtils.writeFloatArray(stream, this.outTan);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = stream.readUInt32();
stream.readKeyframe(this.value);
if (interpolationType.tangential()) {
stream.read(); // "InTan"
stream.readKeyframe(this.inTan);
stream.read(); // "OutTan"
stream.readKeyframe(this.outTan);
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType)
throws IOException {
STRING_BUFFER_HEAP.setLength(0);
STRING_BUFFER_HEAP.append(this.time);
STRING_BUFFER_HEAP.append(':');
stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value);
if (interpolationType.tangential()) {
stream.indent();
stream.writeKeyframe("InTan", this.inTan);
stream.writeKeyframe("OutTan", this.outTan);
stream.unindent();
}
}
@Override
public long getByteLength(final InterpolationType interpolationType) {
final long valueSize = Float.BYTES * this.value.length; // unsigned
long size = 4 + valueSize;
if (interpolationType.tangential()) {
size += valueSize * 2;
}
return size;
}
@Override
public long getTime() {
return time;
}
@Override
public boolean matchingValue(final KeyFrame other) {
if (other instanceof FloatArrayKeyFrame) {
final FloatArrayKeyFrame otherFrame = (FloatArrayKeyFrame) other;
return Arrays.equals(value, otherFrame.value);
}
return false;
}
@Override
public KeyFrame clone(final long time) {
final FloatArrayKeyFrame newKeyFrame = new FloatArrayKeyFrame(value.length);
System.arraycopy(value, 0, newKeyFrame.value, 0, value.length);
// if (inTan != null) {
System.arraycopy(inTan, 0, newKeyFrame.inTan, 0, inTan.length);
System.arraycopy(outTan, 0, newKeyFrame.outTan, 0, outTan.length);
// }
return newKeyFrame;
}
}

View File

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

View File

@ -1,116 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.InterpolationType;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.RenderMathUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A UInt32 animation keyframe in a time track.
*
* Based on the works of Chananya Freiman. I changed the name of Track to
* KeyFrame. A possible future optimization would be to make 2 subclasses, one
* with inTan and one without, so that non-Hermite/non-Bezier animation data
* would use less memory.
*
*/
public class FloatKeyFrame implements KeyFrame {
private long time;
private float value;
private float inTan;
private float outTan;
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer();
@Override
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = ParseUtils.readUInt32(stream);
this.value = stream.readFloat();
if (interpolationType.tangential()) {
this.inTan = stream.readFloat();
this.outTan = stream.readFloat();
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType)
throws IOException {
stream.writeInt((int) this.time);
stream.writeFloat(this.value);
if (interpolationType.tangential()) {
stream.writeFloat(this.inTan);
stream.writeFloat(this.outTan);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = stream.readUInt32();
this.value = stream.readFloat();
if (interpolationType.tangential()) {
stream.read(); // "InTan"
this.inTan = stream.readFloat();
stream.read(); // "OutTan"
this.outTan = stream.readFloat();
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType)
throws IOException {
STRING_BUFFER_HEAP.setLength(0);
STRING_BUFFER_HEAP.append(this.time);
STRING_BUFFER_HEAP.append(':');
stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value);
if (interpolationType.tangential()) {
stream.indent();
stream.writeKeyframe("InTan", this.inTan);
stream.writeKeyframe("OutTan", this.outTan);
stream.unindent();
}
}
@Override
public long getByteLength(final InterpolationType interpolationType) {
final long valueSize = Float.BYTES; // unsigned
long size = 4 + valueSize;
if (interpolationType.tangential()) {
size += valueSize * 2;
}
return size;
}
@Override
public long getTime() {
return time;
}
@Override
public boolean matchingValue(final KeyFrame other) {
if (other instanceof FloatKeyFrame) {
final FloatKeyFrame otherFrame = (FloatKeyFrame) other;
return Math.abs(value - otherFrame.value) <= RenderMathUtils.EPSILON;
}
return false;
}
@Override
public KeyFrame clone(final long time) {
final FloatKeyFrame newKeyFrame = new FloatKeyFrame();
newKeyFrame.value = value;
newKeyFrame.inTan = inTan;
newKeyFrame.outTan = outTan;
return newKeyFrame;
}
}

View File

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

View File

@ -1,27 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.InterpolationType;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public interface KeyFrame {
void readMdx(LittleEndianDataInputStream stream, InterpolationType interpolationType) throws IOException;
void writeMdx(LittleEndianDataOutputStream stream, InterpolationType interpolationType) throws IOException;
void readMdl(MdlTokenInputStream stream, InterpolationType interpolationType) throws IOException;
void writeMdl(MdlTokenOutputStream stream, InterpolationType interpolationType) throws IOException;
long getByteLength(InterpolationType interpolationType);
long getTime();
boolean matchingValue(KeyFrame other);
KeyFrame clone(long time);
}

View File

@ -1,8 +1,6 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.parsers.mdlx.Chunk;
@ -15,18 +13,27 @@ import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public abstract class Timeline implements Chunk {
public abstract class Timeline<TYPE> implements Chunk {
private War3ID name;
private InterpolationType interpolationType;
private int globalSequenceId = -1;
private final List<KeyFrame> keyFrames;
private long[] frames;
private TYPE[] values;
private TYPE[] inTans;
private TYPE[] outTans;
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer();
public War3ID getName() {
return this.name;
}
public Timeline() {
this.keyFrames = new ArrayList<>();
}
public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException {
@ -37,23 +44,38 @@ public abstract class Timeline implements Chunk {
this.interpolationType = InterpolationType.VALUES[stream.readInt()];
this.globalSequenceId = stream.readInt();
this.frames = new long[(int) keyFrameCount];
this.values = (TYPE[]) new Object[(int) keyFrameCount];
if (this.interpolationType.tangential()) {
this.inTans = (TYPE[]) new Object[(int) keyFrameCount];
this.outTans = (TYPE[]) new Object[(int) keyFrameCount];
}
for (int i = 0; i < keyFrameCount; i++) {
final KeyFrame keyFrame = newKeyFrame();
this.frames[i] = (stream.readInt()); // TODO autoboxing is slow
this.values[i] = (this.readMdxValue(stream));
keyFrame.readMdx(stream, this.interpolationType);
this.keyFrames.add(keyFrame);
if (this.interpolationType.tangential()) {
this.inTans[i] = (this.readMdxValue(stream));
this.outTans[i] = (this.readMdxValue(stream));
}
}
}
public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(Integer.reverseBytes(this.name.getValue()));
stream.writeInt(this.keyFrames.size());
final int keyframeCount = this.frames.length;
stream.writeInt(keyframeCount);
stream.writeInt(this.interpolationType.ordinal());
stream.writeInt(this.globalSequenceId);
for (final KeyFrame keyFrame : this.keyFrames) {
keyFrame.writeMdx(stream, this.interpolationType);
for (int i = 0; i < keyframeCount; i++) {
stream.writeInt((int) this.frames[i]);
writeMdxValue(stream, this.values[i]);
if (this.interpolationType.tangential()) {
writeMdxValue(stream, this.inTans[i]);
writeMdxValue(stream, this.outTans[i]);
}
}
}
@ -94,19 +116,29 @@ public abstract class Timeline implements Chunk {
this.globalSequenceId = -1;
}
this.frames = new long[keyFrameCount];
this.values = (TYPE[]) new Object[keyFrameCount];
if (this.interpolationType.tangential()) {
this.inTans = (TYPE[]) new Object[keyFrameCount];
this.outTans = (TYPE[]) new Object[keyFrameCount];
}
for (int i = 0; i < keyFrameCount; i++) {
final KeyFrame keyFrame = newKeyFrame();
keyFrame.readMdl(stream, interpolationType);
this.keyFrames.add(keyFrame);
this.frames[i] = (stream.readInt());
this.values[i] = (this.readMdlValue(stream));
if (interpolationType.tangential()) {
stream.read(); // InTan
this.inTans[i] = (this.readMdlValue(stream));
stream.read(); // OutTan
this.outTans[i] = (this.readMdlValue(stream));
}
}
stream.read(); // }
}
public void writeMdl(final MdlTokenOutputStream stream) throws IOException {
stream.startBlock(AnimationMap.ID_TO_TAG.get(this.name).getMdlToken(), this.keyFrames.size());
final int tracksCount = this.frames.length;
stream.startBlock(AnimationMap.ID_TO_TAG.get(this.name).getMdlToken(), tracksCount);
String token;
switch (this.interpolationType) {
@ -133,8 +165,17 @@ public abstract class Timeline implements Chunk {
stream.writeAttrib(MdlUtils.TOKEN_GLOBAL_SEQ_ID, this.globalSequenceId);
}
for (final KeyFrame keyFrame : this.keyFrames) {
keyFrame.writeMdl(stream, this.interpolationType);
for (int i = 0; i < tracksCount; i++) {
STRING_BUFFER_HEAP.setLength(0);
STRING_BUFFER_HEAP.append(this.frames[i]);
STRING_BUFFER_HEAP.append(':');
this.writeMdlValue(stream, STRING_BUFFER_HEAP.toString(), this.values[i]);
if (this.interpolationType.tangential()) {
stream.indent();
this.writeMdlValue(stream, "InTan", this.inTans[i]);
this.writeMdlValue(stream, "OutTan", this.outTans[i]);
stream.unindent();
}
}
stream.endBlock();
@ -142,22 +183,52 @@ public abstract class Timeline implements Chunk {
@Override
public long getByteLength() {
return 16 + (this.keyFrames.size() * (4 + (4 * (this.size() * (this.interpolationType.tangential() ? 3 : 1)))));
}
final int tracksCount = this.frames.length;
int size = 16;
protected abstract KeyFrame newKeyFrame();
if (tracksCount > 0) {
final int bytesPerValue = size() * 4;
int valuesPerTrack = 1;
if (this.interpolationType.tangential()) {
valuesPerTrack = 3;
}
size += (4 + (valuesPerTrack * bytesPerValue)) * tracksCount;
}
return size;
}
protected abstract int size();
public int getGlobalSequenceId() {
return globalSequenceId;
}
protected abstract TYPE readMdxValue(LittleEndianDataInputStream stream) throws IOException;
public List<KeyFrame> getKeyFrames() {
return keyFrames;
protected abstract TYPE readMdlValue(MdlTokenInputStream stream);
protected abstract void writeMdxValue(LittleEndianDataOutputStream stream, TYPE value) throws IOException;
protected abstract void writeMdlValue(MdlTokenOutputStream stream, String prefix, TYPE value);
public int getGlobalSequenceId() {
return this.globalSequenceId;
}
public InterpolationType getInterpolationType() {
return interpolationType;
return this.interpolationType;
}
public long[] getFrames() {
return this.frames;
}
public TYPE[] getValues() {
return this.values;
}
public TYPE[] getInTans() {
return this.inTans;
}
public TYPE[] getOutTans() {
return this.outTans;
}
}

View File

@ -1,115 +0,0 @@
package com.etheller.warsmash.parsers.mdlx.timeline;
import java.io.IOException;
import com.etheller.warsmash.parsers.mdlx.InterpolationType;
import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream;
import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A UInt32 animation keyframe in a time track.
*
* Based on the works of Chananya Freiman. I changed the name of Track to
* KeyFrame. A possible future optimization would be to make 2 subclasses, one
* with inTan and one without, so that non-Hermite/non-Bezier animation data
* would use less memory.
*
*/
public class UInt32KeyFrame implements KeyFrame {
private long time;
private long value;
private long inTan;
private long outTan;
/**
* Restricts us to only be able to parse models on one thread at a time, in
* return for high performance.
*/
private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer();
@Override
public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = ParseUtils.readUInt32(stream);
this.value = ParseUtils.readUInt32(stream);
if (interpolationType.tangential()) {
this.inTan = ParseUtils.readUInt32(stream);
this.outTan = ParseUtils.readUInt32(stream);
}
}
@Override
public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType)
throws IOException {
stream.writeInt((int) this.time);
stream.writeInt((int) this.value);
if (interpolationType.tangential()) {
stream.writeInt((int) this.inTan);
stream.writeInt((int) this.outTan);
}
}
@Override
public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType)
throws IOException {
this.time = stream.readUInt32();
this.value = stream.readUInt32();
if (interpolationType.tangential()) {
stream.read(); // "InTan"
this.inTan = stream.readUInt32();
stream.read(); // "OutTan"
this.outTan = stream.readUInt32();
}
}
@Override
public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType)
throws IOException {
STRING_BUFFER_HEAP.setLength(0);
STRING_BUFFER_HEAP.append(this.time);
STRING_BUFFER_HEAP.append(':');
stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value);
if (interpolationType.tangential()) {
stream.indent();
stream.writeKeyframe("InTan", this.inTan);
stream.writeKeyframe("OutTan", this.outTan);
stream.unindent();
}
}
@Override
public long getByteLength(final InterpolationType interpolationType) {
final long valueSize = Integer.BYTES; // unsigned
long size = 4 + valueSize;
if (interpolationType.tangential()) {
size += valueSize * 2;
}
return size;
}
@Override
public long getTime() {
return time;
}
@Override
public boolean matchingValue(final KeyFrame other) {
if (other instanceof UInt32KeyFrame) {
final UInt32KeyFrame otherFrame = (UInt32KeyFrame) other;
return value == otherFrame.value;
}
return false;
}
@Override
public KeyFrame clone(final long time) {
final UInt32KeyFrame newKeyFrame = new UInt32KeyFrame();
newKeyFrame.value = value;
newKeyFrame.inTan = inTan;
newKeyFrame.outTan = outTan;
return newKeyFrame;
}
}

View File

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

View File

@ -1,8 +1,8 @@
package com.etheller.warsmash.util;
public class Interpolator {
public void interpolateScalar(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d,
final float t, final int type) {
public static void interpolateScalar(final float[] out, final float[] a, final float[] b, final float[] c,
final float[] d, final float t, final int type) {
switch (type) {
case 0: {
out[0] = a[0];
@ -23,8 +23,8 @@ public class Interpolator {
}
}
public void interpolateVector(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d,
final float t, final int type) {
public static void interpolateVector(final float[] out, final float[] a, final float[] b, final float[] c,
final float[] d, final float t, final int type) {
switch (type) {
case 0: {
System.arraycopy(a, 0, out, 0, a.length);
@ -51,7 +51,7 @@ public class Interpolator {
}
}
public void interpolateQuaternion(final float[] out, final float[] a, final float[] b, final float[] c,
public static void interpolateQuaternion(final float[] out, final float[] a, final float[] b, final float[] c,
final float[] d, final float t, final int type) {
switch (type) {
case 0: {

View File

@ -14,6 +14,9 @@ public enum RenderMathUtils {
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);
public static final float[] FLOAT_VEC3_ZERO = new float[] { 0, 0, 0 };
public static final float[] FLOAT_QUAT_DEFAULT = new float[] { 0, 0, 0, 1 };
public static final float[] FLOAT_VEC3_ONE = new float[] { 1, 1, 1 };
// copied from ghostwolf and
// https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html
@ -356,7 +359,7 @@ public enum RenderMathUtils {
}
public static float randomInRange(final float a, final float b) {
return (float) (a + Math.random() * (b - a));
return (float) (a + (Math.random() * (b - a)));
}
public static float clamp(final float x, final float minVal, final float maxVal) {
@ -364,15 +367,15 @@ public enum RenderMathUtils {
}
public static float lerp(final float a, final float b, final float t) {
return a + t * (b - a);
return a + (t * (b - a));
}
public static float hermite(final float a, final float b, final float c, final float d, final float t) {
final float factorTimes2 = t * t;
final float factor1 = factorTimes2 * (2 * t - 3) + 1;
final float factor2 = factorTimes2 * (t - 2) + t;
final float factor1 = (factorTimes2 * ((2 * t) - 3)) + 1;
final float factor2 = (factorTimes2 * (t - 2)) + t;
final float factor3 = factorTimes2 * (t - 1);
final float factor4 = factorTimes2 * (3 - 2 * t);
final float factor4 = factorTimes2 * (3 - (2 * t));
return (a * factor1) + (b * factor2) + (c * factor3) + (d * factor4);
}
@ -397,7 +400,7 @@ public enum RenderMathUtils {
float omega, cosom, sinom, scale0, scale1;
// calc cosine
cosom = ax * bx + ay * by + az * bz + aw * bw;
cosom = (ax * bx) + (ay * by) + (az * bz) + (aw * bw);
// adjust signs (if necessary)
if (cosom < 0.0) {
cosom = -cosom;
@ -421,10 +424,10 @@ public enum RenderMathUtils {
scale1 = t;
}
// calculate final values
out[0] = scale0 * ax + scale1 * bx;
out[1] = scale0 * ay + scale1 * by;
out[2] = scale0 * az + scale1 * bz;
out[3] = scale0 * aw + scale1 * bw;
out[0] = (scale0 * ax) + (scale1 * bx);
out[1] = (scale0 * ay) + (scale1 * by);
out[2] = (scale0 * az) + (scale1 * bz);
out[3] = (scale0 * aw) + (scale1 * bw);
return out;
}

View File

@ -36,15 +36,15 @@ public class Camera {
public Quaternion inverseRotation;
private final Matrix4 worldMatrix;
private final Matrix4 projectionMatrix;
private final Matrix4 worldProjectionMatrix;
public final Matrix4 worldProjectionMatrix;
private final Matrix4 inverseWorldMatrix;
private final Matrix4 inverseRotationMatrix;
private final Matrix4 inverseWorldProjectionMatrix;
public final Vector3 directionX;
public final Vector3 directionY;
public final Vector3 directionZ;
private final Vector3[] vectors;
private final Vector3[] billboardedVectors;
public final Vector3[] vectors;
public final Vector3[] billboardedVectors;
public final Vector4[] planes;
private boolean dirty;

View File

@ -1,10 +1,10 @@
package com.etheller.warsmash.viewer5;
public abstract class EmittedObject {
abstract void update(float dt);
public abstract class EmittedObject<MODEL_INSTANCE extends ModelInstance, EMITTER extends Emitter<MODEL_INSTANCE, ? extends EmittedObject<MODEL_INSTANCE, EMITTER>>> {
public abstract void update(float dt);
public float health;
public Emitter emitter;
public EMITTER emitter;
public int index;
protected abstract void bind(int flags);

View File

@ -3,31 +3,29 @@ package com.etheller.warsmash.viewer5;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class Emitter<MODEL_INSTANCE extends ModelInstance, EMITTED_OBJECT extends EmittedObject<MODEL_INSTANCE, ? extends Emitter<MODEL_INSTANCE, EMITTED_OBJECT>>> {
public abstract class Emitter {
public final MODEL_INSTANCE instance;
public final List<EMITTED_OBJECT> objects;
public int alive;
protected float currentEmission;
private final ModelInstance instance;
private final EmitterObject emitterObject;
private final List<EmittedObject> objects;
private int alive;
private int currentEmission;
public Emitter(final ModelInstance instance, final EmitterObject emitterObject) {
public Emitter(final MODEL_INSTANCE instance) {
this.instance = instance;
this.emitterObject = emitterObject;
this.objects = new ArrayList<>();
this.alive = 0;
this.currentEmission = 0;
}
public final EmittedObject emitObject(final int flags) {
public final EMITTED_OBJECT emitObject(final int flags) {
if (this.alive == this.objects.size()) {
this.objects.add(this.createObject());
}
final EmittedObject object = this.objects.get(this.alive);
final EMITTED_OBJECT object = this.objects.get(this.alive);
object.index = this.alive;
object.bind(flags);
this.alive += 1;
@ -38,10 +36,11 @@ public abstract class Emitter {
return object;
}
public final void update(final float dt) {
public void update(final float dt) {
this.updateEmission(dt);
final int currentEmission = this.currentEmission;
final float currentEmission = this.currentEmission;
if (currentEmission >= 1) {
for (int i = 0; i < currentEmission; i += 1) {
this.emit();
@ -49,10 +48,10 @@ public abstract class Emitter {
}
}
public final void kill(final EmittedObject object) {
public void kill(final EMITTED_OBJECT object) {
this.alive -= 1;
final EmittedObject otherObject = this.objects.get(this.alive);
final EMITTED_OBJECT otherObject = this.objects.get(this.alive);
this.objects.set(object.index, otherObject);
this.objects.set(this.alive, object);
@ -71,5 +70,5 @@ public abstract class Emitter {
protected abstract void emit();
protected abstract EmittedObject createObject();
protected abstract EMITTED_OBJECT createObject();
}

View File

@ -0,0 +1,33 @@
package com.etheller.warsmash.viewer5;
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 GenericNode {
protected Vector3 pivot;
protected Vector3 localLocation;
protected Quaternion localRotation;
protected Vector3 localScale;
protected Vector3 worldLocation;
protected Quaternion worldRotation;
protected Vector3 worldScale;
protected Vector3 inverseWorldLocation;
protected Quaternion inverseWorldRotation;
protected Vector3 inverseWorldScale;
protected Matrix4 localMatrix;
protected Matrix4 worldMatrix;
protected GenericNode parent;
protected List<GenericNode> children;
public boolean dontInheritTranslation;
public boolean dontInheritRotation;
public boolean dontInheritScaling;
protected boolean visible;
protected boolean wasDirty;
protected boolean dirty;
protected abstract void update(float dt, Scene scene);
}

View File

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

View File

@ -1,6 +1,5 @@
package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.Vector4;
@ -15,7 +14,7 @@ public abstract class ModelInstance extends Node {
public float depth;
public int updateFrame;
public int cullFrame;
public Model model;
public Model<?> model;
public TextureMapper textureMapper;
public boolean paused;
public boolean rendered;

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.viewer5;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
@ -10,10 +11,10 @@ import java.util.Set;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
public class ModelViewer {
private final DataSource dataSource;
@ -28,7 +29,7 @@ public class ModelViewer {
private int visibleInstances;
private int updatedParticles;
public int frame;
private final int rectBuffer;
public final int rectBuffer;
private final boolean enableAudio;
private final Map<Model, List<TextureMapper>> textureMappers;
private final Set<ResourceHandler> handlers;
@ -103,6 +104,77 @@ public class ModelViewer {
this.scenes.clear();
}
public Object[] findHandler(final String ext) {
for (final ResourceHandler handler : this.handlers) {
for (final String[] extension : handler.extensions) {
if (extension[0].equals(ext)) {
return new Object[] { handler, extension[1] };
}
}
}
return null;
}
public Resource<?> load(final String src, final PathSolver pathSolver, final Object solverParams) {
String finalSrc = src;
String extension = "";
boolean isFetch = false;
final boolean resolved = false;
// If a given path solver, resolve.
if (pathSolver != null) {
final SolvedPath solved = pathSolver.solve(src, solverParams);
finalSrc = solved.getFinalSrc();
extension = solved.getExtension();
isFetch = solved.isFetch();
}
// Built-in texture sources
// ---- TODO not using JS code here
if (resolved) {
final Object[] handlerAndDataType = this.findHandler(extension.toLowerCase());
// Is there a handler for this file type?
if (handlerAndDataType != null) {
if (isFetch) {
final Resource<?> resource = this.fetchCache.get(finalSrc);
if (resource != null) {
return resource;
}
}
final ResourceHandler handler = (ResourceHandler) handlerAndDataType[0];
final Resource<?> resource = handler.construct(new ResourceHandlerConstructionParams(this, handler,
extension, pathSolver, isFetch ? finalSrc : ""));
this.resources.add(resource);
// if (isFetch) {
this.fetchCache.put(finalSrc, resource);
// }
// TODO this is a synchronous hack, skipped some Ghostwolf code
try {
resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null);
}
catch (final IOException e) {
throw new IllegalStateException("Unable to load data: " + finalSrc);
}
return resource;
}
else {
throw new IllegalStateException("Missing handler for: " + finalSrc);
}
}
else {
throw new IllegalStateException("Load unresolved: " + finalSrc);
}
}
public boolean has(final String key) {
return this.fetchCache.containsKey(key);
}

View File

@ -9,32 +9,11 @@ import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.Descriptor;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class Node {
public abstract class Node extends GenericNode {
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 Node parent;
protected final List<Node> children;
protected final boolean dontInheritTranslation;
protected final boolean dontInheritRotation;
protected final boolean dontInheritScaling;
protected boolean visible;
protected boolean wasDirty;
protected boolean dirty;
public Node() {
this.pivot = new Vector3();
this.localLocation = new Vector3();
@ -165,7 +144,7 @@ public abstract class Node {
return this;
}
public Node setParent(final Node parent) {
public Node setParent(final GenericNode parent) {
if (this.parent != null) {
this.parent.children.remove(this);
}
@ -183,7 +162,7 @@ public abstract class Node {
public void recalculateTransformation() {
boolean dirty = this.dirty;
final Node parent = this.parent;
final GenericNode parent = this.parent;
this.wasDirty = this.dirty;
@ -281,6 +260,7 @@ public abstract class Node {
}
@Override
public void update(final float dt, final Scene scene) {
if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) {
this.dirty = true; // in case this node isn't dirty, but the parent was

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5;
public interface PathSolver {
SolvedPath solve(String src, Object solverParams);
}

View File

@ -11,11 +11,15 @@ public abstract class Resource<HANDLER extends ResourceHandler> {
public final String fetchUrl;
public boolean ok;
public boolean loaded;
public final PathSolver pathSolver;
public final Object solverParams = null;
public Resource(final ModelViewer viewer, final HANDLER handler, final String extension, final String fetchUrl) {
public Resource(final ModelViewer viewer, final HANDLER handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
this.viewer = viewer;
this.handler = handler;
this.extension = extension;
this.pathSolver = pathSolver;
this.fetchUrl = fetchUrl;
this.ok = false;
this.loaded = false;

View File

@ -147,7 +147,7 @@ public class Scene {
Batch batch = this.batches.get(textureMapper);
if (batch == null) {
final Model model = instance.model;
final Model<?> model = instance.model;
final BatchDescriptor batchDescriptor = model.handler.batchDescriptor;
batch = batchDescriptor.create(this, model, textureMapper);

View File

@ -41,7 +41,7 @@ public class Shaders {
" return enc;\r\n" + //
" }";
public static final String quadTransform = "\r\n" + //
public static final String quatTransform = "\r\n" + //
" // A 2D quaternion*vector.\r\n" + //
" // q is the zw components of the original quaternion.\r\n" + //
" vec2 quat_transform(vec2 q, vec2 v) {\r\n" + //

View File

@ -8,31 +8,31 @@ import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SkeletalNode {
public abstract class SkeletalNode extends GenericNode {
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 SkeletalNode parent;
protected final List<Node> children;
protected final boolean dontInheritTranslation;
protected final boolean dontInheritRotation;
protected final boolean dontInheritScaling;
protected boolean visible;
protected boolean wasDirty;
protected boolean dirty;
public final Vector3 pivot;
public final Vector3 localLocation;
public final Quaternion localRotation;
public final Vector3 localScale;
public final Vector3 worldLocation;
public final Quaternion worldRotation;
public final Vector3 worldScale;
public final Vector3 inverseWorldLocation;
public final Quaternion inverseWorldRotation;
public final Vector3 inverseWorldScale;
public final Matrix4 localMatrix;
public final Matrix4 worldMatrix;
public SkeletalNode parent;
public final List<Node> children;
public final boolean dontInheritTranslation;
public final boolean dontInheritRotation;
public final boolean dontInheritScaling;
public boolean visible;
public boolean wasDirty;
public boolean dirty;
public Object object;

View File

@ -0,0 +1,26 @@
package com.etheller.warsmash.viewer5;
public class SolvedPath {
public String finalSrc;
public String extension;
public boolean fetch;
public SolvedPath(final String finalSrc, final String extension, final boolean fetch) {
this.finalSrc = finalSrc;
this.extension = extension;
this.fetch = fetch;
}
public String getFinalSrc() {
return this.finalSrc;
}
public String getExtension() {
return this.extension;
}
public boolean isFetch() {
return this.fetch;
}
}

View File

@ -0,0 +1,42 @@
package com.etheller.warsmash.viewer5;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class Texture extends Resource<ResourceHandler> {
private com.badlogic.gdx.graphics.Texture gdxTexture;
public Texture(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(viewer, handler, extension, pathSolver, fetchUrl);
}
public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) {
this.gdxTexture = gdxTexture;
}
@Override
protected void error(final Exception e) {
throw new RuntimeException(e);
}
public void bind(final int unit) {
this.viewer.webGL.bindTexture(this, unit);
}
public void internalBind() {
this.gdxTexture.bind();
}
public int getWidth() {
return this.gdxTexture.getWidth();
}
public int getHeight() {
return this.gdxTexture.getHeight();
}
public int getGlTarget() {
return this.gdxTexture.glTarget;
}
}

View File

@ -3,8 +3,6 @@ package com.etheller.warsmash.viewer5;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.graphics.Texture;
public class TextureMapper {
public final Model model;
public final Map<Object, Texture> textures;

View File

@ -5,8 +5,8 @@ import java.util.Map;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.deprecated.ShaderUnitDeprecated;
/**
@ -19,7 +19,7 @@ public class WebGL {
public Map<Integer, ShaderProgram> shaderPrograms;
public ShaderProgram currentShaderProgram;
public String floatPrecision;
public final Texture emptyTexture;
public final com.badlogic.gdx.graphics.Texture emptyTexture;
public WebGL(final GL20 gl) {
gl.glDepthFunc(GL20.GL_LEQUAL);
@ -42,7 +42,7 @@ public class WebGL {
imageData.drawPixel(i, j, 0x000000FF);
}
}
this.emptyTexture = new Texture(imageData);
this.emptyTexture = new com.badlogic.gdx.graphics.Texture(imageData);
}
public ShaderUnitDeprecated createShaderUnit(final String src, final int type) {
@ -115,7 +115,7 @@ public class WebGL {
gl.glActiveTexture(GL20.GL_TEXTURE0 + unit);
if (texture != null /* && texture.ok */) {
texture.bind();
texture.internalBind();
}
else {
this.emptyTexture.bind();

View File

@ -1,5 +1,7 @@
package com.etheller.warsmash.viewer5.handlers;
public class EmitterObject {
public interface EmitterObject {
boolean ok();
int getGeometryEmitterType();
}

View File

@ -1,10 +1,16 @@
package com.etheller.warsmash.viewer5.handlers;
import java.util.List;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Resource;
public abstract class ResourceHandler {
public ResourceHandler handler;
public boolean load;
public List<String[]> extensions;
public abstract boolean load(ModelViewer modelViewer);
public abstract Resource<?> construct(ResourceHandlerConstructionParams params);
}

View File

@ -0,0 +1,42 @@
package com.etheller.warsmash.viewer5.handlers;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
public class ResourceHandlerConstructionParams {
public final ModelViewer viewer;
public final ResourceHandler handler;
public final String extension;
public final PathSolver pathSolver;
public final String fetchUrl;
public ResourceHandlerConstructionParams(final ModelViewer viewer, final ResourceHandler handler,
final String extension, final PathSolver pathSolver, final String fetchUrl) {
this.viewer = viewer;
this.handler = handler;
this.extension = extension;
this.pathSolver = pathSolver;
this.fetchUrl = fetchUrl;
}
public ModelViewer getViewer() {
return this.viewer;
}
public ResourceHandler getHandler() {
return this.handler;
}
public String getExtension() {
return this.extension;
}
public PathSolver getPathSolver() {
return this.pathSolver;
}
public String getFetchUrl() {
return this.fetchUrl;
}
}

View File

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

View File

@ -0,0 +1,43 @@
package com.etheller.warsmash.viewer5.handlers.blp;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public class BlpTexture extends Texture {
public BlpTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(viewer, handler, extension, pathSolver, fetchUrl);
}
@Override
protected void lateLoad() {
}
@Override
protected void load(final InputStream src, final Object options) {
BufferedImage img;
try {
img = ImageIO.read(src);
final com.badlogic.gdx.graphics.Texture texture = ImageUtils
.getTexture(ImageUtils.forceBufferedImagesRGB(img));
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
setGdxTexture(texture);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,6 +1,109 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.HashMap;
import java.util.Map;
import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline;
import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline;
import com.etheller.warsmash.util.War3ID;
public class AnimatedObject {
public Model model;
public
public MdxModel model;
public Map<War3ID, Sd<?>> timelines;
public AnimatedObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.AnimatedObject object) {
this.model = model;
this.timelines = new HashMap<>();
for (final Timeline<?> timeline : object.getTimelines()) {
this.timelines.put(timeline.getName(), createTypedSd(model, timeline));
}
}
public int getScalarValue(final float[] out, final War3ID name, final MdxComplexInstance instance,
final float defaultValue) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof ScalarSd) {
return ((ScalarSd) animation).getValue(out, instance);
}
out[0] = defaultValue;
return -1;
}
public int getScalarValue(final long[] out, final War3ID name, final MdxComplexInstance instance,
final long defaultValue) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof UInt32Sd) {
return ((UInt32Sd) animation).getValue(out, instance);
}
out[0] = defaultValue;
return -1;
}
public int getVectorValue(final float[] out, final War3ID name, final MdxComplexInstance instance,
final float[] defaultValue) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof VectorSd) {
return ((VectorSd) animation).getValue(out, instance);
}
System.arraycopy(defaultValue, 0, out, 0, 3);
return -1;
}
public int getQuadValue(final float[] out, final War3ID name, final MdxComplexInstance instance,
final float[] defaultValue) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof QuaternionSd) {
return ((QuaternionSd) animation).getValue(out, instance);
}
System.arraycopy(defaultValue, 0, out, 0, 4);
return -1;
}
public boolean isVariant(final War3ID name, final int sequence) {
final Sd<?> timeline = this.timelines.get(name);
if (timeline != null) {
return timeline.isVariant(sequence);
}
return false;
}
private Sd<?> createTypedSd(final MdxModel model, final Timeline<?> timeline) {
if (timeline instanceof UInt32Timeline) {
return new UInt32Sd(model, (UInt32Timeline) timeline);
}
else if (timeline instanceof FloatTimeline) {
return new ScalarSd(model, (FloatTimeline) timeline);
}
else if (timeline instanceof FloatArrayTimeline) {
final FloatArrayTimeline faTimeline = (FloatArrayTimeline) timeline;
final int arraySize = faTimeline.getArraySize();
if (arraySize == 3) {
return new VectorSd(model, faTimeline);
}
else if (arraySize == 4) {
return new QuaternionSd(model, faTimeline);
}
else {
throw new IllegalStateException("Unsupported arraySize = " + arraySize);
}
}
throw new IllegalStateException("Unsupported timeline type " + timeline.getClass());
}
}

View File

@ -0,0 +1,30 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
public class Attachment extends GenericObject {
protected final String path;
protected final int attachmentId;
protected MdxModel internalModel;
public Attachment(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Attachment attachment,
final int index) {
super(model, attachment, index);
final String path = attachment.getPath().replace("\\", "/").toLowerCase().replace(".mdl", ".mdx");
this.path = path;
this.attachmentId = attachment.getAttachmentId();
this.internalModel = null;
// Second condition is against custom resources using arbitrary paths
if (!path.equals("") && (path.indexOf(".mdx") != -1)) {
this.internalModel = (MdxModel) model.viewer.load(path, model.pathSolver, model.solverParams);
}
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), instance, 1);
}
}

View File

@ -0,0 +1,49 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class AttachmentInstance {
private static final float[] visbilityHeap = new float[1];
private final MdxComplexInstance instance;
private final Attachment attachment;
private final MdxComplexInstance internalInstance;
public AttachmentInstance(final MdxComplexInstance instance, final Attachment attachment) {
final MdxModel internalModel = attachment.internalModel;
final MdxComplexInstance internalInstance = (MdxComplexInstance) internalModel.addInstance();
internalInstance.setSequenceLoopMode(2);
internalInstance.dontInheritScaling = false;
internalInstance.hide();
internalInstance.setParent(instance.nodes[attachment.objectId]);
this.instance = instance;
this.attachment = attachment;
this.internalInstance = internalInstance;
}
public void update() {
final MdxComplexInstance internalInstance = this.internalInstance;
if (internalInstance.model.ok) {
this.attachment.getVisibility(visbilityHeap, this.instance);
if (visbilityHeap[0] > 0.1) {
// The parent instance might not actually be in a scene.
// This happens if loading a local model, where loading is instant and adding to
// a scene always comes afterwards.
// Therefore, do it here dynamically.
this.instance.scene.addInstance(internalInstance);
if (internalInstance.hidden()) {
internalInstance.show();
// Every time the attachment becomes visible again, restart its first sequence.
internalInstance.setSequence(0);
}
}
else {
internalInstance.hide();
}
}
}
}

View File

@ -0,0 +1,16 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class Batch {
public int index;
public Geoset geoset;
public Layer layer;
public boolean isExtended;
public Batch(final int index, final Geoset geoset, final Layer layer, final boolean isExtended) {
this.index = index;
this.geoset = geoset;
this.layer = layer;
this.isExtended = isExtended;
}
}

View File

@ -0,0 +1,124 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
public class BatchGroup {
private final MdxModel model;
private final boolean isExtended;
private final List<Integer> objects;
public BatchGroup(final MdxModel model, final boolean isExtended) {
this.model = model;
this.isExtended = isExtended;
this.objects = new ArrayList<>(); // TODO IntArrayList
}
public void render(final MdxComplexInstance instance) {
final Scene scene = instance.scene;
final MdxModel model = this.model;
final List<Texture> textures = model.getTextures();
final MdxHandler handler = model.handler;
final List<Texture> teamColors = MdxHandler.teamColors;
final List<Texture> teamGlows = MdxHandler.teamGlows;
final List<Batch> batches = model.batches;
final List<Integer> replaceables = model.replaceables;
final ModelViewer viewer = model.viewer;
final GL20 gl = viewer.gl;
final boolean isExtended = this.isExtended;
final ShaderProgram shader;
if (isExtended) {
shader = MdxHandler.Shaders.extended;
}
else {
shader = MdxHandler.Shaders.complex;
}
shader.begin();
shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix);
final Texture boneTexture = instance.boneTexture;
// Instances of models with no bones don't have a bone texture.
if (boneTexture != null) {
boneTexture.bind(15);
shader.setUniformf("u_hasBones", 1);
shader.setUniformf("u_boneMap", 15);
shader.setUniformf("u_vectorSize", 1f / boneTexture.getWidth());
shader.setUniformf("u_rowSize", 1);
}
else {
shader.setUniformf("u_hasBones", 0);
}
shader.setUniformi("u_texture", 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer);
shader.setUniform4fv("u_vertexColor", instance.vertexColor, 0, instance.vertexColor.length);
for (final int index : this.objects) {
final Batch batch = batches.get(index);
final Geoset geoset = batch.geoset;
final Layer layer = batch.layer;
final int geosetIndex = geoset.index;
final int layerIndex = layer.index;
final float[] geosetColor = instance.geosetColors[geosetIndex];
final float layerAlpha = instance.layerAlphas[layerIndex];
if ((geosetColor[3] > 0) && (layerAlpha > 0)) {
final int layerTexture = instance.layerTextures[layerIndex];
final float[] uvAnim = instance.uvAnims[layerIndex];
shader.setUniform4fv("u_geosetColor", geosetColor, 0, geosetColor.length);
shader.setUniformf("u_layerAlpha", layerAlpha);
shader.setUniform2fv("u_uvTrans", uvAnim, 0, 2);
shader.setUniform2fv("u_uvRot", uvAnim, 2, 2);
shader.setUniform1fv("u_uvRot", uvAnim, 4, 1);
layer.bind(shader);
final Integer replaceable = replaceables.get(layerTexture); // TODO is this OK?
Texture texture;
if (replaceable == 1) {
texture = teamColors.get(instance.teamColor);
}
else if (replaceable == 2) {
texture = teamGlows.get(instance.teamColor);
}
else {
texture = textures.get(layerTexture);
}
Texture textureLookup = instance.textureMapper.get(texture);
if (textureLookup == null) {
textureLookup = texture;
}
viewer.webGL.bindTexture(textureLookup, 0);
if (isExtended) {
geoset.bindExtended(shader, layer.coordId);
}
else {
geoset.bind(shader, layer.coordId);
}
geoset.render();
}
}
}
}

View File

@ -0,0 +1,24 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class Bone extends GenericObject {
private final GeosetAnimation geosetAnimation;
public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) {
super(model, bone, index);
this.geosetAnimation = model.getGeosetAnimations().get(bone.getGeosetAnimationId());
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
if (this.geosetAnimation != null) {
return this.geosetAnimation.getAlpha(out, instance);
}
out[0] = 1;
return -1;
}
}

View File

@ -0,0 +1,37 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
public class Camera extends AnimatedObject {
private final String name;
private final float[] position;
private final float fieldOfView;
private final float farClippingPlane;
private final float nearClippingPlane;
private final float[] targetPosition;
public Camera(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Camera camera) {
super(model, camera);
this.name = camera.getName();
this.position = camera.getPosition();
this.fieldOfView = camera.getFieldOfView();
this.farClippingPlane = camera.getFarClippingPlane();
this.nearClippingPlane = camera.getNearClippingPlane();
this.targetPosition = camera.getTargetPosition();
}
public int getPositionTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), instance, this.position);
}
public int getTargetTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), instance, this.targetPosition);
}
public int getRotation(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KCRL.getWar3id(), instance, 0);
}
}

View File

@ -0,0 +1,10 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class CollisionShape extends GenericObject {
public CollisionShape(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.CollisionShape object,
final int index) {
super(model, object, index);
}
}

View File

@ -0,0 +1,64 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SkeletalNode;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class EmitterGroup {
private final MdxModel model;
private final List<Integer> objects;
public EmitterGroup(final MdxModel model) {
this.model = model;
this.objects = new ArrayList<>();
}
public void render(final MdxComplexInstance instance) {
final Scene scene = instance.scene;
final SkeletalNode[] nodes = instance.nodes;
final Model<?> model = instance.model;
final ModelViewer viewer = model.viewer;
final GL20 gl = viewer.gl;
final ShaderProgram shader = MdxHandler.Shaders.particles;
gl.glDepthMask(false);
gl.glEnable(GL20.GL_BLEND);
gl.glDisable(GL20.GL_CULL_FACE);
gl.glEnable(GL20.GL_DEPTH_TEST);
shader.begin();
shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix);
shader.setUniformf("u_texture", 0);
final int a_position = shader.getAttributeLocation("a_position");
Gdx.gl30.glVertexAttribDivisor(a_position, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, viewer.rectBuffer);
gl.glVertexAttribPointer(a_position, 1, GL20.GL_UNSIGNED_BYTE, false, 0, 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 1);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 1);
for (final int index : this.objects) {
}
}
protected abstract void renderEmitter(EmitterObject emitter, ShaderProgram shader);
}

View File

@ -0,0 +1,31 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.EmittedObject;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class EventObjectEmitter<EMITTER_OBJECT extends EventObjectEmitterObject, EMITTED_OBJECT extends EmittedObject<MdxComplexInstance, ? extends MdxEmitter<MdxComplexInstance, EMITTER_OBJECT, EMITTED_OBJECT>>>
extends MdxEmitter<MdxComplexInstance, EMITTER_OBJECT, EMITTED_OBJECT> {
private final int number = 0;
public EventObjectEmitter(final MdxComplexInstance instance, final EMITTER_OBJECT emitterObject) {
super(instance, emitterObject);
}
@Override
protected void updateEmission(final float dt) {
final MdxComplexInstance instance = this.instance;
if (instance.allowParticleSpawn) {
final EMITTER_OBJECT emitterObject = this.emitterObject;
emitterObject.getV
}
}
@Override
protected void emit() {
this.emitObject(0);
}
}

View File

@ -0,0 +1,69 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class EventObjectEmitterObject extends GenericObject implements EmitterObject {
private int geometryEmitterType = -1;
private final String type;
private final String id;
private final long[] keyFrames;
private long globalSequence;
private final long[] defval = { 1 };
private MdxModel internalModel;
private Texture internalTexture;
private float[][] colors;
private float[] intervalTimes;
private float scale;
private int columns;
private int rows;
private float lifeSpan;
private int blendSrc;
private int blendDst;
private float[][] intervals;
private float distanceCutoff;
private float maxDistance;
private float minDistance;
private float pitch;
private float pitchVariance;
private float volume;
// private AudioBuffer[] decodedBuffers;
/**
* If this is an SPL/UBR emitter object, ok will be set to true if the tables
* are loaded.
*
* This is because, like the other geometry emitters, it is fine to use them
* even if the textures don't load.
*
* The particles will simply be black.
*/
private final boolean ok = false;
public EventObjectEmitterObject(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) {
super(model, eventObject, index);
final ModelViewer viewer = model.viewer;
final String name = eventObject.getName();
String type = name.substring(0, 3);
final String id = name.substring(4);
// Same thing
if ("FPT".equals(type)) {
type = "SPL";
}
if ("SPL".equals(type)) {
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT;
}
else if ("UBR".equals(type)) {
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT;
}
this.type = type;
this.id = id;
this.keyFrames = eventObject.getKeyFrames();
}
}

View File

@ -0,0 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.EmittedObject;
public class EventObjectSplUbr<EMITTER extends MdxEmitter<?, ?, ?>> extends EmittedObject<MdxComplexInstance, EMITTER> {
private final float[] vertices = new float[12];
@Override
protected void bind(final int flags) {
}
}

View File

@ -0,0 +1,46 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.graphics.GL20;
public class FilterMode {
private static final int[] ERROR_DEFAULT = new int[] { 0, 0 };
private static final int[] MODULATE_2X = new int[] { GL20.GL_DST_COLOR, GL20.GL_SRC_COLOR };
private static final int[] MODULATE = new int[] { GL20.GL_ZERO, GL20.GL_SRC_COLOR };
private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_ALPHA, GL20.GL_ONE };
private static final int[] BLEND = new int[] { GL20.GL_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA };
public static int[] layerFilterMode(final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode) {
switch (filterMode) {
case BLEND:
return BLEND; // Blend
case ADDITIVE:
return ADDITIVE_ALPHA; // Additive
case ADDALPHA:
return ADDITIVE_ALPHA; // Add alpha
case MODULATE:
return MODULATE; // Modulate
case MODULATE2X:
return MODULATE_2X; // Modulate 2x
default:
return ERROR_DEFAULT;
}
}
public static int[] emitterFilterMode(
final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode filterMode) {
switch (filterMode) {
case BLEND:
return BLEND; // Blend
case ADDITIVE:
return ADDITIVE_ALPHA; // Add alpha
case MODULATE:
return MODULATE; // Modulate
case MODULATE2X:
return MODULATE_2X; // Modulate 2x
case ALPHAKEY:
return ADDITIVE_ALPHA; // Add alpha
default:
return ERROR_DEFAULT;
}
}
}

View File

@ -0,0 +1,159 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.RenderMathUtils;
public class GenericObject extends AnimatedObject {
public final int index;
public final String name;
public final int objectId;
public final int parentId;
public final float[] pivot;
public final int dontInheritTranslation;
public final int dontInheritRotation;
public final int dontInheritScaling;
public final int billboarded;
public final int billboardedX;
public final int billboardedY;
public final int billboardedZ;
public final int cameraAnchored;
public final int bone;
public final int light;
public final int eventObject;
public final int attachment;
public final int particleEmitter;
public final int collisionShape;
public final int ribbonEmitter;
public final int emitterUsesMdlOrUnshaded;
public final int emitterUsesTgaOrSortPrimitivesFarZ;
public final int lineEmitter;
public final int unfogged;
public final int modelSpace;
public final int xYQuad;
public final boolean anyBillboarding;
public final Variants variants;
public final boolean hasTranslationAnim;
public final boolean hasRotationAnim;
public final boolean hasScaleAnim;
public final boolean hasGenericAnim;
public GenericObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object,
final int index) {
super(model, object);
this.index = index;
this.name = object.getName();
this.objectId = object.getObjectId();
int parentId = object.getParentId();
this.pivot = (this.objectId < model.getPivotPoints().size()) ? model.getPivotPoints().get(this.objectId)
: new float[] { 0, 0, 0 };
final int flags = object.getFlags();
this.dontInheritTranslation = flags & 0x1;
this.dontInheritRotation = flags & 0x2;
this.dontInheritScaling = flags & 0x4;
this.billboarded = flags & 0x8;
this.billboardedX = flags & 0x10;
this.billboardedY = flags & 0x20;
this.billboardedZ = flags & 0x40;
this.cameraAnchored = flags & 0x80;
this.bone = flags & 0x100;
this.light = flags & 0x200;
this.eventObject = flags & 0x400;
this.attachment = flags & 0x800;
this.particleEmitter = flags & 0x1000;
this.collisionShape = flags & 0x2000;
this.ribbonEmitter = flags & 0x4000;
this.emitterUsesMdlOrUnshaded = flags & 0x8000;
this.emitterUsesTgaOrSortPrimitivesFarZ = flags & 0x10000;
this.lineEmitter = flags & 0x20000;
this.unfogged = flags & 0x40000;
this.modelSpace = flags & 0x80000;
this.xYQuad = flags & 0x100000;
this.anyBillboarding = (this.billboarded != 0) || (this.billboardedX != 0) || (this.billboardedY != 0)
|| (this.billboardedZ != 0);
if (object.getObjectId() == object.getParentId()) {
parentId = -1; //
}
this.parentId = parentId;
final Variants variants = new Variants(model.getSequences().size());
boolean hasTranslationAnim = false;
boolean hasRotationAnim = false;
boolean hasScaleAnim = false;
for (int i = 0; i < model.getSequences().size(); i++) {
final boolean translation = this.isTranslationVariant(i);
final boolean rotation = this.isRotationVariant(i);
final boolean scale = this.isScaleVariant(i);
variants.translation[i] = translation;
variants.rotation[i] = rotation;
variants.scale[i] = scale;
variants.generic[i] = translation || rotation || scale;
hasTranslationAnim = hasTranslationAnim || translation;
hasRotationAnim = hasRotationAnim || rotation;
hasScaleAnim = hasScaleAnim || scale;
}
this.variants = variants;
this.hasTranslationAnim = hasTranslationAnim;
this.hasRotationAnim = hasRotationAnim;
this.hasScaleAnim = hasScaleAnim;
this.hasGenericAnim = hasTranslationAnim || hasRotationAnim || hasScaleAnim;
}
/**
* Many of the generic objects have animated visibilities. This is a generic
* getter to allow the code to be consistent.
*/
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
out[0] = 1;
return -1;
}
public int getTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KGTR.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO);
}
public int getRotation(final float[] out, final MdxComplexInstance instance) {
return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT);
}
public int getScale(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KGSC.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE);
}
public boolean isTranslationVariant(final int sequence) {
return this.isVariant(AnimationMap.KGTR.getWar3id(), sequence);
}
public boolean isRotationVariant(final int sequence) {
return this.isVariant(AnimationMap.KGRT.getWar3id(), sequence);
}
public boolean isScaleVariant(final int sequence) {
return this.isVariant(AnimationMap.KGSC.getWar3id(), sequence);
}
private static final class Variants {
boolean[] translation;
boolean[] rotation;
boolean[] scale;
boolean[] generic;
public Variants(final int sequencesCount) {
this.translation = new boolean[sequencesCount];
this.rotation = new boolean[sequencesCount];
this.scale = new boolean[sequencesCount];
this.generic = new boolean[sequencesCount];
}
}
}

View File

@ -0,0 +1,287 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
//The total storage that emitted objects can use.
//This is enough to support all of the MDX geometry emitters.
//The memory layout is the same as this C struct:
//
//struct {
// float p0[3]
// float p1[3]
// float p2[3]
// float p3[3]
// float health
// byte color[4]
// byte tail
// byte leftRightTop[3]
//}
//
public class GeometryEmitterFuncs {
public static final int BYTES_PER_OBJECT = 60;
public static final int FLOATS_PER_OBJECT = BYTES_PER_OBJECT >> 2;
// Offsets into the emitted object structure
public static final int BYTE_OFFSET_P0 = 0;
public static final int BYTE_OFFSET_P1 = 12;
public static final int BYTE_OFFSET_P2 = 24;
public static final int BYTE_OFFSET_P3 = 36;
public static final int BYTE_OFFSET_HEALTH = 48;
public static final int BYTE_OFFSET_COLOR = 52;
public static final int BYTE_OFFSET_TAIL = 56;
public static final int BYTE_OFFSET_LEFT_RIGHT_TOP = 57;
// Offset aliases
public static final int FLOAT_OFFSET_P0 = BYTE_OFFSET_P0 >> 2;
public static final int FLOAT_OFFSET_P1 = BYTE_OFFSET_P1 >> 2;
public static final int FLOAT_OFFSET_P2 = BYTE_OFFSET_P2 >> 2;
public static final int FLOAT_OFFSET_P3 = BYTE_OFFSET_P3 >> 2;
public static final int FLOAT_OFFSET_HEALTH = BYTE_OFFSET_HEALTH >> 2;
public static final int BYTE_OFFSET_TEAM_COLOR = BYTE_OFFSET_LEFT_RIGHT_TOP;
// Head or tail.
public static final int HEAD = 0;
public static final int TAIL = 1;
// Emitter types
public static final int EMITTER_PARTICLE2 = 0;
public static final int EMITTER_RIBBON = 1;
public static final int EMITTER_SPLAT = 2;
public static final int EMITTER_UBERSPLAT = 3;
private static final Vector3 locationHeap = new Vector3();
private static final Vector3 startHeap = new Vector3();
private static final Vector3 endHeap = new Vector3();
private static final float[] vectorTemp = new float[3];
public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ByteBuffer buffer) {
final MdxComplexInstance instance = emitter.instance;
final List<Particle2> objects = emitter.objects;
final ByteBuffer byteView = buffer;
final FloatBuffer floatView = buffer.asFloatBuffer();
final ParticleEmitter2Object emitterObject = emitter.emitterObject;
final int modelSpace = emitterObject.modelSpace;
final float tailLength = emitterObject.tailLength;
final int teamColor = instance.teamColor;
int offset = 0;
for (final Particle2 object : objects) {
final int byteOffset = offset * BYTES_PER_OBJECT;
final int floatOffset = offset * FLOATS_PER_OBJECT;
final int p0Offset = floatOffset + FLOAT_OFFSET_P0;
Vector3 location = object.location;
final Vector3 scale = object.scale;
final int tail = object.tail;
if (tail == HEAD) {
// If this is a model space emitter, the location is in local space, so convert
// it to world space.
if (modelSpace != 0) {
location = locationHeap.set(location).prj(emitter.node.worldMatrix);
}
floatView.put(p0Offset + 0, location.x);
floatView.put(p0Offset + 1, location.y);
floatView.put(p0Offset + 2, location.z);
}
else {
final Vector3 velocity = object.velocity;
final Vector3 start = startHeap;
Vector3 end = location;
start.x = end.x - (tailLength * velocity.x);
start.y = end.y - (tailLength * velocity.y);
start.z = end.z - (tailLength * velocity.z);
// If this is a model space emitter, the start and end are in local space, so
// convert them to world space.
if (modelSpace != 0) {
start.prj(emitter.node.worldMatrix);
end = endHeap.set(end).prj(emitter.node.worldMatrix);
}
floatView.put(p0Offset + 0, start.x);
floatView.put(p0Offset + 1, start.y);
floatView.put(p0Offset + 2, start.z);
floatView.put(p0Offset + 3, end.x);
floatView.put(p0Offset + 4, end.y);
floatView.put(p0Offset + 5, end.z);
}
floatView.put(p0Offset + 6, scale.x);
floatView.put(p0Offset + 7, scale.y);
floatView.put(p0Offset + 8, scale.z);
floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health);
byteView.put(byteOffset + BYTE_OFFSET_TAIL, (byte) tail);
byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) teamColor);
offset += 1;
}
}
public static void bindParticleEmitter2Shader(final ParticleEmitter2 emitter, final ShaderProgram shader) {
final MdxComplexInstance instance = emitter.instance;
final Scene scene = instance.scene;
final Camera camera = scene.camera;
final ParticleEmitter2Object emitterObject = emitter.emitterObject;
final MdxModel model = emitterObject.model;
final ModelViewer viewer = model.viewer;
final GL20 gl = viewer.gl;
final float[][] colors = emitterObject.colors;
final float[][] intervals = emitterObject.intervals;
final long replaceableId = emitterObject.replaceableId;
Vector3[] vectors;
Texture texture;
gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst);
if (replaceableId == 1) {
final List<Texture> teamColors = model.reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors;
texture = teamColors.get(instance.teamColor);
}
else if (replaceableId == 2) {
final List<Texture> teamGlows = model.reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows;
texture = teamGlows.get(instance.teamColor);
}
else {
texture = emitterObject.internalTexture;
}
viewer.webGL.bindTexture(texture, 0);
// Choose between a default rectangle and a billboarded one
if (emitterObject.xYQuad != 0) {
vectors = camera.vectors;
}
else {
vectors = camera.billboardedVectors;
}
shader.setUniformf("u_emitter", EMITTER_PARTICLE2);
shader.setUniformf("u_lifeSpan", emitterObject.lifeSpan);
shader.setUniformf("u_timeMiddle", emitterObject.timeMiddle);
shader.setUniformf("u_columns", emitterObject.columns);
shader.setUniformf("u_rows", emitterObject.rows);
shader.setUniformf("u_teamColored", emitterObject.teamColored);
shader.setUniform3fv("u_intervals[0]", intervals[0], 0, 3);
shader.setUniform3fv("u_intervals[1]", intervals[1], 0, 3);
shader.setUniform3fv("u_intervals[2]", intervals[2], 0, 3);
shader.setUniform3fv("u_intervals[3]", intervals[3], 0, 3);
shader.setUniform4fv("u_colors[0]", colors[0], 0, 3);
shader.setUniform4fv("u_colors[1]", colors[1], 0, 3);
shader.setUniform4fv("u_colors[2]", colors[2], 0, 3);
shader.setUniform3fv("u_scaling", emitterObject.scaling, 0, 3);
if (emitterObject.head) {
shader.setUniform3fv("u_vertices[0]", asFloatArray(vectors[0]), 0, 3);
shader.setUniform3fv("u_vertices[1]", asFloatArray(vectors[1]), 0, 3);
shader.setUniform3fv("u_vertices[2]", asFloatArray(vectors[2]), 0, 3);
shader.setUniform3fv("u_vertices[3]", asFloatArray(vectors[3]), 0, 3);
}
if (emitterObject.tail) {
shader.setUniform3fv("u_cameraZ", asFloatArray(camera.billboardedVectors[6]), 0, 3);
}
}
public static void bindRibbonEmitterBuffer(final RibbonEmitter emitter, final ByteBuffer buffer) {
Ribbon object = emitter.first;
final ByteBuffer byteView = buffer;
final FloatBuffer floatView = buffer.asFloatBuffer();
final RibbonEmitterObject emitterObject = emitter.emitterObject;
final long columns = emitterObject.columns;
final int alive = emitter.alive;
final float chainLengthFactor = 1 / (float) (alive - 1);
int offset = 0;
while (object.next != null) {
final float[] next = object.next.vertices;
final int byteOffset = offset * BYTES_PER_OBJECT;
final int floatOffset = offset * FLOATS_PER_OBJECT;
final int p0Offset = floatOffset + FLOAT_OFFSET_P0;
final int colorOffset = byteOffset + BYTE_OFFSET_COLOR;
final int leftRightTopOffset = byteOffset + BYTE_OFFSET_LEFT_RIGHT_TOP;
final float left = ((object.slot % columns) + (1 - (offset * chainLengthFactor) - chainLengthFactor))
/ columns;
final float top = object.slot / (float) columns;
final float right = left + chainLengthFactor;
final float[] vertices = object.vertices;
final byte[] color = object.color;
floatView.put(p0Offset + 0, vertices[0]);
floatView.put(p0Offset + 1, vertices[1]);
floatView.put(p0Offset + 2, vertices[2]);
floatView.put(p0Offset + 3, vertices[3]);
floatView.put(p0Offset + 4, vertices[4]);
floatView.put(p0Offset + 5, vertices[5]);
floatView.put(p0Offset + 6, next[3]);
floatView.put(p0Offset + 7, next[4]);
floatView.put(p0Offset + 8, next[5]);
floatView.put(p0Offset + 9, next[0]);
floatView.put(p0Offset + 10, next[1]);
floatView.put(p0Offset + 11, next[2]);
byteView.put(colorOffset + 0, color[0]);
byteView.put(colorOffset + 1, color[1]);
byteView.put(colorOffset + 2, color[2]);
byteView.put(colorOffset + 3, color[3]);
byteView.put(leftRightTopOffset + 0, (byte) (left * 255));
byteView.put(leftRightTopOffset + 1, (byte) (right * 255));
byteView.put(leftRightTopOffset + 2, (byte) (top * 255));
object = object.next;
offset += 1;
}
}
public static void bindRibbonEmitterShader(final RibbonEmitter emitter, final ShaderProgram shader) {
final TextureMapper textureMapper = emitter.instance.textureMapper;
final RibbonEmitterObject emitterObject = emitter.emitterObject;
final Layer layer = emitterObject.layer;
final MdxModel model = emitterObject.model;
final GL20 gl = model.viewer.gl;
final Texture texture = model.getTextures().get(layer.textureId);
layer.bind(shader);
Texture mappedTexture = textureMapper.get(texture);
if (mappedTexture == null) {
mappedTexture = texture;
}
model.viewer.webGL.bindTexture(mappedTexture, 0);
shader.setUniformf("u_emitter", EMITTER_RIBBON);
shader.setUniformf("u_columns", emitterObject.columns);
shader.setUniformf("u_rows", emitterObject.rows);
}
private static final float[] asFloatArray(final Vector3 vec) {
vectorTemp[0] = vec.x;
vectorTemp[1] = vec.y;
vectorTemp[2] = vec.z;
return vectorTemp;
}
}

View File

@ -0,0 +1,150 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.Arrays;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
public class Geoset {
public MdxModel model;
public int index;
public int positionOffset;
public int normalOffset;
public int uvOffset;
public int skinOffset;
public int faceOffset;
public int vertices;
public int elements;
public GeosetAnimation geosetAnimation;
public Variants variants;
public boolean hasAlphaAnim;
public boolean hasColorAnim;
public boolean hasObjectAnim;
public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset,
final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements) {
this.model = model;
this.index = index;
this.positionOffset = positionOffset;
this.normalOffset = normalOffset;
this.uvOffset = uvOffset;
this.skinOffset = skinOffset;
this.faceOffset = faceOffset;
this.vertices = vertices;
this.elements = elements;
for (final GeosetAnimation geosetAnimation : model.getGeosetAnimations()) {
if (geosetAnimation.geosetId == index) {
this.geosetAnimation = geosetAnimation;
}
}
final Variants variants = new Variants(model.getSequences().size());
final GeosetAnimation geosetAnimation = this.geosetAnimation;
boolean hasAlphaAnim = false;
boolean hasColorAnim = false;
if (geosetAnimation != null) {
for (int i = 0, l = model.getSequences().size(); i < l; i++) {
final boolean alpha = geosetAnimation.isAlphaVariant(i);
final boolean color = geosetAnimation.isColorVariant(i);
variants.alpha[i] = alpha;
variants.color[i] = color;
variants.object[i] = alpha || color;
hasAlphaAnim = hasAlphaAnim || alpha;
hasColorAnim = hasColorAnim || color;
}
}
else {
for (int i = 0, l = model.getSequences().size(); i < l; i++) {
variants.alpha[i] = false;
variants.color[i] = false;
variants.object[i] = false;
}
}
this.variants = variants;
this.hasAlphaAnim = hasAlphaAnim;
this.hasColorAnim = hasColorAnim;
this.hasObjectAnim = hasAlphaAnim || hasColorAnim;
}
public int getAlpha(final float[] out, final MdxComplexInstance instance) {
if (this.geosetAnimation != null) {
return this.geosetAnimation.getAlpha(out, instance);
}
out[0] = 1;
return -1;
}
public int getColor(final float[] out, final MdxComplexInstance instance) {
if (this.geosetAnimation != null) {
return this.geosetAnimation.getAlpha(out, instance);
}
Arrays.fill(out, 1);
return -1;
}
public void bind(final ShaderProgram shader, final int coordId) {
// TODO use indices instead of strings for attributes
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset);
shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8));
shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset);
shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset + 4);
}
public void bindExtended(final ShaderProgram shader, final int coordId) {
// TODO use indices instead of strings for attributes
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset);
shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8));
shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset);
shader.setVertexAttribute("a_extendedBones", 4, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset + 4);
shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset + 8);
}
public void render() {
final GL20 gl = this.model.viewer.gl;
gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset);
}
public void bindSimple(final ShaderProgram shader) {
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset);
}
public void renderSimple(final int instances) {
Gdx.gl30.glDrawElementsInstanced(GL30.GL_TRIANGLES, this.elements, GL30.GL_UNSIGNED_SHORT, this.faceOffset,
instances);
}
public void bindHd(final ShaderProgram shader, final int coordId) {
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8));
shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 8, this.skinOffset);
shader.setVertexAttribute("a_weights", 4, GL20.GL_UNSIGNED_BYTE, false, 8, this.skinOffset + 4);
}
private static final class Variants {
private final boolean[] alpha;
private final boolean[] color;
private final boolean[] object;
public Variants(final int size) {
this.alpha = new boolean[size];
this.color = new boolean[size];
this.object = new boolean[size];
}
}
}

View File

@ -0,0 +1,37 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
public class GeosetAnimation extends AnimatedObject {
private final float alpha;
private final float[] color;
public final int geosetId;
public GeosetAnimation(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation) {
super(model, geosetAnimation);
final float[] color = geosetAnimation.getColor();
this.alpha = geosetAnimation.getAlpha();
this.color = new float[] { color[2], color[1], color[0] };
this.geosetId = geosetAnimation.getGeosetId();
}
public int getAlpha(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KGAO.getWar3id(), instance, this.alpha);
}
public int getColor(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KGAC.getWar3id(), instance, this.color);
}
public boolean isAlphaVariant(final int sequence) {
return this.isVariant(AnimationMap.KGAO.getWar3id(), sequence);
}
public boolean isColorVariant(final int sequence) {
return this.isVariant(AnimationMap.KGAC.getWar3id(), sequence);
}
}

View File

@ -1,9 +1,10 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
public class Layer {
public Model model;
public MdxModel model;
public com.etheller.warsmash.parsers.mdlx.Layer layer;
public int layerId;
public int priorityPlane;
@ -13,7 +14,9 @@ public class Layer {
public int coordId;
public float alpha;
public Layer(final Model model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId,
public int index = -666;
public Layer(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId,
final int priorityPlane) {
super(model, layer);
this.model = model;
@ -25,6 +28,12 @@ public class Layer {
this.filterMode = filterMode2.ordinal();
this.textureId = layer.getTextureId();
// this.coo
this.index
}
public void bind(final ShaderProgram shader) {
// TODO Auto-generated method stub
}
}

View File

@ -3,11 +3,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.List;
public class Material {
public final Model model;
public final MdxModel model;
public final String shader;
public final List<Layer> layers;
public Material(final Model model, final String shader, final List<Layer> layers) {
public Material(final MdxModel model, final String shader, final List<Layer> layers) {
this.model = model;
this.shader = shader;
this.layers = layers;

View File

@ -0,0 +1,70 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.SkeletalNode;
import com.etheller.warsmash.viewer5.Texture;
public class MdxComplexInstance extends ModelInstance {
public MdxNode[] nodes;
public SkeletalNode[] sortedNodes;
public int frame;
public int counter;
public int sequence;
public int sequenceLoopMode;
public boolean sequenceEnded;
public int teamColor;
public Texture boneTexture;
// TODO more fields, these few are to make related classes compile
public float[] vertexColor;
public float[][] geosetColors;
public float[] layerAlphas;
public int[] layerTextures;
public float[][] uvAnims;
public boolean allowParticleSpawn;
public MdxComplexInstance(final MdxModel model) {
super(model);
}
@Override
public void updateAnimations(final float dt) {
// TODO Auto-generated method stub
}
@Override
public void clearEmittedObjects() {
// TODO Auto-generated method stub
}
@Override
public void renderOpaque() {
// TODO Auto-generated method stub
}
@Override
public void renderTranslucent() {
// TODO Auto-generated method stub
}
@Override
public void load() {
// TODO Auto-generated method stub
}
public MdxComplexInstance setSequenceLoopMode(final int mode) {
this.sequenceLoopMode = mode;
return this;
}
public void setSequence(final int sequence) {
this.sequence = sequence;
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -0,0 +1,25 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.EmittedObject;
import com.etheller.warsmash.viewer5.Emitter;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class MdxEmitter<MODEL_INSTANCE extends ModelInstance, EMITTER_OBJECT extends EmitterObject, EMITTED_OBJECT extends EmittedObject<MODEL_INSTANCE, ? extends Emitter<MODEL_INSTANCE, EMITTED_OBJECT>>>
extends Emitter<MODEL_INSTANCE, EMITTED_OBJECT> {
protected final EMITTER_OBJECT emitterObject;
public MdxEmitter(final MODEL_INSTANCE instance, final EMITTER_OBJECT emitterObject) {
super(instance);
this.emitterObject = emitterObject;
}
@Override
public void update(final float dt) {
if (this.emitterObject.ok()) {
super.update(dt);
}
}
}

View File

@ -0,0 +1,64 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Resource;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.ModelHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler;
public class MdxHandler extends ModelHandler {
// Team color/glow textures, shared between all models, but loaded with the
// first model that uses them.
public static final List<Texture> teamColors = new ArrayList<>();
public static final List<Texture> teamGlows = new ArrayList<>();
public static final List<Texture> reforgedTeamColors = new ArrayList<>();
public static final List<Texture> reforgedTeamGlows = new ArrayList<>();
public MdxHandler() {
this.extensions = new ArrayList<>();
this.extensions.add(new String[] { ".mdx", "arrayBuffer" });
this.extensions.add(new String[] { ".mdl", "text" });
}
@Override
public boolean load(final ModelViewer viewer) {
viewer.addHandler(new BlpHandler());
Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex);
Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex,
MdxShaders.fsComplex);
Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles);
Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple);
Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd);
// If a shader failed to compile, don't allow the handler to be registered, and
// send an error instead.
return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled()
&& Shaders.simple.isCompiled() && Shaders.hd.isCompiled();
}
@Override
public Resource<?> construct(final ResourceHandlerConstructionParams params) {
return new MdxModel((MdxHandler) params.getHandler(), params.getViewer(), params.getExtension(),
params.getPathSolver(), params.getFetchUrl());
}
public static final class Shaders {
private Shaders() {
}
public static ShaderProgram complex;
public static ShaderProgram extended;
public static ShaderProgram simple;
public static ShaderProgram particles;
public static ShaderProgram hd;
}
}

View File

@ -0,0 +1,73 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
private MdlxModel model;
public int arrayBuffer;
public int elementBuffer;
public List<Batch> batches = new ArrayList<>(); // TODO??
public List<Integer> replaceables = new ArrayList<>();
public boolean reforged = false;
public MdxModel(final MdxHandler handler, final ModelViewer viewer, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(handler, viewer, extension, pathSolver, fetchUrl);
}
@Override
protected void lateLoad() {
// TODO Auto-generated method stub
}
@Override
protected void load(final InputStream src, final Object options) {
// TODO Auto-generated method stub
}
@Override
protected void error(final Exception e) {
// TODO Auto-generated method stub
}
// TODO typing
public List<Long> getGlobalSequences() {
return this.model.getGlobalSequences();
}
public List<Sequence> getSequences() {
return this.model.getSequences();
}
public List<float[]> getPivotPoints() {
return this.model.getPivotPoints();
}
public List<GeosetAnimation> getGeosetAnimations() {
throw new UnsupportedOperationException("NYI");
}
public List<Texture> getTextures() {
throw new UnsupportedOperationException("NYI");
}
public List<Material> getMaterials() {
throw new UnsupportedOperationException("NYI");
}
}

View File

@ -0,0 +1,22 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SkeletalNode;
public class MdxNode extends SkeletalNode {
private static final Quaternion HALF_PI_X = new Quaternion().setFromAxisRad(1, 0, 0, (float) (-Math.PI / 2));
private static final Quaternion HALF_PI_Y = new Quaternion().setFromAxisRad(0, 1, 0, (float) (-Math.PI / 2));
@Override
protected void convertBasis(final Quaternion computedRotation) {
computedRotation.mulLeft(HALF_PI_Y);
computedRotation.mulLeft(HALF_PI_X);
}
@Override
protected void update(final float dt, final Scene scene) {
}
}

View File

@ -0,0 +1,408 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.Shaders;
public class MdxShaders {
public static final String vsHd = Shaders.boneTexture + "\r\n" + //
" uniform mat4 u_mvp;\r\n" + //
" uniform float u_layerAlpha;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_normal;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" attribute vec4 a_bones;\r\n" + //
" attribute vec4 a_weights;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying float v_layerAlpha;\r\n" + //
" void transform(inout vec3 position, inout vec3 normal) {\r\n" + //
" mat4 bone;\r\n" + //
" bone += fetchMatrix(a_bones[0], 0.0) * a_weights[0];\r\n" + //
" bone += fetchMatrix(a_bones[1], 0.0) * a_weights[1];\r\n" + //
" bone += fetchMatrix(a_bones[2], 0.0) * a_weights[2];\r\n" + //
" bone += fetchMatrix(a_bones[3], 0.0) * a_weights[3];\r\n" + //
" position = vec3(bone * vec4(position, 1.0));\r\n" + //
" normal = mat3(bone) * normal;\r\n" + //
" }\r\n" + //
" void main() {\r\n" + //
" vec3 position = a_position;\r\n" + //
" vec3 normal = a_normal;\r\n" + //
" transform(position, normal);\r\n" + //
" v_normal = normal;\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_layerAlpha = u_layerAlpha;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }";
public static final String fsHd = "\r\n" + //
" uniform sampler2D u_diffuseMap;\r\n" + //
" uniform sampler2D u_ormMap;\r\n" + //
" uniform sampler2D u_teamColorMap;\r\n" + //
" uniform float u_filterMode;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying float v_layerAlpha;\r\n" + //
" void main() {\r\n" + //
" vec4 texel = texture2D(u_diffuseMap, v_uv);\r\n" + //
" vec4 color = vec4(texel.rgb, texel.a * v_layerAlpha);\r\n" + //
" vec4 orma = texture2D(u_ormMap, v_uv);\r\n" + //
" if (orma.a > 0.1) {\r\n" + //
" color *= texture2D(u_teamColorMap, v_uv) * orma.a;\r\n" + //
" }\r\n" + //
" // 1bit Alpha\r\n" + //
" if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" gl_FragColor = color;\r\n" + //
" }";
public static final String vsSimple = "\r\n" + //
" uniform mat4 u_mvp;\r\n" + //
" attribute vec3 a_m0;\r\n" + //
" attribute vec3 a_m1;\r\n" + //
" attribute vec3 a_m2;\r\n" + //
" attribute vec3 a_m3;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" void main() {\r\n" + //
" v_uv = a_uv;\r\n" + //
" gl_Position = u_mvp * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n"
+ //
" }";
public static final String fsSimple = "\r\n" + //
" uniform sampler2D u_texture;\r\n" + //
" uniform float u_filterMode;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" void main() {\r\n" + //
" vec4 color = texture2D(u_texture, v_uv);\r\n" + //
" // 1bit Alpha\r\n" + //
" if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" gl_FragColor = color;\r\n" + //
" }";
public static final String vsComplex = Shaders.boneTexture + "\r\n" + //
" uniform mat4 u_mvp;\r\n" + //
" uniform vec4 u_vertexColor;\r\n" + //
" uniform vec4 u_geosetColor;\r\n" + //
" uniform float u_layerAlpha;\r\n" + //
" uniform vec2 u_uvTrans;\r\n" + //
" uniform vec2 u_uvRot;\r\n" + //
" uniform float u_uvScale;\r\n" + //
" uniform bool u_hasBones;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_normal;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" attribute vec4 a_bones;\r\n" + //
" #ifdef EXTENDED_BONES\r\n" + //
" attribute vec4 a_extendedBones;\r\n" + //
" #endif\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
" void transform(inout vec3 position, inout vec3 normal) {\r\n" + //
" // For the broken models out there, since the game supports this.\r\n" + //
" if (a_boneNumber > 0.0) {\r\n" + //
" vec4 position4 = vec4(position, 1.0);\r\n" + //
" vec4 normal4 = vec4(normal, 0.0);\r\n" + //
" mat4 bone;\r\n" + //
" vec4 p;\r\n" + //
" vec4 n;\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_bones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + //
" p += bone * position4;\r\n" + //
" n += bone * normal4;\r\n" + //
" }\r\n" + //
" }\r\n" + //
" #ifdef EXTENDED_BONES\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_extendedBones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_extendedBones[i] - 1.0, 0.0);\r\n" + //
" p += bone * position4;\r\n" + //
" n += bone * normal4;\r\n" + //
" }\r\n" + //
" }\r\n" + //
" #endif\r\n" + //
" position = p.xyz / a_boneNumber;\r\n" + //
" normal = normalize(n.xyz);\r\n" + //
" }\r\n" + //
" }\r\n" + //
" void main() {\r\n" + //
" vec3 position = a_position;\r\n" + //
" vec3 normal = a_normal;\r\n" + //
" if (u_hasBones) {\r\n" + //
" transform(position, normal);\r\n" + //
" }\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_color = u_vertexColor * u_geosetColor.bgra * vec4(1.0, 1.0, 1.0, u_layerAlpha);\r\n" + //
" v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + //
" v_uvScale = u_uvScale;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }";
public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + //
" uniform sampler2D u_texture;\r\n" + //
" uniform float u_filterMode;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
" void main() {\r\n" + //
" vec2 uv = v_uv;\r\n" + //
" // Translation animation\r\n" + //
" uv += v_uvTransRot.xy;\r\n" + //
" // Rotation animation\r\n" + //
" uv = quat_transform(v_uvTransRot.zw, uv - 0.5) + 0.5;\r\n" + //
" // Scale animation\r\n" + //
" uv = v_uvScale * (uv - 0.5) + 0.5;\r\n" + //
" vec4 texel = texture2D(u_texture, uv);\r\n" + //
" vec4 color = texel * v_color;\r\n" + //
" // 1bit Alpha\r\n" + //
" if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" // \"Close to 0 alpha\"\r\n" + //
" if (u_filterMode >= 5.0 && color.a < 0.02) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" // if (!u_unshaded) {\r\n" + //
" // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + //
" // }\r\n" + //
" gl_FragColor = color;\r\n" + //
" }";
public static final String vsParticles = "\r\n" + //
" #define EMITTER_PARTICLE2 0.0\r\n" + //
" #define EMITTER_RIBBON 1.0\r\n" + //
" #define EMITTER_SPLAT 2.0\r\n" + //
" #define EMITTER_UBERSPLAT 3.0\r\n" + //
" #define HEAD 0.0\r\n" + //
" uniform mat4 u_mvp;\r\n" + //
" uniform mediump float u_emitter;\r\n" + //
" // Shared\r\n" + //
" uniform vec4 u_colors[3];\r\n" + //
" uniform vec3 u_vertices[4];\r\n" + //
" uniform vec3 u_intervals[4];\r\n" + //
" uniform float u_lifeSpan;\r\n" + //
" uniform float u_columns;\r\n" + //
" uniform float u_rows;\r\n" + //
" // Particle2\r\n" + //
" uniform vec3 u_scaling;\r\n" + //
" uniform vec3 u_cameraZ;\r\n" + //
" uniform float u_timeMiddle;\r\n" + //
" uniform bool u_teamColored;\r\n" + //
" // Splat and Uber.\r\n" + //
" uniform vec3 u_intervalTimes;\r\n" + //
" // Vertices\r\n" + //
" attribute float a_position;\r\n" + //
" // Instances\r\n" + //
" attribute vec3 a_p0;\r\n" + //
" attribute vec3 a_p1;\r\n" + //
" attribute vec3 a_p2;\r\n" + //
" attribute vec3 a_p3;\r\n" + //
" attribute float a_health;\r\n" + //
" attribute vec4 a_color;\r\n" + //
" attribute float a_tail;\r\n" + //
" attribute vec3 a_leftRightTop;\r\n" + //
" varying vec2 v_texcoord;\r\n" + //
" varying vec4 v_color;\r\n" + //
" float getCell(vec3 interval, float factor) {\r\n" + //
" float start = interval[0];\r\n" + //
" float end = interval[1];\r\n" + //
" float repeat = interval[2];\r\n" + //
" float spriteCount = end - start;\r\n" + //
" if (spriteCount > 0.0) {\r\n" + //
" // Repeating speeds up the sprite animation, which makes it effectively run N times in its interval.\r\n"
+ //
" // E.g. if repeat is 4, the sprite animation will be seen 4 times, and thus also run 4 times as fast.\r\n"
+ //
" // The sprite index is limited to the number of actual sprites.\r\n" + //
" return min(start + mod(floor(spriteCount * repeat * factor), spriteCount), u_columns * u_rows - 1.0);\r\n"
+ //
" }\r\n" + //
" return 0.0;\r\n" + //
" }\r\n" + //
" void particle2() {\r\n" + //
" float factor = (u_lifeSpan - a_health) / u_lifeSpan;\r\n" + //
" int index = 0;\r\n" + //
" if (factor < u_timeMiddle) {\r\n" + //
" factor = factor / u_timeMiddle;\r\n" + //
" index = 0;\r\n" + //
" } else {\r\n" + //
" factor = (factor - u_timeMiddle) / (1.0 - u_timeMiddle);\r\n" + //
" index = 1;\r\n" + //
" }\r\n" + //
" factor = min(factor, 1.0);\r\n" + //
" float scale = mix(u_scaling[index], u_scaling[index + 1], factor);\r\n" + //
" vec4 color = mix(u_colors[index], u_colors[index + 1], factor);\r\n" + //
" float cell = 0.0;\r\n" + //
" if (u_teamColored) {\r\n" + //
" cell = a_leftRightTop[0];\r\n" + //
" } else {\r\n" + //
" vec3 interval;\r\n" + //
" if (a_tail == HEAD) {\r\n" + //
" interval = u_intervals[index];\r\n" + //
" } else {\r\n" + //
" interval = u_intervals[index + 2];\r\n" + //
" }\r\n" + //
" cell = getCell(interval, factor);\r\n" + //
" }\r\n" + //
" float left = floor(mod(cell, u_columns));\r\n" + //
" float top = floor(cell / u_columns);\r\n" + //
" float right = left + 1.0;\r\n" + //
" float bottom = top + 1.0;\r\n" + //
" left /= u_columns;\r\n" + //
" right /= u_columns;\r\n" + //
" top /= u_rows;\r\n" + //
" bottom /= u_rows;\r\n" + //
" if (a_position == 0.0) {\r\n" + //
" v_texcoord = vec2(right, top);\r\n" + //
" } else if (a_position == 1.0) {\r\n" + //
" v_texcoord = vec2(left, top);\r\n" + //
" } else if (a_position == 2.0) {\r\n" + //
" v_texcoord = vec2(left, bottom);\r\n" + //
" } else if (a_position == 3.0) {\r\n" + //
" v_texcoord = vec2(right, bottom);\r\n" + //
" }\r\n" + //
" v_color = color;\r\n" + //
" \r\n" + //
" if (a_tail == HEAD) {\r\n" + //
" gl_Position = u_mvp * vec4(a_p0 + (u_vertices[int(a_position)] * scale), 1.0);\r\n" + //
" } else {\r\n" + //
" // Get the normal to the tail in camera space.\r\n" + //
" // This allows to build a 2D rectangle around the 3D tail.\r\n" + //
" vec3 normal = cross(u_cameraZ, normalize(a_p1 - a_p0));\r\n" + //
" vec3 boundary = normal * scale * a_p2[0];\r\n" + //
" vec3 position;\r\n" + //
" if (a_position == 0.0) {\r\n" + //
" position = a_p0 - boundary;\r\n" + //
" } else if (a_position == 1.0) {\r\n" + //
" position = a_p1 - boundary;\r\n" + //
" } else if (a_position == 2.0) {\r\n" + //
" position = a_p1 + boundary;\r\n" + //
" } else if (a_position == 3.0) {\r\n" + //
" position = a_p0 + boundary;\r\n" + //
" }\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }\r\n" + //
" }\r\n" + //
" void ribbon() {\r\n" + //
" vec3 position;\r\n" + //
" float left = a_leftRightTop[0] / 255.0;\r\n" + //
" float right = a_leftRightTop[1] / 255.0;\r\n" + //
" float top = a_leftRightTop[2] / 255.0;\r\n" + //
" float bottom = top + 1.0;\r\n" + //
" if (a_position == 0.0) {\r\n" + //
" v_texcoord = vec2(right, top);\r\n" + //
" position = a_p0;\r\n" + //
" } else if (a_position == 1.0) {\r\n" + //
" v_texcoord = vec2(right, bottom);\r\n" + //
" position = a_p1;\r\n" + //
" } else if (a_position == 2.0) {\r\n" + //
" v_texcoord = vec2(left, bottom);\r\n" + //
" position = a_p2;\r\n" + //
" } else if (a_position == 3.0) {\r\n" + //
" v_texcoord = vec2(left, top);\r\n" + //
" position = a_p3;\r\n" + //
" }\r\n" + //
" v_texcoord[0] /= u_columns;\r\n" + //
" v_texcoord[1] /= u_rows;\r\n" + //
" v_color = a_color;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }\r\n" + //
" void splat() {\r\n" + //
" float factor = u_lifeSpan - a_health;\r\n" + //
" int index;\r\n" + //
" if (factor < u_intervalTimes[0]) {\r\n" + //
" factor = factor / u_intervalTimes[0];\r\n" + //
" index = 0;\r\n" + //
" } else {\r\n" + //
" factor = (factor - u_intervalTimes[0]) / u_intervalTimes[1];\r\n" + //
" index = 1;\r\n" + //
" }\r\n" + //
" float cell = getCell(u_intervals[index], factor);\r\n" + //
" float left = floor(mod(cell, u_columns));\r\n" + //
" float top = floor(cell / u_columns);\r\n" + //
" float right = left + 1.0;\r\n" + //
" float bottom = top + 1.0;\r\n" + //
" vec3 position;\r\n" + //
" if (a_position == 0.0) {\r\n" + //
" v_texcoord = vec2(left, top);\r\n" + //
" position = a_p0;\r\n" + //
" } else if (a_position == 1.0) {\r\n" + //
" v_texcoord = vec2(left, bottom);\r\n" + //
" position = a_p1;\r\n" + //
" } else if (a_position == 2.0) {\r\n" + //
" v_texcoord = vec2(right, bottom);\r\n" + //
" position = a_p2;\r\n" + //
" } else if (a_position == 3.0) {\r\n" + //
" v_texcoord = vec2(right, top);\r\n" + //
" position = a_p3;\r\n" + //
" }\r\n" + //
" v_texcoord[0] /= u_columns;\r\n" + //
" v_texcoord[1] /= u_rows;\r\n" + //
" v_color = mix(u_colors[index], u_colors[index + 1], factor) / 255.0;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }\r\n" + //
" void ubersplat() {\r\n" + //
" float factor = u_lifeSpan - a_health;\r\n" + //
" vec4 color;\r\n" + //
" if (factor < u_intervalTimes[0]) {\r\n" + //
" color = mix(u_colors[0], u_colors[1], factor / u_intervalTimes[0]);\r\n" + //
" } else if (factor < u_intervalTimes[0] + u_intervalTimes[1]) {\r\n" + //
" color = u_colors[1];\r\n" + //
" } else {\r\n" + //
" color = mix(u_colors[1], u_colors[2], (factor - u_intervalTimes[0] - u_intervalTimes[1]) / u_intervalTimes[2]);\r\n"
+ //
" }\r\n" + //
" vec3 position;\r\n" + //
" if (a_position == 0.0) {\r\n" + //
" v_texcoord = vec2(0.0, 0.0);\r\n" + //
" position = a_p0;\r\n" + //
" } else if (a_position == 1.0) {\r\n" + //
" v_texcoord = vec2(0.0, 1.0);\r\n" + //
" position = a_p1;\r\n" + //
" } else if (a_position == 2.0) {\r\n" + //
" v_texcoord = vec2(1.0, 1.0);\r\n" + //
" position = a_p2;\r\n" + //
" } else if (a_position == 3.0) {\r\n" + //
" v_texcoord = vec2(1.0, 0.0);\r\n" + //
" position = a_p3;\r\n" + //
" }\r\n" + //
" v_color = color / 255.0;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }\r\n" + //
" void main() {\r\n" + //
" if (u_emitter == EMITTER_PARTICLE2) {\r\n" + //
" particle2();\r\n" + //
" } else if (u_emitter == EMITTER_RIBBON) {\r\n" + //
" ribbon();\r\n" + //
" } else if (u_emitter == EMITTER_SPLAT) {\r\n" + //
" splat();\r\n" + //
" } else if (u_emitter == EMITTER_UBERSPLAT) {\r\n" + //
" ubersplat();\r\n" + //
" }\r\n" + //
" }";
public static final String fsParticles = "\r\n" + //
" #define EMITTER_RIBBON 1.0\r\n" + //
" uniform sampler2D u_texture;\r\n" + //
" uniform mediump float u_emitter;\r\n" + //
" uniform float u_filterMode;\r\n" + //
" varying vec2 v_texcoord;\r\n" + //
" varying vec4 v_color;\r\n" + //
" void main() {\r\n" + //
" vec4 texel = texture2D(u_texture, v_texcoord);\r\n" + //
" vec4 color = texel * v_color;\r\n" + //
" // 1bit Alpha, used by ribbon emitters.\r\n" + //
" if (u_emitter == EMITTER_RIBBON && u_filterMode == 1.0 && color.a < 0.75) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" gl_FragColor = color;\r\n" + //
" }";
}

View File

@ -0,0 +1,37 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.ModelInstance;
public class MdxSimpleInstance extends ModelInstance {
public MdxSimpleInstance(final Model model) {
super(model);
}
@Override
public boolean isBatched() {
return true;
}
@Override
public void updateAnimations(final float dt) {
}
@Override
public void clearEmittedObjects() {
}
@Override
public void renderOpaque() {
}
@Override
public void renderTranslucent() {
}
@Override
public void load() {
}
}

View File

@ -1,25 +0,0 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.io.InputStream;
public class Model extends com.etheller.warsmash.viewer5.Model {
@Override
protected void lateLoad() {
// TODO Auto-generated method stub
}
@Override
protected void load(final InputStream src, final Object options) {
// TODO Auto-generated method stub
}
@Override
protected void error(final Exception e) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,111 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.EmittedObject;
public class Particle2 extends EmittedObject<MdxComplexInstance, ParticleEmitter2> {
private static final Quaternion HALF_PI_Z = new Quaternion().setFromAxisRad(0, 0, 1, (float) (Math.PI / 2));
public int tail = 0;
private float gravity = 0;
public final Vector3 location = new Vector3();
public final Vector3 velocity = new Vector3();
public final Vector3 scale = new Vector3();
private final ParticleEmitter2 emitter2;
private static final Quaternion rotationHeap = new Quaternion();
private static final Quaternion rotationHeap2 = new Quaternion();
private static final float[] widthHeap = new float[1];
private static final float[] lengthHeap = new float[1];
private static final float[] latitudeHeap = new float[1];
private static final float[] variationHeap = new float[1];
private static final float[] speedHeap = new float[1];
private static final float[] gravityHeap = new float[1];
public Particle2(final ParticleEmitter2 emitter) {
this.emitter2 = emitter;
}
@Override
protected void bind(final int flags) {
final MdxComplexInstance instance = this.emitter.instance;
final ParticleEmitter2Object emitterObject = this.emitter.emitterObject;
emitterObject.getWidth(widthHeap, instance);
emitterObject.getLength(lengthHeap, instance);
emitterObject.getLatitude(latitudeHeap, instance);
emitterObject.getVariation(variationHeap, instance);
emitterObject.getSpeed(speedHeap, instance);
emitterObject.getGravity(gravityHeap, instance);
final MdxNode node = this.emitter.node;
final Vector3 pivot = node.pivot;
final Vector3 scale = node.worldScale;
final float width = widthHeap[0] * 0.5f;
final float length = lengthHeap[0] * 0.5f;
final float latitude = (float) Math.toRadians(latitudeHeap[0]);
final float variation = variationHeap[0];
final float speed = speedHeap[0];
final Vector3 location = this.location;
final Vector3 velocity = this.velocity;
this.health = emitterObject.lifeSpan;
this.tail = flags;
this.gravity = gravityHeap[0] * scale.z;
this.scale.set(scale);
// Local location
location.x = pivot.x + RenderMathUtils.randomInRange(-width, width);
location.y = pivot.y + RenderMathUtils.randomInRange(-length, length);
location.z = pivot.z;
// World location
if (emitterObject.modelSpace == 0) {
location.prj(node.worldMatrix);
}
// Local rotation
rotationHeap.idt();
rotationHeap.mulLeft(HALF_PI_Z);
rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 1, 0, RenderMathUtils.randomInRange(-latitude, latitude)));
// If this is not a line emitter, emit in a sphere rather than a circle
if (emitterObject.lineEmitter == 0) {
rotationHeap
.mulLeft(rotationHeap2.setFromAxisRad(1, 0, 0, RenderMathUtils.randomInRange(-latitude, latitude)));
}
// World rotation
if (emitterObject.modelSpace == 0) {
rotationHeap.mulLeft(node.worldRotation);
}
// Apply the rotation
velocity.set(RenderMathUtils.VEC3_UNIT_Z);
rotationHeap.transform(velocity);
// Apply speed
velocity.scl(speed + RenderMathUtils.randomInRange(-variation, variation));
// Apply the parent's scale
if (emitterObject.modelSpace == 0) {
velocity.scl(scale);
}
}
@Override
public void update(final float dt) {
this.health -= dt;
if (this.health > 0) {
this.velocity.z -= this.gravity * dt;
this.location.x += this.velocity.x * dt;
this.location.y += this.velocity.y * dt;
this.location.z += this.velocity.z * dt;
}
}
}

View File

@ -0,0 +1,54 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class ParticleEmitter2 extends MdxEmitter<MdxComplexInstance, ParticleEmitter2Object, Particle2> {
private static final float[] emissionRateHeap = new float[1];
protected final MdxNode node;
private int lastEmissionKey;
public ParticleEmitter2(final MdxComplexInstance instance, final ParticleEmitter2Object emitterObject) {
super(instance, emitterObject);
this.node = instance.nodes[emitterObject.index];
this.lastEmissionKey = -1;
}
@Override
protected void updateEmission(final float dt) {
final MdxComplexInstance instance = this.instance;
if (instance.allowParticleSpawn) {
final ParticleEmitter2Object emitterObject = this.emitterObject;
final int keyframe = emitterObject.getEmissionRate(emissionRateHeap, instance);
if (emitterObject.squirt != 0) {
if (keyframe != this.lastEmissionKey) {
this.currentEmission += emissionRateHeap[0];
}
this.lastEmissionKey = keyframe;
}
else {
this.currentEmission += emissionRateHeap[0] * dt;
}
}
}
@Override
protected void emit() {
if (this.emitterObject.head) {
this.emitObject(0);
}
if (this.emitterObject.tail) {
this.emitObject(1);
}
}
@Override
protected Particle2 createObject() {
return new Particle2(this);
}
}

View File

@ -0,0 +1,151 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class ParticleEmitter2Object extends GenericObject implements EmitterObject {
public float width;
public float length;
public float speed;
public float latitude;
public float gravity;
public float emissionRate;
public long squirt;
public float lifeSpan;
public float variation;
public float tailLength;
public float timeMiddle;
public long columns;
public long rows;
public int teamColored = 0;
public Texture internalTexture;
public long replaceableId;
public boolean head;
public boolean tail;
public float cellWidth;
public float cellHeight;
public float[][] colors;
public float[] scaling;
public float[][] intervals;
public int blendSrc;
public int blendDst;
public int priorityPlane;
public ParticleEmitter2Object(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 emitter, final int index) {
super(model, emitter, index);
this.width = emitter.getWidth();
this.length = emitter.getLength();
this.speed = emitter.getSpeed();
this.latitude = emitter.getLatitude();
this.gravity = emitter.getGravity();
this.emissionRate = emitter.getEmissionRate();
this.squirt = emitter.getSquirt();
this.lifeSpan = emitter.getLifeSpan();
this.variation = emitter.getVariation();
this.tailLength = emitter.getTailLength();
this.timeMiddle = emitter.getTimeMiddle();
final long replaceableId = emitter.getReplaceableId();
this.columns = emitter.getColumns();
this.rows = emitter.getRows();
if (this.replaceableId == 0) {
this.internalTexture = model.getTextures().get(emitter.getTextureId());
}
else if ((replaceableId == 1) || (replaceableId == 2)) {
this.teamColored = 1;
}
else {
this.internalTexture = (Texture) model.viewer.load(
"ReplaceableTextures\\" + ReplaceableIds.get(replaceableId) + ".blp", model.pathSolver,
model.solverParams);
}
this.replaceableId = emitter.getReplaceableId();
final long headOrTail = emitter.getHeadOrTail();
this.head = ((headOrTail == 0) || (headOrTail == 2));
this.tail = ((headOrTail == 1) || (headOrTail == 2));
this.cellWidth = 1f / emitter.getColumns();
this.cellHeight = 1f / emitter.getRows();
this.colors = new float[3][0];
final float[][] colors = emitter.getSegmentColors();
final short[] alpha = emitter.getSegmentAlphas();
for (int i = 0; i < 3; i++) {
final float[] color = colors[i];
this.colors[i] = new float[] { color[0], color[1], color[2], alpha[i] / 255f };
}
this.scaling = emitter.getSegmentScaling();
final long[][] headIntervals = emitter.getHeadIntervals();
final long[][] tailIntervals = emitter.getTailIntervals();
// Change to Float32Array instead of Uint32Array to be able to pass the
// intervals directly using uniform3fv().
this.intervals = new float[][] { { headIntervals[0][0], headIntervals[0][1], headIntervals[0][2] },
{ headIntervals[1][0], headIntervals[1][1], headIntervals[1][2] },
{ tailIntervals[0][0], tailIntervals[0][1], tailIntervals[0][2] },
{ tailIntervals[1][0], tailIntervals[1][1], tailIntervals[1][2] }, };
final int[] blendModes = FilterMode.emitterFilterMode(emitter.getFilterMode());
this.blendSrc = blendModes[0];
this.blendDst = blendModes[1];
this.priorityPlane = emitter.getPriorityPlane();
}
public int getWidth(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), instance, this.width);
}
public int getLength(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), instance, this.length);
}
public int getSpeed(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2S.getWar3id(), instance, this.speed);
}
public int getLatitude(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2L.getWar3id(), instance, this.latitude);
}
public int getGravity(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2G.getWar3id(), instance, this.gravity);
}
public int getEmissionRate(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2E.getWar3id(), instance, this.emissionRate);
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2V.getWar3id(), instance, 1);
}
public int getVariation(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2R.getWar3id(), instance, this.variation);
}
@Override
public boolean ok() {
return true;
}
@Override
public int getGeometryEmitterType() {
return GeometryEmitterFuncs.EMITTER_PARTICLE2;
}
}

View File

@ -0,0 +1,29 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.Interpolator;
public class QuaternionSd extends Sd<float[]> {
public QuaternionSd(final MdxModel model, final Timeline<float[]> timeline) {
super(model, timeline);
}
@Override
protected float[] convertDefaultValue(final float[] defaultValue) {
return defaultValue;
}
@Override
protected void copy(final float[] out, final float[] value) {
System.arraycopy(value, 0, out, 0, value.length);
}
@Override
protected void interpolate(final float[] out, final float[][] values, final float[][] inTans,
final float[][] outTans, final int start, final int end, final float t) {
Interpolator.interpolateQuaternion(out, values[start], outTans[start], inTans[end], values[end], t,
this.interpolationType);
}
}

View File

@ -0,0 +1,22 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.HashMap;
import java.util.Map;
public class ReplaceableIds {
private static final Map<Long, String> ID_TO_STR = new HashMap<>();
static {
for (int i = 0; i < 28; i++) {
ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0'));
}
}
public static void main(final String[] args) {
System.out.println(ID_TO_STR);
}
public static String get(final long replaceableId) {
return ID_TO_STR.get(replaceableId);
}
}

View File

@ -0,0 +1,89 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.EmittedObject;
public class Ribbon extends EmittedObject<MdxComplexInstance, RibbonEmitter> {
private static final float[] vectorHeap = new float[3];
private static final Vector3 belowHeap = new Vector3();
private static final Vector3 aboveHeap = new Vector3();
private static final float[] colorHeap = new float[3];
private static final float[] alphaHeap = new float[1];
private static final long[] slotHeap = new long[1];
public float[] vertices = new float[6];
public byte[] color = new byte[4];
public int slot;
public Ribbon prev;
public Ribbon next;
@Override
protected void bind(final int flags) {
final RibbonEmitter emitter = this.emitter;
final MdxComplexInstance instance = emitter.instance;
final RibbonEmitterObject emitterObject = emitter.emitterObject;
final MdxNode node = instance.nodes[emitterObject.index];
final Vector3 pivot = node.pivot;
final float x = pivot.x, y = pivot.y, z = pivot.z;
final Matrix4 worldMatrix = node.worldMatrix;
final float[] vertices = this.vertices;
this.health = emitter.emitterObject.lifeSpan;
emitterObject.getHeightBelow(vectorHeap, instance);
belowHeap.set(vectorHeap);
emitterObject.getHeightAbove(vectorHeap, instance);
aboveHeap.set(vectorHeap);
belowHeap.y = y - belowHeap.x;
belowHeap.x = x;
belowHeap.z = z;
aboveHeap.y = y + aboveHeap.x;
aboveHeap.x = x;
aboveHeap.z = z;
belowHeap.prj(worldMatrix);
aboveHeap.prj(worldMatrix);
vertices[0] = aboveHeap.x;
vertices[1] = aboveHeap.y;
vertices[2] = aboveHeap.z;
vertices[3] = belowHeap.x;
vertices[4] = belowHeap.y;
vertices[5] = belowHeap.z;
}
public Ribbon(final RibbonEmitter emitter) {
}
@Override
public void update(final float dt) {
this.health -= dt;
if (this.health > 0) {
final RibbonEmitter emitter = this.emitter;
final MdxComplexInstance instance = emitter.instance;
final RibbonEmitterObject emitterObject = emitter.emitterObject;
final byte[] color = this.color;
final float[] vertices = this.vertices;
final float gravity = emitterObject.gravity * dt * dt;
emitterObject.getColor(colorHeap, instance);
emitterObject.getAlpha(alphaHeap, instance);
emitterObject.getTextureSlot(slotHeap, instance);
vertices[1] -= gravity;
vertices[4] -= gravity;
color[0] = (byte) (colorHeap[0] * 255);
color[1] = (byte) (colorHeap[1] * 255);
color[2] = (byte) (colorHeap[2] * 255);
color[3] = (byte) (alphaHeap[0] * 255);
this.slot = (int) slotHeap[0];
}
}
}

View File

@ -0,0 +1,73 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class RibbonEmitter extends MdxEmitter<MdxComplexInstance, RibbonEmitterObject, Ribbon> {
public Ribbon first;
public Ribbon last;
public RibbonEmitter(final MdxComplexInstance instance, final RibbonEmitterObject emitterObject) {
super(instance, emitterObject);
}
@Override
protected void updateEmission(final float dt) {
final MdxComplexInstance instance = this.instance;
if (instance.allowParticleSpawn) {
final RibbonEmitterObject emitterObject = this.emitterObject;
// It doesn't make sense to emit more than 1 ribbon at the same time.
this.currentEmission = Math.min(this.currentEmission + (emitterObject.emissionRate * dt), 1);
}
}
@Override
protected void emit() {
final Ribbon ribbon = this.emitObject(0);
final Ribbon last = this.last;
if (last != null) {
last.next = ribbon;
ribbon.prev = last;
}
else {
this.first = ribbon;
}
this.last = ribbon;
}
@Override
public void kill(final Ribbon object) {
super.kill(object);
final Ribbon prev = object.prev;
final Ribbon next = object.next;
if (object == this.first) {
this.first = next;
}
if (object == this.last) {
this.first = null;
this.last = null;
}
if (prev != null) {
prev.next = next;
}
if (next != null) {
next.prev = prev;
}
object.prev = null;
object.next = null;
}
@Override
protected Ribbon createObject() {
return new Ribbon(this);
}
}

View File

@ -0,0 +1,77 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class RibbonEmitterObject extends GenericObject implements EmitterObject {
public Layer layer;
public float heightAbove;
public float heightBelow;
public float alpha;
public float[] color;
public float lifeSpan;
public long textureSlot;
public long emissionRate;
public float gravity;
public long columns;
public long rows;
/**
* Even if the internal texture isn't loaded, it's fine to run emitters based on
* this emitter object.
*
* The ribbons will simply be black.
*/
public boolean ok = true;
public RibbonEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.RibbonEmitter emitter,
final int index) {
super(model, emitter, index);
this.layer = model.getMaterials().get(emitter.getMaterialId()).layers.get(0);
this.heightAbove = emitter.getHeightAbove();
this.heightBelow = emitter.getHeightBelow();
this.alpha = emitter.getAlpha();
this.color = emitter.getColor();
this.lifeSpan = emitter.getLifeSpan();
this.textureSlot = emitter.getTextureSlot();
this.emissionRate = emitter.getEmissionRate();
this.gravity = emitter.getGravity();
this.columns = emitter.getColumns();
this.rows = emitter.getRows();
}
public int getHeightBelow(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRHB.getWar3id(), instance, this.heightBelow);
}
public int getHeightAbove(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRHA.getWar3id(), instance, this.heightAbove);
}
public int getTextureSlot(final long[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRTX.getWar3id(), instance, 0);
}
public int getColor(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KRCO.getWar3id(), instance, this.color);
}
public int getAlpha(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRAL.getWar3id(), instance, this.alpha);
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRVS.getWar3id(), instance, 1f);
}
@Override
public int getGeometryEmitterType() {
return GeometryEmitterFuncs.EMITTER_RIBBON;
}
@Override
public boolean ok() {
return this.ok;
}
}

View File

@ -0,0 +1,44 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.RenderMathUtils;
public class ScalarSd extends Sd<float[]> {
public ScalarSd(final MdxModel model, final Timeline<float[]> timeline) {
super(model, timeline);
}
@Override
protected float[] convertDefaultValue(final float[] defaultValue) {
return defaultValue;
}
@Override
protected void copy(final float[] out, final float[] value) {
out[0] = value[0];
}
@Override
protected void interpolate(final float[] out, final float[][] values, final float[][] inTans,
final float[][] outTans, final int start, final int end, final float t) {
final float startValue = values[start][0];
switch (this.interpolationType) {
case 0:
out[0] = startValue;
break;
case 1:
out[0] = RenderMathUtils.lerp(startValue, values[end][0], t);
break;
case 2:
out[0] = RenderMathUtils.hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t);
break;
case 3:
out[0] = RenderMathUtils.bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t);
break;
}
}
}

View File

@ -5,18 +5,15 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.ModelInstance;
public abstract class Sd<TYPE> {
public MdlxModel model;
public MdxModel model;
public int interpolationType;
public War3ID name;
public float[] defval;
public TYPE defval;
public SdSequence<TYPE> globalSequence;
public List<SdSequence<TYPE>> sequences;
@ -88,15 +85,14 @@ public abstract class Sd<TYPE> {
}
public Sd(final MdlxModel model, final Timeline timeline) {
public Sd(final MdxModel model, final Timeline<TYPE> timeline) {
final List<Long> globalSequences = model.getGlobalSequences();
final int globalSequenceId = timeline.getGlobalSequenceId();
final List<KeyFrame> keyFrames = timeline.getKeyFrames();
final Integer forcedInterp = forcedInterpMap.get(timeline.getName());
this.model = model;
this.name = timeline.getName();
this.defval = defVals.get(timeline.getName());
this.defval = convertDefaultValue(defVals.get(timeline.getName()));
this.globalSequence = null;
this.sequences = new ArrayList<>();
@ -107,24 +103,46 @@ public abstract class Sd<TYPE> {
// type.
this.interpolationType = forcedInterp != null ? forcedInterp : timeline.getInterpolationType().ordinal();
if (globalSequenceId != -1 && globalSequences.size() > 0) {
this.globalSequence = newSequenceTyped(this, 0, globalSequences.get(globalSequenceId).longValue(),
keyFrames, true);
if ((globalSequenceId != -1) && (globalSequences.size() > 0)) {
this.globalSequence = new SdSequence<TYPE>(this, 0, globalSequences.get(globalSequenceId).longValue(),
timeline, true);
}
else {
for (final Sequence sequence : model.getSequences()) {
final long[] interval = sequence.getInterval();
this.sequences.add(newSequenceTyped(this, interval[0], interval[1], keyFrames, false));
this.sequences.add(new SdSequence<TYPE>(this, interval[0], interval[1], timeline, false));
}
}
}
public int getValue(final TYPE out, final ModelInstance instance) {
if(this.globalSequence != null) {
return this.globalSequence.getValue(out, instance.cout)
public int getValue(final TYPE out, final MdxComplexInstance instance) {
if (this.globalSequence != null) {
return this.globalSequence.getValue(out, instance.counter % this.globalSequence.end);
}
else if (instance.sequence != -1) {
return this.sequences.get(instance.sequence).getValue(out, instance.frame);
}
else {
this.copy(out, this.defval);
return -1;
}
}
protected abstract SdSequence<TYPE> newSequenceTyped(final Sd<TYPE> parent, final long start, final long end,
final List<KeyFrame> keyframes, final boolean isGlobalSequence);
public boolean isVariant(final int sequence) {
if (this.globalSequence != null) {
return !this.globalSequence.constant;
}
else {
return !this.sequences.get(sequence).constant;
}
}
protected abstract TYPE convertDefaultValue(float[] defaultValue);
protected abstract void copy(TYPE out, TYPE value);
protected abstract void interpolate(TYPE out, TYPE[] values, TYPE[] inTans, TYPE[] outTans, int start, int end,
float t);
}

View File

@ -1,27 +1,39 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SdSequence<TYPE> {
public final class SdSequence<TYPE> {
private final Sd<TYPE> sd;
private final long start; // UInt32
private final long end; // UInt32
private final List<KeyFrame> keyframes;
private boolean constant;
public final long start; // UInt32
public final long end; // UInt32
public long[] frames;
public TYPE[] values;
public TYPE[] inTans;
public TYPE[] outTans;
public boolean constant;
public SdSequence(final Sd<TYPE> sd, final long start, final long end, final List<KeyFrame> keyframes,
public SdSequence(final Sd<TYPE> sd, final long start, final long end, final Timeline<TYPE> timeline,
final boolean isGlobalSequence) {
final TYPE defval = convertDefaultValue(sd.defval);
this.sd = sd;
this.start = start;
this.end = end;
this.keyframes = new ArrayList<>();
final ArrayList<Long> framesBuilder = new ArrayList<>();
final ArrayList<TYPE> valuesBuilder = new ArrayList<>();
final ArrayList<TYPE> inTansBuilder = new ArrayList<>();
final ArrayList<TYPE> outTansBuilder = new ArrayList<>();
this.constant = false;
final int interpolationType = sd.interpolationType;
final long[] frames = timeline.getFrames();
final TYPE[] values = timeline.getValues();
final TYPE[] inTans = timeline.getInTans();
final TYPE[] outTans = timeline.getOutTans();
final TYPE defval = sd.defval;
// When using a global sequence, where the first key is outside of the
// sequence's length, it becomes its constant value.
@ -34,40 +46,47 @@ public abstract class SdSequence<TYPE> {
// Therefore, only handle the case where the first key is outside.
// This fixes problems spread over many models, e.g. HeroMountainKing
// (compare in WE and in Magos).
if (isGlobalSequence && keyframes.size() > 0 && keyframes.get(0).getTime() > end) {
this.keyframes.add(keyframes.get(0));
if (isGlobalSequence && (frames.length > 0) && (frames[0] > end)) {
this.frames[0] = frames[0];
this.values[0] = values[0];
}
// Go over the keyframes, and add all of the ones that are in this
// sequence (start <= frame <= end).
for (int i = 0, l = keyframes.size(); i < l; i++) {
final KeyFrame keyFrame = keyframes.get(i);
final long frame = keyFrame.getTime();
for (int i = 0, l = frames.length; i < l; i++) {
final long frame = frames[i];
if (frame >= start && frame <= end) {
this.keyframes.add(keyFrame);
if ((frame >= start) && (frame <= end)) {
framesBuilder.add(frame);
valuesBuilder.add(values[i]);
if (interpolationType > 1) {
inTansBuilder.add(inTans[i]);
outTansBuilder.add(outTans[i]);
}
}
}
final int keyframeCount = this.keyframes.size();
final int keyframeCount = framesBuilder.size();
if (keyframeCount == 0) {
// if there are no keys, use the default value directly.
this.constant = true;
this.keyframes.add(createKeyFrame(start, defval));
framesBuilder.add(start);
valuesBuilder.add(defval);
}
else if (keyframeCount == 1) {
// If there's only one key, use it directly
this.constant = true;
}
else {
final KeyFrame firstFrame = this.keyframes.get(0);
final TYPE firstValue = valuesBuilder.get(0);
// If all of the values in this sequence are the same, might as well
// make it constant.
boolean allFramesMatch = true;
for (final KeyFrame frame : this.keyframes) {
if (!frame.matchingValue(firstFrame)) {
for (final TYPE value : valuesBuilder) {
if (!equals(firstValue, value)) {
allFramesMatch = false;
}
}
@ -76,75 +95,78 @@ public abstract class SdSequence<TYPE> {
if (!this.constant) {
// If there is no opening keyframe for this sequence, inject one
// with the default value.
if (this.keyframes.get(0).getTime() != start) {
this.keyframes.add(0, createKeyFrame(start, defval));
if (framesBuilder.get(0) != start) {
framesBuilder.add(start);
valuesBuilder.add(defval);
if (interpolationType > 1) {
inTansBuilder.add(defval);
outTansBuilder.add(defval);
}
}
// If there is no closing keyframe for this sequence, inject one
// with the default value.
if (this.keyframes.get(this.keyframes.size() - 1).getTime() != end) {
this.keyframes.add(this.keyframes.get(0).clone(end));
}
}
}
}
if (framesBuilder.get(framesBuilder.size() - 1) != end) {
framesBuilder.add(end);
valuesBuilder.add(valuesBuilder.get(0));
public int getValue(final TYPE out, final long frame) {
final int index = this.getKeyframe(frame);
final int size = keyframes.size();
if (index == -1) {
set(out, keyframes.get(0));
return 0;
}
else if (index == size) {
set(out, keyframes.get(size - 1));
return size - 1;
}
else {
final KeyFrame start = keyframes.get(index - 1);
final KeyFrame end = keyframes.get(index);
final float t = RenderMathUtils.clamp((frame - start.getTime()) / (end.getTime() - start.getTime()), 0, 1);
interpolate(out, start, end, t);
return index;
}
}
public int getKeyframe(final long frame) {
if (this.constant) {
return -1;
}
else {
final int l = keyframes.size();
if (frame < this.start) {
return -1;
}
else if (frame >= this.end) {
return 1;
}
else {
for (int i = 1; i < l; i++) {
final KeyFrame keyframe = keyframes.get(i);
if (keyframe.getTime() > frame) {
return i;
if (interpolationType > 1) {
inTansBuilder.add(inTansBuilder.get(0));
outTansBuilder.add(outTansBuilder.get(0));
}
}
}
}
return -1;
this.frames = new long[framesBuilder.size()];
for (int i = 0; i < framesBuilder.size(); i++) {
frames[i] = framesBuilder.get(i);
}
this.values = valuesBuilder.toArray((TYPE[]) new Object[valuesBuilder.size()]);
this.inTans = inTansBuilder.toArray((TYPE[]) new Object[inTansBuilder.size()]);
this.outTans = outTansBuilder.toArray((TYPE[]) new Object[outTansBuilder.size()]);
}
protected abstract void set(TYPE out, KeyFrame frameForValue);
public int getValue(final TYPE out, final long frame) {
final int l = this.frames.length;
protected abstract TYPE convertDefaultValue(float[] defaultValue);
if (this.constant || (frame < this.start)) {
this.sd.copy(out, this.values[0]);
protected abstract KeyFrame createKeyFrame(long time, TYPE value);
return -1;
}
else if (frame >= this.end) {
this.sd.copy(out, this.values[l - 1]);
protected abstract void interpolate(TYPE out, KeyFrame a, KeyFrame b, float t);
return l - 1;
}
else {
for (int i = 1; i < l; i++) {
if (this.frames[i] > frame) {
final long start = this.frames[i = 1];
final long end = this.frames[i];
final float t = RenderMathUtils.clamp((frame - start) / (end - start), 0, 1);
this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t);
return i;
}
}
return -1;
}
}
protected final boolean equals(final TYPE a, final TYPE b) {
if ((a instanceof Float) && (b instanceof Float)) {
return a.equals(b);
}
else if ((a instanceof Long) && (b instanceof Long)) {
return a.equals(b);
}
else if ((a instanceof float[]) && (b instanceof float[])) {
return Arrays.equals(((float[]) a), (float[]) b);
}
return false;
}
}

View File

@ -0,0 +1,37 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.RenderMathUtils;
public class TextureAnimation extends AnimatedObject {
public TextureAnimation(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation) {
super(model, textureAnimation);
}
public int getTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTAT.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO);
}
public int getRotation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTAR.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT);
}
public int getScale(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTAS.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE);
}
public boolean isTranslationVariant(final int sequence) {
return this.isVariant(AnimationMap.KTAT.getWar3id(), sequence);
}
public boolean isRotationVariant(final int sequence) {
return this.isVariant(AnimationMap.KTAR.getWar3id(), sequence);
}
public boolean isScaleVariant(final int sequence) {
return this.isVariant(AnimationMap.KTAS.getWar3id(), sequence);
}
}

View File

@ -0,0 +1,48 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.RenderMathUtils;
public class UInt32Sd extends Sd<long[]> {
public UInt32Sd(final MdxModel model, final Timeline<long[]> timeline) {
super(model, timeline);
}
@Override
protected long[] convertDefaultValue(final float[] defaultValue) {
final long[] returnValue = new long[defaultValue.length];
for (int i = 0; i < defaultValue.length; i++) {
returnValue[i] = (long) defaultValue[i];
}
return returnValue;
}
@Override
protected void copy(final long[] out, final long[] value) {
out[0] = value[0];
}
@Override
protected void interpolate(final long[] out, final long[][] values, final long[][] inTans, final long[][] outTans,
final int start, final int end, final float t) {
final long startValue = values[start][0];
switch (this.interpolationType) {
case 0:
out[0] = startValue;
break;
case 1:
out[0] = (long) RenderMathUtils.lerp(startValue, values[end][0], t);
break;
case 2:
out[0] = (long) RenderMathUtils.hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t);
break;
case 3:
out[0] = (long) RenderMathUtils.bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t);
break;
}
}
}

View File

@ -0,0 +1,29 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.Interpolator;
public class VectorSd extends Sd<float[]> {
public VectorSd(final MdxModel model, final Timeline<float[]> timeline) {
super(model, timeline);
}
@Override
protected float[] convertDefaultValue(final float[] defaultValue) {
return defaultValue;
}
@Override
protected void copy(final float[] out, final float[] value) {
System.arraycopy(value, 0, out, 0, value.length);
}
@Override
protected void interpolate(final float[] out, final float[][] values, final float[][] inTans,
final float[][] outTans, final int start, final int end, final float t) {
Interpolator.interpolateVector(out, values[start], outTans[start], inTans[end], values[end], t,
this.interpolationType);
}
}