Add first person spectator keyframes to simplepathing

This commit is contained in:
johni0702
2016-08-01 22:50:48 +02:00
parent de6e05d0f8
commit 36c973d13a
8 changed files with 233 additions and 219 deletions

View File

@@ -50,7 +50,9 @@ public class CameraProperties extends AbstractPropertyGroup {
@Override
public void applyToGame(Triple<Double, Double, Double> value, @NonNull Object replayHandler) {
((ReplayHandler) replayHandler).getCameraEntity().setCameraPosition(value.getLeft(), value.getMiddle(), value.getRight());
ReplayHandler handler = ((ReplayHandler) replayHandler);
handler.spectateCamera();
handler.getCameraEntity().setCameraPosition(value.getLeft(), value.getMiddle(), value.getRight());
}
@Override
@@ -86,7 +88,9 @@ public class CameraProperties extends AbstractPropertyGroup {
@Override
public void applyToGame(Triple<Float, Float, Float> value, @NonNull Object replayHandler) {
((ReplayHandler) replayHandler).getCameraEntity().setCameraRotation(value.getLeft(), value.getMiddle(), value.getRight());
ReplayHandler handler = ((ReplayHandler) replayHandler);
handler.spectateCamera();
handler.getCameraEntity().setCameraRotation(value.getLeft(), value.getMiddle(), value.getRight());
}
@Override

View File

@@ -0,0 +1,51 @@
package com.replaymod.pathing.properties;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.replaymod.replay.ReplayHandler;
import com.replaymod.replaystudio.pathing.property.AbstractProperty;
import com.replaymod.replaystudio.pathing.property.PropertyPart;
import com.replaymod.replaystudio.pathing.property.PropertyParts;
import lombok.NonNull;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
/**
* Property for the camera spectating an entity.
*/
public class SpectatorProperty extends AbstractProperty<Integer> {
public static final SpectatorProperty PROPERTY = new SpectatorProperty();
public final PropertyPart<Integer> ENTITY_ID = new PropertyParts.ForInteger(this, false);
private SpectatorProperty() {
super("spectate", "replaymod.gui.playeroverview.spectate", null, -1);
}
@Override
public Collection<PropertyPart<Integer>> getParts() {
return Collections.singletonList(ENTITY_ID);
}
@Override
public void applyToGame(Integer value, @NonNull Object replayHandler) {
ReplayHandler handler = ((ReplayHandler) replayHandler);
World world = handler.getCameraEntity().getEntityWorld();
// Lookup entity by id, returns null if an entity with the id does not exists
Entity target = world.getEntityByID(value);
// Spectate entity, when called with null, returns to camera
handler.spectateEntity(target);
}
@Override
public void toJson(JsonWriter writer, Integer value) throws IOException {
writer.value(value);
}
@Override
public Integer fromJson(JsonReader reader) throws IOException {
return reader.nextInt();
}
}

View File

@@ -4,6 +4,7 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.replaymod.core.ReplayMod;
import com.replaymod.pathing.properties.CameraProperties;
import com.replaymod.pathing.properties.SpectatorProperty;
import com.replaymod.pathing.properties.TimestampProperty;
import com.replaymod.replay.events.ReplayOpenEvent;
import com.replaymod.replaystudio.pathing.PathingRegistry;
@@ -79,6 +80,7 @@ public class ReplayModSimplePathing implements PathingRegistry {
timeline.registerProperty(TimestampProperty.PROPERTY);
timeline.registerProperty(CameraProperties.POSITION);
timeline.registerProperty(CameraProperties.ROTATION);
timeline.registerProperty(SpectatorProperty.PROPERTY);
return timeline;
}
@@ -107,4 +109,8 @@ public class ReplayModSimplePathing implements PathingRegistry {
}
}
public ReplayMod getCore() {
return core;
}
}

View File

@@ -4,9 +4,11 @@ import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.replaymod.core.ReplayMod;
import com.replaymod.pathing.properties.CameraProperties;
import com.replaymod.pathing.properties.SpectatorProperty;
import com.replaymod.pathing.properties.TimestampProperty;
import com.replaymod.replaystudio.pathing.path.Keyframe;
import com.replaymod.replaystudio.pathing.path.Path;
import com.replaymod.replaystudio.pathing.path.PathSegment;
import com.replaymod.simplepathing.ReplayModSimplePathing;
import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline;
@@ -53,6 +55,9 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
? KEYFRAME_SIZE : 0);
int v = KEYFRAME_TEXTURE_Y;
if (keyframe.getValue(CameraProperties.POSITION).isPresent()) {
if (keyframe.getValue(SpectatorProperty.PROPERTY).isPresent()) {
v += 2 * KEYFRAME_SIZE;
}
renderer.drawTexturedRect(positonX, BORDER_TOP, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
}
if (keyframe.getValue(TimestampProperty.PROPERTY).isPresent()) {
@@ -62,6 +67,27 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
}
}
// Draw colored quads on spectator path segments
for (PathSegment segment : mod.getCurrentTimeline().getPaths().get(GuiPathing.POSITION_PATH).getSegments()) {
if (segment.getInterpolator() == null
|| !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) {
continue; // Not a spectator segment
}
long startFrameTime = segment.getStartKeyframe().getTime();
long endFrameTime = segment.getEndKeyframe().getTime();
if (startFrameTime >= endTime || endFrameTime <= startTime) {
continue; // Segment out of display range
}
double relativeStart = startFrameTime - startTime;
double relativeEnd = endFrameTime - startTime;
int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + KEYFRAME_SIZE / 2 + 1);
int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2);
if (startX < endX) {
renderer.drawRect(startX + 1, BORDER_TOP + 1, endX - startX - 2, KEYFRAME_SIZE - 2, 0xFF0088FF);
}
}
super.drawTimelineCursor(renderer, size);
}

