From 45f64faa774744938696d17746d72c5d495f3cd8 Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Sun, 12 Jul 2015 16:21:11 +0200 Subject: [PATCH] Created CustomObjectRepository which can be serialized by Gson, storing all CustomImageObjects created CustomObjectRepository is now being saved to and loaded from the ReplayFile The CustomObjectRenderer uses the GuiObjectManager's timeline cursor position as interpolation timestamp for the CustomImageObjects which are drawn if a GuiObjectManager is open (instant preview) KeyframeList's add() method now removes Keyframes with the same timestamp before adding the new Keyframe Transformations class now holds default values to prevent NullPointerExceptions in methods calling getTransformationForTimestamp() Added Javadoc to Transformation POJO Created NumberValueChangeListener which can be applied to GuiNumberInput objects Made step size customizable in GuiDraggingNumberInput Changing a ReplayImageAssets image now notifies the CustomImageObjects linked to that asset Modified RoundUtils to provide a round() method which uses the DecimalFormat class instead of Math.round which is unstable --- .../replaymod/assets/CustomImageObject.java | 21 ++- .../assets/CustomObjectRepository.java | 18 ++ .../replaymod/assets/ReplayImageAsset.java | 7 + .../replaymod/gui/GuiObjectManager.java | 167 ++++++++++++++++-- .../gui/elements/GuiDraggingNumberInput.java | 18 +- .../replaymod/gui/elements/GuiEntryList.java | 2 +- .../gui/elements/GuiNumberInput.java | 30 +++- .../listeners/NumberValueChangeListener.java | 7 + .../replaymod/holders/Transformation.java | 20 +++ .../replaymod/holders/Transformations.java | 28 ++- .../replaymod/interpolation/KeyframeList.java | 10 ++ .../renderer/CustomObjectRenderer.java | 63 ++++--- .../replaymod/replay/ReplayHandler.java | 16 +- .../replaymod/utils/ReplayFile.java | 27 ++- .../replaymod/utils/ReplayFileIO.java | 5 + .../replaymod/utils/RoundUtils.java | 18 +- .../assets/replaymod/lang/en_US.lang | 1 + 17 files changed, 393 insertions(+), 65 deletions(-) create mode 100644 src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java diff --git a/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java b/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java index 5792fc7a..d41dea3d 100644 --- a/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java +++ b/src/main/java/eu/crushedpixel/replaymod/assets/CustomImageObject.java @@ -27,8 +27,8 @@ public class CustomImageObject implements GuiEntryListEntry { @Getter @Setter private float width, height; - private ResourceLocation resourceLocation; - private DynamicTexture dynamicTexture; + private transient ResourceLocation resourceLocation; + private transient DynamicTexture dynamicTexture; @Getter private float textureWidth, textureHeight; @@ -38,6 +38,7 @@ public class CustomImageObject implements GuiEntryListEntry { ReplayAsset asset = ReplayHandler.getAssetRepository().getAssetByUUID(assetUUID); if(asset instanceof ReplayImageAsset) { + this.linkedAsset = assetUUID; setImage(((ReplayImageAsset)asset).getObject()); } else if(asset != null) { throw new UnsupportedOperationException("A CustomImageObject requires a ReplayImageAsset"); @@ -63,16 +64,30 @@ public class CustomImageObject implements GuiEntryListEntry { this.setWidth(w); this.setHeight(h); + resourceLocation = new ResourceLocation(linkedAsset.toString()); + Minecraft.getMinecraft().addScheduledTask(new Runnable() { @Override public void run() { - resourceLocation = new ResourceLocation("customImages/"+linkedAsset.toString()); dynamicTexture = new DynamicTexture(bufferedImage); + + ResourceHelper.freeResource(resourceLocation); } }); } public ResourceLocation getResourceLocation() { + if(resourceLocation == null) { + ReplayAsset asset = ReplayHandler.getAssetRepository().getAssetByUUID(linkedAsset); + if(asset instanceof ReplayImageAsset) { + try { + setImage(((ReplayImageAsset) asset).getObject()); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + if(!ResourceHelper.isRegistered(resourceLocation)) { ResourceHelper.registerResource(resourceLocation); Minecraft.getMinecraft().getTextureManager().loadTexture(resourceLocation, dynamicTexture); diff --git a/src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java b/src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java new file mode 100644 index 00000000..22856e4d --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/assets/CustomObjectRepository.java @@ -0,0 +1,18 @@ +package eu.crushedpixel.replaymod.assets; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.ArrayList; + +@Data +@AllArgsConstructor +public class CustomObjectRepository { + + public CustomObjectRepository() { + this.objects = new ArrayList(); + } + + private ArrayList objects; + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java b/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java index 501a6f90..df3e3f78 100644 --- a/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java +++ b/src/main/java/eu/crushedpixel/replaymod/assets/ReplayImageAsset.java @@ -1,6 +1,7 @@ package eu.crushedpixel.replaymod.assets; import eu.crushedpixel.replaymod.registry.ResourceHelper; +import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.utils.BoundingUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; @@ -46,6 +47,12 @@ public class ReplayImageAsset implements ReplayAsset { public void loadFromStream(InputStream inputStream) throws IOException { this.object = ImageIO.read(inputStream); ResourceHelper.freeResource(previewResource); + + for(CustomImageObject object : ReplayHandler.getCustomImageObjects()) { + if(object.getLinkedAsset().equals(ReplayHandler.getAssetRepository().getUUIDForAsset(this))) { + object.setImage(this.object); + } + } } @Override diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java index d87b7b5f..e0cd9ec5 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiObjectManager.java @@ -1,8 +1,11 @@ package eu.crushedpixel.replaymod.gui; +import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.assets.CustomImageObject; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.assets.ReplayAsset; import eu.crushedpixel.replaymod.gui.elements.*; +import eu.crushedpixel.replaymod.gui.elements.listeners.NumberValueChangeListener; import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener; import eu.crushedpixel.replaymod.gui.elements.timelines.GuiTimeline; import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; @@ -10,6 +13,8 @@ import eu.crushedpixel.replaymod.holders.*; import eu.crushedpixel.replaymod.interpolation.KeyframeList; import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.utils.MouseUtils; +import eu.crushedpixel.replaymod.utils.ReplayFile; +import eu.crushedpixel.replaymod.utils.ReplayFileIO; import lombok.Getter; import lombok.Setter; import net.minecraft.client.Minecraft; @@ -19,10 +24,14 @@ import net.minecraft.client.resources.I18n; import org.lwjgl.util.Point; import java.awt.*; +import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.UUID; +import org.lwjgl.util.Point; + public class GuiObjectManager extends GuiScreen { private boolean initialized = false; @@ -37,11 +46,15 @@ public class GuiObjectManager extends GuiScreen { private GuiDraggingNumberInput anchorXInput, anchorYInput, anchorZInput; private GuiDraggingNumberInput positionXInput, positionYInput, positionZInput; - private GuiDraggingNumberInput scaleXInput, scaleYInput, scaleZInput; private GuiDraggingNumberInput orientationXInput, orientationYInput, orientationZInput; + private GuiDraggingNumberInput scaleXInput, scaleYInput, scaleZInput; private GuiDraggingNumberInput opacityInput; + + private NumberInputGroup anchorNumberInputs, positionNumberInputs, orientationNumberInputs, scaleNumberInputs, opacityNumberInputs; + @Getter private GuiObjectKeyframeTimeline objectKeyframeTimeline; + private GuiScrollbar timelineScrollbar; private GuiTexturedButton zoomInButton, zoomOutButton; @@ -97,29 +110,35 @@ public class GuiObjectManager extends GuiScreen { @Override public void initGui() { if(!initialized) { - anchorXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); - anchorYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); - anchorZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); + anchorXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true, "", 0.1); + anchorYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true, "", 0.1); + anchorZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true, "", 0.1); + anchorNumberInputs = new NumberPositionInputGroup(null, anchorXInput, anchorYInput, anchorZInput); positionXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); positionYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); positionZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); - - scaleXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%"); - scaleYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%"); - scaleZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%"); + positionNumberInputs = new NumberPositionInputGroup(null, positionXInput, positionYInput, positionZInput); orientationXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); orientationYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); orientationZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 0d, true); + orientationNumberInputs = new NumberPositionInputGroup(null, orientationXInput, orientationYInput, orientationZInput); - opacityInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, 0d, 100d, 100d, true, "%"); + scaleXInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%", 0.5); + scaleYInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%", 0.5); + scaleZInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, null, null, 100d, true, "%", 0.5); + scaleNumberInputs = new NumberPositionInputGroup(null, scaleXInput, scaleYInput, scaleZInput); + + opacityInput = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 50, 0d, 100d, 100d, true, "%", 0.5); + opacityNumberInputs = new NumberValueInputGroup(null, opacityInput); dropdownLabel = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.assets.filechooser")+": "); assetDropdown = new GuiDropdown>(fontRendererObj, 0, 0, 0, 5); if(ReplayHandler.getAssetRepository().getCopyOfReplayAssets().isEmpty()) { assetDropdown.addElement(new GuiEntryListValueEntry(I18n.format("replaymod.gui.assets.emptylist"), null)); } else { + assetDropdown.addElement(new GuiEntryListValueEntry(I18n.format("replaymod.gui.assets.noselection"), null)); for(ReplayAsset asset : ReplayHandler.getAssetRepository().getCopyOfReplayAssets()) { assetDropdown.addElement(new GuiEntryListValueEntry( asset.getDisplayString(), ReplayHandler.getAssetRepository().getUUIDForAsset(asset))); @@ -152,6 +171,10 @@ public class GuiObjectManager extends GuiScreen { nameInput = new GuiAdvancedTextField(fontRendererObj, 0, 0, 0, 20); nameInput.hint = I18n.format("replaymod.gui.objects.properties.name"); + for(CustomImageObject customImageObject : ReplayHandler.getCustomImageObjects()) { + objectList.addElement(customImageObject); + } + objectList.addSelectionListener(new SelectionListener() { @Override public void onSelectionChanged(int selectionIndex) { @@ -176,7 +199,17 @@ public class GuiObjectManager extends GuiScreen { assetDropdown.setSelectionIndexQuietly(sel); - objectKeyframeTimeline.setTransformations(selectedObject.getTransformations()); + Transformations transformations = selectedObject.getTransformations(); + + objectKeyframeTimeline.setTransformations(transformations); + + anchorNumberInputs.setUnderlyingKeyframeList(transformations.getAnchorKeyframes()); + positionNumberInputs.setUnderlyingKeyframeList(transformations.getPositionKeyframes()); + orientationNumberInputs.setUnderlyingKeyframeList(transformations.getOrientationKeyframes()); + scaleNumberInputs.setUnderlyingKeyframeList(transformations.getScaleKeyframes()); + opacityNumberInputs.setUnderlyingKeyframeList(transformations.getOpacityKeyframes()); + + updateValuesForTransformation(objectKeyframeTimeline.getTransformations().getTransformationForTimestamp(objectKeyframeTimeline.cursorPosition)); } else { disableElements.setEnabled(false); } @@ -202,8 +235,8 @@ public class GuiObjectManager extends GuiScreen { String[] labelStrings = new String[] { I18n.format("replaymod.gui.objects.properties.anchor"), I18n.format("replaymod.gui.objects.properties.position"), - I18n.format("replaymod.gui.objects.properties.scale"), I18n.format("replaymod.gui.objects.properties.orientation"), + I18n.format("replaymod.gui.objects.properties.scale"), I18n.format("replaymod.gui.objects.properties.opacity"), }; @@ -222,10 +255,10 @@ public class GuiObjectManager extends GuiScreen { anchorInputs = new ComposedElement(anchorXInput, anchorYInput, anchorZInput); positionInputs = new ComposedElement(positionXInput, positionYInput, positionZInput); - scaleInputs = new ComposedElement(scaleXInput, scaleYInput, scaleZInput); orientationInputs = new ComposedElement(orientationXInput, orientationYInput, orientationZInput); + scaleInputs = new ComposedElement(scaleXInput, scaleYInput, scaleZInput); opacityInputs = new ComposedElement(opacityInput); - numberInputs = new ComposedElement(anchorInputs, positionInputs, scaleInputs, orientationInputs, opacityInputs); + numberInputs = new ComposedElement(anchorInputs, positionInputs, orientationInputs, scaleInputs, opacityInputs); for(int i = numberInputs.getParts().size()-1; i >= 0; i--) { int yPos = this.height-5-10-(25*(numberInputs.getParts().size()-i)); @@ -329,6 +362,43 @@ public class GuiObjectManager extends GuiScreen { initialized = true; } + private void saveOnQuit() { + ArrayList objects = objectList.getCopyOfElements(); + ReplayHandler.setCustomImageObjects(objects); + + if(objects.size() > 0) { + try { + File f = File.createTempFile(ReplayFile.ENTRY_CUSTOM_OBJECTS, "json"); + ReplayFileIO.write(new CustomObjectRepository(objects), f); + ReplayMod.replayFileAppender.registerModifiedFile(f, ReplayFile.ENTRY_CUSTOM_OBJECTS, ReplayHandler.getReplayFile()); + } catch(Exception e) { + e.printStackTrace(); + } + } else { + ReplayMod.replayFileAppender.registerModifiedFile(null, ReplayFile.ENTRY_CUSTOM_OBJECTS, ReplayHandler.getReplayFile()); + } + } + + private void updateValuesForTransformation(Transformation transformation) { + anchorXInput.setValueQuietly(transformation.getAnchor().getX()); + anchorYInput.setValueQuietly(transformation.getAnchor().getY()); + anchorZInput.setValueQuietly(transformation.getAnchor().getZ()); + + positionXInput.setValueQuietly(transformation.getPosition().getX()); + positionYInput.setValueQuietly(transformation.getPosition().getY()); + positionZInput.setValueQuietly(transformation.getPosition().getZ()); + + orientationXInput.setValueQuietly(transformation.getOrientation().getX()); + orientationYInput.setValueQuietly(transformation.getOrientation().getY()); + orientationZInput.setValueQuietly(transformation.getOrientation().getZ()); + + scaleXInput.setValueQuietly(transformation.getScale().getX()); + scaleYInput.setValueQuietly(transformation.getScale().getY()); + scaleZInput.setValueQuietly(transformation.getScale().getZ()); + + opacityInput.setValueQuietly(transformation.getOpacity()); + } + @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawDefaultBackground(); @@ -377,6 +447,70 @@ public class GuiObjectManager extends GuiScreen { super.mouseReleased(mouseX, mouseY, mouseButton); } + @Override + public void onGuiClosed() { + saveOnQuit(); + } + + public abstract class NumberInputGroup implements NumberValueChangeListener { + + KeyframeList toModify; + + public void setUnderlyingKeyframeList(KeyframeList toModify) { + this.toModify = toModify; + } + + } + + public class NumberValueInputGroup extends NumberInputGroup { + + public NumberValueInputGroup(KeyframeList toModify, GuiNumberInput input) { + this.toModify = toModify; + + input.addValueChangeListener(this); + } + + @Override + public void onValueChange(double value) { + NumberValue numberValue = new NumberValue(value); + + //if keyframe selected, overwrite its value + if(toModify.contains(objectKeyframeTimeline.getSelectedKeyframe())) { + objectKeyframeTimeline.getSelectedKeyframe().setValue(numberValue); + } else { + toModify.add(new Keyframe(objectKeyframeTimeline.cursorPosition, numberValue)); + } + } + } + + public class NumberPositionInputGroup extends NumberInputGroup { + + public NumberPositionInputGroup(KeyframeList toModify, GuiNumberInput xInput, GuiNumberInput yInput, GuiNumberInput zInput) { + this.toModify = toModify; + this.xInput = xInput; + this.yInput = yInput; + this.zInput = zInput; + + xInput.addValueChangeListener(this); + yInput.addValueChangeListener(this); + zInput.addValueChangeListener(this); + } + + private GuiNumberInput xInput, yInput, zInput; + + @Override + public void onValueChange(double value) { + Position position = new Position(xInput.getPreciseValue(), yInput.getPreciseValue(), zInput.getPreciseValue()); + + //if keyframe selected, overwrite its value + if(toModify.contains(objectKeyframeTimeline.getSelectedKeyframe())) { + objectKeyframeTimeline.getSelectedKeyframe().setValue(position); + } else { + toModify.add(new Keyframe(objectKeyframeTimeline.cursorPosition, position)); + } + } + } + public class GuiObjectKeyframeTimeline extends GuiTimeline { private static final int KEYFRAME_X = 74; @@ -481,6 +615,8 @@ public class GuiObjectManager extends GuiScreen { } cursorPosition = time; + updateValuesForTransformation(getTransformations().getTransformationForTimestamp(cursorPosition)); + dragging = true; return true; } @@ -494,7 +630,10 @@ public class GuiObjectManager extends GuiScreen { super.mouseDrag(mc, mouseX, mouseY, button); if(dragging) { int time = (int) getTimeAt(mouseX, mouseY); - if(time != -1) cursorPosition = time; + if(time != -1) { + cursorPosition = time; + updateValuesForTransformation(getTransformations().getTransformationForTimestamp(cursorPosition)); + } } } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java index 25d51858..6e7b26dc 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiDraggingNumberInput.java @@ -9,19 +9,23 @@ public class GuiDraggingNumberInput extends GuiNumberInputWithText { public GuiDraggingNumberInput(FontRenderer fontRenderer, int xPos, int yPos, int width, Double minimum, Double maximum, Double defaultValue, boolean acceptFloats) { - this(fontRenderer, xPos, yPos, width, minimum, maximum, defaultValue, acceptFloats, ""); + this(fontRenderer, xPos, yPos, width, minimum, maximum, defaultValue, acceptFloats, "", 0.5f); } public GuiDraggingNumberInput(FontRenderer fontRenderer, int xPos, int yPos, int width, Double minimum, - Double maximum, Double defaultValue, boolean acceptFloats, String suffix) { + Double maximum, Double defaultValue, boolean acceptFloats, String suffix, double stepSize) { super(fontRenderer, xPos, yPos, width, minimum, maximum, defaultValue, acceptFloats, suffix); + + this.stepSize = stepSize; } private int prevMouseX; private boolean dragging; private boolean clicked; + private double stepSize; + @Override public boolean mouseClick(Minecraft mc, int mouseX, int mouseY, int button) { if(MouseUtils.isMouseWithinBounds(xPosition, yPosition, width, height) && isEnabled) { @@ -40,16 +44,10 @@ public class GuiDraggingNumberInput extends GuiNumberInputWithText { int diff = mouseX - prevMouseX; prevMouseX = mouseX; - int bounds = 100; - - if(minimum != null && maximum != null) { - bounds = (int)(maximum-minimum); - } - double value = getPreciseValue(); - double valueDiff = RoundUtils.round2Decimals((diff / 200f) * bounds); + double valueDiff = diff * stepSize; - this.setValue(value+valueDiff); + this.setValue(RoundUtils.round2Decimals(value + valueDiff)); } super.mouseDrag(mc, mouseX, mouseY, button); } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java index fa2de206..c44553f2 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java @@ -150,7 +150,7 @@ public class GuiEntryList extends GuiAdvancedTextFi fireSelectionChangeEvent(); } - public List getCopyOfElements() { + public ArrayList getCopyOfElements() { return new ArrayList(elements); } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java index a26cc5c5..4772ae1c 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiNumberInput.java @@ -1,9 +1,15 @@ package eu.crushedpixel.replaymod.gui.elements; +import eu.crushedpixel.replaymod.gui.elements.listeners.NumberValueChangeListener; import net.minecraft.client.gui.FontRenderer; +import java.util.ArrayList; +import java.util.List; + public class GuiNumberInput extends GuiAdvancedTextField { + private List valueChangeListeners = new ArrayList(); + protected Double minimum, maximum; protected boolean acceptFloats = false; @@ -25,7 +31,11 @@ public class GuiNumberInput extends GuiAdvancedTextField { this(fontRenderer, xPos, yPos, width, (double) minimum, (double) maximum, (double) defaultValue, acceptFloats); } - public void setValue(double value) { + /** + * Sets this GuiNumberInput's value without notifying the listeners. + * @param value The value to set + */ + public void setValueQuietly(double value) { if(minimum != null && value < minimum) { setText(acceptFloats ? minimum.toString() : Integer.toString((int) Math.round(minimum))); } else if(maximum != null && value > maximum) { @@ -40,6 +50,22 @@ public class GuiNumberInput extends GuiAdvancedTextField { setCursorPositionZero(); } + public void setValue(double value) { + setValueQuietly(value); + fireValueChangeEvent(); + } + + public void addValueChangeListener(NumberValueChangeListener listener) { + this.valueChangeListeners.add(listener); + } + + private void fireValueChangeEvent() { + double val = acceptFloats ? getPreciseValue() : getIntValue(); + for(NumberValueChangeListener listener : valueChangeListeners) { + listener.onValueChange(val); + } + } + @Override public void writeText(String text) { String textBefore = getText(); @@ -59,6 +85,8 @@ public class GuiNumberInput extends GuiAdvancedTextField { } else if(maximum != null && val > maximum) { setText(acceptFloats ? maximum.toString() : Integer.toString((int) Math.round(maximum))); } + + fireValueChangeEvent(); } catch(NumberFormatException e) { setText(textBefore); setCursorPosition(cursorPositionBefore); diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java new file mode 100644 index 00000000..dd1f0181 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/listeners/NumberValueChangeListener.java @@ -0,0 +1,7 @@ +package eu.crushedpixel.replaymod.gui.elements.listeners; + +public interface NumberValueChangeListener { + + void onValueChange(double value); + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java b/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java index af766dec..d0383cf2 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Transformation.java @@ -7,10 +7,30 @@ import lombok.Data; @AllArgsConstructor public class Transformation { + /** + * The object's anchor around which modifiers like orientation or position should be applied. + * x=0, y=0, z=0 is the object's center. + */ private Position anchor; + + /** + * The object's position in the Minecraft world. + */ private Position position; + + /** + * The object's orientation. + */ private Position orientation; + + /** + * The object's scale, individual values between 0 and 100. + */ private Position scale; + + /** + * The object's opacity. Value between 0 and 100. + */ private double opacity; } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java b/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java index 55805bf1..7b5db43c 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Transformations.java @@ -10,6 +10,12 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class Transformations { + private Position defaultAnchor = new Position(0, 0, 0), + defaultPosition = new Position(0, 0, 0), + defaultOrientation = new Position(0, 0, 0), + defaultScale = new Position(100, 100, 100); + private NumberValue defaultOpacity = new NumberValue(100); + private KeyframeList anchorKeyframes = new KeyframeList(); private KeyframeList positionKeyframes = new KeyframeList(); private KeyframeList orientationKeyframes = new KeyframeList(); @@ -34,12 +40,22 @@ public class Transformations { } public Transformation getTransformationForTimestamp(int timestamp) { - return new Transformation( - anchorKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - positionKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - orientationKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - scaleKeyframes.getInterpolatedValueForTimestamp(timestamp, true), - opacityKeyframes.getInterpolatedValueForTimestamp(timestamp, true).value); + Position anchor = anchorKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(anchor == null) anchor = defaultAnchor; + + Position position = positionKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(position == null) position = defaultPosition; + + Position orientation = orientationKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(orientation == null) orientation = defaultOrientation; + + Position scale = scaleKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(scale == null) scale = defaultScale; + + NumberValue opacity = opacityKeyframes.getInterpolatedValueForTimestamp(timestamp, true); + if(opacity == null) opacity = defaultOpacity; + + return new Transformation(anchor, position, orientation, scale, opacity.value); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java index ce25a975..4e9fc8de 100644 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java @@ -17,6 +17,13 @@ public class KeyframeList extends ArrayList @Override public boolean add(Keyframe t) { + //remove keyframes that have same timestamp + for(Keyframe kf : new ArrayList(this)) { + if(kf.getRealTimestamp() == t.getRealTimestamp()) { + super.remove(kf); + } + } + boolean success = super.add(t); sort(); return success; @@ -137,6 +144,9 @@ public class KeyframeList extends ArrayList } public K getInterpolatedValueForPathPosition(float pathPosition, boolean linear) { + if(first() == null) return null; + if(size() == 1) return first().getValue(); + K toApply = (K)first().getValue().newInstance(); if(previousCallLinear != (Boolean)linear) { diff --git a/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java b/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java index 942c2289..e50e03ae 100644 --- a/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/renderer/CustomObjectRenderer.java @@ -1,12 +1,18 @@ package eu.crushedpixel.replaymod.renderer; import eu.crushedpixel.replaymod.assets.CustomImageObject; +import eu.crushedpixel.replaymod.gui.GuiObjectManager; +import eu.crushedpixel.replaymod.holders.Position; +import eu.crushedpixel.replaymod.holders.Transformation; import eu.crushedpixel.replaymod.replay.ReplayHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.entity.Entity; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.lwjgl.opengl.GL11; /** * Allows users to render custom images in the World. @@ -19,12 +25,9 @@ public class CustomObjectRenderer { public void renderCustomObjects(RenderWorldLastEvent event) { if(!ReplayHandler.isInReplay()) return; - Entity entity = ReplayHandler.getCameraEntity(); - if(entity == null) return; - - double doubleX = entity.posX; - double doubleY = entity.posY; - double doubleZ = entity.posZ; + double dX = mc.getRenderViewEntity().lastTickPosX + (mc.getRenderViewEntity().posX - mc.getRenderViewEntity().lastTickPosX) * (double)event.partialTicks; + double dY = mc.getRenderViewEntity().lastTickPosY + (mc.getRenderViewEntity().posY - mc.getRenderViewEntity().lastTickPosY) * (double)event.partialTicks; + double dZ = mc.getRenderViewEntity().lastTickPosZ + (mc.getRenderViewEntity().posZ - mc.getRenderViewEntity().lastTickPosZ) * (double)event.partialTicks; GlStateManager.pushAttrib(); GlStateManager.pushMatrix(); @@ -35,7 +38,7 @@ public class CustomObjectRenderer { GlStateManager.disableTexture2D(); for(CustomImageObject object : ReplayHandler.getCustomImageObjects()) { - drawCustomImageObject(doubleX, doubleY, doubleZ, object); + drawCustomImageObject(dX, dY, dZ, object); } GlStateManager.enableTexture2D(); @@ -47,8 +50,12 @@ public class CustomObjectRenderer { } private void drawCustomImageObject(double playerX, double playerY, double playerZ, CustomImageObject customImageObject) { - //TODO: Get current Transformations for given Timestamp - /* + ResourceLocation resourceLocation = customImageObject.getResourceLocation(); + + if(customImageObject.getLinkedAsset() == null + || ReplayHandler.getAssetRepository().getAssetByUUID(customImageObject.getLinkedAsset()) == null + || resourceLocation == null) return; + GlStateManager.pushMatrix(); GlStateManager.enableTexture2D(); GlStateManager.enableLighting(); @@ -60,9 +67,20 @@ public class CustomObjectRenderer { Tessellator tessellator = Tessellator.getInstance(); WorldRenderer renderer = tessellator.getWorldRenderer(); - mc.renderEngine.bindTexture(customImageObject.getResourceLocation()); + mc.renderEngine.bindTexture(resourceLocation); - Transformations objectPosition = customImageObject.getPosition(); + int renderTimestamp; + if(mc.currentScreen instanceof GuiObjectManager) { + renderTimestamp = ((GuiObjectManager)mc.currentScreen).getObjectKeyframeTimeline().cursorPosition; + } else { + renderTimestamp = ReplayHandler.getRealTimelineCursor(); + } + + Transformation transformation = customImageObject.getTransformations().getTransformationForTimestamp(renderTimestamp); + + Position objectAnchor = transformation.getAnchor(); + Position objectPosition = transformation.getPosition(); + Position objectOrientation = transformation.getOrientation(); double x = objectPosition.getX() - playerX; double y = objectPosition.getY() - playerY; @@ -72,23 +90,23 @@ public class CustomObjectRenderer { GlStateManager.translate(x, y + 1.4, z); - GlStateManager.rotate(-objectPosition.getYaw(), 0, 1, 0); - GlStateManager.rotate(objectPosition.getRoll(), 0, 0, 1); - GlStateManager.rotate(objectPosition.getPitch(), 1, 0, 0); + GlStateManager.rotate((float)-objectOrientation.getX(), 0, 1, 0); + GlStateManager.rotate((float)objectOrientation.getY(), 0, 0, 1); + GlStateManager.rotate((float) objectOrientation.getZ(), 1, 0, 0); GlStateManager.enableBlend(); GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - float opacity = objectPosition.getOpacity(); + float opacity = (float)transformation.getOpacity() / 100; GlStateManager.color(1, 1, 1, opacity); - float width = objectPosition.getWidth() * objectPosition.getScale(); - float height = objectPosition.getHeight() * objectPosition.getScale(); + float width = (float)(customImageObject.getWidth() * transformation.getScale().getX() / 100f); + float height = (float)(customImageObject.getHeight() * transformation.getScale().getY() / 100f); - float minX = -width/2 + objectPosition.getAnchorX(); - float maxX = width/2 + objectPosition.getAnchorX(); - float minY = -height/2 + objectPosition.getAnchorY(); - float maxY = height/2 + objectPosition.getAnchorY(); + float minX = (float)(-width/2 + objectAnchor.getX()); + float maxX = (float)(width/2 + objectAnchor.getX()); + float minY = (float)(-height/2 + objectAnchor.getY()); + float maxY = (float)(height/2 + objectAnchor.getY()); renderer.startDrawingQuads(); @@ -109,6 +127,5 @@ public class CustomObjectRenderer { GlStateManager.disableTexture2D(); GlStateManager.enableLighting(); GlStateManager.popMatrix(); - */ } } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index 7dcda87e..c056da48 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -4,6 +4,7 @@ import com.mojang.authlib.GameProfile; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.assets.AssetRepository; import eu.crushedpixel.replaymod.assets.CustomImageObject; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.entities.CameraEntity; import eu.crushedpixel.replaymod.events.KeyframesModifyEvent; import eu.crushedpixel.replaymod.events.ReplayExitEvent; @@ -68,7 +69,7 @@ public class ReplayHandler { @Getter @Setter private static AssetRepository assetRepository; - private static List customImageObjects = new ArrayList(); + private static CustomObjectRepository customImageObjects = new CustomObjectRepository(); /** * The file currently being played. @@ -455,6 +456,11 @@ public class ReplayHandler { AssetRepository assets = currentReplayFile.assetRepository().get(); assetRepository = assets; + //load custom image objects + CustomObjectRepository objectRepository = currentReplayFile.customImageObjects().get(); + customImageObjects = objectRepository; + if(customImageObjects == null) customImageObjects = new CustomObjectRepository(); + ReplayMod.replaySender = new ReplaySender(currentReplayFile, asyncMode); channel.pipeline().addFirst(ReplayMod.replaySender); channel.pipeline().fireChannelActive(); @@ -604,15 +610,15 @@ public class ReplayHandler { } public static List getCustomImageObjects() { - return customImageObjects; + return customImageObjects.getObjects(); } public static void addCustomImageObject(CustomImageObject object) { - customImageObjects.add(object); + customImageObjects.getObjects().add(object); } - public static void setCustomImageObjects(List objects) { - customImageObjects = objects; + public static void setCustomImageObjects(ArrayList objects) { + customImageObjects.setObjects(objects); } public static void fireKeyframesModifyEvent() { diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java index 6e993d83..73b9ecf5 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java @@ -5,6 +5,7 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import eu.crushedpixel.replaymod.assets.AssetRepository; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.holders.MarkerKeyframe; import eu.crushedpixel.replaymod.holders.PlayerVisibility; @@ -31,11 +32,12 @@ public class ReplayFile extends ZipFile { public static final String ENTRY_PATHS = "paths" + JSON_FILE_EXTENSION; public static final String ENTRY_THUMB = "thumb"; public static final String ENTRY_RESOURCE_PACK = "resourcepack/%s.zip"; - public static final String ENTRY_RESOURCE_PACK_INDEX = "resourcepack/index.json"; + public static final String ENTRY_RESOURCE_PACK_INDEX = "resourcepack/index"+JSON_FILE_EXTENSION; public static final String ENTRY_VISIBILITY_OLD = "visibility"; public static final String ENTRY_VISIBILITY = "visibility" + JSON_FILE_EXTENSION; public static final String ENTRY_MARKERS = "markers" + JSON_FILE_EXTENSION; public static final String ENTRY_ASSET_FOLDER = "asset/"; + public static final String ENTRY_CUSTOM_OBJECTS = "objects"+JSON_FILE_EXTENSION; private final File file; @@ -258,4 +260,27 @@ public class ReplayFile extends ZipFile { } }; } + + public ZipArchiveEntry customObjectsEntry() { + return getEntry(ENTRY_CUSTOM_OBJECTS); + } + + public Supplier customImageObjects() { + return new Supplier() { + @Override + public CustomObjectRepository get() { + try { + ZipArchiveEntry entry = customObjectsEntry(); + if(entry == null) return null; + + BufferedReader reader = new BufferedReader(new InputStreamReader(getInputStream(entry))); + return new Gson().fromJson(reader, CustomObjectRepository.class); + } catch(Exception e) { + e.printStackTrace(); + } + + return null; + } + }; + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java index dd298749..d0f2c646 100755 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java @@ -2,6 +2,7 @@ package eu.crushedpixel.replaymod.utils; import com.google.gson.Gson; import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.assets.CustomObjectRepository; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.holders.MarkerKeyframe; import eu.crushedpixel.replaymod.holders.PacketData; @@ -169,6 +170,10 @@ public class ReplayFileIO { write((Object) markers, file); } + public static void write(CustomObjectRepository repository, File file) throws IOException { + write((Object) repository, file); + } + /** * Adds Files as entries to a ZIP File. * @param zipFile The ZIP File to add the files to. diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java index d90e8ff6..05e5a5d3 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java @@ -1,7 +1,23 @@ package eu.crushedpixel.replaymod.utils; +import java.text.DecimalFormat; + public class RoundUtils { - public static double round2Decimals(double val) { return Math.round(val*100.0) / 100.0; } + public static double round2Decimals(double val) { + return round(val, 2); + } + + public static double round(double value, int places) { + if (places < 0) throw new IllegalArgumentException(); + + StringBuilder format = new StringBuilder("#."); + for(int i=0; i