mirror of
https://github.com/Retera/WarsmashModEngine.git
synced 2022-07-31 17:38:59 +02:00
Working towards mdx handler
This commit is contained in:
parent
eb49c33eef
commit
6acbd13f27
@ -192,4 +192,29 @@ public class Layer extends AnimatedObject {
|
||||
public long getByteLength() {
|
||||
return 28 + super.getByteLength();
|
||||
}
|
||||
|
||||
public FilterMode getFilterMode() {
|
||||
return filterMode;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public int getTextureId() {
|
||||
return textureId;
|
||||
}
|
||||
|
||||
public int getTextureAnimationId() {
|
||||
return textureAnimationId;
|
||||
}
|
||||
|
||||
public long getCoordId() {
|
||||
return coordId;
|
||||
}
|
||||
|
||||
public float getAlpha() {
|
||||
return alpha;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,9 +23,12 @@ import com.google.common.io.LittleEndianDataOutputStream;
|
||||
* and text MDL file formats.
|
||||
*/
|
||||
public class MdlxModel {
|
||||
// Below, these can't call a function on a string to make their value because
|
||||
// switch/case statements require the value to be compile-time defined in order
|
||||
// to be legal, and it appears to only allow basic binary operators for that.
|
||||
// Below, these can't call a function on a string to make their value
|
||||
// because
|
||||
// switch/case statements require the value to be compile-time defined in
|
||||
// order
|
||||
// to be legal, and it appears to only allow basic binary operators for
|
||||
// that.
|
||||
// I would love a clearer way to just type 'MDLX' in a character constant in
|
||||
// Java for this
|
||||
private static final int MDLX = ('M' << 24) | ('D' << 16) | ('L' << 8) | ('X');// War3ID.fromString("MDLX").getValue();
|
||||
@ -52,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}
|
||||
*/
|
||||
@ -205,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();
|
||||
@ -456,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()) {
|
||||
@ -639,4 +642,12 @@ public class MdlxModel {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public List<Long> getGlobalSequences() {
|
||||
return globalSequences;
|
||||
}
|
||||
|
||||
public List<Sequence> getSequences() {
|
||||
return sequences;
|
||||
}
|
||||
}
|
||||
|
@ -101,4 +101,8 @@ public class Sequence implements MdlxBlock {
|
||||
this.extent.writeMdl(stream);
|
||||
stream.endBlock();
|
||||
}
|
||||
|
||||
public long[] getInterval() {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
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;
|
||||
@ -96,4 +97,28 @@ public class FloatArrayKeyFrame implements KeyFrame {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ 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;
|
||||
|
||||
@ -90,4 +91,26 @@ public class FloatKeyFrame implements KeyFrame {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,10 @@ public interface KeyFrame {
|
||||
void writeMdl(MdlTokenOutputStream stream, InterpolationType interpolationType) throws IOException;
|
||||
|
||||
long getByteLength(InterpolationType interpolationType);
|
||||
|
||||
long getTime();
|
||||
|
||||
boolean matchingValue(KeyFrame other);
|
||||
|
||||
KeyFrame clone(long time);
|
||||
}
|
||||
|
@ -148,4 +148,16 @@ public abstract class Timeline implements Chunk {
|
||||
protected abstract KeyFrame newKeyFrame();
|
||||
|
||||
protected abstract int size();
|
||||
|
||||
public int getGlobalSequenceId() {
|
||||
return globalSequenceId;
|
||||
}
|
||||
|
||||
public List<KeyFrame> getKeyFrames() {
|
||||
return keyFrames;
|
||||
}
|
||||
|
||||
public InterpolationType getInterpolationType() {
|
||||
return interpolationType;
|
||||
}
|
||||
}
|
||||
|
@ -90,4 +90,26 @@ public class UInt32KeyFrame implements KeyFrame {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
72
core/src/com/etheller/warsmash/util/Interpolator.java
Normal file
72
core/src/com/etheller/warsmash/util/Interpolator.java
Normal file
@ -0,0 +1,72 @@
|
||||
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) {
|
||||
switch (type) {
|
||||
case 0: {
|
||||
out[0] = a[0];
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
out[0] = RenderMathUtils.lerp(a[0], d[0], t);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
out[0] = RenderMathUtils.hermite(a[0], b[0], c[0], d[0], t);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
out[0] = RenderMathUtils.bezier(a[0], b[0], c[0], d[0], t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
out[0] = RenderMathUtils.lerp(a[0], d[0], t);
|
||||
out[1] = RenderMathUtils.lerp(a[1], d[1], t);
|
||||
out[2] = RenderMathUtils.lerp(a[2], d[2], t);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
out[0] = RenderMathUtils.hermite(a[0], b[0], c[0], d[0], t);
|
||||
out[1] = RenderMathUtils.hermite(a[1], b[1], c[1], d[1], t);
|
||||
out[2] = RenderMathUtils.hermite(a[2], b[2], c[2], d[2], t);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
out[0] = RenderMathUtils.bezier(a[0], b[0], c[0], d[0], t);
|
||||
out[1] = RenderMathUtils.bezier(a[1], b[1], c[1], d[1], t);
|
||||
out[2] = RenderMathUtils.bezier(a[2], b[2], c[2], d[2], t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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: {
|
||||
System.arraycopy(a, 0, out, 0, a.length);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
RenderMathUtils.slerp(out, a, d, t);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
case 3: {
|
||||
RenderMathUtils.sqlerp(out, a, b, c, d, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -101,12 +101,14 @@ public enum RenderMathUtils {
|
||||
}
|
||||
|
||||
public static void mul(final Matrix4 dest, final Matrix4 left, final Matrix4 right) {
|
||||
dest.set(left); // TODO better performance here, remove the extra copying
|
||||
dest.set(left); // TODO better performance here, remove the extra
|
||||
// copying
|
||||
dest.mul(right);
|
||||
}
|
||||
|
||||
public static void mul(final Quaternion dest, final Quaternion left, final Quaternion right) {
|
||||
dest.set(left); // TODO better performance here, remove the extra copying
|
||||
dest.set(left); // TODO better performance here, remove the extra
|
||||
// copying
|
||||
dest.mul(right);
|
||||
}
|
||||
|
||||
@ -352,4 +354,90 @@ public enum RenderMathUtils {
|
||||
public static float distanceToPlane3(final Vector4 plane, final float px, final float py, final float pz) {
|
||||
return (plane.x * px) + (plane.y * py) + (plane.z * pz) + plane.w;
|
||||
}
|
||||
|
||||
public static float randomInRange(final float a, final float b) {
|
||||
return (float) (a + Math.random() * (b - a));
|
||||
}
|
||||
|
||||
public static float clamp(final float x, final float minVal, final float maxVal) {
|
||||
return Math.min(Math.max(x, minVal), maxVal);
|
||||
}
|
||||
|
||||
public static float lerp(final float a, final float b, final float t) {
|
||||
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 factor3 = factorTimes2 * (t - 1);
|
||||
final float factor4 = factorTimes2 * (3 - 2 * t);
|
||||
return (a * factor1) + (b * factor2) + (c * factor3) + (d * factor4);
|
||||
}
|
||||
|
||||
public static float bezier(final float a, final float b, final float c, final float d, final float t) {
|
||||
final float invt = 1 - t;
|
||||
final float factorTimes2 = t * t;
|
||||
final float inverseFactorTimesTwo = invt * invt;
|
||||
final float factor1 = inverseFactorTimesTwo * invt;
|
||||
final float factor2 = 3 * t * inverseFactorTimesTwo;
|
||||
final float factor3 = 3 * factorTimes2 * invt;
|
||||
final float factor4 = factorTimes2 * t;
|
||||
|
||||
return (a * factor1) + (b * factor2) + (c * factor3) + (d * factor4);
|
||||
}
|
||||
|
||||
public static final float EPSILON = 0.000001f;
|
||||
|
||||
public static float[] slerp(final float[] out, final float[] a, final float[] b, final float t) {
|
||||
final float ax = a[0], ay = a[1], az = a[2], aw = a[3];
|
||||
float bx = b[0], by = b[1], bz = b[2], bw = b[3];
|
||||
|
||||
float omega, cosom, sinom, scale0, scale1;
|
||||
|
||||
// calc cosine
|
||||
cosom = ax * bx + ay * by + az * bz + aw * bw;
|
||||
// adjust signs (if necessary)
|
||||
if (cosom < 0.0) {
|
||||
cosom = -cosom;
|
||||
bx = -bx;
|
||||
by = -by;
|
||||
bz = -bz;
|
||||
bw = -bw;
|
||||
}
|
||||
// calculate coefficients
|
||||
if ((1.0 - cosom) > EPSILON) {
|
||||
// standard case (slerp)
|
||||
omega = (float) Math.acos(cosom);
|
||||
sinom = (float) Math.sin(omega);
|
||||
scale0 = (float) (Math.sin((1.0 - t) * omega) / sinom);
|
||||
scale1 = (float) (Math.sin(t * omega) / sinom);
|
||||
}
|
||||
else {
|
||||
// "from" and "to" quaternions are very close
|
||||
// ... so we can do a linear interpolation
|
||||
scale0 = 1.0f - t;
|
||||
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;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static final float[] sqlerpHeap1 = new float[4];
|
||||
private static final float[] sqlerpHeap2 = new float[4];
|
||||
|
||||
public static float[] sqlerp(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d,
|
||||
final float t) {
|
||||
slerp(sqlerpHeap1, a, d, t);
|
||||
slerp(sqlerpHeap2, b, c, t);
|
||||
slerp(out, sqlerpHeap1, sqlerpHeap2, 2 * t * (1 - t));
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,10 @@ public abstract class ModelView {
|
||||
@Override
|
||||
public abstract boolean equals(Object view);
|
||||
|
||||
// public boo
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
// public boo
|
||||
public void addSceneData(final ModelInstance instance, final Scene scene) {
|
||||
if (this.model.ok && (scene != null)) {
|
||||
SceneData data = this.sceneData.get(scene);
|
||||
|
@ -0,0 +1,6 @@
|
||||
package com.etheller.warsmash.viewer5.handlers.mdx;
|
||||
|
||||
public class AnimatedObject {
|
||||
public Model model;
|
||||
public
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.etheller.warsmash.viewer5.handlers.mdx;
|
||||
|
||||
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
|
||||
|
||||
public class Layer {
|
||||
public Model model;
|
||||
public com.etheller.warsmash.parsers.mdlx.Layer layer;
|
||||
public int layerId;
|
||||
public int priorityPlane;
|
||||
|
||||
public int filterMode;
|
||||
public int textureId;
|
||||
public int coordId;
|
||||
public float alpha;
|
||||
|
||||
public Layer(final Model model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId,
|
||||
final int priorityPlane) {
|
||||
super(model, layer);
|
||||
this.model = model;
|
||||
this.layer = layer;
|
||||
this.layerId = layerId;
|
||||
this.priorityPlane = priorityPlane;
|
||||
|
||||
final FilterMode filterMode2 = layer.getFilterMode();
|
||||
this.filterMode = filterMode2.ordinal();
|
||||
this.textureId = layer.getTextureId();
|
||||
// this.coo
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.etheller.warsmash.viewer5.handlers.mdx;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Material {
|
||||
public final Model model;
|
||||
public final String shader;
|
||||
public final List<Layer> layers;
|
||||
|
||||
public Material(final Model model, final String shader, final List<Layer> layers) {
|
||||
this.model = model;
|
||||
this.shader = shader;
|
||||
this.layers = layers;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
}
|
130
core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java
Normal file
130
core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java
Normal file
@ -0,0 +1,130 @@
|
||||
package com.etheller.warsmash.viewer5.handlers.mdx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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 int interpolationType;
|
||||
public War3ID name;
|
||||
public float[] defval;
|
||||
public SdSequence<TYPE> globalSequence;
|
||||
public List<SdSequence<TYPE>> sequences;
|
||||
|
||||
public static Map<War3ID, Integer> forcedInterpMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
forcedInterpMap.put(War3ID.fromString("KLAV"), 0);
|
||||
forcedInterpMap.put(War3ID.fromString("KATV"), 0);
|
||||
forcedInterpMap.put(War3ID.fromString("KPEV"), 0);
|
||||
forcedInterpMap.put(War3ID.fromString("KP2V"), 0);
|
||||
forcedInterpMap.put(War3ID.fromString("KRVS"), 0);
|
||||
}
|
||||
|
||||
public static Map<War3ID, float[]> defVals = new HashMap<>();
|
||||
|
||||
static {
|
||||
// LAYS
|
||||
defVals.put(War3ID.fromString("KMTF"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KMTA"), new float[] { 1 });
|
||||
// TXAN
|
||||
defVals.put(War3ID.fromString("KTAT"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KTAR"), new float[] { 0, 0, 0, 1 });
|
||||
defVals.put(War3ID.fromString("KTAS"), new float[] { 1, 1, 1 });
|
||||
// GEOA
|
||||
defVals.put(War3ID.fromString("KGAO"), new float[] { 1 });
|
||||
defVals.put(War3ID.fromString("KGAC"), new float[] { 0, 0, 0 });
|
||||
// LITE
|
||||
defVals.put(War3ID.fromString("KLAS"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KLAE"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KLAC"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KLAI"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KLBI"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KLBC"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KLAV"), new float[] { 1 });
|
||||
// ATCH
|
||||
defVals.put(War3ID.fromString("KATV"), new float[] { 1 });
|
||||
// PREM
|
||||
defVals.put(War3ID.fromString("KPEE"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KPEG"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KPLN"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KPLT"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KPEL"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KPES"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KPEV"), new float[] { 1 });
|
||||
// PRE2
|
||||
defVals.put(War3ID.fromString("KP2S"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2R"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2L"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2G"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2E"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2N"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2W"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KP2V"), new float[] { 1 });
|
||||
// RIBB
|
||||
defVals.put(War3ID.fromString("KRHA"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KRHB"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KRAL"), new float[] { 1 });
|
||||
defVals.put(War3ID.fromString("KRCO"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KRTX"), new float[] { 0 });
|
||||
defVals.put(War3ID.fromString("KRVS"), new float[] { 1 });
|
||||
// CAMS
|
||||
defVals.put(War3ID.fromString("KCTR"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KTTR"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KCRL"), new float[] { 0 });
|
||||
// NODE
|
||||
defVals.put(War3ID.fromString("KGTR"), new float[] { 0, 0, 0 });
|
||||
defVals.put(War3ID.fromString("KGRT"), new float[] { 0, 0, 0, 1 });
|
||||
defVals.put(War3ID.fromString("KGSC"), new float[] { 1, 1, 1 });
|
||||
|
||||
}
|
||||
|
||||
public Sd(final MdlxModel model, final Timeline 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.globalSequence = null;
|
||||
this.sequences = new ArrayList<>();
|
||||
|
||||
// Allow to force an interpolation type.
|
||||
// The game seems to do this with visibility tracks, where the type is
|
||||
// forced to None.
|
||||
// It came up as a bug report by a user who used the wrong interpolation
|
||||
// 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);
|
||||
}
|
||||
else {
|
||||
for (final Sequence sequence : model.getSequences()) {
|
||||
final long[] interval = sequence.getInterval();
|
||||
this.sequences.add(newSequenceTyped(this, interval[0], interval[1], keyFrames, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue(final TYPE out, final ModelInstance instance) {
|
||||
if(this.globalSequence != null) {
|
||||
return this.globalSequence
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract SdSequence<TYPE> newSequenceTyped(final Sd<TYPE> parent, final long start, final long end,
|
||||
final List<KeyFrame> keyframes, final boolean isGlobalSequence);
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package com.etheller.warsmash.viewer5.handlers.mdx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame;
|
||||
import com.etheller.warsmash.util.RenderMathUtils;
|
||||
|
||||
public abstract 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 SdSequence(final Sd<TYPE> sd, final long start, final long end, final List<KeyFrame> keyframes,
|
||||
final boolean isGlobalSequence) {
|
||||
final TYPE defval = convertDefaultValue(sd.defval);
|
||||
|
||||
this.sd = sd;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.keyframes = new ArrayList<>();
|
||||
|
||||
// When using a global sequence, where the first key is outside of the
|
||||
// sequence's length, it becomes its constant value.
|
||||
// When having one key in the sequence's range, and one key outside of
|
||||
// it, results seem to be non-deterministic.
|
||||
// Sometimes the second key is used too, sometimes not.
|
||||
// It also differs depending where the model is viewed - the WE
|
||||
// previewer, the WE itself, or the game.
|
||||
// All three show different results, none of them make sense.
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
if (frame >= start && frame <= end) {
|
||||
this.keyframes.add(keyFrame);
|
||||
}
|
||||
}
|
||||
|
||||
final int keyframeCount = this.keyframes.size();
|
||||
|
||||
if (keyframeCount == 0) {
|
||||
// if there are no keys, use the default value directly.
|
||||
this.constant = true;
|
||||
this.keyframes.add(createKeyFrame(start, 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);
|
||||
|
||||
// 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)) {
|
||||
allFramesMatch = false;
|
||||
}
|
||||
}
|
||||
this.constant = allFramesMatch;
|
||||
|
||||
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 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected abstract void set(TYPE out, KeyFrame frameForValue);
|
||||
|
||||
protected abstract TYPE convertDefaultValue(float[] defaultValue);
|
||||
|
||||
protected abstract KeyFrame createKeyFrame(long time, TYPE value);
|
||||
|
||||
protected abstract void interpolate(TYPE out, KeyFrame a, KeyFrame b, float t);
|
||||
}
|
Loading…
Reference in New Issue
Block a user