View File

@@ -6,6 +6,7 @@ import com.replaymod.core.ReplayMod;
import com.replaymod.pathing.gui.GuiKeyframeRepository;
import com.replaymod.pathing.player.RealtimeTimelinePlayer;
import com.replaymod.pathing.properties.CameraProperties;
import com.replaymod.pathing.properties.SpectatorProperty;
import com.replaymod.pathing.properties.TimestampProperty;
import com.replaymod.render.gui.GuiRenderSettings;
import com.replaymod.replay.ReplayHandler;
@@ -19,18 +20,26 @@ import com.replaymod.replaystudio.pathing.path.Keyframe;
import com.replaymod.replaystudio.pathing.path.Path;
import com.replaymod.replaystudio.pathing.path.PathSegment;
import com.replaymod.replaystudio.pathing.path.Timeline;
import com.replaymod.replaystudio.util.EntityPositionTracker;
import com.replaymod.replaystudio.util.Location;
import com.replaymod.simplepathing.ReplayModSimplePathing;
import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.element.*;
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import net.minecraft.entity.Entity;
import net.minecraftforge.fml.common.Loader;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
@@ -38,6 +47,9 @@ import org.lwjgl.util.ReadablePoint;
import org.lwjgl.util.WritablePoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
@@ -47,6 +59,8 @@ public class GuiPathing {
public static final int TIME_PATH = 0;
public static final int POSITION_PATH = 1;
private static final Logger logger = LogManager.getLogger();
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20)
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
@@ -174,6 +188,8 @@ public class GuiPathing {
private final ReplayHandler replayHandler;
private final RealtimeTimelinePlayer player;
private EntityPositionTracker entityTracker;
public GuiPathing(final ReplayMod core, final ReplayModSimplePathing mod, final ReplayHandler replayHandler) {
this.mod = mod;
this.replayHandler = replayHandler;
@@ -315,6 +331,11 @@ public class GuiPathing {
* @param isTime {@code true} for the time property button, {@code false} for the place property button
*/
private void updateKeyframe(final boolean isTime) {
if (entityTracker == null) {
loadEntityTracker(() -> updateKeyframe(isTime));
return;
}
int time = timeline.getCursorPosition();
Timeline timeline = mod.getCurrentTimeline();
Path path = timeline.getPaths().get(isTime ? TIME_PATH : POSITION_PATH);
@@ -339,31 +360,64 @@ public class GuiPathing {
CameraEntity camera = replayHandler.getCameraEntity();
builder.setValue(CameraProperties.POSITION, Triple.of(camera.posX, camera.posY, camera.posZ));
builder.setValue(CameraProperties.ROTATION, Triple.of(camera.rotationYaw, camera.rotationPitch, camera.roll));
if (!replayHandler.isCameraView()) {
Entity spectated = replayHandler.getOverlay().getMinecraft().getRenderViewEntity();
builder.setValue(SpectatorProperty.PROPERTY, spectated.getEntityId());
}
}
UpdateKeyframeProperties updateChange = builder.done();
updateChange.apply(timeline);
// If this new keyframe formed the first segment, then create and set the appropriate interpolator
if (path.getSegments().size() == 1) {
change = CombinedChange.createFromApplied(change, updateChange);
// If this new keyframe formed the first segment of the time path
if (isTime && path.getSegments().size() == 1) {
PathSegment segment = path.getSegments().iterator().next();
Interpolator interpolator;
if (isTime) {
interpolator = new LinearInterpolator();
interpolator.registerProperty(TimestampProperty.PROPERTY);
} else {
interpolator = new CubicSplineInterpolator();
interpolator.registerProperty(CameraProperties.POSITION);
interpolator.registerProperty(CameraProperties.ROTATION);
}
Interpolator interpolator = new LinearInterpolator();
interpolator.registerProperty(TimestampProperty.PROPERTY);
SetInterpolator setInterpolator = SetInterpolator.create(segment, interpolator);
setInterpolator.apply(timeline);
timeline.pushChange(CombinedChange.createFromApplied(change, updateChange, setInterpolator));
} else {
timeline.pushChange(CombinedChange.createFromApplied(change, updateChange));
change = CombinedChange.createFromApplied(change, setInterpolator);
}
} else {
timeline.pushChange(change);
}
// Update interpolators for spectator keyframes
// while this is overkill, it is far simpler than updating differently for every possible case
if (!isTime) {
Interpolator interpolator = null;
boolean isSpectatorInterpolator = false;
for (PathSegment segment : path.getSegments()) {
if (segment.getStartKeyframe().getValue(SpectatorProperty.PROPERTY).isPresent()
&& segment.getEndKeyframe().getValue(SpectatorProperty.PROPERTY).isPresent()) {
// Spectator segment
if (!isSpectatorInterpolator) {
isSpectatorInterpolator = true;
interpolator = new LinearInterpolator();
interpolator.registerProperty(SpectatorProperty.PROPERTY);
}
SetInterpolator setInterpolator = SetInterpolator.create(segment, interpolator);
setInterpolator.apply(timeline);
change = CombinedChange.createFromApplied(change, setInterpolator);
} else {
// Normal segment
if (isSpectatorInterpolator || interpolator == null) {
isSpectatorInterpolator = false;
interpolator = new CubicSplineInterpolator();
interpolator.registerProperty(CameraProperties.POSITION);
interpolator.registerProperty(CameraProperties.ROTATION);
}
SetInterpolator setInterpolator = SetInterpolator.create(segment, interpolator);
setInterpolator.apply(timeline);
change = CombinedChange.createFromApplied(change, setInterpolator);
}
}
}
Change specPosUpdate = updateSpectatorPositions();
specPosUpdate.apply(timeline);
change = CombinedChange.createFromApplied(change, specPosUpdate);
timeline.pushChange(change);
if (isTime) {
mod.setSelectedTimeKeyframe(keyframe);
} else {
@@ -371,7 +425,78 @@ public class GuiPathing {
}
}
private Change updateSpectatorPositions() {
List<Change> changes = new ArrayList<>();
Path positionPath = mod.getCurrentTimeline().getPaths().get(POSITION_PATH);
Path timePath = mod.getCurrentTimeline().getPaths().get(TIME_PATH);
timePath.updateAll();
for (Keyframe keyframe : positionPath.getKeyframes()) {
Optional<Integer> spectator = keyframe.getValue(SpectatorProperty.PROPERTY);
if (spectator.isPresent()) {
Optional<Integer> time = timePath.getValue(TimestampProperty.PROPERTY, keyframe.getTime());
if (!time.isPresent()) {
continue; // No time keyframes set at this video time, cannot determine replay time
}
Location expected = entityTracker.getEntityPositionAtTimestamp(spectator.get(), time.get());
if (expected == null) {
continue; // We don't have any data on this entity for some reason
}
Triple<Double, Double, Double> pos = keyframe.getValue(CameraProperties.POSITION).orElse(Triple.of(0D, 0D, 0D));
Triple<Float, Float, Float> rot = keyframe.getValue(CameraProperties.ROTATION).orElse(Triple.of(0F, 0F, 0F));
Location actual = new Location(pos.getLeft(), pos.getMiddle(), pos.getRight(), rot.getLeft(), rot.getRight());
if (!expected.equals(actual)) {
changes.add(UpdateKeyframeProperties.create(positionPath, keyframe)
.setValue(CameraProperties.POSITION, Triple.of(expected.getX(), expected.getY(), expected.getZ()))
.setValue(CameraProperties.ROTATION, Triple.of(expected.getYaw(), expected.getPitch(), 0f)).done()
);
}
}
}
return CombinedChange.create(changes.toArray(new Change[changes.size()]));
}
private void loadEntityTracker(Runnable runnable) {
LoadEntityTrackerPopup popup = new LoadEntityTrackerPopup(replayHandler.getOverlay());
new Thread(() -> {
EntityPositionTracker tracker = new EntityPositionTracker(replayHandler.getReplayFile());
try {
tracker.load(c -> popup.progressBar.setProgress((float)(double) c));
} catch (IOException e) {
logger.error("Loading entity tracker:", e);
mod.getCore().runLater(() -> {
mod.getCore().printWarningToChat("Error loading entity tracker: %s", e.getLocalizedMessage());
popup.close();
});
}
entityTracker = tracker;
mod.getCore().runLater(() -> {
popup.close();
runnable.run();
});
}).start();
}
public ReplayModSimplePathing getMod() {
return mod;
}
private class LoadEntityTrackerPopup extends AbstractGuiPopup<LoadEntityTrackerPopup> {
private final GuiProgressBar progressBar = new GuiProgressBar(popup).setSize(300, 20)
.setI18nLabel("replaymod.gui.loadentitytracker");
public LoadEntityTrackerPopup(GuiContainer container) {
super(container);
open();
}
@Override
public void close() {
super.close();
}
@Override
protected LoadEntityTrackerPopup getThis() {
return this;
}
}
}