Created AdvancedPositionKeyframeList, whose interpolation methods properly handle Spectator Keyframes using the SpectatorDataInterpolation
This commit is contained in:
@@ -62,6 +62,15 @@ public class AdvancedPosition extends Position {
|
|||||||
return new AdvancedPosition(position.x, position.y, position.z, pitch, yaw, roll);
|
return new AdvancedPosition(position.x, position.y, position.z, pitch, yaw, roll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void apply(AdvancedPosition toApply) {
|
||||||
|
this.x = toApply.getX();
|
||||||
|
this.y = toApply.getY();
|
||||||
|
this.z = toApply.getZ();
|
||||||
|
this.pitch = toApply.getPitch();
|
||||||
|
this.yaw = toApply.getYaw();
|
||||||
|
this.roll = toApply.getRoll();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AdvancedPosition newInstance() {
|
public AdvancedPosition newInstance() {
|
||||||
return new AdvancedPosition();
|
return new AdvancedPosition();
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package eu.crushedpixel.replaymod.interpolation;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
|
||||||
|
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||||
|
import eu.crushedpixel.replaymod.holders.SpectatorData;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class AdvancedPositionKeyframeList extends KeyframeList<AdvancedPosition> {
|
||||||
|
|
||||||
|
private Set<SpectatorDataInterpolation> spectatorDataInterpolators;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdvancedPosition getInterpolatedValueForPathPosition(float pathPosition, boolean linear) {
|
||||||
|
if(first() == null) return null;
|
||||||
|
if(size() == 1) return first().getValue();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
AdvancedPosition toApply = first().getValue().newInstance();
|
||||||
|
|
||||||
|
if(previousCallLinear != (Boolean)linear) {
|
||||||
|
recalculate(linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
int keyframeIndex = (int)(pathPosition/size());
|
||||||
|
float percentage = (pathPosition/size() - keyframeIndex)/(1f/size());
|
||||||
|
AdvancedPosition current = get(keyframeIndex).getValue();
|
||||||
|
|
||||||
|
SpectatorDataInterpolation dataInterpolation = null;
|
||||||
|
if(current instanceof SpectatorData) {
|
||||||
|
for(SpectatorDataInterpolation interpolation : spectatorDataInterpolators) {
|
||||||
|
if(interpolation.contains((SpectatorData)current)) {
|
||||||
|
dataInterpolation = interpolation;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataInterpolation == null) {
|
||||||
|
interpolation.applyPoint(pathPosition, toApply);
|
||||||
|
} else {
|
||||||
|
SpectatorData spectatorData = (SpectatorData)current;
|
||||||
|
int index = dataInterpolation.indexOf(spectatorData);
|
||||||
|
int size = dataInterpolation.size();
|
||||||
|
float one = 1f/size;
|
||||||
|
dataInterpolation.applyPoint((index/size)+(percentage*one), toApply);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toApply;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void recalculate(boolean linear) {
|
||||||
|
previousCallLinear = linear;
|
||||||
|
|
||||||
|
if(size() < 2) return;
|
||||||
|
|
||||||
|
interpolation = linear ? first().getValue().getLinearInterpolator() : first().getValue().getCubicInterpolator();
|
||||||
|
|
||||||
|
for(Keyframe<AdvancedPosition> keyframe : this) {
|
||||||
|
interpolation.addPoint(keyframe.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolation.prepare();
|
||||||
|
|
||||||
|
spectatorDataInterpolators = new HashSet<SpectatorDataInterpolation>();
|
||||||
|
|
||||||
|
//find subsequent Spectator Keyframes with the same Entity ID
|
||||||
|
ListIterator<Keyframe<AdvancedPosition>> iterator = this.listIterator();
|
||||||
|
while(iterator.hasNext()) {
|
||||||
|
Keyframe<AdvancedPosition> keyframe = iterator.next();
|
||||||
|
SpectatorDataInterpolation spectatorInterpolation = null;
|
||||||
|
|
||||||
|
boolean found = keyframe.getValue() instanceof SpectatorData;
|
||||||
|
|
||||||
|
while(found) {
|
||||||
|
if(spectatorInterpolation == null) {
|
||||||
|
spectatorInterpolation = new SpectatorDataInterpolation(linear);
|
||||||
|
}
|
||||||
|
spectatorInterpolation.addPoint((SpectatorData)keyframe.getValue(), keyframe.getRealTimestamp());
|
||||||
|
|
||||||
|
if(iterator.hasNext()) {
|
||||||
|
keyframe = iterator.next();
|
||||||
|
found = keyframe.getValue() instanceof SpectatorData;
|
||||||
|
} else {
|
||||||
|
found = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(spectatorInterpolation != null && spectatorInterpolation.size() > 1) {
|
||||||
|
spectatorInterpolation.prepare();
|
||||||
|
spectatorDataInterpolators.add(spectatorInterpolation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,11 @@ import java.util.List;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class KeyframeList<K extends KeyframeValue> extends ArrayList<Keyframe<K>> {
|
public class KeyframeList<K extends KeyframeValue> extends ArrayList<Keyframe<K>> {
|
||||||
|
|
||||||
private static final KeyframeComparator KEYFRAME_COMPARATOR = new KeyframeComparator();
|
protected static final KeyframeComparator KEYFRAME_COMPARATOR = new KeyframeComparator();
|
||||||
|
|
||||||
private Boolean previousCallLinear = null;
|
protected Boolean previousCallLinear = null;
|
||||||
|
|
||||||
private Interpolation<K> interpolation;
|
protected Interpolation<K> interpolation;
|
||||||
|
|
||||||
public KeyframeList(List<Keyframe<K>> initial) {
|
public KeyframeList(List<Keyframe<K>> initial) {
|
||||||
for(Keyframe<K> kf : initial) {
|
for(Keyframe<K> kf : initial) {
|
||||||
@@ -186,7 +186,7 @@ public class KeyframeList<K extends KeyframeValue> extends ArrayList<Keyframe<K>
|
|||||||
* @param timestamp The value to use
|
* @param timestamp The value to use
|
||||||
* @return A value between 0 and 1
|
* @return A value between 0 and 1
|
||||||
*/
|
*/
|
||||||
private float getPositionOnPath(int timestamp) {
|
protected float getPositionOnPath(int timestamp) {
|
||||||
Keyframe previousKeyframe = getPreviousKeyframe(timestamp, true);
|
Keyframe previousKeyframe = getPreviousKeyframe(timestamp, true);
|
||||||
Keyframe nextKeyframe = getNextKeyframe(timestamp, true);
|
Keyframe nextKeyframe = getNextKeyframe(timestamp, true);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package eu.crushedpixel.replaymod.interpolation;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
|
||||||
|
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||||
|
import eu.crushedpixel.replaymod.holders.SpectatorData;
|
||||||
|
import eu.crushedpixel.replaymod.holders.SpectatorDataThirdPersonInfo;
|
||||||
|
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SpectatorDataInterpolation object calculates Camera Paths between multiple Spectator Keyframes.
|
||||||
|
* It does not matter which SpectatingMethod these Keyframes have, the SpectatorDataInterpolation treats them specifically.
|
||||||
|
*/
|
||||||
|
public class SpectatorDataInterpolation {
|
||||||
|
|
||||||
|
private Integer entityID = null;
|
||||||
|
|
||||||
|
private List<Pair<Integer, SpectatorData>> points = new ArrayList<Pair<Integer, SpectatorData>>();
|
||||||
|
private KeyframeList<AdvancedPosition> underlyingKeyframes;
|
||||||
|
|
||||||
|
private final boolean linear;
|
||||||
|
|
||||||
|
public SpectatorDataInterpolation(boolean linear) {
|
||||||
|
this.linear = linear;
|
||||||
|
underlyingKeyframes = new KeyframeList<AdvancedPosition>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return points.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(SpectatorData spectatorData) {
|
||||||
|
return indexOf(spectatorData) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int indexOf(SpectatorData spectatorData) {
|
||||||
|
int i = 0;
|
||||||
|
for(Pair<Integer, SpectatorData> pair : points) {
|
||||||
|
if(pair.getValue().equals(spectatorData)) return i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prepare() {
|
||||||
|
//first, create an Interpolation instance to retreive a SpectatorDataThirdPersonInfo object for every
|
||||||
|
//position on the Spectator Path
|
||||||
|
Interpolation<SpectatorDataThirdPersonInfo> thirdPersonInfoInterpolation =
|
||||||
|
linear ? new GenericLinearInterpolation<SpectatorDataThirdPersonInfo>() :
|
||||||
|
new GenericSplineInterpolation<SpectatorDataThirdPersonInfo>();
|
||||||
|
|
||||||
|
double previousSmoothness = 0.5;
|
||||||
|
for(Pair<Integer, SpectatorData> pair : points) {
|
||||||
|
if(pair.getValue().getSpectatingMethod() == SpectatorData.SpectatingMethod.FIRST_PERSON) {
|
||||||
|
thirdPersonInfoInterpolation.addPoint(new SpectatorDataThirdPersonInfo(0, 0, 0, previousSmoothness));
|
||||||
|
} else {
|
||||||
|
thirdPersonInfoInterpolation.addPoint(pair.getValue().getThirdPersonInfo());
|
||||||
|
previousSmoothness = pair.getValue().getThirdPersonInfo().shoulderCamSmoothness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thirdPersonInfoInterpolation.prepare();
|
||||||
|
|
||||||
|
//feed the underlying interpolator with AdvancedPosition Keyframes that are derived from the Spectator Keyframes
|
||||||
|
int i = 0;
|
||||||
|
int size = points.size();
|
||||||
|
for(Pair<Integer, SpectatorData> pair : points) {
|
||||||
|
int timestamp = pair.getKey();
|
||||||
|
int currentTimestamp = timestamp;
|
||||||
|
int nextTimestamp = timestamp;
|
||||||
|
|
||||||
|
if(i+1 < points.size()) {
|
||||||
|
nextTimestamp = points.get(i+1).getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
int difference = nextTimestamp - timestamp;
|
||||||
|
|
||||||
|
int smoothness = 0;
|
||||||
|
while(currentTimestamp + smoothness < nextTimestamp) {
|
||||||
|
currentTimestamp += smoothness;
|
||||||
|
|
||||||
|
SpectatorDataThirdPersonInfo thirdPersonInfo = new SpectatorDataThirdPersonInfo();
|
||||||
|
float percentage = (float)i/size;
|
||||||
|
percentage += ((currentTimestamp-timestamp)/(float)difference) * 1f/size;
|
||||||
|
|
||||||
|
thirdPersonInfoInterpolation.applyPoint(percentage, thirdPersonInfo);
|
||||||
|
|
||||||
|
smoothness = (int)(thirdPersonInfo.shoulderCamSmoothness*1000);
|
||||||
|
|
||||||
|
//calculate the Position relative to the Entity
|
||||||
|
AdvancedPosition entityPosition = ReplayHandler.getEntityPositionTracker().getEntityPositionAtTimestamp(entityID, currentTimestamp);
|
||||||
|
if(entityPosition == null) continue;
|
||||||
|
|
||||||
|
//first, rotate the camera pitch and yaw according to the settings
|
||||||
|
entityPosition.setYaw(entityPosition.getYaw() + thirdPersonInfo.shoulderCamYawOffset);
|
||||||
|
entityPosition.setPitch(entityPosition.getPitch() + thirdPersonInfo.shoulderCamPitchOffset);
|
||||||
|
|
||||||
|
//next, move the camera point to fulfill the specified distance to the entity
|
||||||
|
entityPosition = entityPosition.getDestination(-1 * thirdPersonInfo.shoulderCamDistance);
|
||||||
|
underlyingKeyframes.add(new Keyframe<AdvancedPosition>(currentTimestamp, entityPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
underlyingKeyframes.recalculate(linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyPoint(float position, AdvancedPosition toEdit) {
|
||||||
|
int keyframeIndex = (int)position*points.size();
|
||||||
|
|
||||||
|
//the progress between this and the next keyframe (between 0 and 1)
|
||||||
|
float partial = (position - ((float)keyframeIndex/points.size())) / (1f/points.size());
|
||||||
|
|
||||||
|
Pair<Integer, SpectatorData> pair = points.get(keyframeIndex);
|
||||||
|
Pair<Integer, SpectatorData> next = keyframeIndex < points.size()-1 ? points.get(keyframeIndex+1) : null;
|
||||||
|
|
||||||
|
SpectatorData spectatorData = pair.getValue();
|
||||||
|
|
||||||
|
//decide whether the Entity is spectated in First Person Mode or not
|
||||||
|
boolean firstPerson = true;
|
||||||
|
|
||||||
|
//firstPerson is false if either the SpectatorKeyframe
|
||||||
|
//or the following SpectatorKeyframe are no FIRST_PERSON Keyframes
|
||||||
|
//it's only true if the position value is between two FIRST_PERSON Keyframes
|
||||||
|
if(spectatorData.getSpectatingMethod() != SpectatorData.SpectatingMethod.FIRST_PERSON) {
|
||||||
|
firstPerson = false;
|
||||||
|
} else if(next != null) {
|
||||||
|
if(next.getValue().getSpectatingMethod() != SpectatorData.SpectatingMethod.FIRST_PERSON) firstPerson = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(firstPerson) {
|
||||||
|
int firstTimestamp = pair.getKey();
|
||||||
|
int nextTimestamp = next == null ? pair.getKey() : next.getKey();
|
||||||
|
|
||||||
|
int diff = nextTimestamp - firstTimestamp;
|
||||||
|
int interpolatedTimestamp = firstTimestamp+(int)(partial*diff);
|
||||||
|
|
||||||
|
AdvancedPosition pos = ReplayHandler.getEntityPositionTracker().getEntityPositionAtTimestamp(entityID, interpolatedTimestamp);
|
||||||
|
if(pos != null)
|
||||||
|
toEdit.apply(pos);
|
||||||
|
} else {
|
||||||
|
toEdit.apply(underlyingKeyframes.getInterpolatedValueForPathPosition(position, linear));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPoint(SpectatorData spectatorData, int realTimestamp) {
|
||||||
|
if(entityID == null) entityID = spectatorData.getSpectatedEntityID();
|
||||||
|
else if(entityID != spectatorData.getSpectatedEntityID()) throw new IllegalArgumentException();
|
||||||
|
points.add(Pair.of(realTimestamp, spectatorData));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user