mirror of
https://github.com/Retera/WarsmashModEngine.git
synced 2022-07-31 17:38:59 +02:00
More updates for handlers mdx
This commit is contained in:
parent
dca8289d14
commit
005dd375ad
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -85,4 +85,7 @@ public class Bone extends GenericObject {
|
||||
return 8 + super.getByteLength();
|
||||
}
|
||||
|
||||
public int getGeosetAnimationId() {
|
||||
return this.geosetAnimationId;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
33
core/src/com/etheller/warsmash/viewer5/GenericNode.java
Normal file
33
core/src/com/etheller/warsmash/viewer5/GenericNode.java
Normal 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);
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
5
core/src/com/etheller/warsmash/viewer5/PathSolver.java
Normal file
5
core/src/com/etheller/warsmash/viewer5/PathSolver.java
Normal file
@ -0,0 +1,5 @@
|
||||
package com.etheller.warsmash.viewer5;
|
||||
|
||||
public interface PathSolver {
|
||||
SolvedPath solve(String src, Object solverParams);
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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" + //
|
||||
|
@ -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;
|
||||
|
||||
|
26
core/src/com/etheller/warsmash/viewer5/SolvedPath.java
Normal file
26
core/src/com/etheller/warsmash/viewer5/SolvedPath.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
42
core/src/com/etheller/warsmash/viewer5/Texture.java
Normal file
42
core/src/com/etheller/warsmash/viewer5/Texture.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.etheller.warsmash.viewer5.handlers;
|
||||
|
||||
public class EmitterObject {
|
||||
public interface EmitterObject {
|
||||
boolean ok();
|
||||
|
||||
int getGeometryEmitterType();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
150
core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java
Normal file
150
core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java
Normal 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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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" + //
|
||||
" }";
|
||||
}
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user