Add common pathing code and SimplePathing module
This commit is contained in:
43
src/main/java/com/replaymod/pathing/impl/KeyframeImpl.java
Normal file
43
src/main/java/com/replaymod/pathing/impl/KeyframeImpl.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class KeyframeImpl implements Keyframe {
|
||||
private final long time;
|
||||
private final Map<Property, Object> properties = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property) {
|
||||
return properties.containsKey(property) ? Optional.of((T) properties.get(property)) : Optional.<T>absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setValue(Property<T> property, T value) {
|
||||
properties.put(property, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProperty(Property property) {
|
||||
properties.remove(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Property> getProperties() {
|
||||
return Collections.unmodifiableSet(properties.keySet());
|
||||
}
|
||||
}
|
||||
175
src/main/java/com/replaymod/pathing/impl/PathImpl.java
Normal file
175
src/main/java/com/replaymod/pathing/impl/PathImpl.java
Normal file
@@ -0,0 +1,175 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.interpolation.InterpolationParameters;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PathImpl implements Path {
|
||||
private final Timeline timeline;
|
||||
private Map<Long, Keyframe> keyframes = new TreeMap<>();
|
||||
private List<PathSegment> segments = new LinkedList<>();
|
||||
|
||||
public PathImpl(Timeline timeline) {
|
||||
this.timeline = timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getTimeline() {
|
||||
return timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Keyframe> getKeyframes() {
|
||||
return Collections.unmodifiableCollection(keyframes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PathSegment> getSegments() {
|
||||
return Collections.unmodifiableCollection(segments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
update(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll() {
|
||||
update(false);
|
||||
}
|
||||
|
||||
private void update(boolean force) {
|
||||
Interpolator interpolator = null;
|
||||
Map<PropertyPart, InterpolationParameters> parameters = new HashMap<>();
|
||||
for (PathSegment segment : segments) {
|
||||
if (segment.getInterpolator() != interpolator) {
|
||||
interpolator = segment.getInterpolator();
|
||||
if (force || interpolator.isDirty()) {
|
||||
parameters = interpolator.bake(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property, long time) {
|
||||
PathSegment segment = getSegment(time);
|
||||
if (segment != null) {
|
||||
Interpolator interpolator = segment.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
if (interpolator.getKeyframeProperties().contains(property)) {
|
||||
return interpolator.getValue(property, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyframe insert(long time) {
|
||||
Keyframe keyframe = new KeyframeImpl(time);
|
||||
insert(keyframe);
|
||||
return keyframe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyframe getKeyframe(long time) {
|
||||
return keyframes.get(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Keyframe keyframe) {
|
||||
if (keyframes.containsKey(keyframe.getTime())) {
|
||||
throw new IllegalStateException("A keyframe at " + keyframe.getTime() + " already exists.");
|
||||
}
|
||||
keyframes.put(keyframe.getTime(), keyframe);
|
||||
|
||||
if (segments.isEmpty()) {
|
||||
if (keyframes.size() >= 2) {
|
||||
Iterator<Keyframe> iter = keyframes.values().iterator();
|
||||
segments.add(new PathSegmentImpl(iter.next(), iter.next()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ListIterator<PathSegment> iter = segments.listIterator();
|
||||
PathSegment next = iter.next();
|
||||
if (keyframe.getTime() < next.getStartKeyframe().getTime()) {
|
||||
iter.previous();
|
||||
iter.add(new PathSegmentImpl(keyframe, next.getStartKeyframe(), next.getInterpolator()));
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (next.getStartKeyframe().getTime() <= keyframe.getTime()
|
||||
&& next.getEndKeyframe().getTime() >= keyframe.getTime()) {
|
||||
iter.remove();
|
||||
next.setInterpolator(null);
|
||||
iter.add(new PathSegmentImpl(next.getStartKeyframe(), keyframe, next.getInterpolator()));
|
||||
iter.add(new PathSegmentImpl(keyframe, next.getEndKeyframe(), next.getInterpolator()));
|
||||
return;
|
||||
}
|
||||
if (iter.hasNext()) {
|
||||
next = iter.next();
|
||||
} else {
|
||||
iter.add(new PathSegmentImpl(next.getEndKeyframe(), keyframe, next.getInterpolator()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Keyframe keyframe, boolean useFirstInterpolator) {
|
||||
if (keyframes.get(keyframe.getTime()) != keyframe) {
|
||||
throw new IllegalArgumentException("The keyframe " + keyframe + " is not part of this path.");
|
||||
}
|
||||
keyframes.remove(keyframe.getTime());
|
||||
|
||||
if (segments.size() < 2) {
|
||||
for (PathSegment segment : segments) {
|
||||
segment.setInterpolator(null);
|
||||
}
|
||||
segments.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ListIterator<PathSegment> iter = segments.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
PathSegment next = iter.next();
|
||||
if (next.getEndKeyframe() == keyframe) {
|
||||
iter.remove();
|
||||
if (iter.hasNext()) {
|
||||
PathSegment next2 = iter.next();
|
||||
iter.remove();
|
||||
iter.add(new PathSegmentImpl(next.getStartKeyframe(), next2.getEndKeyframe(),
|
||||
(useFirstInterpolator ? next : next2).getInterpolator()));
|
||||
} else {
|
||||
next.setInterpolator(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (next.getStartKeyframe() == keyframe) {
|
||||
next.setInterpolator(null);
|
||||
iter.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("No segment for keyframe found!");
|
||||
}
|
||||
|
||||
private PathSegment getSegment(long time) {
|
||||
for (PathSegment segment : segments) {
|
||||
if (segment.getStartKeyframe().getTime() <= time && segment.getEndKeyframe().getTime() >= time) {
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class PathSegmentImpl implements PathSegment {
|
||||
private final Keyframe startKeyframe;
|
||||
private final Keyframe endKeyframe;
|
||||
private Interpolator interpolator;
|
||||
|
||||
public PathSegmentImpl(Keyframe startKeyframe, Keyframe endKeyframe, Interpolator interpolator) {
|
||||
this.startKeyframe = startKeyframe;
|
||||
this.endKeyframe = endKeyframe;
|
||||
setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
if (this.interpolator != null) {
|
||||
this.interpolator.removeSegment(this);
|
||||
}
|
||||
this.interpolator = interpolator;
|
||||
if (this.interpolator != null) {
|
||||
this.interpolator.addSegment(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/main/java/com/replaymod/pathing/impl/TimelineImpl.java
Normal file
101
src/main/java/com/replaymod/pathing/impl/TimelineImpl.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.change.Change;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class TimelineImpl implements Timeline {
|
||||
private final List<Path> paths = new ArrayList<>();
|
||||
private Map<String, Property> properties = new HashMap<>();
|
||||
private Deque<Change> undoStack = new ArrayDeque<>();
|
||||
private Deque<Change> redoStack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public List<Path> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path createPath() {
|
||||
Path path = new PathImpl(this);
|
||||
paths.add(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property, long time) {
|
||||
for (Path path : paths) {
|
||||
Optional<T> value = path.getValue(property, time);
|
||||
if (value.isPresent()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToGame(long time, ReplayHandler replayHandler) {
|
||||
for (Property<?> property : properties.values()) {
|
||||
applyToGame(time, replayHandler, property);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void applyToGame(long time, ReplayHandler replayHandler, Property<T> property) {
|
||||
Optional<T> value = getValue(property, time);
|
||||
if (value.isPresent()) {
|
||||
property.applyToGame(value.get(), replayHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerProperty(Property property) {
|
||||
String id = (property.getGroup() == null ? "" : property.getGroup().getId() + ":") + property.getId();
|
||||
properties.put(id, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty(String id) {
|
||||
return properties.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyChange(Change change) {
|
||||
change.apply(this);
|
||||
pushChange(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushChange(Change change) {
|
||||
undoStack.push(change);
|
||||
redoStack.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoLastChange() {
|
||||
Change change = undoStack.pop();
|
||||
change.undo(this);
|
||||
redoStack.push(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void redoLastChange() {
|
||||
Change change = redoStack.pop();
|
||||
change.apply(this);
|
||||
undoStack.push(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change peekUndoStack() {
|
||||
return undoStack.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change peekRedoStack() {
|
||||
return undoStack.peek();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user