Separate rendering into its own module
This commit is contained in:
@@ -14,13 +14,10 @@ import eu.crushedpixel.replaymod.registry.UploadedFileHandler;
|
||||
import eu.crushedpixel.replaymod.renderer.CustomObjectRenderer;
|
||||
import eu.crushedpixel.replaymod.renderer.PathPreviewRenderer;
|
||||
import eu.crushedpixel.replaymod.renderer.SpectatorRenderer;
|
||||
import eu.crushedpixel.replaymod.settings.EncodingPreset;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.settings.ReplaySettings;
|
||||
import eu.crushedpixel.replaymod.sound.SoundHandler;
|
||||
import eu.crushedpixel.replaymod.utils.OpenGLUtils;
|
||||
import eu.crushedpixel.replaymod.utils.TooltipRenderer;
|
||||
import eu.crushedpixel.replaymod.video.rendering.Pipelines;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
@@ -204,6 +201,7 @@ public class ReplayMod {
|
||||
String height = System.getProperty("replaymod.render.height");
|
||||
String exportCommand = System.getProperty("replaymod.render.exportcommand");
|
||||
String exportCommandArgs = System.getProperty("replaymod.render.exportcommandargs");
|
||||
/*
|
||||
final RenderOptions options = new RenderOptions();
|
||||
if (bitrate != null) {
|
||||
options.setBitrate(bitrate);
|
||||
@@ -265,6 +263,7 @@ public class ReplayMod {
|
||||
}
|
||||
}
|
||||
options.setMode(pipelinePreset);
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Queue<ListenableFutureTask> tasks = mc.scheduledTasks;
|
||||
|
||||
86
src/main/java/com/replaymod/render/RenderSettings.java
Normal file
86
src/main/java/com/replaymod/render/RenderSettings.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.replaymod.render;
|
||||
|
||||
import lombok.Data;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import org.lwjgl.util.ReadableColor;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Data
|
||||
public class RenderSettings {
|
||||
public enum RenderMethod {
|
||||
DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return I18n.format("replaymod.gui.rendersettings.renderer." + name().toLowerCase());
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return I18n.format("replaymod.gui.rendersettings.renderer." + name().toLowerCase() + ".description");
|
||||
}
|
||||
}
|
||||
|
||||
public enum EncodingPreset {
|
||||
MP4_CUSTOM("-an -c:v libx264 -b:v %BITRATE% -pix_fmt yuv420p \"%FILENAME%\"", "mp4"),
|
||||
|
||||
MP4_HIGH("-an -c:v libx264 -preset ultrafast -qp 1 -pix_fmt yuv420p \"%FILENAME%\"", "mp4"),
|
||||
|
||||
MP4_DEFAULT("-an -c:v libx264 -preset ultrafast -pix_fmt yuv420p \"%FILENAME%\"", "mp4"),
|
||||
|
||||
MP4_POTATO("-an -c:v libx264 -preset ultrafast -crf 51 -pix_fmt yuv420p \"%FILENAME%\"", "mp4"),
|
||||
|
||||
WEBM_CUSTOM("-an -c:v libvpx -b:v %BITRATE% \"%FILENAME%\"", "webm"),
|
||||
|
||||
MKV_LOSSLESS("-an -c:v libx264 -preset ultrafast -qp 0 \"%FILENAME%\"", "mkv"),
|
||||
|
||||
PNG("\"%FILENAME%-%06d.png\"", "png");
|
||||
|
||||
private final String preset;
|
||||
private final String fileExtension;
|
||||
|
||||
EncodingPreset(String preset, String fileExtension) {
|
||||
this.preset = preset;
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return "-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - " + preset;
|
||||
}
|
||||
|
||||
public String getFileExtension() {
|
||||
return fileExtension;
|
||||
}
|
||||
|
||||
public boolean hasBitrateSetting() {
|
||||
return preset.contains("%BITRATE%");
|
||||
}
|
||||
|
||||
public boolean isYuv420() { return preset.contains("-pix_fmt yuv420p"); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return I18n.format("replaymod.gui.rendersettings.presets." + name().replace('_', '.').toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
private final RenderMethod renderMethod;
|
||||
private final EncodingPreset encodingPreset;
|
||||
private final int videoWidth;
|
||||
private final int videoHeight;
|
||||
private final int framesPerSecond;
|
||||
private final int bitRate;
|
||||
private final File outputFile;
|
||||
|
||||
private final boolean renderNameTags;
|
||||
private final boolean stabilizeYaw;
|
||||
private final boolean stabilizePitch;
|
||||
private final boolean stabilizeRoll;
|
||||
private final ReadableColor chromaKeyingColor;
|
||||
private final boolean inject360Metadata;
|
||||
|
||||
private final String exportCommand;
|
||||
private final String exportArguments;
|
||||
|
||||
private final boolean highPerformance;
|
||||
}
|
||||
32
src/main/java/com/replaymod/render/ReplayModRender.java
Normal file
32
src/main/java/com/replaymod/render/ReplayModRender.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.replaymod.render;
|
||||
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mod(modid = ReplayModRender.MOD_ID, useMetadata = true)
|
||||
public class ReplayModRender {
|
||||
public static final String MOD_ID = "replaymod-render";
|
||||
|
||||
@Mod.Instance(MOD_ID)
|
||||
public static ReplayModRender instance;
|
||||
|
||||
@Mod.Instance(ReplayMod.MOD_ID)
|
||||
private static ReplayMod core;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
private Configuration configuration;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
logger = event.getModLog();
|
||||
configuration = new Configuration(event.getSuggestedConfigurationFile());
|
||||
}
|
||||
|
||||
public Configuration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
package com.replaymod.render;
|
||||
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.rendering.FrameConsumer;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.utils.StreamPipe;
|
||||
import eu.crushedpixel.replaymod.utils.StringUtils;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import eu.crushedpixel.replaymod.video.rendering.FrameConsumer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.crash.CrashReport;
|
||||
import net.minecraft.crash.CrashReportCategory;
|
||||
@@ -26,30 +25,30 @@ import static org.apache.commons.lang3.Validate.isTrue;
|
||||
|
||||
public class VideoWriter implements FrameConsumer<RGBFrame> {
|
||||
|
||||
private final RenderOptions options;
|
||||
private final RenderSettings settings;
|
||||
private final Process process;
|
||||
private final OutputStream outputStream;
|
||||
private final WritableByteChannel channel;
|
||||
private final String commandArgs;
|
||||
private volatile boolean aborted;
|
||||
|
||||
public VideoWriter(final RenderOptions options) throws IOException {
|
||||
this.options = options.copy();
|
||||
public VideoWriter(final RenderSettings settings) throws IOException {
|
||||
this.settings = settings;
|
||||
|
||||
File outputFolder = options.getOutputFile().getParentFile();
|
||||
String fileName = options.getOutputFile().getName();
|
||||
File outputFolder = settings.getOutputFile().getParentFile();
|
||||
String fileName = settings.getOutputFile().getName();
|
||||
|
||||
commandArgs = options.getExportCommandArgs()
|
||||
.replace("%WIDTH%", String.valueOf(options.getWidth()))
|
||||
.replace("%HEIGHT%", String.valueOf(options.getHeight()))
|
||||
.replace("%FPS%", String.valueOf(options.getFps()))
|
||||
commandArgs = settings.getExportArguments()
|
||||
.replace("%WIDTH%", String.valueOf(settings.getVideoWidth()))
|
||||
.replace("%HEIGHT%", String.valueOf(settings.getVideoHeight()))
|
||||
.replace("%FPS%", String.valueOf(settings.getFramesPerSecond()))
|
||||
.replace("%FILENAME%", fileName)
|
||||
.replace("%BITRATE%", options.getBitrate());
|
||||
.replace("%BITRATE%", String.valueOf(settings.getBitRate()));
|
||||
|
||||
List<String> command = new ArrayList<String>();
|
||||
command.add(options.getExportCommand());
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(settings.getExportCommand().isEmpty() ? "ffmpeg" : settings.getExportCommand());
|
||||
command.addAll(StringUtils.translateCommandline(commandArgs));
|
||||
System.out.println("Starting " + options.getExportCommand() + " with args: " + commandArgs);
|
||||
System.out.println("Starting " + settings.getExportCommand() + " with args: " + commandArgs);
|
||||
process = new ProcessBuilder(command).directory(outputFolder).start();
|
||||
OutputStream exportLogOut = new FileOutputStream("export.log");
|
||||
new StreamPipe(process.getInputStream(), exportLogOut).start();
|
||||
@@ -96,7 +95,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
|
||||
}
|
||||
CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame");
|
||||
CrashReportCategory exportDetails = report.makeCategory("Export details");
|
||||
exportDetails.addCrashSection("Export command", options.getExportCommand());
|
||||
exportDetails.addCrashSection("Export command", settings.getExportCommand());
|
||||
exportDetails.addCrashSection("Export args", commandArgs);
|
||||
Minecraft.getMinecraft().crashed(report);
|
||||
}
|
||||
@@ -107,8 +106,8 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
|
||||
}
|
||||
|
||||
private void checkSize(int width, int height) {
|
||||
isTrue(width == options.getWidth(), "Width has to be %d but was %d", options.getWidth(), width);
|
||||
isTrue(height == options.getHeight(), "Height has to be %d but was %d", options.getHeight(), height);
|
||||
isTrue(width == settings.getVideoWidth(), "Width has to be %d but was %d", settings.getVideoWidth(), width);
|
||||
isTrue(height == settings.getVideoHeight(), "Height has to be %d but was %d", settings.getVideoHeight(), height);
|
||||
}
|
||||
|
||||
public void abort() {
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
public interface CaptureData {
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||
|
||||
public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
||||
public enum Data implements CaptureData {
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
|
||||
public class CubicPboOpenGlFrameCapturer extends
|
||||
MultiFramePboOpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
||||
@@ -1,9 +1,9 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.opengl.PixelBufferObject;
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import com.replaymod.render.utils.PixelBufferObject;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import eu.crushedpixel.replaymod.video.rendering.FrameCapturer;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import com.replaymod.render.rendering.FrameCapturer;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
public interface RenderInfo {
|
||||
@@ -10,5 +10,5 @@ public interface RenderInfo {
|
||||
|
||||
float updateForNextFrame();
|
||||
|
||||
RenderOptions getRenderOptions();
|
||||
RenderSettings getRenderSettings();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
|
||||
public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.opengl.PixelBufferObject;
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import com.replaymod.render.utils.PixelBufferObject;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||
|
||||
public class StereoscopicOpenGlFrameCapturer
|
||||
extends OpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||
|
||||
public class StereoscopicPboOpenGlFrameCapturer
|
||||
extends MultiFramePboOpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
package com.replaymod.render.capturer;
|
||||
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
package com.replaymod.render.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
package com.replaymod.render.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
package com.replaymod.render.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
@@ -20,7 +20,7 @@ public class RGBFrame implements Frame {
|
||||
public RGBFrame(int frameId, ReadableDimension size, ByteBuffer byteBuffer) {
|
||||
Validate.isTrue(size.getWidth() * size.getHeight() * 3 == byteBuffer.remaining(),
|
||||
"Buffer size is %d (cap: %d) but should be %d",
|
||||
byteBuffer.remaining(), byteBuffer.capacity(), size.getWidth() * size.getHeight() * 4);
|
||||
byteBuffer.remaining(), byteBuffer.capacity(), size.getWidth() * size.getHeight() * 3);
|
||||
this.frameId = frameId;
|
||||
this.size = size;
|
||||
this.byteBuffer = byteBuffer;
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
package com.replaymod.render.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
414
src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
Normal file
414
src/main/java/com/replaymod/render/gui/GuiRenderSettings.java
Normal file
@@ -0,0 +1,414 @@
|
||||
package com.replaymod.render.gui;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import com.replaymod.render.ReplayModRender;
|
||||
import com.replaymod.render.rendering.VideoRenderer;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||
import de.johni0702.minecraft.gui.container.GuiVerticalList;
|
||||
import de.johni0702.minecraft.gui.element.*;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiColorPicker;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
|
||||
import de.johni0702.minecraft.gui.function.Closeable;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||
import de.johni0702.minecraft.gui.popup.GuiFileChooserPopup;
|
||||
import de.johni0702.minecraft.gui.utils.Colors;
|
||||
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||
import de.johni0702.minecraft.gui.utils.Utils;
|
||||
import net.minecraft.client.gui.GuiErrorScreen;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.crash.CrashReport;
|
||||
import net.minecraft.util.ReportedException;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
import net.minecraftforge.common.config.Property;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public class GuiRenderSettings extends GuiScreen implements Closeable {
|
||||
public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
|
||||
public final GuiVerticalList settingsList = new GuiVerticalList(contentPanel).setDrawSlider(true);
|
||||
|
||||
public final GuiDropdownMenu<RenderSettings.RenderMethod> renderMethodDropdown =
|
||||
new GuiDropdownMenu<RenderSettings.RenderMethod>().onSelection(new Consumer<Integer>() {
|
||||
@Override
|
||||
public void consume(Integer old) {
|
||||
updateInputs();
|
||||
}
|
||||
}).setMinSize(new Dimension(0, 20)).setValues(RenderSettings.RenderMethod.values());
|
||||
|
||||
{
|
||||
for (Map.Entry<RenderSettings.RenderMethod, IGuiClickable> entry :
|
||||
renderMethodDropdown.getDropdownEntries().entrySet()) {
|
||||
entry.getValue().setTooltip(new GuiTooltip().setText(entry.getKey().getDescription()));
|
||||
}
|
||||
}
|
||||
|
||||
public final GuiDropdownMenu<RenderSettings.EncodingPreset> encodingPresetDropdown =
|
||||
new GuiDropdownMenu<RenderSettings.EncodingPreset>().onSelection(new Consumer<Integer>() {
|
||||
@Override
|
||||
public void consume(Integer old) {
|
||||
RenderSettings.EncodingPreset newPreset = encodingPresetDropdown.getSelectedValue();
|
||||
// Update export arguments to match new Preset
|
||||
exportArguments.setText(newPreset.getValue());
|
||||
// If the user hasn't changed the output file by themselves,
|
||||
if (!outputFileManuallySet) {
|
||||
// generate a new output file name with updated file extension
|
||||
outputFile = generateOutputFile(newPreset);
|
||||
outputFileButton.setLabel(outputFile.getName());
|
||||
}
|
||||
updateInputs();
|
||||
}
|
||||
}).setMinSize(new Dimension(0, 20)).setValues(RenderSettings.EncodingPreset.values());
|
||||
|
||||
public final GuiNumberField videoWidth = new GuiNumberField().setSize(50, 20).setMinValue(1);
|
||||
public final GuiNumberField videoHeight = new GuiNumberField().setSize(50, 20).setMinValue(1);
|
||||
public final GuiSlider frameRateSlider = new GuiSlider().onValueChanged(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
frameRateSlider.setText(I18n.format("replaymod.gui.rendersettings.framerate")
|
||||
+ ": " + (frameRateSlider.getValue() + 10));
|
||||
}
|
||||
}).setSize(122, 20).setSteps(110);
|
||||
public final GuiPanel videoResolutionPanel = new GuiPanel()
|
||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2))
|
||||
.addElements(null, videoWidth, new GuiLabel().setText("*"), videoHeight);
|
||||
|
||||
public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20);
|
||||
public final GuiDropdownMenu<String> bitRateUnit = new GuiDropdownMenu<String>()
|
||||
.setSize(50, 20).setValues("bps", "kbps", "mbps").setSelected("mbps");
|
||||
|
||||
public final GuiButton outputFileButton = new GuiButton().setMinSize(new Dimension(0, 20)).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Futures.addCallback(
|
||||
GuiFileChooserPopup.openSaveGui(GuiRenderSettings.this, "replaymod.gui.save",
|
||||
encodingPresetDropdown.getSelectedValue().getFileExtension()).getFuture(),
|
||||
new FutureCallback<File>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable File result) {
|
||||
if (result != null) {
|
||||
outputFile = result;
|
||||
outputFileManuallySet = true;
|
||||
outputFileButton.setLabel(result.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
public final GuiPanel mainPanel = new GuiPanel()
|
||||
.addElements(new GridLayout.Data(1, 0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.renderer"), renderMethodDropdown,
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.presets"), encodingPresetDropdown,
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.customresolution"), videoResolutionPanel,
|
||||
new GuiLabel().setI18nText("replaymod.gui.settings.bitrate"), new GuiPanel().addElements(null,
|
||||
new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()),
|
||||
frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton)
|
||||
.setLayout(new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(3));
|
||||
|
||||
public final GuiCheckbox nametagCheckbox = new GuiCheckbox()
|
||||
.setI18nLabel("replaymod.gui.rendersettings.nametags");
|
||||
|
||||
public final GuiPanel stabilizePanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(10));
|
||||
public final GuiCheckbox stabilizeYaw = new GuiCheckbox(stabilizePanel)
|
||||
.setI18nLabel("replaymod.gui.yaw");
|
||||
public final GuiCheckbox stabilizePitch = new GuiCheckbox(stabilizePanel)
|
||||
.setI18nLabel("replaymod.gui.pitch");
|
||||
public final GuiCheckbox stabilizeRoll = new GuiCheckbox(stabilizePanel)
|
||||
.setI18nLabel("replaymod.gui.roll");
|
||||
|
||||
public final GuiCheckbox chromaKeyingCheckbox = new GuiCheckbox()
|
||||
.setI18nLabel("replaymod.gui.rendersettings.chromakey");
|
||||
public final GuiColorPicker chromaKeyingColor = new GuiColorPicker().setSize(30, 15);
|
||||
|
||||
public final GuiCheckbox inject360Metadata = new GuiCheckbox()
|
||||
.setI18nLabel("replaymod.gui.rendersettings.360metadata");
|
||||
|
||||
public final GuiPanel advancedPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(15))
|
||||
.addElements(null, nametagCheckbox, new GuiPanel().setLayout(
|
||||
new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15))
|
||||
.addElements(new GridLayout.Data(0, 0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel,
|
||||
chromaKeyingCheckbox, chromaKeyingColor,
|
||||
inject360Metadata));
|
||||
|
||||
public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command")
|
||||
.setSize(55, 20).setMaxLength(100);
|
||||
public final GuiTextField exportArguments = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.arguments")
|
||||
.setMinSize(new Dimension(245, 20)).setMaxLength(500);
|
||||
|
||||
public final GuiPanel commandlinePanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10))
|
||||
.addElements(null,
|
||||
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)).addElements(null, exportCommand, exportArguments),
|
||||
new GuiLabel(new GuiPanel().setLayout(new CustomLayout<GuiPanel>() {
|
||||
@Override
|
||||
protected void layout(GuiPanel container, int width, int height) {
|
||||
size(container.getChildren().iterator().next(), width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
||||
return new Dimension(300, 50);
|
||||
}
|
||||
})).setI18nText("replaymod.gui.rendersettings.ffmpeg.description").getContainer());
|
||||
|
||||
|
||||
public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(4));
|
||||
public final GuiButton renderButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
VideoRenderer videoRenderer = new VideoRenderer(save(true), replayHandler, timeline);
|
||||
videoRenderer.renderVideo();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
GuiErrorScreen errorScreen = new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"),
|
||||
I18n.format("replaymod.gui.rendering.error.message"));
|
||||
getMinecraft().displayGuiScreen(errorScreen);
|
||||
} catch (Throwable t) {
|
||||
CrashReport crashReport = CrashReport.makeCrashReport(t, "Rendering video");
|
||||
throw new ReportedException(crashReport);
|
||||
}
|
||||
}
|
||||
}).setSize(148, 20).setI18nLabel("replaymod.gui.render");
|
||||
public final GuiButton cancelButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getMinecraft().displayGuiScreen(null);
|
||||
}
|
||||
}).setSize(148, 20).setI18nLabel("replaymod.gui.cancel");
|
||||
|
||||
{
|
||||
setBackground(Background.NONE);
|
||||
Utils.link(videoWidth, videoHeight, bitRateField);
|
||||
setLayout(new CustomLayout<GuiScreen>() {
|
||||
@Override
|
||||
protected void layout(GuiScreen container, int width, int height) {
|
||||
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
|
||||
}
|
||||
});
|
||||
|
||||
contentPanel.setLayout(new CustomLayout<GuiPanel>() {
|
||||
@Override
|
||||
protected void layout(GuiPanel container, int width, int height) {
|
||||
size(settingsList, width, height - height(buttonPanel) - 25);
|
||||
pos(settingsList, width / 2 - width(settingsList) / 2, 5);
|
||||
pos(buttonPanel, width / 2 - width(buttonPanel) / 2, y(settingsList) + height(settingsList) + 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
||||
ReadableDimension screenSize = getMinSize();
|
||||
return new Dimension(screenSize.getWidth() - 40, screenSize.getHeight() - 40);
|
||||
}
|
||||
});
|
||||
|
||||
settingsList.getListPanel().setLayout(new VerticalLayout().setSpacing(10))
|
||||
.addElements(new VerticalLayout.Data(0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.video"), mainPanel, new GuiPanel(),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.advanced"), advancedPanel, new GuiPanel(),
|
||||
new GuiLabel().setI18nText("replaymod.gui.rendersettings.commandline"), commandlinePanel);
|
||||
|
||||
videoWidth.onTextChanged(new Consumer<String>() {
|
||||
@Override
|
||||
public void consume(String old) {
|
||||
updateInputs();
|
||||
}
|
||||
});
|
||||
videoHeight.onTextChanged(new Consumer<String>() {
|
||||
@Override
|
||||
public void consume(String obj) {
|
||||
updateInputs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final ReplayHandler replayHandler;
|
||||
private final Timeline timeline;
|
||||
private File outputFile;
|
||||
private boolean outputFileManuallySet;
|
||||
|
||||
public GuiRenderSettings(ReplayHandler replayHandler, Timeline timeline) {
|
||||
this.replayHandler = replayHandler;
|
||||
this.timeline = timeline;
|
||||
|
||||
String json = getConfigProperty(ReplayModRender.instance.getConfiguration()).getString();
|
||||
RenderSettings settings = new GsonBuilder().registerTypeAdapter(RenderSettings.class, new InstanceCreator<RenderSettings>() {
|
||||
@Override
|
||||
public RenderSettings createInstance(Type type) {
|
||||
return getDefaultRenderSettings();
|
||||
}
|
||||
}).create().fromJson(json, RenderSettings.class);
|
||||
load(settings);
|
||||
}
|
||||
|
||||
protected void updateInputs() {
|
||||
// Validate video width and height
|
||||
String error = isResolutionValid();
|
||||
if (error == null) {
|
||||
renderButton.setEnabled().setTooltip(null);
|
||||
videoWidth.setTextColor(Colors.WHITE);
|
||||
videoHeight.setTextColor(Colors.WHITE);
|
||||
} else {
|
||||
renderButton.setDisabled().setTooltip(new GuiTooltip().setI18nText(error));
|
||||
videoWidth.setTextColor(Colors.RED);
|
||||
videoHeight.setTextColor(Colors.RED);
|
||||
}
|
||||
|
||||
// Enable/Disable camera stabilization checkboxes
|
||||
switch (renderMethodDropdown.getSelectedValue()) {
|
||||
case CUBIC:
|
||||
case EQUIRECTANGULAR:
|
||||
stabilizePanel.forEach(IGuiCheckbox.class).setEnabled();
|
||||
break;
|
||||
default:
|
||||
stabilizePanel.forEach(IGuiCheckbox.class).setDisabled();
|
||||
}
|
||||
|
||||
// Enable/Disable inject metadata checkbox
|
||||
if (encodingPresetDropdown.getSelectedValue().getFileExtension().equals("mp4")
|
||||
&& renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR) {
|
||||
inject360Metadata.setEnabled().setTooltip(null);
|
||||
} else {
|
||||
inject360Metadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED)
|
||||
.setI18nText("replaymod.gui.rendersettings.360metadata.error"));
|
||||
}
|
||||
}
|
||||
|
||||
protected String isResolutionValid() {
|
||||
RenderSettings.EncodingPreset preset = encodingPresetDropdown.getSelectedValue();
|
||||
RenderSettings.RenderMethod method = renderMethodDropdown.getSelectedValue();
|
||||
int videoWidth = this.videoWidth.getInteger();
|
||||
int videoHeight = this.videoHeight.getInteger();
|
||||
|
||||
// Make sure the export arguments haven't been changed manually
|
||||
if (exportArguments.getText().equals(preset.getValue())) {
|
||||
// Yuv420 requires both dimensions to be even
|
||||
if (preset.isYuv420()
|
||||
&& (videoWidth % 2 != 0 || videoHeight % 2 != 0)) {
|
||||
return "replaymod.gui.rendersettings.customresolution.warning.yuv420";
|
||||
}
|
||||
}
|
||||
|
||||
if (method == RenderSettings.RenderMethod.CUBIC
|
||||
&& (videoWidth * 3 / 4 != videoHeight || videoWidth * 3 % 4 != 0)) {
|
||||
return "replaymod.gui.rendersettings.customresolution.warning.cubic";
|
||||
}
|
||||
if (method == RenderSettings.RenderMethod.EQUIRECTANGULAR
|
||||
&& (videoWidth / 2 != videoHeight || videoWidth % 2 != 0)) {
|
||||
return "replaymod.gui.rendersettings.customresolution.warning.equirectangular";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void load(RenderSettings settings) {
|
||||
renderMethodDropdown.setSelected(settings.getRenderMethod());
|
||||
encodingPresetDropdown.setSelected(settings.getEncodingPreset());
|
||||
videoWidth.setValue(settings.getVideoWidth());
|
||||
videoHeight.setValue(settings.getVideoHeight());
|
||||
frameRateSlider.setValue(settings.getFramesPerSecond() - 10);
|
||||
if (settings.getBitRate() % (1 << 20) == 0) {
|
||||
bitRateField.setValue(settings.getBitRate() >> 20);
|
||||
bitRateUnit.setSelected(2);
|
||||
} else if (settings.getBitRate() % (1 << 10) == 0) {
|
||||
bitRateField.setValue(settings.getBitRate() >> 10);
|
||||
bitRateUnit.setSelected(1);
|
||||
} else {
|
||||
bitRateField.setValue(settings.getBitRate());
|
||||
bitRateUnit.setSelected(0);
|
||||
}
|
||||
if (settings.getOutputFile() == null) {
|
||||
outputFile = generateOutputFile(settings.getEncodingPreset());
|
||||
outputFileManuallySet = false;
|
||||
} else {
|
||||
outputFile = settings.getOutputFile();
|
||||
outputFileManuallySet = true;
|
||||
}
|
||||
outputFileButton.setLabel(outputFile.getName());
|
||||
nametagCheckbox.setChecked(settings.isRenderNameTags());
|
||||
stabilizeYaw.setChecked(settings.isStabilizeYaw());
|
||||
stabilizePitch.setChecked(settings.isStabilizePitch());
|
||||
stabilizeRoll.setChecked(settings.isStabilizeRoll());
|
||||
if (settings.getChromaKeyingColor() == null) {
|
||||
chromaKeyingCheckbox.setChecked(false);
|
||||
chromaKeyingColor.setColor(Colors.GREEN);
|
||||
} else {
|
||||
chromaKeyingCheckbox.setChecked(true);
|
||||
chromaKeyingColor.setColor(settings.getChromaKeyingColor());
|
||||
}
|
||||
inject360Metadata.setChecked(settings.isInject360Metadata());
|
||||
exportCommand.setText(settings.getExportCommand());
|
||||
exportArguments.setText(settings.getExportArguments());
|
||||
}
|
||||
|
||||
public RenderSettings save(boolean saveOutputFile) {
|
||||
return new RenderSettings(
|
||||
renderMethodDropdown.getSelectedValue(),
|
||||
encodingPresetDropdown.getSelectedValue(),
|
||||
videoWidth.getInteger(),
|
||||
videoHeight.getInteger(),
|
||||
frameRateSlider.getValue() + 10,
|
||||
bitRateField.getInteger() << (10 * bitRateUnit.getSelected()),
|
||||
saveOutputFile ? outputFile : null,
|
||||
nametagCheckbox.isChecked(),
|
||||
stabilizeYaw.isChecked(),
|
||||
stabilizePitch.isChecked(),
|
||||
stabilizeRoll.isChecked(),
|
||||
chromaKeyingCheckbox.isChecked() ? chromaKeyingColor.getColor() : null,
|
||||
inject360Metadata.isChecked(),
|
||||
exportCommand.getText(),
|
||||
exportArguments.getText(),
|
||||
net.minecraft.client.gui.GuiScreen.isCtrlKeyDown()
|
||||
);
|
||||
}
|
||||
|
||||
private File generateOutputFile(RenderSettings.EncodingPreset encodingPreset) {
|
||||
String fileName = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date());
|
||||
return new File("replay_videos/" + fileName + "." + encodingPreset.getFileExtension());
|
||||
}
|
||||
|
||||
private RenderSettings getDefaultRenderSettings() {
|
||||
return new RenderSettings(RenderSettings.RenderMethod.DEFAULT, RenderSettings.EncodingPreset.MP4_DEFAULT, 1920, 1080, 60, 10 << 20, null,
|
||||
true, false, false, false, null, false, "", RenderSettings.EncodingPreset.MP4_DEFAULT.getValue(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
RenderSettings settings = save(false);
|
||||
String json = new Gson().toJson(settings);
|
||||
Configuration config = ReplayModRender.instance.getConfiguration();
|
||||
getConfigProperty(config).set(json);
|
||||
config.save();
|
||||
}
|
||||
|
||||
private Property getConfigProperty(Configuration configuration) {
|
||||
return configuration.get("rendersettings", "settings", "{}",
|
||||
"Last state of the render settings GUI. Internal use only.");
|
||||
}
|
||||
}
|
||||
284
src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java
Normal file
284
src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java
Normal file
@@ -0,0 +1,284 @@
|
||||
package com.replaymod.render.gui;
|
||||
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.rendering.VideoRenderer;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||
import de.johni0702.minecraft.gui.element.GuiButton;
|
||||
import de.johni0702.minecraft.gui.element.GuiCheckbox;
|
||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import eu.crushedpixel.replaymod.utils.BoundingUtils;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
import org.lwjgl.util.ReadablePoint;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static net.minecraft.client.renderer.GlStateManager.bindTexture;
|
||||
|
||||
public class GuiVideoRenderer extends GuiScreen {
|
||||
private static final ResourceLocation NO_PREVIEW_TEXTURE = new ResourceLocation("replaymod", "logo.jpg");
|
||||
|
||||
private final VideoRenderer renderer;
|
||||
|
||||
public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.rendering.title");
|
||||
public final GuiPanel imagePanel = new GuiPanel(){
|
||||
@Override
|
||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||
if (previewCheckbox.isChecked()) {
|
||||
renderPreview(renderer, size);
|
||||
} else {
|
||||
renderNoPreview(renderer, size);
|
||||
}
|
||||
}
|
||||
}.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
public final GuiCheckbox previewCheckbox = new GuiCheckbox().setI18nLabel("replaymod.gui.rendering.preview");
|
||||
public final GuiLabel renderTime = new GuiLabel();
|
||||
public final GuiLabel remainingTime = new GuiLabel();
|
||||
public final GuiProgressBar progressBar = new GuiProgressBar();
|
||||
public final GuiPanel buttonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4));
|
||||
public final GuiButton pauseButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (renderer.isPaused()) {
|
||||
pauseButton.setI18nLabel("replaymod.gui.rendering.pause");
|
||||
renderer.setPaused(false);
|
||||
} else {
|
||||
pauseButton.setI18nLabel("replaymod.gui.rendering.resume");
|
||||
renderer.setPaused(true);
|
||||
}
|
||||
}
|
||||
}).setI18nLabel("replaymod.gui.rendering.pause").setSize(150, 20);
|
||||
|
||||
public final GuiButton cancelButton = new GuiButton(buttonPanel){
|
||||
boolean waitingForConfirmation;
|
||||
|
||||
@Override
|
||||
public boolean mouseClick(ReadablePoint position, int button) {
|
||||
boolean result = super.mouseClick(position, button);
|
||||
if (waitingForConfirmation && !result) {
|
||||
setI18nLabel("replaymod.gui.rendering.cancel");
|
||||
waitingForConfirmation = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
super.onClick();
|
||||
if (!waitingForConfirmation) {
|
||||
setI18nLabel("replaymod.gui.rendering.cancel.callback");
|
||||
waitingForConfirmation = true;
|
||||
} else {
|
||||
renderer.cancel();
|
||||
}
|
||||
}
|
||||
}.setI18nLabel("replaymod.gui.rendering.cancel").setSize(150, 20);
|
||||
|
||||
private DynamicTexture previewTexture;
|
||||
private boolean previewTextureDirty;
|
||||
|
||||
{
|
||||
final GuiPanel contentPanel = new GuiPanel(this).setLayout(new CustomLayout<GuiPanel>() {
|
||||
@Override
|
||||
protected void layout(GuiPanel container, int width, int height) {
|
||||
size(progressBar, width, 20);
|
||||
pos(title, width / 2 - width(title) / 2, 0);
|
||||
pos(imagePanel, 0, y(title) + height(title) + 5);
|
||||
pos(buttonPanel, width / 2 - width(buttonPanel) / 2, height - height(buttonPanel));
|
||||
pos(progressBar, width / 2 - width(progressBar) / 2, y(buttonPanel) - 5 - height(progressBar));
|
||||
pos(renderTime, 0, y(progressBar) - 2 - height(renderTime));
|
||||
pos(remainingTime, width - width(remainingTime), y(progressBar) - 2 - height(renderTime));
|
||||
pos(previewCheckbox, width / 2 - width(previewCheckbox) / 2, y(renderTime) - 10 - height(previewCheckbox));
|
||||
size(imagePanel, width, y(previewCheckbox) - 5 - y(imagePanel));
|
||||
}
|
||||
}).addElements(null, title, imagePanel, previewCheckbox, renderTime, remainingTime, progressBar, buttonPanel);
|
||||
setLayout(new CustomLayout<GuiScreen>() {
|
||||
@Override
|
||||
protected void layout(GuiScreen container, int width, int height) {
|
||||
// Make sure we have some space around our central GUI components
|
||||
pos(contentPanel, 5, 3);
|
||||
size(contentPanel, width - 10, height - 10);
|
||||
}
|
||||
});
|
||||
setBackground(Background.DIRT);
|
||||
}
|
||||
|
||||
public GuiVideoRenderer(VideoRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
//the total render time
|
||||
private int renderTimeTaken = 0;
|
||||
|
||||
//the time at which the Screen was last updated
|
||||
private long prevTime = -1;
|
||||
|
||||
//the time at which the rendering of the current frame started
|
||||
private long frameStartTime = -1;
|
||||
|
||||
//the amount of frames that were rendered when the time left was last updated
|
||||
private int prevRenderedFrames = 0;
|
||||
|
||||
//the estimated render time that is left (in seconds)
|
||||
private int renderTimeLeft = 0;
|
||||
|
||||
//each of the durations the rendering process took for the last 50 frames
|
||||
private int[] renderTimes = new int[50];
|
||||
//the algorithm's current position in the renderTimes array
|
||||
private int currentIndex = 0;
|
||||
|
||||
@Override
|
||||
public void draw(GuiRenderer guiRenderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||
long current = System.currentTimeMillis();
|
||||
|
||||
//first, update the total render time (only if rendering is not paused and has already started)
|
||||
if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) {
|
||||
renderTimeTaken += (current - prevTime);
|
||||
} else {
|
||||
//if the rendering process is paused, we have to update the frame start time to prevent huge render times
|
||||
//for the currently rendered frame(s)
|
||||
frameStartTime = current;
|
||||
}
|
||||
|
||||
//always update prevTime, so we don't get huge time jumps when unpausing the rendering process
|
||||
prevTime = current;
|
||||
|
||||
|
||||
//calculate estimated time left
|
||||
|
||||
//if the amount of rendered frames has increased since the last update
|
||||
if(prevRenderedFrames < renderer.getFramesDone()) {
|
||||
//we don't include the first frame in our calculations,
|
||||
//as setting up the rendering process takes a few seconds
|
||||
if(prevRenderedFrames > 0) {
|
||||
|
||||
//calculate the amount of frames that have been rendered since the last update
|
||||
int framesRendered = renderer.getFramesDone() - prevRenderedFrames;
|
||||
|
||||
//calculate the time it took to render these frames
|
||||
int renderTime = (int)(current - frameStartTime);
|
||||
|
||||
//calculate the average time it took for each of these frames to render
|
||||
int avgRenderTime = renderTime/framesRendered;
|
||||
|
||||
//add all of the average render times to the render times array
|
||||
for(int i=0; i<framesRendered; i++) {
|
||||
renderTimes[currentIndex] = avgRenderTime;
|
||||
currentIndex++;
|
||||
if(currentIndex >= renderTimes.length) currentIndex = 0;
|
||||
}
|
||||
|
||||
//the renderTimes array initially contains lots of zeros,
|
||||
//so we count the amount of valid timespans
|
||||
int validValues = 0;
|
||||
|
||||
int totalTime = 0;
|
||||
for(int i : renderTimes) {
|
||||
if(i > 0) {
|
||||
totalTime += i;
|
||||
validValues++;
|
||||
}
|
||||
}
|
||||
|
||||
//calculate the average render time for the previous [up to 50] frames
|
||||
float averageRenderTime = validValues > 0 ? totalTime / validValues : 0;
|
||||
|
||||
//calculate the remaining render time in seconds
|
||||
renderTimeLeft = Math.round((averageRenderTime * (renderer.getTotalFrames() - renderer.getFramesDone())) / 1000);
|
||||
}
|
||||
|
||||
//set the render start time of the next frame(s) to the current timestamp
|
||||
frameStartTime = current;
|
||||
//update the amount of rendered frames for the last calculation
|
||||
prevRenderedFrames = renderer.getFramesDone();
|
||||
}
|
||||
|
||||
renderTime.setText(I18n.format("replaymod.gui.rendering.timetaken") + ": " + secToString(renderTimeTaken/1000));
|
||||
remainingTime.setText(I18n.format("replaymod.gui.rendering.timeleft") + ": " + secToString(renderTimeLeft));
|
||||
|
||||
int framesDone = renderer.getFramesDone(), framesTotal = renderer.getTotalFrames();
|
||||
progressBar.setI18nLabel("replaymod.gui.rendering.progress", framesDone, framesTotal);
|
||||
progressBar.setProgress((float) framesDone / framesTotal);
|
||||
|
||||
super.draw(guiRenderer, size, renderInfo);
|
||||
}
|
||||
|
||||
private String secToString(int seconds) {
|
||||
int hours = seconds/(60*60);
|
||||
int min = seconds/60 - hours*60;
|
||||
int sec = seconds - ((min*60) + (hours*60*60));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if(hours > 0) builder.append(hours).append(I18n.format("replaymod.gui.hours"));
|
||||
if(min > 0 || hours > 0) builder.append(min).append(I18n.format("replaymod.gui.minutes"));
|
||||
builder.append(sec).append(I18n.format("replaymod.gui.seconds"));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private synchronized void renderPreview(GuiRenderer guiRenderer, ReadableDimension size) {
|
||||
ReadableDimension videoSize = renderer.getFrameSize();
|
||||
final int videoWidth = videoSize.getWidth();
|
||||
final int videoHeight = videoSize.getHeight();
|
||||
|
||||
if (previewTexture == null) {
|
||||
previewTexture = new DynamicTexture(videoWidth, videoHeight) {
|
||||
@Override
|
||||
public void updateDynamicTexture() {
|
||||
bindTexture(getGlTextureId());
|
||||
TextureUtil.uploadTextureSub(0, getTextureData(), videoWidth, videoHeight, 0, 0, true, false, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (previewTextureDirty) {
|
||||
previewTexture.updateDynamicTexture();
|
||||
previewTextureDirty = false;
|
||||
}
|
||||
|
||||
guiRenderer.bindTexture(previewTexture);
|
||||
renderPreviewTexture(guiRenderer, size, videoWidth, videoHeight);
|
||||
}
|
||||
|
||||
private void renderNoPreview(GuiRenderer guiRenderer, ReadableDimension size) {
|
||||
guiRenderer.bindTexture(NO_PREVIEW_TEXTURE);
|
||||
renderPreviewTexture(guiRenderer, size, 1280, 720);
|
||||
}
|
||||
|
||||
private void renderPreviewTexture(GuiRenderer guiRenderer, ReadableDimension size,
|
||||
int videoWidth, int videoHeight) {
|
||||
Dimension dimension = BoundingUtils.fitIntoBounds(new Dimension(videoWidth, videoHeight), size);
|
||||
|
||||
int width = dimension.getWidth();
|
||||
int height = dimension.getHeight();
|
||||
int x = (size.getWidth() - width) / 2;
|
||||
int y = (size.getHeight() - height) / 2;
|
||||
|
||||
guiRenderer.drawTexturedRect(x, y, 0, 0, width, height, videoWidth, videoHeight, videoWidth, videoHeight);
|
||||
}
|
||||
|
||||
public void updatePreview(RGBFrame frame) {
|
||||
if (previewCheckbox.isChecked() && previewTexture != null) {
|
||||
ByteBuffer buffer = frame.getByteBuffer();
|
||||
buffer.mark();
|
||||
synchronized (this) {
|
||||
int[] data = previewTexture.getTextureData();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff);
|
||||
}
|
||||
previewTextureDirty = true;
|
||||
}
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.renderer;
|
||||
package com.replaymod.render.hooks;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.JailingQueue;
|
||||
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
|
||||
@@ -7,9 +7,6 @@ import net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator;
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderWorker;
|
||||
import net.minecraft.client.renderer.chunk.RenderChunk;
|
||||
import net.minecraft.client.renderer.culling.ICamera;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.BlockPos;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Iterator;
|
||||
@@ -31,42 +28,31 @@ public class ChunkLoadingRenderGlobal {
|
||||
|
||||
int workerThreads = renderDispatcher.listThreadedWorkers.size();
|
||||
BlockingQueue<Object> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
|
||||
workerJailingQueue = new JailingQueue<Object>(queueChunkUpdates);
|
||||
workerJailingQueue = new JailingQueue<>(queueChunkUpdates);
|
||||
renderDispatcher.queueChunkUpdates = workerJailingQueue;
|
||||
ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null);
|
||||
element.finish();
|
||||
for (int i = 0; i < workerThreads; i++) {
|
||||
queueChunkUpdates.add(element);
|
||||
}
|
||||
|
||||
// Temporary workaround for dead lock, will be replaced by a new (ShaderMod compatible) mechanism later
|
||||
//noinspection StatementWithEmptyBody
|
||||
while (renderDispatcher.runChunkUploads(0)) {}
|
||||
|
||||
workerJailingQueue.jail(workerThreads);
|
||||
renderDispatcher.queueChunkUpdates = queueChunkUpdates;
|
||||
|
||||
try {
|
||||
Field hookField = RenderGlobal.class.getField("hook");
|
||||
Field hookField = RenderGlobal.class.getField("replayModRender_hook");
|
||||
hookField.set(hooked, this);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new Error(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // Method called by ASM hook
|
||||
public void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) {
|
||||
// There has to be a better way to force minecraft into queueing all chunks at once
|
||||
// Someone should probably find and implement it!
|
||||
do {
|
||||
original_setupTerrain(viewEntity, partialTicks, camera, frame++, playerSpectator);
|
||||
} while (hooked.displayListEntitiesDirty);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // Method called by ASM hook
|
||||
public boolean isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "unused"}) // Method called by ASM hook
|
||||
public void updateChunks(long finishTimeNano) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void updateChunks() {
|
||||
while (renderDispatcher.runChunkUploads(0)) {
|
||||
hooked.displayListEntitiesDirty = true;
|
||||
}
|
||||
@@ -88,35 +74,21 @@ public class ChunkLoadingRenderGlobal {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void original_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) {
|
||||
// Method body generated by ASM
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean original_isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) {
|
||||
// Method body generated by ASM
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void original_updateChunks(long finishTimeNano) {
|
||||
// Method body generated by ASM
|
||||
}
|
||||
|
||||
public void uninstall() {
|
||||
workerJailingQueue.freeAll();
|
||||
|
||||
try {
|
||||
Field hookField = RenderGlobal.class.getField("hook");
|
||||
Field hookField = RenderGlobal.class.getField("replayModRender_hook");
|
||||
hookField.set(hooked, null);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new Error(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int nextFrameId() {
|
||||
return frame++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom ChunkRenderWorker class providing access to the protected processTask method
|
||||
*/
|
||||
@@ -1,8 +1,8 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
package com.replaymod.render.hooks;
|
||||
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.video.capturer.CaptureData;
|
||||
import eu.crushedpixel.replaymod.video.capturer.WorldRenderer;
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import com.replaymod.render.capturer.CaptureData;
|
||||
import com.replaymod.render.capturer.WorldRenderer;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.culling.ClippingHelper;
|
||||
@@ -16,14 +16,14 @@ public class EntityRendererHandler implements WorldRenderer {
|
||||
public final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
@Getter
|
||||
protected final RenderOptions options;
|
||||
protected final RenderSettings settings;
|
||||
|
||||
public CaptureData data;
|
||||
|
||||
public EntityRendererHandler(RenderOptions options) {
|
||||
this.options = options;
|
||||
public EntityRendererHandler(RenderSettings settings) {
|
||||
this.settings = settings;
|
||||
|
||||
((IEntityRenderer) mc.entityRenderer).setHandler(this);
|
||||
((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(this);
|
||||
}
|
||||
|
||||
public void withDisplaySize(int displayWidth, int displayHeight, Runnable runnable) {
|
||||
@@ -61,7 +61,7 @@ public class EntityRendererHandler implements WorldRenderer {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
((IEntityRenderer) mc.entityRenderer).setHandler(null);
|
||||
((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(null);
|
||||
}
|
||||
|
||||
public static final class NoCullingClippingHelper extends ClippingHelper {
|
||||
@@ -72,11 +72,11 @@ public class EntityRendererHandler implements WorldRenderer {
|
||||
}
|
||||
|
||||
public interface GluPerspective {
|
||||
void gluPerspective(float fovY, float aspect, float zNear, float zFar);
|
||||
void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar);
|
||||
}
|
||||
|
||||
public interface IEntityRenderer {
|
||||
void setHandler(EntityRendererHandler handler);
|
||||
EntityRendererHandler getHandler();
|
||||
void replayModRender_setHandler(EntityRendererHandler handler);
|
||||
EntityRendererHandler replayModRender_getHandler();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.replaymod.render.hooks;
|
||||
|
||||
import com.replaymod.core.utils.WrappedTimer;
|
||||
import net.minecraft.util.Timer;
|
||||
|
||||
/**
|
||||
* Wrapper around the current timer that prevents the timer from advancing by itself.
|
||||
*/
|
||||
public class RenderReplayTimer extends WrappedTimer {
|
||||
private final Timer state = new Timer(0);
|
||||
|
||||
public RenderReplayTimer(Timer wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTimer() {
|
||||
copy(this, state); // Save our current state
|
||||
super.updateTimer(); // Update current state
|
||||
copy(state, this); // Restore our old state
|
||||
}
|
||||
|
||||
public Timer getWrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.video.metadata;
|
||||
package com.replaymod.render.metadata;
|
||||
|
||||
import com.coremedia.iso.IsoFile;
|
||||
import com.coremedia.iso.boxes.*;
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.mixin;
|
||||
package com.replaymod.render.mixin;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import eu.crushedpixel.replaymod.video.capturer.CubicOpenGlFrameCapturer;
|
||||
import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.particle.EffectRenderer;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
@@ -12,7 +12,7 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(EffectRenderer.class)
|
||||
public class MixinEffectRenderer {
|
||||
public abstract class MixinEffectRenderer {
|
||||
@Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;func_180434_a(Lnet/minecraft/client/renderer/WorldRenderer;Lnet/minecraft/entity/Entity;FFFFFF)V"))
|
||||
private void renderNormalParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
|
||||
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
@@ -27,7 +27,7 @@ public class MixinEffectRenderer {
|
||||
|
||||
private void renderParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
|
||||
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler();
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
|
||||
if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
// Align all particles towards the camera
|
||||
double dx = fx.prevPosX + (fx.posX - fx.prevPosX) * partialTicks - view.posX;
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.replaymod.render.mixin;
|
||||
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
|
||||
import com.replaymod.render.capturer.StereoscopicOpenGlFrameCapturer;
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import eu.crushedpixel.replaymod.renderer.SpectatorRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.MovingObjectPosition;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.util.ReadableColor;
|
||||
import org.lwjgl.util.glu.Project;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = EntityRenderer.class)
|
||||
public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer, EntityRendererHandler.GluPerspective {
|
||||
@Shadow
|
||||
public Minecraft mc;
|
||||
|
||||
private EntityRendererHandler replayModRender_handler;
|
||||
|
||||
@Shadow(remap = false)
|
||||
private SpectatorRenderer spectatorRenderer;
|
||||
|
||||
@Override
|
||||
public void replayModRender_setHandler(EntityRendererHandler handler) {
|
||||
this.replayModRender_handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRendererHandler replayModRender_getHandler() {
|
||||
return replayModRender_handler;
|
||||
}
|
||||
|
||||
@Inject(method = "setupFog", at = @At("HEAD"), cancellable = true)
|
||||
private void replayModRender_onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) {
|
||||
if (replayModRender_handler == null) return;
|
||||
if (replayModRender_handler.getSettings().getChromaKeyingColor() != null) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/culling/Frustum;setPosition(DDD)V"))
|
||||
public void replayModRender_createNoCullingFrustum(Frustum frustum, double x, double y, double z) {
|
||||
if (replayModRender_handler != null) {
|
||||
frustum.clippingHelper = new EntityRendererHandler.NoCullingClippingHelper();
|
||||
}
|
||||
frustum.setPosition(x, y, z);
|
||||
}
|
||||
|
||||
@Inject(method = "orientCamera", at = @At("HEAD"))
|
||||
private void replayModRender_resetRotationIfNeeded(float partialTicks, CallbackInfo ci) {
|
||||
if (replayModRender_handler != null) {
|
||||
Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
RenderSettings settings = replayModRender_handler.getSettings();
|
||||
if (settings.isStabilizeYaw()) {
|
||||
entity.prevRotationYaw = entity.rotationYaw = 0;
|
||||
}
|
||||
if (settings.isStabilizePitch()) {
|
||||
entity.prevRotationPitch = entity.rotationPitch = 0;
|
||||
}
|
||||
if (settings.isStabilizeRoll() && entity instanceof CameraEntity) {
|
||||
((CameraEntity) entity).roll = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float orgYaw;
|
||||
private float orgPitch;
|
||||
private float orgPrevYaw;
|
||||
private float orgPrevPitch;
|
||||
private float orgRoll;
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At("HEAD"))
|
||||
private void replayModRender_beforeSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (replayModRender_handler != null) {
|
||||
Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
orgYaw = entity.rotationYaw;
|
||||
orgPitch = entity.rotationPitch;
|
||||
orgPrevYaw = entity.prevRotationYaw;
|
||||
orgPrevPitch = entity.prevRotationPitch;
|
||||
orgRoll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At("RETURN"))
|
||||
private void replayModRender_afterSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (replayModRender_handler != null) {
|
||||
Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
entity.rotationYaw = orgYaw;
|
||||
entity.rotationPitch = orgPitch;
|
||||
entity.prevRotationYaw = orgPrevYaw;
|
||||
entity.prevRotationPitch = orgPrevPitch;
|
||||
if (entity instanceof CameraEntity) {
|
||||
((CameraEntity) entity).roll = orgRoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderHand", at = @At("HEAD"), cancellable = true)
|
||||
private void replayModRender_renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (replayModRender_handler != null) {
|
||||
if (replayModRender_handler.data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
ci.cancel();
|
||||
return; // No spectator hands during 360° view, we wouldn't even know where to put it
|
||||
}
|
||||
Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof CameraEntity)) {
|
||||
renderPass = replayModRender_handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0;
|
||||
spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass);
|
||||
ci.cancel(); // No further hand rendering needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V"))
|
||||
private void replayModRender_drawSelectionBox(RenderGlobal instance, EntityPlayer player, MovingObjectPosition mop, int alwaysZero, float partialTicks) {
|
||||
if (replayModRender_handler == null) {
|
||||
instance.drawSelectionBox(player, mop, alwaysZero, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
private int orgRenderDistanceChunks;
|
||||
|
||||
@Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0))
|
||||
private void replayModRender_beforeRenderSky(CallbackInfo ci) {
|
||||
if (replayModRender_handler != null && replayModRender_handler.getSettings().getChromaKeyingColor() != null) {
|
||||
GameSettings settings = Minecraft.getMinecraft().gameSettings;
|
||||
orgRenderDistanceChunks = settings.renderDistanceChunks;
|
||||
settings.renderDistanceChunks = 5; // Set render distance to 5 so we're always rendering sky when chroma keying
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(FI)V"))
|
||||
private void replayModRender_renderSky(RenderGlobal instance, float partialTicks, int renderPass) {
|
||||
if (replayModRender_handler != null && replayModRender_handler.getSettings().getChromaKeyingColor() != null) {
|
||||
Minecraft.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks;
|
||||
ReadableColor color = replayModRender_handler.getSettings().getChromaKeyingColor();
|
||||
GlStateManager.clearColor(color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f, 1);
|
||||
GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
} else {
|
||||
instance.renderSky(partialTicks, renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Stereoscopic Renderer
|
||||
*/
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 0))
|
||||
private void replayModRender_setupStereoscopicProjection(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (replayModRender_getHandler() != null) {
|
||||
if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) {
|
||||
GlStateManager.translate(0.07, 0, 0);
|
||||
} else if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) {
|
||||
GlStateManager.translate(-0.07, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 1))
|
||||
private void replayModRender_setupStereoscopicModelView(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (replayModRender_getHandler() != null) {
|
||||
if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) {
|
||||
GlStateManager.translate(0.1, 0, 0);
|
||||
} else if (replayModRender_getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) {
|
||||
GlStateManager.translate(-0.1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cubic Renderer
|
||||
*/
|
||||
|
||||
@Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false))
|
||||
private void replayModRender_gluPerspective$0(float fovY, float aspect, float zNear, float zFar) {
|
||||
replayModRender_gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false))
|
||||
private void replayModRender_gluPerspective$1(float fovY, float aspect, float zNear, float zFar) {
|
||||
replayModRender_gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false))
|
||||
private void replayModRender_gluPerspective$2(float fovY, float aspect, float zNear, float zFar) {
|
||||
replayModRender_gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) {
|
||||
if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
fovY = 90;
|
||||
aspect = 1;
|
||||
}
|
||||
Project.gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Inject(method = "orientCamera", at = @At("HEAD"))
|
||||
private void replayModRender_setupCubicFrameRotation(float partialTicks, CallbackInfo ci) {
|
||||
if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
switch ((CubicOpenGlFrameCapturer.Data) replayModRender_getHandler().data) {
|
||||
case FRONT:
|
||||
GlStateManager.rotate(0, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case RIGHT:
|
||||
GlStateManager.rotate(90, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case BACK:
|
||||
GlStateManager.rotate(180, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case LEFT:
|
||||
GlStateManager.rotate(-90, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case BOTTOM:
|
||||
GlStateManager.rotate(90, 1.0F, 0.0F, 0.0F);
|
||||
break;
|
||||
case TOP:
|
||||
GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F);
|
||||
break;
|
||||
}
|
||||
|
||||
// Minecraft goes back a little so we have to revert that
|
||||
GlStateManager.translate(0.0F, 0.0F, 0.1F);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.mixin;
|
||||
package com.replaymod.render.mixin;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.Render;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -12,9 +12,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(Render.class)
|
||||
public abstract class MixinRender {
|
||||
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
|
||||
private void areAllNamesHidden(Entity entity, CallbackInfoReturnable<Boolean> ci) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler();
|
||||
if (handler != null && handler.getOptions().isHideNameTags()) {
|
||||
private void replayModRender_areAllNamesHidden(Entity entity, CallbackInfoReturnable<Boolean> ci) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
|
||||
if (handler != null && !handler.getSettings().isRenderNameTags()) {
|
||||
ci.setReturnValue(false);
|
||||
ci.cancel();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.replaymod.render.mixin;
|
||||
|
||||
import com.replaymod.render.hooks.ChunkLoadingRenderGlobal;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
|
||||
import net.minecraft.client.renderer.chunk.RenderChunk;
|
||||
import net.minecraft.client.renderer.culling.ICamera;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(RenderGlobal.class)
|
||||
public abstract class MixinRenderGlobal {
|
||||
public ChunkLoadingRenderGlobal replayModRender_hook;
|
||||
private boolean replayModRender_passThroughSetupTerrain;
|
||||
|
||||
@Shadow
|
||||
public boolean displayListEntitiesDirty;
|
||||
|
||||
@Shadow
|
||||
public ChunkRenderDispatcher renderDispatcher;
|
||||
|
||||
@Shadow
|
||||
public abstract void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera,
|
||||
int frameCount, boolean playerSpectator);
|
||||
|
||||
@Inject(method = "setupTerrain", at = @At("HEAD"), cancellable = true)
|
||||
public void replayModRender_setupTerrain(Entity viewEntity, double partialTicks, ICamera camera,
|
||||
int frameCount, boolean playerSpectator, CallbackInfo ci) {
|
||||
if (replayModRender_hook != null && !replayModRender_passThroughSetupTerrain) {
|
||||
replayModRender_passThroughSetupTerrain = true;
|
||||
|
||||
do {
|
||||
setupTerrain(viewEntity, partialTicks, camera, replayModRender_hook.nextFrameId(), playerSpectator);
|
||||
} while (displayListEntitiesDirty);
|
||||
|
||||
replayModRender_passThroughSetupTerrain = false;
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "isPositionInRenderChunk", at = @At("HEAD"), cancellable = true)
|
||||
public void replayModRender_isPositionInRenderChunk(BlockPos pos, RenderChunk chunk, CallbackInfoReturnable<Boolean> ci) {
|
||||
if (replayModRender_hook != null) {
|
||||
ci.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "updateChunks", at = @At("HEAD"), cancellable = true)
|
||||
public void replayModRender_updateChunks(long finishTimeNano, CallbackInfo ci) {
|
||||
if (replayModRender_hook != null) {
|
||||
replayModRender_hook.updateChunks();
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.replaymod.render.mixin;
|
||||
|
||||
import com.replaymod.render.capturer.CubicOpenGlFrameCapturer;
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(RenderManager.class)
|
||||
public abstract class MixinRenderManager {
|
||||
@Shadow
|
||||
private float playerViewX;
|
||||
|
||||
@Shadow
|
||||
private float playerViewY;
|
||||
|
||||
@Inject(method = "doRenderEntity", at = @At("HEAD"))
|
||||
private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
|
||||
if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
|
||||
double yaw = -Math.atan2(dx, dz);
|
||||
playerViewX = (float) Math.toDegrees(pitch);
|
||||
playerViewY = (float) Math.toDegrees(yaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.mixin;
|
||||
package com.replaymod.render.mixin;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
@@ -14,9 +14,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
@Mixin(RendererLivingEntity.class)
|
||||
public abstract class MixinRendererLivingEntity {
|
||||
@Inject(method = "canRenderName", at = @At("HEAD"), cancellable = true)
|
||||
private void areAllNamesHidden(EntityLivingBase entity, CallbackInfoReturnable<Boolean> ci) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler();
|
||||
if (handler != null && handler.getOptions().isHideNameTags()) {
|
||||
private void replayModRender_areAllNamesHidden(EntityLivingBase entity, CallbackInfoReturnable<Boolean> ci) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler();
|
||||
if (handler != null && !handler.getSettings().isRenderNameTags()) {
|
||||
ci.setReturnValue(false); //this calls the cancel method
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public abstract class MixinRendererLivingEntity {
|
||||
}
|
||||
|
||||
@Redirect(method = "renderModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/EntityLivingBase;isInvisibleToPlayer(Lnet/minecraft/entity/player/EntityPlayer;)Z"))
|
||||
private boolean shouldInvisibleNotBeRendered(EntityLivingBase entity, EntityPlayer thePlayer) {
|
||||
private boolean replayModRender_shouldInvisibleNotBeRendered(EntityLivingBase entity, EntityPlayer thePlayer) {
|
||||
// TODO
|
||||
// if(ReplaySettings.ReplayOptions.renderInvisible.getValue() == Boolean.TRUE|| !ReplayHandler.isInReplay()) {
|
||||
// return entity.isInvisibleToPlayer(thePlayer);
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
package com.replaymod.render.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import eu.crushedpixel.replaymod.video.rendering.FrameProcessor;
|
||||
import com.replaymod.render.rendering.Frame;
|
||||
import com.replaymod.render.rendering.FrameProcessor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
package com.replaymod.render.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import org.lwjgl.util.Dimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static eu.crushedpixel.replaymod.utils.OpenGLUtils.openGlBytesToRBG;
|
||||
import static com.replaymod.render.utils.Utils.openGlBytesToRBG;
|
||||
|
||||
public class CubicToRGBProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, RGBFrame> {
|
||||
@Override
|
||||
@@ -1,8 +1,8 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
package com.replaymod.render.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.lwjgl.util.Dimension;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
package com.replaymod.render.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -1,14 +1,14 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
package com.replaymod.render.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||
import com.replaymod.render.utils.ByteBufferPool;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static eu.crushedpixel.replaymod.utils.OpenGLUtils.openGlBytesToRBG;
|
||||
import static com.replaymod.render.utils.Utils.openGlBytesToRBG;
|
||||
|
||||
public class StereoscopicToRGBProcessor extends AbstractFrameProcessor<StereoscopicOpenGlFrame, RGBFrame> {
|
||||
@Override
|
||||
5
src/main/java/com/replaymod/render/rendering/Frame.java
Normal file
5
src/main/java/com/replaymod/render/rendering/Frame.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
public interface Frame {
|
||||
int getFrameId();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
76
src/main/java/com/replaymod/render/rendering/Pipelines.java
Normal file
76
src/main/java/com/replaymod/render/rendering/Pipelines.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import com.replaymod.render.capturer.*;
|
||||
import com.replaymod.render.frame.CubicOpenGlFrame;
|
||||
import com.replaymod.render.frame.OpenGlFrame;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.frame.StereoscopicOpenGlFrame;
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import com.replaymod.render.processor.CubicToRGBProcessor;
|
||||
import com.replaymod.render.processor.EquirectangularToRGBProcessor;
|
||||
import com.replaymod.render.processor.OpenGlToRGBProcessor;
|
||||
import com.replaymod.render.processor.StereoscopicToRGBProcessor;
|
||||
import com.replaymod.render.utils.PixelBufferObject;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class Pipelines {
|
||||
public static Pipeline newPipeline(RenderSettings.RenderMethod method, RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
switch (method) {
|
||||
case DEFAULT:
|
||||
return newDefaultPipeline(renderInfo, consumer);
|
||||
case STEREOSCOPIC:
|
||||
return newStereoscopicPipeline(renderInfo, consumer);
|
||||
case CUBIC:
|
||||
return newCubicPipeline(renderInfo, consumer);
|
||||
case EQUIRECTANGULAR:
|
||||
return newEquirectangularPipeline(renderInfo, consumer);
|
||||
}
|
||||
throw new UnsupportedOperationException("Unknown method: " + method);
|
||||
}
|
||||
|
||||
public static Pipeline<OpenGlFrame, RGBFrame> newDefaultPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderSettings settings = renderInfo.getRenderSettings();
|
||||
FrameCapturer<OpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new SimplePboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo);
|
||||
} else {
|
||||
capturer = new SimpleOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo);
|
||||
}
|
||||
return new Pipeline<>(capturer, new OpenGlToRGBProcessor(), consumer);
|
||||
}
|
||||
|
||||
public static Pipeline<StereoscopicOpenGlFrame, RGBFrame> newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderSettings settings = renderInfo.getRenderSettings();
|
||||
FrameCapturer<StereoscopicOpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new StereoscopicPboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo);
|
||||
} else {
|
||||
capturer = new StereoscopicOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo);
|
||||
}
|
||||
return new Pipeline<>(capturer, new StereoscopicToRGBProcessor(), consumer);
|
||||
}
|
||||
|
||||
public static Pipeline<CubicOpenGlFrame, RGBFrame> newCubicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderSettings settings = renderInfo.getRenderSettings();
|
||||
FrameCapturer<CubicOpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4);
|
||||
} else {
|
||||
capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4);
|
||||
}
|
||||
return new Pipeline<>(capturer, new CubicToRGBProcessor(), consumer);
|
||||
}
|
||||
|
||||
public static Pipeline<CubicOpenGlFrame, RGBFrame> newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderSettings settings = renderInfo.getRenderSettings();
|
||||
FrameCapturer<CubicOpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4);
|
||||
} else {
|
||||
capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4);
|
||||
}
|
||||
return new Pipeline<>(capturer, new EquirectangularToRGBProcessor(settings.getVideoWidth() / 4), consumer);
|
||||
}
|
||||
}
|
||||
308
src/main/java/com/replaymod/render/rendering/VideoRenderer.java
Normal file
308
src/main/java/com/replaymod/render/rendering/VideoRenderer.java
Normal file
@@ -0,0 +1,308 @@
|
||||
package com.replaymod.render.rendering;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.render.RenderSettings;
|
||||
import com.replaymod.render.VideoWriter;
|
||||
import com.replaymod.render.capturer.RenderInfo;
|
||||
import com.replaymod.render.frame.RGBFrame;
|
||||
import com.replaymod.render.gui.GuiVideoRenderer;
|
||||
import com.replaymod.render.hooks.ChunkLoadingRenderGlobal;
|
||||
import com.replaymod.render.hooks.RenderReplayTimer;
|
||||
import com.replaymod.render.metadata.MetadataInjector;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.audio.SoundCategory;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.util.Timer;
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import static com.google.common.collect.Iterables.getLast;
|
||||
import static net.minecraft.client.renderer.GlStateManager.*;
|
||||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
||||
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
||||
|
||||
public class VideoRenderer implements RenderInfo {
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private final RenderSettings settings;
|
||||
private final ReplayHandler replayHandler;
|
||||
private final Timeline timeline;
|
||||
private final Pipeline renderingPipeline;
|
||||
private final VideoWriter videoWriter;
|
||||
|
||||
private int fps;
|
||||
private boolean mouseWasGrabbed;
|
||||
private boolean debugInfoWasShown;
|
||||
private Map originalSoundLevels;
|
||||
|
||||
private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal;
|
||||
|
||||
private int framesDone;
|
||||
private int totalFrames;
|
||||
|
||||
private final GuiVideoRenderer gui;
|
||||
private boolean paused;
|
||||
private boolean cancelled;
|
||||
|
||||
public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException {
|
||||
this.settings = settings;
|
||||
this.replayHandler = replayHandler;
|
||||
this.timeline = timeline;
|
||||
this.gui = new GuiVideoRenderer(this);
|
||||
this.renderingPipeline = Pipelines.newPipeline(settings.getRenderMethod(), this,
|
||||
videoWriter = new VideoWriter(settings) {
|
||||
@Override
|
||||
public void consume(RGBFrame frame) {
|
||||
gui.updatePreview(frame);
|
||||
super.consume(frame);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this video.
|
||||
* @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed)
|
||||
*/
|
||||
public boolean renderVideo() {
|
||||
setup();
|
||||
|
||||
// Because this might take some time to prepare we'll render the GUI at least once to not confuse the user
|
||||
drawGui();
|
||||
|
||||
Timer timer = mc.timer;
|
||||
|
||||
// Play up to one second before starting to render
|
||||
// This is necessary in order to ensure that all entities have at least two position packets
|
||||
// and their first position in the recording is correct.
|
||||
// Note that it is impossible to also get the interpolation between their latest position
|
||||
// and the one in the recording correct as there's no reliable way to tell when the server ticks
|
||||
// or when we should be done with the interpolation of the entity
|
||||
Optional<Integer> optionalVideoStartTime = timeline.getValue(TimestampProperty.PROPERTY, 0);
|
||||
if (optionalVideoStartTime.isPresent()) {
|
||||
int videoStart = optionalVideoStartTime.get();
|
||||
|
||||
if (videoStart > 1000) {
|
||||
int replayTime = videoStart - 1000;
|
||||
timer.elapsedPartialTicks = timer.renderPartialTicks = 0;
|
||||
timer.timerSpeed = 1;
|
||||
while (replayTime < videoStart) {
|
||||
timer.elapsedTicks = 1;
|
||||
replayTime += 50;
|
||||
replayHandler.getReplaySender().sendPacketsTill(replayTime);
|
||||
tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mc.renderGlobal.renderEntitiesStartupCounter = 0;
|
||||
|
||||
renderingPipeline.run();
|
||||
|
||||
if (settings.isInject360Metadata()) {
|
||||
MetadataInjector.inject360Metadata(settings.getOutputFile());
|
||||
}
|
||||
|
||||
finish();
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float updateForNextFrame() {
|
||||
if (!settings.isHighPerformance() || framesDone % fps == 0) {
|
||||
drawGui();
|
||||
}
|
||||
|
||||
timeline.applyToGame(getVideoTime(), replayHandler);
|
||||
|
||||
int elapsedTicks = mc.timer.elapsedTicks;
|
||||
while (elapsedTicks-- > 0) {
|
||||
tick();
|
||||
}
|
||||
|
||||
framesDone++;
|
||||
return mc.timer.renderPartialTicks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderSettings getRenderSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
replayHandler.getReplaySender().setSyncModeAndWait();
|
||||
|
||||
if (!OpenGlHelper.isFramebufferEnabled()) {
|
||||
Display.setResizable(false);
|
||||
}
|
||||
if (mc.gameSettings.showDebugInfo) {
|
||||
debugInfoWasShown = true;
|
||||
mc.gameSettings.showDebugInfo = false;
|
||||
}
|
||||
if (Mouse.isGrabbed()) {
|
||||
mouseWasGrabbed = true;
|
||||
}
|
||||
Mouse.setGrabbed(false);
|
||||
mc.timer = new RenderReplayTimer(mc.timer);
|
||||
mc.timer.timerSpeed = 1;
|
||||
|
||||
// Mute all sounds except GUI sounds (buttons, etc.)
|
||||
Map<SoundCategory, Float> mutedSounds = new EnumMap<>(SoundCategory.class);
|
||||
for (SoundCategory category : SoundCategory.values()) {
|
||||
mutedSounds.put(category, 0f);
|
||||
}
|
||||
originalSoundLevels = mc.gameSettings.mapSoundLevels;
|
||||
mutedSounds.put(SoundCategory.MASTER, (Float) originalSoundLevels.get(SoundCategory.MASTER));
|
||||
mc.gameSettings.mapSoundLevels = mutedSounds;
|
||||
|
||||
fps = settings.getFramesPerSecond();
|
||||
|
||||
long duration = 0;
|
||||
for (Path path : timeline.getPaths()) {
|
||||
if (!path.isActive()) continue;
|
||||
|
||||
// Prepare path interpolations
|
||||
path.updateAll();
|
||||
// Find end time
|
||||
Collection<Keyframe> keyframes = path.getKeyframes();
|
||||
if (keyframes.size() > 0) {
|
||||
duration = Math.max(duration, getLast(keyframes).getTime());
|
||||
}
|
||||
}
|
||||
|
||||
totalFrames = (int) (duration*fps/1000);
|
||||
|
||||
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
gui.toMinecraft().setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
|
||||
|
||||
chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal);
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
replayHandler.getReplaySender().setAsyncMode(true);
|
||||
|
||||
if (!OpenGlHelper.isFramebufferEnabled()) {
|
||||
Display.setResizable(true);
|
||||
}
|
||||
mc.gameSettings.showDebugInfo = debugInfoWasShown;
|
||||
if (mouseWasGrabbed) {
|
||||
mc.mouseHelper.grabMouseCursor();
|
||||
}
|
||||
mc.timer = ((RenderReplayTimer) mc.timer).getWrapped();
|
||||
mc.gameSettings.mapSoundLevels = originalSoundLevels;
|
||||
mc.displayGuiScreen(null);
|
||||
if (chunkLoadingRenderGlobal != null) {
|
||||
chunkLoadingRenderGlobal.uninstall();
|
||||
}
|
||||
|
||||
ReplayMod.soundHandler.playRenderSuccessSound();
|
||||
|
||||
mc.displayGuiScreen(null);
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
synchronized (mc.scheduledTasks) {
|
||||
while (!mc.scheduledTasks.isEmpty()) {
|
||||
((FutureTask) mc.scheduledTasks.poll()).run();
|
||||
}
|
||||
}
|
||||
|
||||
mc.currentScreen = gui.toMinecraft();
|
||||
try {
|
||||
mc.runTick();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawGui() {
|
||||
do {
|
||||
pushMatrix();
|
||||
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
enableTexture2D();
|
||||
mc.getFramebuffer().bindFramebuffer(true);
|
||||
mc.entityRenderer.setupOverlayRendering();
|
||||
|
||||
try {
|
||||
gui.toMinecraft().handleInput();
|
||||
} catch (IOException e) {
|
||||
// That's a strange exception from this kind of method O_o
|
||||
// It isn't actually thrown here, so we'll deal with it the easy way
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth;
|
||||
int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1;
|
||||
gui.toMinecraft().drawScreen(mouseX, mouseY, 0);
|
||||
|
||||
mc.getFramebuffer().unbindFramebuffer();
|
||||
popMatrix();
|
||||
pushMatrix();
|
||||
mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight);
|
||||
popMatrix();
|
||||
|
||||
|
||||
// if not in high performance mode, update the gui size if screen size changed
|
||||
// otherwise just swap the progress gui to screen
|
||||
if (settings.isHighPerformance()) {
|
||||
Display.update();
|
||||
} else {
|
||||
mc.updateDisplay();
|
||||
}
|
||||
if (Mouse.isGrabbed()) {
|
||||
Mouse.setGrabbed(false);
|
||||
}
|
||||
if (paused) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (paused);
|
||||
}
|
||||
|
||||
public int getFramesDone() {
|
||||
return framesDone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableDimension getFrameSize() {
|
||||
return new Dimension(settings.getVideoWidth(), settings.getVideoHeight());
|
||||
}
|
||||
|
||||
public int getTotalFrames() {
|
||||
return totalFrames;
|
||||
}
|
||||
|
||||
public int getVideoTime() { return framesDone * 1000 / fps; }
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
videoWriter.abort();
|
||||
this.cancelled = true;
|
||||
renderingPipeline.cancel();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.crushedpixel.replaymod.utils;
|
||||
package com.replaymod.render.utils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.lwjgl.BufferUtils;
|
||||
@@ -40,9 +40,9 @@ public class ByteBufferPool {
|
||||
int size = buffer.capacity();
|
||||
List<SoftReference<ByteBuffer>> available = bufferPool.get(size);
|
||||
if (available == null) {
|
||||
available = new LinkedList<SoftReference<ByteBuffer>>();
|
||||
available = new LinkedList<>();
|
||||
bufferPool.put(size, available);
|
||||
}
|
||||
available.add(new SoftReference<ByteBuffer>(buffer));
|
||||
available.add(new SoftReference<>(buffer));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.opengl;
|
||||
package com.replaymod.render.utils;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.Api;
|
||||
import eu.crushedpixel.replaymod.utils.Objects;
|
||||
import com.google.common.base.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -15,7 +14,6 @@ import static org.lwjgl.opengl.ARBPixelBufferObject.*;
|
||||
import static org.lwjgl.opengl.GL15.*;
|
||||
import static org.lwjgl.opengl.GL21.GL_PIXEL_PACK_BUFFER;
|
||||
|
||||
@Api
|
||||
public class PixelBufferObject {
|
||||
@RequiredArgsConstructor
|
||||
public enum Usage {
|
||||
@@ -29,8 +27,8 @@ public class PixelBufferObject {
|
||||
public static final boolean SUPPORTED = GLContext.getCapabilities().GL_ARB_pixel_buffer_object || GLContext.getCapabilities().OpenGL15;
|
||||
private static final boolean arb = !GLContext.getCapabilities().OpenGL15;
|
||||
|
||||
private static ThreadLocal<Integer> bound = new ThreadLocal<Integer>();
|
||||
private static ThreadLocal<Integer> mapped = new ThreadLocal<Integer>();
|
||||
private static ThreadLocal<Integer> bound = new ThreadLocal<>();
|
||||
private static ThreadLocal<Integer> mapped = new ThreadLocal<>();
|
||||
|
||||
private final long size;
|
||||
private long handle;
|
||||
@@ -52,7 +50,6 @@ public class PixelBufferObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Api
|
||||
private int getHandle() {
|
||||
if (handle == -1) {
|
||||
throw new IllegalStateException("PBO not allocated.");
|
||||
@@ -60,7 +57,6 @@ public class PixelBufferObject {
|
||||
return (int) handle;
|
||||
}
|
||||
|
||||
@Api
|
||||
public void bind() {
|
||||
if (arb) {
|
||||
ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle());
|
||||
@@ -70,7 +66,6 @@ public class PixelBufferObject {
|
||||
bound.set(getHandle());
|
||||
}
|
||||
|
||||
@Api
|
||||
public void unbind() {
|
||||
checkBound();
|
||||
if (arb) {
|
||||
@@ -82,18 +77,17 @@ public class PixelBufferObject {
|
||||
}
|
||||
|
||||
private void checkBound() {
|
||||
if (!Objects.equals(getHandle(), bound.get())) {
|
||||
if (!Objects.equal(getHandle(), bound.get())) {
|
||||
throw new IllegalStateException("Buffer not bound.");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNotMapped() {
|
||||
if (Objects.equals(getHandle(), mapped.get())) {
|
||||
if (Objects.equal(getHandle(), mapped.get())) {
|
||||
throw new IllegalStateException("Buffer already mapped.");
|
||||
}
|
||||
}
|
||||
|
||||
@Api
|
||||
public ByteBuffer mapReadOnly() {
|
||||
checkBound();
|
||||
checkNotMapped();
|
||||
@@ -110,7 +104,6 @@ public class PixelBufferObject {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Api
|
||||
public ByteBuffer mapWriteOnly() {
|
||||
checkBound();
|
||||
checkNotMapped();
|
||||
@@ -127,7 +120,6 @@ public class PixelBufferObject {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Api
|
||||
public ByteBuffer mapReadWrite() {
|
||||
checkBound();
|
||||
checkNotMapped();
|
||||
@@ -144,10 +136,9 @@ public class PixelBufferObject {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Api
|
||||
public void unmap() {
|
||||
checkBound();
|
||||
if (!Objects.equals(mapped.get(), getHandle())) {
|
||||
if (!Objects.equal(mapped.get(), getHandle())) {
|
||||
throw new IllegalStateException("Buffer not mapped.");
|
||||
}
|
||||
if (arb) {
|
||||
@@ -158,7 +149,6 @@ public class PixelBufferObject {
|
||||
mapped.set(0);
|
||||
}
|
||||
|
||||
@Api
|
||||
public void delete() {
|
||||
if (handle != -1) {
|
||||
if (arb) {
|
||||
26
src/main/java/com/replaymod/render/utils/Utils.java
Normal file
26
src/main/java/com/replaymod/render/utils/Utils.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.replaymod.render.utils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class Utils {
|
||||
/**
|
||||
* Copies the rgb image (flipped vertically) to the specified position in the target buffer
|
||||
* @param buffer Source image
|
||||
* @param bufferWidth Source image width
|
||||
* @param xOffset X offset in target image
|
||||
* @param yOffset Y offset in target image
|
||||
* @param to Target image
|
||||
* @param width Target image width
|
||||
*/
|
||||
public static void openGlBytesToRBG(ByteBuffer buffer, int bufferWidth, int xOffset, int yOffset, ByteBuffer to, int width) {
|
||||
byte[] rowBuf = new byte[bufferWidth * 3];
|
||||
// Copy image flipped vertically to target buffer
|
||||
int rows = buffer.remaining() / 3 / bufferWidth;
|
||||
for (int i = 0; i < rows; i++) {
|
||||
buffer.get(rowBuf);
|
||||
to.position(((yOffset + rows - i - 1) * width + xOffset) * 3);
|
||||
to.put(rowBuf);
|
||||
}
|
||||
to.rewind();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.player.RealtimeTimelinePlayer;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.render.gui.GuiRenderSettings;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
|
||||
@@ -28,6 +29,7 @@ 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 net.minecraftforge.fml.common.Loader;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.util.Dimension;
|
||||
@@ -48,6 +50,14 @@ public class GuiPathing {
|
||||
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20)
|
||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
|
||||
|
||||
public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
preparePathsForPlayback();
|
||||
new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display();
|
||||
}
|
||||
}).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0);
|
||||
|
||||
public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton().setSize(20, 20)
|
||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
|
||||
|
||||
@@ -117,9 +127,15 @@ public class GuiPathing {
|
||||
}).addElements(null, timelineTime, timeline, scrollbar, zoomButtonPanel);
|
||||
|
||||
public final GuiPanel panel = new GuiPanel()
|
||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5))
|
||||
.addElements(new HorizontalLayout.Data(0.5), playPauseButton, positionKeyframeButton, timeKeyframeButton,
|
||||
timelinePanel);
|
||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5));
|
||||
|
||||
{
|
||||
panel.addElements(new HorizontalLayout.Data(0.5), playPauseButton);
|
||||
if (Loader.isModLoaded("replaymod-render")) {
|
||||
panel.addElements(new HorizontalLayout.Data(0.5), renderButton);
|
||||
}
|
||||
panel.addElements(new HorizontalLayout.Data(0.5), positionKeyframeButton, timeKeyframeButton, timelinePanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* IGuiClickable dummy component that is inserted at a high level.
|
||||
@@ -187,22 +203,8 @@ public class GuiPathing {
|
||||
} else {
|
||||
Timeline timeline = mod.getCurrentTimeline();
|
||||
Path timePath = timeline.getPaths().get(TIME_PATH);
|
||||
Path positionPath = timeline.getPaths().get(POSITION_PATH);
|
||||
|
||||
// TODO Change interpolator via Change class
|
||||
AbstractInterpolator interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(TimestampProperty.PROPERTY);
|
||||
for (PathSegment segment : timePath.getSegments()) {
|
||||
segment.setInterpolator(interpolator);
|
||||
}
|
||||
interpolator = new CubicSplineInterpolator();
|
||||
interpolator.registerProperty(CameraProperties.POSITION);
|
||||
interpolator.registerProperty(CameraProperties.ROTATION);
|
||||
for (PathSegment segment : positionPath.getSegments()) {
|
||||
segment.setInterpolator(interpolator);
|
||||
}
|
||||
timePath.updateAll();
|
||||
positionPath.updateAll();
|
||||
preparePathsForPlayback();
|
||||
|
||||
timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT));
|
||||
player.start(timeline);
|
||||
@@ -298,6 +300,27 @@ public class GuiPathing {
|
||||
});
|
||||
}
|
||||
|
||||
private void preparePathsForPlayback() {
|
||||
Timeline timeline = mod.getCurrentTimeline();
|
||||
Path timePath = timeline.getPaths().get(TIME_PATH);
|
||||
Path positionPath = timeline.getPaths().get(POSITION_PATH);
|
||||
|
||||
// TODO Change interpolator via Change class
|
||||
AbstractInterpolator interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(TimestampProperty.PROPERTY);
|
||||
for (PathSegment segment : timePath.getSegments()) {
|
||||
segment.setInterpolator(interpolator);
|
||||
}
|
||||
interpolator = new CubicSplineInterpolator();
|
||||
interpolator.registerProperty(CameraProperties.POSITION);
|
||||
interpolator.registerProperty(CameraProperties.ROTATION);
|
||||
for (PathSegment segment : positionPath.getSegments()) {
|
||||
segment.setInterpolator(interpolator);
|
||||
}
|
||||
timePath.updateAll();
|
||||
positionPath.updateAll();
|
||||
}
|
||||
|
||||
public void zoomTimeline(double factor) {
|
||||
scrollbar.setZoom(scrollbar.getZoom() * factor);
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.coremod;
|
||||
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
/**
|
||||
* Transforms the RenderGlobal class:
|
||||
* - Adds a new field 'ChunkLoadingRenderGlobal hook'
|
||||
* - Moves code of 'setupTerrain' into 'setupTerrain$Original'
|
||||
* - Creates 'setupTerrain' method (see {@link #createSetupTerrain(ClassNode, MethodNode)} ()}
|
||||
* - Moves code of 'isPositionInRenderChunk' into 'isPositionInRenderChunk$Original'
|
||||
* - Creates 'isPositionInRenderChunk' method (see {@link #createIsPositionInRenderChunk(ClassNode, MethodNode)} ()}
|
||||
* - Moves code of 'updateChunks' into 'updateChunk$Original'
|
||||
* - Creates 'updateChunks' method (see {@link #createUpdateChunks(ClassNode, MethodNode)} ()}
|
||||
*
|
||||
* Transforms the ChunkLoadingRenderGlobal class:
|
||||
* - Method 'original_setupTerrain' calls 'setupTerrain$Original'
|
||||
* - Method 'original_isPositionInRenderChunk' calls 'isPositionInRenderChunk$Original'
|
||||
* - Method 'original_updateChunks' calls 'updateChunks$Original'
|
||||
*/
|
||||
public class ForceChunkLoadingCT implements IClassTransformer {
|
||||
|
||||
private static final String HOOK = "eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal";
|
||||
private static final String HOOK_JVM = HOOK.replace('.', '/');
|
||||
private static final String HOOK_TYPE = "L" + HOOK_JVM + ";";
|
||||
private static final String RENDER_GLOBAL = "net.minecraft.client.renderer.RenderGlobal";
|
||||
private static final String RENDER_GLOBAL_JVM = RENDER_GLOBAL.replace('.', '/');
|
||||
private static final String RENDER_GLOBAL_TYPE = "L" + RENDER_GLOBAL_JVM + ";";
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
if (RENDER_GLOBAL.equals(transformedName)) {
|
||||
if (name.equals(transformedName)) {
|
||||
return transformRenderGlobal(bytes, "setupTerrain", "(Lnet/minecraft/entity/Entity;DLnet/minecraft/client/renderer/culling/ICamera;IZ)V",
|
||||
"isPositionInRenderChunk", "(Lnet/minecraft/util/BlockPos;Lnet/minecraft/client/renderer/chunk/RenderChunk;)Z",
|
||||
"updateChunks", "(J)V");
|
||||
} else {
|
||||
return transformRenderGlobal(bytes, "a", "(Lwv;DLcox;IZ)V",
|
||||
"a", "(Ldt;Lcop;)Z",
|
||||
"a", "(J)V");
|
||||
}
|
||||
} else if (HOOK.equals(transformedName)) {
|
||||
return transformHook(bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private byte[] transformHook(byte[] bytes) {
|
||||
ClassReader classReader = new ClassReader(bytes);
|
||||
ClassNode classNode = new ClassNode();
|
||||
classReader.accept(classNode, 0);
|
||||
|
||||
// Find methods
|
||||
for (MethodNode m : classNode.methods) {
|
||||
if ("original_setupTerrain".equals(m.name)) {
|
||||
m.instructions.clear();
|
||||
InsnList iter = m.instructions;
|
||||
iter.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
iter.add(new FieldInsnNode(GETFIELD, classNode.name, "hooked", RENDER_GLOBAL_TYPE));
|
||||
iter.add(new VarInsnNode(ALOAD, 1)); // viewEntity
|
||||
iter.add(new VarInsnNode(DLOAD, 2)); // partialTicks
|
||||
iter.add(new VarInsnNode(ALOAD, 4)); // camera
|
||||
iter.add(new VarInsnNode(ILOAD, 5)); // frameCount
|
||||
iter.add(new VarInsnNode(ILOAD, 6)); // playerSpectator
|
||||
iter.add(new MethodInsnNode(INVOKEVIRTUAL, RENDER_GLOBAL_JVM, "setupTerrain$Original", m.desc, false));
|
||||
iter.add(new InsnNode(RETURN));
|
||||
}
|
||||
if ("original_isPositionInRenderChunk".equals(m.name)) {
|
||||
m.instructions.clear();
|
||||
InsnList iter = m.instructions;
|
||||
iter.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
iter.add(new FieldInsnNode(GETFIELD, classNode.name, "hooked", RENDER_GLOBAL_TYPE));
|
||||
iter.add(new VarInsnNode(ALOAD, 1)); // pos
|
||||
iter.add(new VarInsnNode(ALOAD, 2)); // chunk
|
||||
iter.add(new MethodInsnNode(INVOKEVIRTUAL, RENDER_GLOBAL_JVM, "isPositionInRenderChunk$Original", m.desc, false));
|
||||
iter.add(new InsnNode(IRETURN));
|
||||
}
|
||||
if ("original_updateChunks".equals(m.name)) {
|
||||
m.instructions.clear();
|
||||
InsnList iter = m.instructions;
|
||||
iter.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
iter.add(new FieldInsnNode(GETFIELD, classNode.name, "hooked", RENDER_GLOBAL_TYPE));
|
||||
iter.add(new VarInsnNode(LLOAD, 1)); // finishTimeNano
|
||||
iter.add(new MethodInsnNode(INVOKEVIRTUAL, RENDER_GLOBAL_JVM, "updateChunks$Original", m.desc, false));
|
||||
iter.add(new InsnNode(RETURN));
|
||||
}
|
||||
}
|
||||
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
classNode.accept(classWriter);
|
||||
return classWriter.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] transformRenderGlobal(byte[] bytes, String name_setupTerrain, String desc_setupTerrain,
|
||||
String name_isPositionInRenderChunk, String desc_isPositionInRenderChunk,
|
||||
String name_updateChunks, String desc_updateChunks) {
|
||||
ClassReader classReader = new ClassReader(bytes);
|
||||
ClassNode classNode = new ClassNode();
|
||||
classReader.accept(classNode, 0);
|
||||
|
||||
// Add field
|
||||
classNode.visitField(ACC_PUBLIC, "hook", HOOK_TYPE, null, null);
|
||||
|
||||
// Find methods
|
||||
MethodNode setupTerrain = null;
|
||||
MethodNode isPositionInRenderChunk = null;
|
||||
MethodNode updateChunks = null;
|
||||
for (MethodNode m : classNode.methods) {
|
||||
if (name_setupTerrain.equals(m.name) && desc_setupTerrain.equals(m.desc)) {
|
||||
setupTerrain = m;
|
||||
}
|
||||
if (name_isPositionInRenderChunk.equals(m.name) && desc_isPositionInRenderChunk.equals(m.desc)) {
|
||||
isPositionInRenderChunk = m;
|
||||
}
|
||||
if (name_updateChunks.equals(m.name) && desc_updateChunks.equals(m.desc)) {
|
||||
updateChunks = m;
|
||||
}
|
||||
}
|
||||
if (setupTerrain == null) {
|
||||
throw new NoSuchMethodError("setupTerrain");
|
||||
}
|
||||
if (isPositionInRenderChunk == null) {
|
||||
throw new NoSuchMethodError("isPositionInRenderChunk");
|
||||
}
|
||||
if (updateChunks == null) {
|
||||
throw new NoSuchMethodError("updateChunks");
|
||||
}
|
||||
|
||||
// Generate new methods
|
||||
classNode.methods.add(createSetupTerrain(classNode, setupTerrain));
|
||||
classNode.methods.add(createIsPositionInRenderChunk(classNode, isPositionInRenderChunk));
|
||||
classNode.methods.add(createUpdateChunks(classNode, updateChunks));
|
||||
|
||||
// Rename original methods
|
||||
setupTerrain.name = "setupTerrain$Original";
|
||||
isPositionInRenderChunk.name = "isPositionInRenderChunk$Original";
|
||||
updateChunks.name = "updateChunks$Original";
|
||||
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
classNode.accept(classWriter);
|
||||
return classWriter.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* public void setupTerrain(Entity viewEntity, double partialTicks, ICamera camera, int frameCount, boolean playerSpectator) {
|
||||
* if (this.hook == null) {
|
||||
* setupTerrain$Original(viewEntity, partialTicks, camera, frameCount, playerSpectator);
|
||||
* return;
|
||||
* }
|
||||
* hook.setupTerrain(viewEntity, partialTicks, camera, frameCount, playerSpectator);
|
||||
* }
|
||||
*/
|
||||
private MethodNode createSetupTerrain(ClassNode classNode, MethodNode org) {
|
||||
MethodNode method = new MethodNode(org.access, org.name, org.desc, org.signature, new String[0]);
|
||||
InsnList list = method.instructions;
|
||||
list.add(new VarInsnNode(ALOAD, 0));
|
||||
list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE));
|
||||
LabelNode labelNotNull = new LabelNode();
|
||||
list.add(new JumpInsnNode(IFNONNULL, labelNotNull));
|
||||
|
||||
list.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
list.add(new VarInsnNode(ALOAD, 1)); // viewEntity
|
||||
list.add(new VarInsnNode(DLOAD, 2)); // partialTicks
|
||||
list.add(new VarInsnNode(ALOAD, 4)); // camera
|
||||
list.add(new VarInsnNode(ILOAD, 5)); // frameCount
|
||||
list.add(new VarInsnNode(ILOAD, 6)); // playerSpectator
|
||||
list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "setupTerrain$Original", org.desc, false));
|
||||
list.add(new InsnNode(RETURN));
|
||||
|
||||
list.add(labelNotNull);
|
||||
list.add(new FrameNode(F_SAME, 0, null, 0, null));
|
||||
list.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE));
|
||||
list.add(new VarInsnNode(ALOAD, 1)); // viewEntity
|
||||
list.add(new VarInsnNode(DLOAD, 2)); // partialTicks
|
||||
list.add(new VarInsnNode(ALOAD, 4)); // camera
|
||||
list.add(new VarInsnNode(ILOAD, 5)); // frameCount
|
||||
list.add(new VarInsnNode(ILOAD, 6)); // playerSpectator
|
||||
list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "setupTerrain", org.desc, false));
|
||||
list.add(new InsnNode(RETURN));
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* public boolean isPositionInRenderChunk(BlockPos pos, RenderChunk chunk) {
|
||||
* if (this.hook == null) {
|
||||
* return isPositionInRenderChunk$Original(pos, chunk);
|
||||
* }
|
||||
* return hook.isPositionInRenderChunk(pos, chunk);
|
||||
* }
|
||||
*/
|
||||
private MethodNode createIsPositionInRenderChunk(ClassNode classNode, MethodNode org) {
|
||||
MethodNode method = new MethodNode(org.access, org.name, org.desc, org.signature, new String[0]);
|
||||
InsnList list = method.instructions;
|
||||
list.add(new VarInsnNode(ALOAD, 0));
|
||||
list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE));
|
||||
LabelNode labelNotNull = new LabelNode();
|
||||
list.add(new JumpInsnNode(IFNONNULL, labelNotNull));
|
||||
|
||||
list.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
list.add(new VarInsnNode(ALOAD, 1)); // pos
|
||||
list.add(new VarInsnNode(ALOAD, 2)); // chunk
|
||||
list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "isPositionInRenderChunk$Original", org.desc, false));
|
||||
list.add(new InsnNode(IRETURN));
|
||||
|
||||
list.add(labelNotNull);
|
||||
list.add(new FrameNode(F_SAME, 0, null, 0, null));
|
||||
list.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE));
|
||||
list.add(new VarInsnNode(ALOAD, 1)); // pos
|
||||
list.add(new VarInsnNode(ALOAD, 2)); // chunk
|
||||
list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "isPositionInRenderChunk", org.desc, false));
|
||||
list.add(new InsnNode(IRETURN));
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* public void updateChunks(long finishTimeNano) {
|
||||
* if (this.hook == null) {
|
||||
* updateChunks$Original(finishTimeNano);
|
||||
* return;
|
||||
* }
|
||||
* hook.updateChunks(finishTimeNano);
|
||||
* }
|
||||
*/
|
||||
private MethodNode createUpdateChunks(ClassNode classNode, MethodNode org) {
|
||||
MethodNode method = new MethodNode(org.access, org.name, org.desc, org.signature, new String[0]);
|
||||
InsnList list = method.instructions;
|
||||
list.add(new VarInsnNode(ALOAD, 0));
|
||||
list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE));
|
||||
LabelNode labelNotNull = new LabelNode();
|
||||
list.add(new JumpInsnNode(IFNONNULL, labelNotNull));
|
||||
|
||||
list.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
list.add(new VarInsnNode(LLOAD, 1)); // finishTimeNano
|
||||
list.add(new MethodInsnNode(INVOKEVIRTUAL, classNode.name, "updateChunks$Original", org.desc, false));
|
||||
list.add(new InsnNode(RETURN));
|
||||
|
||||
list.add(labelNotNull);
|
||||
list.add(new FrameNode(F_SAME, 0, null, 0, null));
|
||||
list.add(new VarInsnNode(ALOAD, 0)); // this
|
||||
list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE));
|
||||
list.add(new VarInsnNode(LLOAD, 1)); // finishTimeNano
|
||||
list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "updateChunks", org.desc, false));
|
||||
list.add(new InsnNode(RETURN));
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
|
||||
MixinBootstrap.init();
|
||||
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.replaymod.json");
|
||||
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.recording.replaymod.json");
|
||||
MixinEnvironment.getDefaultEnvironment().addConfiguration("mixins.render.replaymod.json");
|
||||
|
||||
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
@@ -41,7 +42,6 @@ public class LoadingPlugin implements IFMLLoadingPlugin {
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
return new String[]{
|
||||
ForceChunkLoadingCT.class.getName()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,675 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.gui;
|
||||
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import eu.crushedpixel.replaymod.gui.elements.*;
|
||||
import eu.crushedpixel.replaymod.gui.elements.listeners.CheckBoxListener;
|
||||
import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener;
|
||||
import eu.crushedpixel.replaymod.holders.GuiEntryListEntry;
|
||||
import eu.crushedpixel.replaymod.settings.EncodingPreset;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.utils.MouseUtils;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import eu.crushedpixel.replaymod.utils.StringUtils;
|
||||
import eu.crushedpixel.replaymod.video.rendering.Pipelines;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiErrorScreen;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraftforge.fml.client.FMLClientHandler;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.util.Point;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
public class GuiRenderSettings extends GuiScreen {
|
||||
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
//for default file
|
||||
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
|
||||
private static final SimpleDateFormat FILE_FORMAT = new SimpleDateFormat(DATE_FORMAT);
|
||||
|
||||
private static final int LEFT_BORDER = 10;
|
||||
|
||||
private final RenderOptions initialRenderOptions = RenderOptions.loadFromConfig(ReplayMod.config);
|
||||
private boolean initialized = false;
|
||||
|
||||
private GuiAdvancedButton renderButton = new GuiAdvancedButton(0, 0, 100, 20, I18n.format("replaymod.gui.render"), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startRendering();
|
||||
}
|
||||
}, null);
|
||||
|
||||
private GuiAdvancedButton cancelButton = new GuiAdvancedButton(0, 0, 100, 20, I18n.format("replaymod.gui.cancel"), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mc.displayGuiScreen(null);
|
||||
}
|
||||
}, null);
|
||||
|
||||
private GuiAdvancedButton toggleTabButton = new GuiAdvancedButton(0, 0, 150, 20, I18n.format("replaymod.gui.rendersettings.advanced"), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
toggleTab();
|
||||
}
|
||||
}, null);
|
||||
|
||||
private GuiNumberInput xRes = new GuiNumberInput(mc.fontRendererObj, 0, 0, 50, 1, 100000, mc.displayWidth, false) {
|
||||
@Override
|
||||
public void moveCursorBy(int move) {
|
||||
super.moveCursorBy(move);
|
||||
RendererSettings renderer = rendererDropdown.getElement(rendererDropdown.getSelectionIndex());
|
||||
Integer value = getIntValueNullable();
|
||||
if (value != null) {
|
||||
if (renderer == RendererSettings.CUBIC) {
|
||||
yRes.text = Integer.toString(Math.max(1, value * 3 / 4));
|
||||
}
|
||||
if (renderer == RendererSettings.EQUIRECTANGULAR) {
|
||||
yRes.text = Integer.toString(Math.max(1, value / 2));
|
||||
}
|
||||
}
|
||||
yRes.setCursorPositionEnd();
|
||||
validateInputs();
|
||||
}
|
||||
};
|
||||
|
||||
private GuiNumberInput yRes = new GuiNumberInput(mc.fontRendererObj, 0, 0, 50, 1, 10000, mc.displayHeight, false) {
|
||||
@Override
|
||||
public void moveCursorBy(int move) {
|
||||
super.moveCursorBy(move);
|
||||
RendererSettings renderer = rendererDropdown.getElement(rendererDropdown.getSelectionIndex());
|
||||
Integer value = getIntValueNullable();
|
||||
if (value != null) {
|
||||
if (renderer == RendererSettings.CUBIC) {
|
||||
xRes.text = Integer.toString(value * 4 / 3);
|
||||
}
|
||||
if (renderer == RendererSettings.EQUIRECTANGULAR) {
|
||||
xRes.text = Integer.toString(value * 2);
|
||||
}
|
||||
}
|
||||
xRes.setCursorPositionEnd();
|
||||
validateInputs();
|
||||
}
|
||||
};
|
||||
|
||||
private GuiString rendererString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.renderer")+":");
|
||||
private GuiString presetsString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.presets")+":");
|
||||
private GuiString resolutionString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.customresolution")+":");
|
||||
private GuiString asteriksString = new GuiString(0, 0, Color.WHITE, "*");
|
||||
private GuiString bitrateString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.settings.bitrate")+":");
|
||||
private GuiString fileChooserString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.outputfile")+":");
|
||||
|
||||
private GuiDropdown<RendererSettings> rendererDropdown = new GuiDropdown<RendererSettings>(mc.fontRendererObj, 0, 0, 200, 5);
|
||||
private GuiDropdown<EncodingPreset> encodingPresetDropdown = new GuiDropdown<EncodingPreset>(mc.fontRendererObj, 0, 1, 200, 5);
|
||||
|
||||
private GuiString stabilizeString = new GuiString(0, 0, Color.WHITE, I18n.format("replaymod.gui.rendersettings.stabilizecamera")+":");
|
||||
|
||||
private GuiAdvancedCheckBox stablePitch = new GuiAdvancedCheckBox(I18n.format("replaymod.gui.pitch"), false, false);
|
||||
private GuiAdvancedCheckBox stableYaw = new GuiAdvancedCheckBox(I18n.format("replaymod.gui.yaw"), false, false);
|
||||
private GuiAdvancedCheckBox stableRoll = new GuiAdvancedCheckBox(I18n.format("replaymod.gui.roll"), false, false);
|
||||
|
||||
private int virtualY, virtualHeight;
|
||||
|
||||
private GuiAdvancedCheckBox enableGreenscreen = new GuiAdvancedCheckBox(0, 0, I18n.format("replaymod.gui.rendersettings.chromakey"), false);
|
||||
private GuiAdvancedCheckBox renderNameTags = new GuiAdvancedCheckBox(0, 0, I18n.format("replaymod.gui.rendersettings.nametags"), true);
|
||||
|
||||
{
|
||||
enableGreenscreen.addCheckBoxListener(new CheckBoxListener() {
|
||||
@Override
|
||||
public void onCheck(boolean checked) {
|
||||
colorPicker.setElementEnabled(checked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private GuiAdvancedCheckBox injectMetadata = new GuiAdvancedCheckBox(0, 0, I18n.format("replaymod.gui.rendersettings.360metadata"), false);
|
||||
|
||||
private GuiVideoFramerateSlider framerateSlider = new GuiVideoFramerateSlider(0, 0, ReplayMod.replaySettings.getVideoFramerate(),
|
||||
I18n.format("replaymod.gui.rendersettings.framerate"));
|
||||
|
||||
private GuiNumberInput bitrateInput = new GuiNumberInputWithText(mc.fontRendererObj, 0, 0, 50, 1D, null, 10000D, false, " kbps");
|
||||
private GuiColorPicker colorPicker = new GuiColorPicker(GuiConstants.RENDER_SETTINGS_COLOR_PICKER, 0, 0, I18n.format("replaymod.gui.rendersettings.skycolor")+": ", 0, 0);
|
||||
private GuiAdvancedTextField commandInput = new GuiAdvancedTextField(mc.fontRendererObj, I18n.format("replaymod.gui.rendersettings.command"), 3000);
|
||||
private GuiAdvancedTextField ffmpegArguments = new GuiAdvancedTextField(mc.fontRendererObj, I18n.format("replaymod.gui.rendersettings.ffmpeghint"), 3000) {
|
||||
@Override
|
||||
public void moveCursorBy(int move) {
|
||||
super.moveCursorBy(move);
|
||||
validateInputs(); //the file extension may have changed, so we revalidate the inputs
|
||||
}
|
||||
};
|
||||
|
||||
private File defaultFile = null;
|
||||
{
|
||||
try {
|
||||
defaultFile = new File(ReplayFileIO.getRenderFolder(), FILE_FORMAT.format(Calendar.getInstance().getTime())+"."+ EncodingPreset.MP4DEFAULT.getFileExtension());
|
||||
} catch(IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private GuiFileChooser outputFileChooser = new GuiFileChooser(GuiConstants.RENDER_SETTINGS_OUTPUT_CHOOSER, 0, 0,
|
||||
"", defaultFile, new String[]{"webm"}, true);
|
||||
|
||||
{
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
|
||||
rendererDropdown.addSelectionListener(new RendererDropdownListener());
|
||||
|
||||
int i = 0;
|
||||
for (RendererSettings r : RendererSettings.values()) {
|
||||
rendererDropdown.addElement(r);
|
||||
rendererDropdown.setHoverText(i, r.getDescription());
|
||||
i++;
|
||||
}
|
||||
|
||||
encodingPresetDropdown.addSelectionListener(new EncodingDropdownListener());
|
||||
|
||||
for (EncodingPreset preset : EncodingPreset.values()) {
|
||||
encodingPresetDropdown.addElement(preset);
|
||||
}
|
||||
|
||||
encodingPresetDropdown.setSelectionIndex(2);
|
||||
}
|
||||
|
||||
private String[] tabNames = new String[]{I18n.format("replaymod.gui.rendersettings.video"),
|
||||
I18n.format("replaymod.gui.rendersettings.advanced"), I18n.format("replaymod.gui.rendersettings.commandline")};
|
||||
|
||||
private int currentTab = 0;
|
||||
|
||||
private void toggleTab() {
|
||||
currentTab++;
|
||||
if(currentTab >= 3) currentTab = 0;
|
||||
|
||||
int nextTab = currentTab+1;
|
||||
if(nextTab >= 3) nextTab = 0;
|
||||
toggleTabButton.displayString = tabNames[nextTab];
|
||||
}
|
||||
|
||||
private DelegatingElement currentScreen = new DelegatingElement() {
|
||||
|
||||
private ComposedElement videoSettings = new ComposedElement(renderButton, cancelButton, toggleTabButton,
|
||||
rendererString, rendererDropdown, presetsString, encodingPresetDropdown, resolutionString, asteriksString,
|
||||
xRes, yRes, framerateSlider, bitrateString, bitrateInput, fileChooserString, outputFileChooser);
|
||||
|
||||
private ComposedElement advancedSettings = new ComposedElement(renderButton, cancelButton, toggleTabButton,
|
||||
renderNameTags, injectMetadata, stabilizeString, stablePitch, stableYaw, stableRoll, enableGreenscreen, colorPicker);
|
||||
|
||||
private ComposedElement commandLineSettings = new ComposedElement(renderButton, cancelButton, toggleTabButton,
|
||||
commandInput, ffmpegArguments);
|
||||
|
||||
@Override
|
||||
public GuiElement delegate() {
|
||||
switch(currentTab) {
|
||||
case 0:
|
||||
return videoSettings;
|
||||
case 1:
|
||||
return advancedSettings;
|
||||
case 2:
|
||||
return commandLineSettings;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void initGui() {
|
||||
virtualHeight = 200;
|
||||
virtualY = (this.height-virtualHeight)/2;
|
||||
|
||||
cancelButton.xPosition = width-LEFT_BORDER-5-100;
|
||||
renderButton.xPosition = cancelButton.xPosition-5-100;
|
||||
toggleTabButton.xPosition = renderButton.xPosition-5-150;
|
||||
|
||||
cancelButton.yPosition = renderButton.yPosition = toggleTabButton.yPosition = virtualY+virtualHeight-5-20;
|
||||
|
||||
initializeVideoTab();
|
||||
initializeAdvancedTab();
|
||||
initializeCommandLineTab();
|
||||
|
||||
if(!initialized) {
|
||||
initializeValues();
|
||||
}
|
||||
|
||||
validateInputs();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private void initializeVideoTab() {
|
||||
int largerWidth = Math.max(Math.max(rendererString.getWidth(), presetsString.getWidth()),
|
||||
resolutionString.getWidth())+10;
|
||||
int rowWidth = largerWidth+rendererDropdown.getWidth();
|
||||
rendererDropdown.xPosition = encodingPresetDropdown.xPosition = xRes.xPosition =
|
||||
bitrateInput.xPosition = outputFileChooser.xPosition = (width - rowWidth)/2 + largerWidth;
|
||||
|
||||
rendererString.positionX = presetsString.positionX = fileChooserString.positionX =
|
||||
resolutionString.positionX = bitrateString.positionX = (width - rowWidth)/2;
|
||||
|
||||
rendererString.positionX += (largerWidth-10)-rendererString.getWidth();
|
||||
presetsString.positionX += (largerWidth-10)-presetsString.getWidth();
|
||||
fileChooserString.positionX += (largerWidth-10)-fileChooserString.getWidth();
|
||||
resolutionString.positionX += (largerWidth-10)-resolutionString.getWidth();
|
||||
bitrateString.positionX += (largerWidth-10)-bitrateString.getWidth();
|
||||
|
||||
rendererDropdown.yPosition = virtualY + 25;
|
||||
rendererString.positionY = rendererDropdown.yPosition + 6;
|
||||
|
||||
encodingPresetDropdown.yPosition = rendererDropdown.yPosition+20+10;
|
||||
presetsString.positionY = encodingPresetDropdown.yPosition + 6;
|
||||
|
||||
asteriksString.positionX = xRes.xPosition+xRes.width+5;
|
||||
yRes.xPosition = asteriksString.positionX+asteriksString.getWidth()+5;
|
||||
|
||||
xRes.yPosition = yRes.yPosition = encodingPresetDropdown.yPosition+20+10;
|
||||
resolutionString.positionY = asteriksString.positionY = xRes.yPosition + 6;
|
||||
|
||||
bitrateInput.width = 70;
|
||||
|
||||
framerateSlider.xPosition = bitrateInput.xPosition + bitrateInput.width + 5;
|
||||
framerateSlider.width = 125;
|
||||
|
||||
bitrateInput.yPosition = framerateSlider.yPosition = xRes.yPosition+20+10;
|
||||
bitrateString.positionY = bitrateInput.yPosition+6;
|
||||
|
||||
outputFileChooser.yPosition = bitrateInput.yPosition+20+10;
|
||||
fileChooserString.positionY = outputFileChooser.yPosition+6;
|
||||
|
||||
outputFileChooser.width = 200;
|
||||
}
|
||||
|
||||
private void initializeAdvancedTab() {
|
||||
int singleWidth = 150;
|
||||
int middleGap = 10;
|
||||
|
||||
int totalWidth = (2*singleWidth)+middleGap;
|
||||
|
||||
int leftX = width/2 - (singleWidth+middleGap/2);
|
||||
|
||||
//might be of use in the future
|
||||
//int rightX = width/2 + (middleGap/2);
|
||||
|
||||
int heightDiff = 25;
|
||||
|
||||
renderNameTags.xPosition = leftX;
|
||||
renderNameTags.yPosition = virtualY + 25;
|
||||
renderNameTags.width = singleWidth;
|
||||
|
||||
stabilizeString.positionX = leftX;
|
||||
|
||||
int left = leftX+stabilizeString.getWidth()+middleGap;
|
||||
int width = (totalWidth - (stabilizeString.getWidth()+middleGap)) / 3;
|
||||
|
||||
stableYaw.xPosition = left;
|
||||
stablePitch.xPosition = left + width;
|
||||
stableRoll.xPosition = left + 2*width;
|
||||
|
||||
stablePitch.yPosition = stableYaw.yPosition = stableRoll.yPosition = renderNameTags.yPosition+heightDiff;
|
||||
stabilizeString.positionY = stablePitch.yPosition + 2;
|
||||
|
||||
enableGreenscreen.xPosition = leftX;
|
||||
colorPicker.xPosition = leftX + (enableGreenscreen.width+5);
|
||||
colorPicker.width = totalWidth - (enableGreenscreen.width+5);
|
||||
|
||||
colorPicker.yPosition = stablePitch.yPosition+heightDiff;
|
||||
enableGreenscreen.yPosition = colorPicker.yPosition+4;
|
||||
|
||||
colorPicker.pickerX = colorPicker.xPosition;
|
||||
colorPicker.pickerY = colorPicker.yPosition + 20;
|
||||
|
||||
colorPicker.setElementEnabled(enableGreenscreen.isChecked());
|
||||
|
||||
injectMetadata.xPos(leftX);
|
||||
injectMetadata.yPos(enableGreenscreen.yPos()+heightDiff);
|
||||
}
|
||||
|
||||
private void initializeCommandLineTab() {
|
||||
commandInput.width = 55;
|
||||
commandInput.xPosition = (this.width-305)/2;
|
||||
commandInput.yPosition = ffmpegArguments.yPosition = virtualY + 25;
|
||||
|
||||
ffmpegArguments.width = 245;
|
||||
ffmpegArguments.xPosition = commandInput.xPosition+commandInput.width+5;
|
||||
}
|
||||
|
||||
private void initializeValues() {
|
||||
RendererSettings defRS = RendererSettings.valueOf(initialRenderOptions.getMode().name());
|
||||
|
||||
List<RendererSettings> settingsList = rendererDropdown.getAllElements();
|
||||
for(int i=0; i<settingsList.size(); i++) {
|
||||
if(defRS == settingsList.get(i)) {
|
||||
rendererDropdown.setSelectionIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EncodingPreset preset = null;
|
||||
for(EncodingPreset p : EncodingPreset.values()) {
|
||||
if(p.getCommandLineArgs().equals(initialRenderOptions.getExportCommandArgs())) {
|
||||
preset = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<EncodingPreset> presetList = encodingPresetDropdown.getAllElements();
|
||||
if(preset != null) {
|
||||
for(int i=0; i<presetList.size(); i++) {
|
||||
if(preset == encodingPresetDropdown.getElement(i)) {
|
||||
encodingPresetDropdown.setSelectionIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xRes.setValueQuietly(initialRenderOptions.getWidth());
|
||||
yRes.setValueQuietly(initialRenderOptions.getHeight());
|
||||
|
||||
stableYaw.setIsChecked(initialRenderOptions.getIgnoreCameraRotation()[0]);
|
||||
stablePitch.setIsChecked(initialRenderOptions.getIgnoreCameraRotation()[1]);
|
||||
stableRoll.setIsChecked(initialRenderOptions.getIgnoreCameraRotation()[2]);
|
||||
|
||||
enableGreenscreen.setIsChecked(!initialRenderOptions.isDefaultSky());
|
||||
renderNameTags.setIsChecked(!initialRenderOptions.isHideNameTags());
|
||||
|
||||
injectMetadata.setIsChecked(initialRenderOptions.isInject360Metadata());
|
||||
|
||||
framerateSlider.setFPS(initialRenderOptions.getFps());
|
||||
|
||||
String bitrateString = initialRenderOptions.getBitrate();
|
||||
//trim away the "K" at the end of the returned String
|
||||
int bitrateValue = Integer.valueOf(bitrateString.substring(0, bitrateString.length() - 1));
|
||||
bitrateInput.setValueQuietly(bitrateValue);
|
||||
|
||||
if(!initialRenderOptions.isDefaultSky())
|
||||
colorPicker.setPickerColor(initialRenderOptions.getSkyColor());
|
||||
|
||||
commandInput.setText(initialRenderOptions.getExportCommand());
|
||||
|
||||
if(!initialRenderOptions.getExportCommandArgs().isEmpty()) {
|
||||
ffmpegArguments.setText(initialRenderOptions.getExportCommandArgs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
drawGradientRect(LEFT_BORDER, virtualY, width - LEFT_BORDER, virtualY + virtualHeight, -1072689136, -804253680);
|
||||
|
||||
this.drawCenteredString(fontRendererObj, tabNames[currentTab],
|
||||
this.width / 2, virtualY + 5, Color.WHITE.getRGB());
|
||||
|
||||
if(currentTab == 2) {
|
||||
String[] rows = StringUtils.splitStringInMultipleRows(I18n.format("replaymod.gui.rendersettings.ffmpeg.description"), 305);
|
||||
|
||||
int i = 0;
|
||||
for(String row : rows) {
|
||||
drawString(fontRendererObj, row, commandInput.xPosition, commandInput.yPosition + 30 + (15 * i), Color.WHITE.getRGB());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
currentScreen.draw(mc, mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
|
||||
currentScreen.mouseClick(mc, mouseX, mouseY, mouseButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseReleased(int mouseX, int mouseY, int state) {
|
||||
currentScreen.mouseRelease(mc, mouseX, mouseY, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
|
||||
currentScreen.mouseDrag(mc, mouseX, mouseY, clickedMouseButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char typedChar, int keyCode) throws IOException {
|
||||
Point mousePos = MouseUtils.getMousePos();
|
||||
currentScreen.buttonPressed(mc, mousePos.getX(), mousePos.getY(), typedChar, keyCode);
|
||||
super.keyTyped(typedChar, keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
xRes.updateCursorCounter();
|
||||
yRes.updateCursorCounter();
|
||||
bitrateInput.updateCursorCounter();
|
||||
commandInput.updateCursorCounter();
|
||||
ffmpegArguments.updateCursorCounter();
|
||||
super.updateScreen();
|
||||
}
|
||||
|
||||
private enum RendererSettings implements GuiEntryListEntry {
|
||||
DEFAULT("default"),
|
||||
STEREOSCOPIC("stereoscopic"),
|
||||
CUBIC("cubic"),
|
||||
EQUIRECTANGULAR("equirectangular");
|
||||
|
||||
private String name, desc;
|
||||
|
||||
RendererSettings(String name) {
|
||||
this.name = "replaymod.gui.rendersettings.renderer."+name;
|
||||
this.desc = this.name+".description";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return I18n.format(name);
|
||||
}
|
||||
|
||||
public String getDescription() { return I18n.format(desc); }
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
private void startRendering() {
|
||||
RendererSettings r = rendererDropdown.getElement(rendererDropdown.getSelectionIndex());
|
||||
|
||||
RenderOptions options = new RenderOptions();
|
||||
|
||||
options.setLinearMovement(ReplayMod.replaySettings.isLinearMovement());
|
||||
|
||||
options.setWaitForChunks(true);
|
||||
|
||||
options.setFps(framerateSlider.getFPS());
|
||||
ReplayMod.replaySettings.setVideoFramerate(framerateSlider.getFPS());
|
||||
|
||||
options.setBitrate(bitrateInput.getIntValue() + "K"); //Bitrate value is in Kilobytes
|
||||
|
||||
//remove the extension from the output file
|
||||
File outputFile = outputFileChooser.getSelectedFile();
|
||||
outputFile = new File(outputFile.getParent(), FilenameUtils.getBaseName(outputFile.getAbsolutePath()));
|
||||
|
||||
options.setOutputFile(outputFile);
|
||||
|
||||
if(enableGreenscreen.isChecked()) {
|
||||
options.setSkyColor(colorPicker.getPickedColor());
|
||||
}
|
||||
|
||||
options.setHideNameTags(!renderNameTags.isChecked());
|
||||
|
||||
if(injectMetadata.enabled) {
|
||||
options.setInject360Metadata(injectMetadata.isChecked());
|
||||
}
|
||||
|
||||
options.setWidth(getWidthSetting());
|
||||
options.setHeight(getHeightSetting());
|
||||
|
||||
Pipelines.Preset pipePreset = Pipelines.Preset.DEFAULT;
|
||||
if(r == RendererSettings.DEFAULT) {
|
||||
pipePreset = Pipelines.Preset.DEFAULT;
|
||||
} else if(r == RendererSettings.STEREOSCOPIC) {
|
||||
pipePreset = Pipelines.Preset.STEREOSCOPIC;
|
||||
} else if(r == RendererSettings.CUBIC) {
|
||||
pipePreset = Pipelines.Preset.CUBIC;
|
||||
} else if(r == RendererSettings.EQUIRECTANGULAR) {
|
||||
pipePreset = Pipelines.Preset.EQUIRECTANGULAR;
|
||||
}
|
||||
options.setMode(pipePreset);
|
||||
|
||||
options.setIgnoreCameraRotation(stableYaw.enabled && stableYaw.isChecked(),
|
||||
stablePitch.enabled && stablePitch.isChecked(), stableRoll.enabled && stableRoll.isChecked());
|
||||
|
||||
if (isCtrlKeyDown()) {
|
||||
options.setHighPerformance(true);
|
||||
}
|
||||
|
||||
if(commandInput.getText().trim().length() > 0) {
|
||||
options.setExportCommand(commandInput.getText().trim());
|
||||
}
|
||||
|
||||
if(ffmpegArguments.getText().trim().length() > 0) {
|
||||
options.setExportCommandArgs(ffmpegArguments.getText().trim());
|
||||
}
|
||||
|
||||
options.saveToConfig(ReplayMod.config);
|
||||
|
||||
if(FMLClientHandler.instance().hasOptifine()) {
|
||||
mc.displayGuiScreen(new GuiErrorScreen(I18n.format("replaymod.gui.rendering.error.title"), I18n.format("replaymod.gui.rendering.error.optifine")));
|
||||
} else {
|
||||
// TODO
|
||||
// ReplayHandler.startPath(options, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int getWidthSetting() {
|
||||
return xRes.getIntValue();
|
||||
}
|
||||
|
||||
private int getHeightSetting() { return yRes.getIntValue(); }
|
||||
|
||||
private class RendererDropdownListener implements SelectionListener {
|
||||
@Override
|
||||
public void onSelectionChanged(int selectionIndex) {
|
||||
xRes.setCursorPositionEnd();
|
||||
xRes.setSelectionPos(xRes.getCursorPosition());
|
||||
|
||||
yRes.setCursorPositionEnd();
|
||||
yRes.setSelectionPos(yRes.getCursorPosition());
|
||||
|
||||
yRes.moveCursorBy(0); //This causes the Aspect Ratio to be recalculated based on the Y Resolution
|
||||
|
||||
validateInputs();
|
||||
}
|
||||
}
|
||||
|
||||
private class EncodingDropdownListener implements SelectionListener {
|
||||
@Override
|
||||
public void onSelectionChanged(int selectionIndex) {
|
||||
EncodingPreset preset = encodingPresetDropdown.getElement(selectionIndex);
|
||||
|
||||
bitrateInput.setEnabled(preset.hasBitrateSetting());
|
||||
ffmpegArguments.setText(preset.getCommandLineArgs());
|
||||
ffmpegArguments.setCursorPositionZero();
|
||||
|
||||
outputFileChooser.setAllowedExtensions(new String[]{preset.getFileExtension()});
|
||||
|
||||
File selectedFile = outputFileChooser.getSelectedFile();
|
||||
if(selectedFile != null) {
|
||||
String newName = FilenameUtils.getBaseName(selectedFile.getAbsolutePath())+"."+preset.getFileExtension();
|
||||
outputFileChooser.setSelectedFile(new File(selectedFile.getParent(), newName));
|
||||
}
|
||||
|
||||
validateInputs();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
Keyboard.enableRepeatEvents(false);
|
||||
super.onGuiClosed();
|
||||
}
|
||||
|
||||
private void validateInputs() {
|
||||
boolean valid = true;
|
||||
|
||||
boolean isMp4 = false;
|
||||
|
||||
boolean isPreset = false;
|
||||
EncodingPreset curPreset = encodingPresetDropdown.getElement(encodingPresetDropdown.getSelectionIndex());
|
||||
if(ffmpegArguments.getText().trim().equals(curPreset.getCommandLineArgs())) {
|
||||
isPreset = true;
|
||||
}
|
||||
|
||||
if(isPreset) {
|
||||
if(curPreset.isYuv420()) {
|
||||
if(getWidthSetting() % 2 != 0 || getHeightSetting() % 2 != 0) {
|
||||
valid = false;
|
||||
renderButton.hoverText = I18n.format("replaymod.gui.rendersettings.customresolution.warning.yuv420");
|
||||
}
|
||||
}
|
||||
|
||||
//injecting Metadata is only possible with mp4 containers.
|
||||
//To assure the mp4 file extension (which we need to obtain the file path),
|
||||
//we can't allow custom command line arguments
|
||||
if("mp4".equals(curPreset.getFileExtension())) {
|
||||
isMp4 = true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPanoramic = false;
|
||||
boolean isEquirectangular = false;
|
||||
|
||||
switch (rendererDropdown.getElement(rendererDropdown.getSelectionIndex())) {
|
||||
case CUBIC:
|
||||
isPanoramic = true;
|
||||
if (getWidthSetting() * 3 / 4 != getHeightSetting()
|
||||
|| getWidthSetting() * 3 % 4 != 0) {
|
||||
valid = false;
|
||||
renderButton.hoverText = I18n.format("replaymod.gui.rendersettings.customresolution.warning.cubic");
|
||||
}
|
||||
break;
|
||||
case EQUIRECTANGULAR:
|
||||
isPanoramic = true;
|
||||
isEquirectangular = true;
|
||||
if (getWidthSetting() / 2 != getHeightSetting()
|
||||
|| getWidthSetting() % 2 != 0) {
|
||||
valid = false;
|
||||
renderButton.hoverText = I18n.format("replaymod.gui.rendersettings.customresolution.warning.equirectangular");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
stabilizeString.setElementEnabled(isPanoramic);
|
||||
stableYaw.setElementEnabled(isPanoramic);
|
||||
stablePitch.setElementEnabled(isPanoramic);
|
||||
stableRoll.setElementEnabled(isPanoramic);
|
||||
|
||||
injectMetadata.setElementEnabled(isEquirectangular && isMp4);
|
||||
if(injectMetadata.enabled) {
|
||||
injectMetadata.hoverText = null;
|
||||
} else {
|
||||
injectMetadata.hoverText = I18n.format("replaymod.gui.rendersettings.360metadata.error");
|
||||
injectMetadata.hoverTextColor = Color.RED;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
renderButton.enabled = true;
|
||||
renderButton.hoverText = "";
|
||||
xRes.setTextColor(0xffffffff);
|
||||
yRes.setTextColor(0xffffffff);
|
||||
xRes.setDisabledTextColour(0xff707070);
|
||||
yRes.setDisabledTextColour(0xff707070);
|
||||
} else {
|
||||
renderButton.enabled = false;
|
||||
xRes.setTextColor(0xffff0000);
|
||||
yRes.setTextColor(0xffff0000);
|
||||
xRes.setDisabledTextColour(0xffff0000);
|
||||
yRes.setDisabledTextColour(0xffff0000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.gui;
|
||||
|
||||
import eu.crushedpixel.replaymod.gui.elements.GuiProgressBar;
|
||||
import eu.crushedpixel.replaymod.utils.BoundingUtils;
|
||||
import eu.crushedpixel.replaymod.utils.DurationUtils;
|
||||
import eu.crushedpixel.replaymod.video.VideoRenderer;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.fml.client.config.GuiCheckBox;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static net.minecraft.client.renderer.GlStateManager.bindTexture;
|
||||
import static net.minecraft.client.renderer.GlStateManager.color;
|
||||
|
||||
public class GuiVideoRenderer extends GuiScreen {
|
||||
private static final ResourceLocation noPreviewTexture = new ResourceLocation("replaymod", "logo.jpg");
|
||||
|
||||
private final VideoRenderer renderer;
|
||||
|
||||
private final String SCREEN_TITLE = I18n.format("replaymod.gui.rendering.title");
|
||||
private final String PAUSE_RENDERING = I18n.format("replaymod.gui.rendering.pause");
|
||||
private final String RESUME_RENDERING = I18n.format("replaymod.gui.rendering.resume");
|
||||
private final String CANCEL = I18n.format("replaymod.gui.rendering.cancel");
|
||||
private final String CANCEL_CONFIRM = I18n.format("replaymod.gui.rendering.cancel.callback");
|
||||
private final String PREVIEW = I18n.format("replaymod.gui.rendering.preview");
|
||||
|
||||
private GuiButton pauseButton;
|
||||
private GuiButton cancelButton;
|
||||
private GuiCheckBox previewCheckBox;
|
||||
private GuiProgressBar progressBar;
|
||||
|
||||
private DynamicTexture previewTexture;
|
||||
private boolean previewTextureDirty;
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
public GuiVideoRenderer(VideoRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // I blame forge for not re-adding generics to that list
|
||||
public void initGui() {
|
||||
if(!initialized) {
|
||||
String text = PAUSE_RENDERING;
|
||||
pauseButton = new GuiButton(1, width / 2 - 152, height - 10 - 20, text);
|
||||
|
||||
text = CANCEL;
|
||||
cancelButton = new GuiButton(1, width / 2 + 2, height - 10 - 20, text);
|
||||
|
||||
pauseButton.width = cancelButton.width = 150;
|
||||
|
||||
progressBar = new GuiProgressBar(10, height - 10 - 20 - 10 - 20, width-20, 20);
|
||||
|
||||
text = PREVIEW;
|
||||
previewCheckBox = new GuiCheckBox(0, (width - fontRendererObj.getStringWidth(text)) / 2 - 8,
|
||||
pauseButton.yPosition - 10 - 20 - 10 - 20 - 5 , text, false);
|
||||
} else {
|
||||
pauseButton.xPosition = width / 2 - 152;
|
||||
pauseButton.yPosition = height - 10 - 20;
|
||||
|
||||
cancelButton.xPosition = width / 2 + 2;
|
||||
cancelButton.yPosition = height - 10 - 20;
|
||||
|
||||
progressBar.setBounds(10, height - 10 - 20 - 10 - 20, width - 20, 20);
|
||||
|
||||
previewCheckBox.xPosition = (width - fontRendererObj.getStringWidth(PREVIEW)) / 2 - 8;
|
||||
previewCheckBox.yPosition = pauseButton.yPosition - 10 - 20 - 10 - 20 - 5;
|
||||
}
|
||||
|
||||
buttonList.add(pauseButton);
|
||||
buttonList.add(cancelButton);
|
||||
buttonList.add(previewCheckBox);
|
||||
|
||||
initialized = true;
|
||||
|
||||
super.initGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
|
||||
String cancelBefore = cancelButton.displayString;
|
||||
super.mouseClicked(mouseX, mouseY, mouseButton);
|
||||
String cancelAfter = cancelButton.displayString;
|
||||
if (cancelBefore.equals(cancelAfter)) {
|
||||
cancelButton.displayString = CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton button) throws IOException {
|
||||
if (button == pauseButton) {
|
||||
if (PAUSE_RENDERING.equals(pauseButton.displayString)) {
|
||||
renderer.setPaused(true);
|
||||
pauseButton.displayString = RESUME_RENDERING;
|
||||
} else if (RESUME_RENDERING.equals(pauseButton.displayString)) {
|
||||
renderer.setPaused(false);
|
||||
pauseButton.displayString = PAUSE_RENDERING;
|
||||
} else {
|
||||
throw new IllegalStateException(pauseButton.displayString);
|
||||
}
|
||||
} else if (button == cancelButton) {
|
||||
if (CANCEL.equals(cancelButton.displayString)) {
|
||||
cancelButton.displayString = CANCEL_CONFIRM;
|
||||
} else if (CANCEL_CONFIRM.equals(cancelButton.displayString)) {
|
||||
renderer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//the total render time
|
||||
private int renderTimeTaken = 0;
|
||||
|
||||
//the time at which the Screen was last updated
|
||||
private long prevTime = -1;
|
||||
|
||||
//the time at which the rendering of the current frame started
|
||||
private long frameStartTime = -1;
|
||||
|
||||
//the amount of frames that were rendered when the time left was last updated
|
||||
private int prevRenderedFrames = 0;
|
||||
|
||||
//the estimated render time that is left (in seconds)
|
||||
private int renderTimeLeft = 0;
|
||||
|
||||
//each of the durations the rendering process took for the last 50 frames
|
||||
private int[] renderTimes = new int[50];
|
||||
//the algorithm's current position in the renderTimes array
|
||||
private int currentIndex = 0;
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
long current = System.currentTimeMillis();
|
||||
|
||||
//first, update the total render time (only if rendering is not paused and has already started)
|
||||
if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) {
|
||||
renderTimeTaken += (current - prevTime);
|
||||
} else {
|
||||
//if the rendering process is paused, we have to update the frame start time to prevent huge render times
|
||||
//for the currently rendered frame(s)
|
||||
frameStartTime = current;
|
||||
}
|
||||
|
||||
//always update prevTime, so we don't get huge time jumps when unpausing the rendering process
|
||||
prevTime = current;
|
||||
|
||||
|
||||
//calculate estimated time left
|
||||
|
||||
//if the amount of rendered frames has increased since the last update
|
||||
if(prevRenderedFrames < renderer.getFramesDone()) {
|
||||
//we don't include the first frame in our calculations,
|
||||
//as setting up the rendering process takes a few seconds
|
||||
if(prevRenderedFrames > 0) {
|
||||
|
||||
//calculate the amount of frames that have been rendered since the last update
|
||||
int framesRendered = renderer.getFramesDone() - prevRenderedFrames;
|
||||
|
||||
//calculate the time it took to render these frames
|
||||
int renderTime = (int)(current - frameStartTime);
|
||||
|
||||
//calculate the average time it took for each of these frames to render
|
||||
int avgRenderTime = renderTime/framesRendered;
|
||||
|
||||
//add all of the average render times to the render times array
|
||||
for(int i=0; i<framesRendered; i++) {
|
||||
renderTimes[currentIndex] = avgRenderTime;
|
||||
currentIndex++;
|
||||
if(currentIndex >= renderTimes.length) currentIndex = 0;
|
||||
}
|
||||
|
||||
//the renderTimes array initially contains lots of zeros,
|
||||
//so we count the amount of valid timespans
|
||||
int validValues = 0;
|
||||
|
||||
int totalTime = 0;
|
||||
for(int i : renderTimes) {
|
||||
if(i > 0) {
|
||||
totalTime += i;
|
||||
validValues++;
|
||||
}
|
||||
}
|
||||
|
||||
//calculate the average render time for the previous [up to 50] frames
|
||||
float averageRenderTime = validValues > 0 ? totalTime / validValues : 0;
|
||||
|
||||
//calculate the remaining render time in seconds
|
||||
renderTimeLeft = Math.round((averageRenderTime * (renderer.getTotalFrames() - renderer.getFramesDone())) / 1000);
|
||||
}
|
||||
|
||||
//set the render start time of the next frame(s) to the current timestamp
|
||||
frameStartTime = current;
|
||||
//update the amount of rendered frames for the last calculation
|
||||
prevRenderedFrames = renderer.getFramesDone();
|
||||
}
|
||||
|
||||
String takenString = I18n.format("replaymod.gui.rendering.timetaken")+": "+DurationUtils.convertSecondsToString(renderTimeTaken/1000);
|
||||
String leftString = I18n.format("replaymod.gui.rendering.timeleft")+": "+DurationUtils.convertSecondsToString(renderTimeLeft);
|
||||
|
||||
int centerX = width / 2;
|
||||
|
||||
drawBackground(0);
|
||||
|
||||
drawCenteredString(fontRendererObj, SCREEN_TITLE, centerX, 5, Color.WHITE.getRGB());
|
||||
|
||||
String framesProgress = I18n.format("replaymod.gui.rendering.progress", renderer.getFramesDone(), renderer.getTotalFrames());
|
||||
progressBar.setProgressString(framesProgress);
|
||||
progressBar.setProgress((float) renderer.getFramesDone() / renderer.getTotalFrames());
|
||||
|
||||
progressBar.drawProgressBar();
|
||||
|
||||
int previewX = 10;
|
||||
int previewWidth = width - 20;
|
||||
|
||||
int previewHeight = previewCheckBox.yPosition - 10 - 20;
|
||||
int previewY = previewCheckBox.yPosition - 10 - previewHeight;
|
||||
|
||||
if(previewCheckBox.isChecked()) {
|
||||
renderPreview(previewX, previewY, previewWidth, previewHeight);
|
||||
} else {
|
||||
renderNoPreview(previewX, previewY, previewWidth, previewHeight);
|
||||
}
|
||||
|
||||
drawString(fontRendererObj, takenString, 12, previewCheckBox.yPosition + 5 + 20, Color.WHITE.getRGB());
|
||||
drawString(fontRendererObj, leftString, width - 12 - fontRendererObj.getStringWidth(leftString),
|
||||
previewCheckBox.yPosition + 5 + 20, Color.WHITE.getRGB());
|
||||
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
private synchronized void renderPreview(int x, int y, int width, int height) {
|
||||
ReadableDimension videoSize = renderer.getFrameSize();
|
||||
final int videoWidth = videoSize.getWidth();
|
||||
final int videoHeight = videoSize.getHeight();
|
||||
|
||||
if (previewTexture == null) {
|
||||
previewTexture = new DynamicTexture(videoWidth, videoHeight) {
|
||||
@Override
|
||||
public void updateDynamicTexture() {
|
||||
bindTexture(getGlTextureId());
|
||||
TextureUtil.uploadTextureSub(0, getTextureData(), videoWidth, videoHeight, 0, 0, true, false, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (previewTextureDirty) {
|
||||
previewTexture.updateDynamicTexture();
|
||||
previewTextureDirty = false;
|
||||
}
|
||||
|
||||
Dimension dimension = BoundingUtils.fitIntoBounds(new Dimension(videoSize), new Dimension(width, height));
|
||||
|
||||
x += (width - dimension.getWidth()) / 2;
|
||||
y += (height - dimension.getHeight()) / 2;
|
||||
width = dimension.getWidth();
|
||||
height = dimension.getHeight();
|
||||
|
||||
color(1, 1, 1, 1);
|
||||
bindTexture(previewTexture.getGlTextureId());
|
||||
drawScaledCustomSizeModalRect(x, y, 0, 0, videoWidth, videoHeight, width, height, videoWidth, videoHeight);
|
||||
}
|
||||
|
||||
private void renderNoPreview(int x, int y, int width, int height) {
|
||||
int actualWidth = width;
|
||||
int actualHeight = height;
|
||||
if (width / height > 1280 / 720) {
|
||||
actualWidth = height * 1280 / 720;
|
||||
} else {
|
||||
actualHeight = width * 720 / 1280;
|
||||
}
|
||||
|
||||
x += (width - actualWidth) / 2;
|
||||
y += (height - actualHeight) / 2;
|
||||
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture(noPreviewTexture);
|
||||
color(1, 1, 1, 1);
|
||||
drawScaledCustomSizeModalRect(x, y, 0, 0, 1280, 720, actualWidth, actualHeight, 1280, 720);
|
||||
}
|
||||
|
||||
public void updatePreview(RGBFrame frame) {
|
||||
if (previewCheckBox.isChecked() && previewTexture != null) {
|
||||
ByteBuffer buffer = frame.getByteBuffer();
|
||||
buffer.mark();
|
||||
synchronized (this) {
|
||||
int[] data = previewTexture.getTextureData();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | (buffer.get() & 0xff);
|
||||
}
|
||||
previewTextureDirty = true;
|
||||
}
|
||||
buffer.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,251 +2,34 @@ package eu.crushedpixel.replaymod.mixin;
|
||||
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import eu.crushedpixel.replaymod.renderer.SpectatorRenderer;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import eu.crushedpixel.replaymod.video.capturer.CubicOpenGlFrameCapturer;
|
||||
import eu.crushedpixel.replaymod.video.capturer.StereoscopicOpenGlFrameCapturer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityPlayerSP;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.MovingObjectPosition;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.util.glu.Project;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(EntityRenderer.class)
|
||||
public abstract class MixinEntityRenderer implements EntityRendererHandler.IEntityRenderer, EntityRendererHandler.GluPerspective {
|
||||
public abstract class MixinEntityRenderer {
|
||||
@Shadow
|
||||
public Minecraft mc;
|
||||
|
||||
private EntityRendererHandler handler;
|
||||
private SpectatorRenderer spectatorRenderer = new SpectatorRenderer();
|
||||
|
||||
@Override
|
||||
public void setHandler(EntityRendererHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRendererHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Inject(method = "setupFog", at = @At("HEAD"), cancellable = true)
|
||||
private void onSetupFog(int fogDistanceFlag, float partialTicks, CallbackInfo ci) {
|
||||
if (handler == null) return;
|
||||
if (!handler.getOptions().isDefaultSky()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/culling/Frustum;setPosition(DDD)V"))
|
||||
public void createNoCullingFrustum(Frustum frustum, double x, double y, double z) {
|
||||
if (handler != null) {
|
||||
frustum.clippingHelper = new EntityRendererHandler.NoCullingClippingHelper();
|
||||
}
|
||||
frustum.setPosition(x, y, z);
|
||||
}
|
||||
|
||||
@Inject(method = "orientCamera", at = @At("HEAD"))
|
||||
private void resetRotationIfNeeded(float partialTicks, CallbackInfo ci) {
|
||||
if (handler != null) {
|
||||
Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
RenderOptions options = handler.getOptions();
|
||||
if (options.getIgnoreCameraRotation()[0]) {
|
||||
entity.prevRotationYaw = entity.rotationYaw = 0;
|
||||
}
|
||||
if (options.getIgnoreCameraRotation()[1]) {
|
||||
entity.prevRotationPitch = entity.rotationPitch = 0;
|
||||
}
|
||||
if (options.getIgnoreCameraRotation()[2] && entity instanceof CameraEntity) {
|
||||
((CameraEntity) entity).roll = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float orgYaw;
|
||||
private float orgPitch;
|
||||
private float orgPrevYaw;
|
||||
private float orgPrevPitch;
|
||||
private float orgRoll;
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At("HEAD"))
|
||||
private void beforeSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (handler != null) {
|
||||
Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
orgYaw = entity.rotationYaw;
|
||||
orgPitch = entity.rotationPitch;
|
||||
orgPrevYaw = entity.prevRotationYaw;
|
||||
orgPrevPitch = entity.prevRotationPitch;
|
||||
orgRoll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At("RETURN"))
|
||||
private void afterSetupCameraTransform(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (handler != null) {
|
||||
Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
entity.rotationYaw = orgYaw;
|
||||
entity.rotationPitch = orgPitch;
|
||||
entity.prevRotationYaw = orgPrevYaw;
|
||||
entity.prevRotationPitch = orgPrevPitch;
|
||||
if (entity instanceof CameraEntity) {
|
||||
((CameraEntity) entity).roll = orgRoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderHand", at = @At("HEAD"), cancellable = true)
|
||||
@Inject(method = "renderHand", at = @At("HEAD"))
|
||||
private void renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
// TODO
|
||||
// if (handler != null) {
|
||||
// if (handler.data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
// ci.cancel();
|
||||
// return; // No spectator hands during 360° view, we wouldn't even know where to put it
|
||||
// }
|
||||
// Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
// if (!ReplayHandler.isCameraView() && currentEntity instanceof EntityPlayer) {
|
||||
// renderPass = handler.data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE ? 1 : 0;
|
||||
// spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass);
|
||||
// }
|
||||
// } else if (ReplayHandler.isInReplay() && !ReplayHandler.isCameraView()) {
|
||||
// Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
// if (!ReplayHandler.isCameraView() && currentEntity instanceof EntityPlayer) {
|
||||
// spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;drawSelectionBox(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/util/MovingObjectPosition;IF)V"))
|
||||
private void drawSelectionBox(RenderGlobal instance, EntityPlayer player, MovingObjectPosition mop, int alwaysZero, float partialTicks) {
|
||||
if (handler == null) {
|
||||
instance.drawSelectionBox(player, mop, alwaysZero, partialTicks);
|
||||
// TODO Check that this gets cancelled in 360 and doesn't misbehave in SP/MP
|
||||
Entity currentEntity = Minecraft.getMinecraft().getRenderViewEntity();
|
||||
if (currentEntity instanceof EntityPlayer && !(currentEntity instanceof EntityPlayerSP)) {
|
||||
spectatorRenderer.renderSpectatorHand((EntityPlayer) currentEntity, partialTicks, renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
private int orgRenderDistanceChunks;
|
||||
|
||||
@Inject(method = "renderWorldPass", at = @At(value = "JUMP", ordinal = 0))
|
||||
private void beforeRenderSky(CallbackInfo ci) {
|
||||
if (handler != null && !handler.getOptions().isDefaultSky()) {
|
||||
GameSettings settings = Minecraft.getMinecraft().gameSettings;
|
||||
orgRenderDistanceChunks = settings.renderDistanceChunks;
|
||||
settings.renderDistanceChunks = 5; // Set render distance to 5 so we're always rendering sky when chroma keying
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(FI)V"))
|
||||
private void renderSky(RenderGlobal instance, float partialTicks, int renderPass) {
|
||||
if (handler != null && !handler.getOptions().isDefaultSky()) {
|
||||
Minecraft.getMinecraft().gameSettings.renderDistanceChunks = orgRenderDistanceChunks;
|
||||
int c = handler.getOptions().getSkyColor();
|
||||
GlStateManager.clearColor((c >> 16 & 0xff) / (float) 0xff, (c >> 8 & 0xff) / (float) 0xff, (c & 0xff) / (float) 0xff, 1);
|
||||
GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
} else {
|
||||
instance.renderSky(partialTicks, renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cubic Renderer
|
||||
*/
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 0))
|
||||
private void setupStereoscopicProjection(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (getHandler() != null) {
|
||||
if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) {
|
||||
GlStateManager.translate(0.07, 0, 0);
|
||||
} else if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) {
|
||||
GlStateManager.translate(-0.07, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;loadIdentity()V", shift = At.Shift.AFTER, ordinal = 1))
|
||||
private void setupStereoscopicModelView(float partialTicks, int renderPass, CallbackInfo ci) {
|
||||
if (getHandler() != null) {
|
||||
if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.LEFT_EYE) {
|
||||
GlStateManager.translate(0.1, 0, 0);
|
||||
} else if (getHandler().data == StereoscopicOpenGlFrameCapturer.Data.RIGHT_EYE) {
|
||||
GlStateManager.translate(-0.1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cubic Renderer
|
||||
*/
|
||||
|
||||
@Redirect(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false))
|
||||
private void gluPerspective$0(float fovY, float aspect, float zNear, float zFar) {
|
||||
gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Redirect(method = "renderWorldPass", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false))
|
||||
private void gluPerspective$1(float fovY, float aspect, float zNear, float zFar) {
|
||||
gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Redirect(method = "renderCloudsCheck", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false))
|
||||
private void gluPerspective$2(float fovY, float aspect, float zNear, float zFar) {
|
||||
gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gluPerspective(float fovY, float aspect, float zNear, float zFar) {
|
||||
if (getHandler() != null && getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
fovY = 90;
|
||||
aspect = 1;
|
||||
}
|
||||
Project.gluPerspective(fovY, aspect, zNear, zFar);
|
||||
}
|
||||
|
||||
@Inject(method = "orientCamera", at = @At("HEAD"))
|
||||
private void setupCubicFrameRotation(float partialTicks, CallbackInfo ci) {
|
||||
if (getHandler() != null && getHandler().data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
switch ((CubicOpenGlFrameCapturer.Data) getHandler().data) {
|
||||
case FRONT:
|
||||
GlStateManager.rotate(0, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case RIGHT:
|
||||
GlStateManager.rotate(90, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case BACK:
|
||||
GlStateManager.rotate(180, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case LEFT:
|
||||
GlStateManager.rotate(-90, 0.0F, 1.0F, 0.0F);
|
||||
break;
|
||||
case BOTTOM:
|
||||
GlStateManager.rotate(90, 1.0F, 0.0F, 0.0F);
|
||||
break;
|
||||
case TOP:
|
||||
GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F);
|
||||
break;
|
||||
}
|
||||
|
||||
// Minecraft goes back a little so we have to invert that
|
||||
GlStateManager.translate(0.0F, 0.0F, 0.1F);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
|
||||
|
||||
@Inject(method = "orientCamera", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;translate(FFF)V", shift = At.Shift.AFTER, ordinal = 3))
|
||||
private void setupCameraRoll(float partialTicks, CallbackInfo ci) {
|
||||
if (mc.getRenderViewEntity() instanceof CameraEntity) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package eu.crushedpixel.replaymod.mixin;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import eu.crushedpixel.replaymod.video.capturer.CubicOpenGlFrameCapturer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
@@ -14,27 +11,12 @@ import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(RenderManager.class)
|
||||
public class MixinRenderManager {
|
||||
@Shadow
|
||||
private float playerViewX;
|
||||
|
||||
@Shadow
|
||||
private float playerViewY;
|
||||
|
||||
@Inject(method = "doRenderEntity", at = @At("HEAD"))
|
||||
private void reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) {
|
||||
EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).getHandler();
|
||||
if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) {
|
||||
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
|
||||
double yaw = -Math.atan2(dx, dz);
|
||||
playerViewX = (float) Math.toDegrees(pitch);
|
||||
playerViewY = (float) Math.toDegrees(yaw);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "cacheActiveRenderInfo", at = @At("RETURN"))
|
||||
public void fixHeadRotationForAnimals(World world, FontRenderer font, Entity view, Entity target, GameSettings settings, float partialRenderTick, CallbackInfo ci) {
|
||||
if (view instanceof EntityAnimal && !((EntityAnimal) view).isPlayerSleeping()) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.renderer;
|
||||
|
||||
import com.replaymod.render.hooks.EntityRendererHandler;
|
||||
import eu.crushedpixel.replaymod.utils.SkinProvider;
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -57,7 +57,7 @@ public class SpectatorRenderer {
|
||||
GlStateManager.translate((float)(-(renderPass * 2 - 1)) * f1, 0.0F, 0.0F);
|
||||
}
|
||||
|
||||
((EntityRendererHandler.GluPerspective) mc.entityRenderer).gluPerspective(mc.entityRenderer.getFOVModifier(partialTicks, false),
|
||||
((EntityRendererHandler.GluPerspective) mc.entityRenderer).replayModRender_gluPerspective(mc.entityRenderer.getFOVModifier(partialTicks, false),
|
||||
(float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, mc.entityRenderer.farPlaneDistance * 2.0F);
|
||||
|
||||
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.settings;
|
||||
|
||||
import eu.crushedpixel.replaymod.holders.GuiEntryListEntry;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum EncodingPreset implements GuiEntryListEntry {
|
||||
|
||||
MP4CUSTOM("replaymod.gui.rendersettings.presets.mp4.custom",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -b:v %BITRATE% -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"),
|
||||
|
||||
MP4HIGH("replaymod.gui.rendersettings.presets.mp4.high",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -qp 1 -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"),
|
||||
|
||||
MP4DEFAULT("replaymod.gui.rendersettings.presets.mp4.default",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"),
|
||||
|
||||
MP4POTATO("replaymod.gui.rendersettings.presets.mp4.potato",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -crf 51 -pix_fmt yuv420p \"%FILENAME%.mp4\"", "mp4"),
|
||||
|
||||
WEBMCUSTOM("replaymod.gui.rendersettings.presets.webm.custom",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libvpx -b:v %BITRATE% \"%FILENAME%.webm\"", "webm"),
|
||||
|
||||
MKVLOSSLESS("replaymod.gui.rendersettings.presets.mkv.lossless",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - -an -c:v libx264 -preset ultrafast -qp 0 \"%FILENAME%.mkv\"", "mkv"),
|
||||
|
||||
PNGSEQUENCE("replaymod.gui.rendersettings.presets.png",
|
||||
"-y -f rawvideo -pix_fmt rgb24 -s %WIDTH%x%HEIGHT% -r %FPS% -i - \"%FILENAME%-%06d.png\"", "png");
|
||||
|
||||
private String name;
|
||||
|
||||
@Getter
|
||||
private String commandLineArgs;
|
||||
|
||||
@Getter
|
||||
private String fileExtension;
|
||||
|
||||
public boolean hasBitrateSetting() {
|
||||
return commandLineArgs.contains("%BITRATE%");
|
||||
}
|
||||
|
||||
public boolean isYuv420() { return commandLineArgs.contains("-pix_fmt yuv420p"); }
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return getI18nName();
|
||||
}
|
||||
|
||||
public String getI18nName() {
|
||||
return I18n.format(name);
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.settings;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Pipelines;
|
||||
import lombok.Data;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.apache.commons.lang3.Validate.isTrue;
|
||||
|
||||
@Data
|
||||
public final class RenderOptions {
|
||||
private Pipelines.Preset mode = Pipelines.Preset.DEFAULT;
|
||||
private String bitrate = "10000K";
|
||||
private int fps = 30;
|
||||
|
||||
private File outputFile;
|
||||
|
||||
/**
|
||||
* Whether to ignore camera rotation. On yaw, pitch, roll axis.
|
||||
*/
|
||||
private boolean[] ignoreCameraRotation = new boolean[3];
|
||||
|
||||
// Advanced
|
||||
private boolean waitForChunks = true;
|
||||
private boolean isLinearMovement = false;
|
||||
private boolean hideNameTags = false;
|
||||
private int skyColor = -1;
|
||||
private int width = Minecraft.getMinecraft().displayWidth;
|
||||
private int height = Minecraft.getMinecraft().displayHeight;
|
||||
|
||||
private boolean inject360Metadata = false;
|
||||
|
||||
// Highly advanced
|
||||
private boolean highPerformance;
|
||||
private String exportCommand = "ffmpeg";
|
||||
private String exportCommandArgs = "";
|
||||
|
||||
public int getFps() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
public void setFps(int fps) {
|
||||
isTrue(fps > 0, "Fps must be positive.");
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
public boolean isDefaultSky() {
|
||||
return skyColor == -1;
|
||||
}
|
||||
|
||||
public void setIgnoreCameraRotation(boolean yaw, boolean pitch, boolean roll) {
|
||||
ignoreCameraRotation = new boolean[]{yaw, pitch, roll};
|
||||
}
|
||||
|
||||
public RenderOptions copy() {
|
||||
RenderOptions copy = new RenderOptions();
|
||||
copy.mode = this.mode;
|
||||
copy.bitrate = this.bitrate;
|
||||
copy.fps = this.fps;
|
||||
copy.ignoreCameraRotation = Arrays.copyOf(this.ignoreCameraRotation, 3);
|
||||
copy.waitForChunks = this.waitForChunks;
|
||||
copy.isLinearMovement = this.isLinearMovement;
|
||||
copy.hideNameTags = this.hideNameTags;
|
||||
copy.skyColor = this.skyColor;
|
||||
copy.width = this.width;
|
||||
copy.height = this.height;
|
||||
copy.inject360Metadata = this.inject360Metadata;
|
||||
copy.highPerformance = this.highPerformance;
|
||||
copy.exportCommand = this.exportCommand;
|
||||
copy.exportCommandArgs = this.exportCommandArgs;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*/
|
||||
/* Methods and Fields used to save/load a RenderOptions instance from/to a Configuration File
|
||||
/*/
|
||||
|
||||
private static final String CATEGORY_NAME = "settings_cache";
|
||||
|
||||
private static final String MODE_ENTRY = "mode";
|
||||
private static final String BITRATE_ENTRY = "bitrate";
|
||||
private static final String FPS_ENTRY = "fps";
|
||||
private static final String IGNORE_ROTATION_ENTRY = "ignoreRotation";
|
||||
private static final String CHUNKS_ENTRY = "waitForChunks";
|
||||
private static final String LINEAR_ENTRY = "linearMovement";
|
||||
private static final String HIDE_NAMETAGS_ENTRY = "hideNametags";
|
||||
private static final String SKY_COLOR_ENTRY = "skyColor";
|
||||
private static final String INJECT_METADATA_ENTRY = "injectMetadata";
|
||||
private static final String WIDTH_ENTRY = "xRes";
|
||||
private static final String HEIGHT_ENTRY = "yRes";
|
||||
private static final String HIGH_PERFORMANCE_ENTRY = "highPerformance";
|
||||
private static final String EXPORT_COMMAND_ENTRY = "exportCommand";
|
||||
private static final String EXPORT_COMMAND_ARGS_ENTRY = "exportCommandArgs";
|
||||
|
||||
public static RenderOptions loadFromConfig(Configuration config) {
|
||||
RenderOptions loaded = new RenderOptions();
|
||||
|
||||
config.load();
|
||||
|
||||
loaded.mode = getModeFromConfig(config);
|
||||
loaded.bitrate = getBitrateFromConfig(config);
|
||||
loaded.fps = getFramerateFromConfig(config);
|
||||
loaded.ignoreCameraRotation = getIgnoreCameraRotationFromConfig(config);
|
||||
loaded.waitForChunks = getWaitForChunksFromConfig(config);
|
||||
loaded.isLinearMovement = getLinearFromConfig(config);
|
||||
loaded.hideNameTags = getHideNametagsFromConfig(config);
|
||||
loaded.skyColor = getSkyColorFromConfig(config);
|
||||
loaded.inject360Metadata = getInject360MetadataFromConfig(config);
|
||||
loaded.width = getWidthFromConfig(config);
|
||||
loaded.height = getHeightFromConfig(config);
|
||||
loaded.highPerformance = getHighPerformanceFromConfig(config);
|
||||
loaded.exportCommand = getExportCommandFromConfig(config);
|
||||
loaded.exportCommandArgs = getExportCommandArgsFromConfig(config);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public void saveToConfig(Configuration config) {
|
||||
config.load();
|
||||
|
||||
config.removeCategory(config.getCategory(CATEGORY_NAME));
|
||||
|
||||
getModeFromConfig(config, getMode().name());
|
||||
getBitrateFromConfig(config, getBitrate());
|
||||
getFramerateFromConfig(config, getFps());
|
||||
getIgnoreCameraRotationFromConfig(config, getIgnoreCameraRotation());
|
||||
getWaitForChunksFromConfig(config, isWaitForChunks());
|
||||
getLinearFromConfig(config, isLinearMovement());
|
||||
getHideNametagsFromConfig(config, isHideNameTags());
|
||||
getSkyColorFromConfig(config, getSkyColor());
|
||||
getInject360MetadataFromConfig(config, isInject360Metadata());
|
||||
getWidthFromConfig(config, getWidth());
|
||||
getHeightFromConfig(config, getHeight());
|
||||
getHighPerformanceFromConfig(config, isHighPerformance());
|
||||
getExportCommandFromConfig(config, getExportCommand());
|
||||
getExportCommandArgsFromConfig(config, getExportCommandArgs());
|
||||
|
||||
config.save();
|
||||
}
|
||||
|
||||
private static Pipelines.Preset getModeFromConfig(Configuration config) {
|
||||
return getModeFromConfig(config, Pipelines.Preset.DEFAULT.name());
|
||||
}
|
||||
|
||||
private static Pipelines.Preset getModeFromConfig(Configuration config, String def) {
|
||||
return Pipelines.Preset.valueOf(config.get(CATEGORY_NAME, MODE_ENTRY, def).getString());
|
||||
}
|
||||
|
||||
private static String getBitrateFromConfig(Configuration config) {
|
||||
return getBitrateFromConfig(config, "10000K");
|
||||
}
|
||||
|
||||
private static String getBitrateFromConfig(Configuration config, String def) {
|
||||
return config.get(CATEGORY_NAME, BITRATE_ENTRY, def).getString();
|
||||
}
|
||||
|
||||
private static int getFramerateFromConfig(Configuration config) {
|
||||
return getFramerateFromConfig(config, 30);
|
||||
}
|
||||
|
||||
private static int getFramerateFromConfig(Configuration config, int def) {
|
||||
return config.get(CATEGORY_NAME, FPS_ENTRY, def).getInt();
|
||||
}
|
||||
|
||||
private static boolean[] getIgnoreCameraRotationFromConfig(Configuration config) {
|
||||
return getIgnoreCameraRotationFromConfig(config, new boolean[3]);
|
||||
}
|
||||
|
||||
private static boolean[] getIgnoreCameraRotationFromConfig(Configuration config, boolean[] def) {
|
||||
String converted = "";
|
||||
for(boolean b : def) converted += b+" ";
|
||||
|
||||
String result = config.get(CATEGORY_NAME, IGNORE_ROTATION_ENTRY, converted).getString();
|
||||
|
||||
String[] split = result.split(" ");
|
||||
|
||||
boolean[] ret = new boolean[3];
|
||||
for(int i=0; i<3; i++) {
|
||||
ret[i] = Boolean.valueOf(split[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static boolean getWaitForChunksFromConfig(Configuration config) {
|
||||
return getWaitForChunksFromConfig(config, true);
|
||||
}
|
||||
|
||||
private static boolean getWaitForChunksFromConfig(Configuration config, boolean def) {
|
||||
return config.get(CATEGORY_NAME, CHUNKS_ENTRY, def).getBoolean();
|
||||
}
|
||||
|
||||
private static boolean getLinearFromConfig(Configuration config) {
|
||||
return getLinearFromConfig(config, false);
|
||||
}
|
||||
|
||||
private static boolean getLinearFromConfig(Configuration config, boolean def) {
|
||||
return config.get(CATEGORY_NAME, LINEAR_ENTRY, def).getBoolean();
|
||||
}
|
||||
|
||||
private static boolean getHideNametagsFromConfig(Configuration config) {
|
||||
return getHideNametagsFromConfig(config, false);
|
||||
}
|
||||
|
||||
private static boolean getHideNametagsFromConfig(Configuration config, boolean def) {
|
||||
return config.get(CATEGORY_NAME, HIDE_NAMETAGS_ENTRY, def).getBoolean();
|
||||
}
|
||||
|
||||
private static int getSkyColorFromConfig(Configuration config) {
|
||||
return getSkyColorFromConfig(config, -1);
|
||||
}
|
||||
|
||||
private static int getSkyColorFromConfig(Configuration config, int def) {
|
||||
return config.get(CATEGORY_NAME, SKY_COLOR_ENTRY, def).getInt();
|
||||
}
|
||||
|
||||
private static boolean getInject360MetadataFromConfig(Configuration config) {
|
||||
return getInject360MetadataFromConfig(config, false);
|
||||
}
|
||||
|
||||
private static boolean getInject360MetadataFromConfig(Configuration config, boolean def) {
|
||||
return config.get(CATEGORY_NAME, INJECT_METADATA_ENTRY, def).getBoolean();
|
||||
}
|
||||
|
||||
private static int getWidthFromConfig(Configuration config) {
|
||||
return getWidthFromConfig(config, Minecraft.getMinecraft().displayWidth);
|
||||
}
|
||||
|
||||
private static int getWidthFromConfig(Configuration config, int def) {
|
||||
return config.get(CATEGORY_NAME, WIDTH_ENTRY, def).getInt();
|
||||
}
|
||||
|
||||
private static int getHeightFromConfig(Configuration config) {
|
||||
return getHeightFromConfig(config, Minecraft.getMinecraft().displayHeight);
|
||||
}
|
||||
|
||||
private static int getHeightFromConfig(Configuration config, int def) {
|
||||
return config.get(CATEGORY_NAME, HEIGHT_ENTRY, def).getInt();
|
||||
}
|
||||
|
||||
private static boolean getHighPerformanceFromConfig(Configuration config) {
|
||||
return getHighPerformanceFromConfig(config, false);
|
||||
}
|
||||
|
||||
private static boolean getHighPerformanceFromConfig(Configuration config, boolean def) {
|
||||
return config.get(CATEGORY_NAME, HIGH_PERFORMANCE_ENTRY, def).getBoolean();
|
||||
}
|
||||
|
||||
private static String getExportCommandFromConfig(Configuration config) {
|
||||
return getExportCommandFromConfig(config, "ffmpeg");
|
||||
}
|
||||
|
||||
private static String getExportCommandFromConfig(Configuration config, String def) {
|
||||
return config.get(CATEGORY_NAME, EXPORT_COMMAND_ENTRY, def).getString();
|
||||
}
|
||||
|
||||
private static String getExportCommandArgsFromConfig(Configuration config) {
|
||||
return getExportCommandArgsFromConfig(config, "");
|
||||
}
|
||||
|
||||
private static String getExportCommandArgsFromConfig(Configuration config, String def) {
|
||||
return config.get(CATEGORY_NAME, EXPORT_COMMAND_ARGS_ENTRY, def).getString();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.timer;
|
||||
|
||||
import eu.crushedpixel.replaymod.events.handlers.MinecraftTicker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Timer;
|
||||
|
||||
public class ReplayTimer extends Timer {
|
||||
|
||||
public static ReplayTimer get(Minecraft mc) {
|
||||
Timer timer = mc.timer;
|
||||
if (!(timer instanceof ReplayTimer)) {
|
||||
throw new IllegalStateException("ReplayTimer not installed");
|
||||
}
|
||||
return (ReplayTimer) timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the timer is set to passive, it does not advance the (render) ticks on it's own.
|
||||
*/
|
||||
public boolean passive;
|
||||
|
||||
public ReplayTimer() {
|
||||
super(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTimer() {
|
||||
if (!passive) {
|
||||
super.updateTimer();
|
||||
}
|
||||
|
||||
if (timerSpeed == 0) {
|
||||
try {
|
||||
MinecraftTicker.runMouseKeyboardTick(Minecraft.getMinecraft());
|
||||
} catch (OutOfMemoryError e) {
|
||||
// Disable passive mode and reset timer speed so we can use the buttons on the OOM screen
|
||||
passive = false;
|
||||
timerSpeed = 1;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package eu.crushedpixel.replaymod.utils;
|
||||
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
public class BoundingUtils {
|
||||
|
||||
public static Dimension fitIntoBounds(Dimension toFit, Dimension bounds) {
|
||||
public static Dimension fitIntoBounds(ReadableDimension toFit, ReadableDimension bounds) {
|
||||
int width = toFit.getWidth();
|
||||
int height = toFit.getHeight();
|
||||
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.utils;
|
||||
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
public class DurationUtils {
|
||||
|
||||
public static String convertSecondsToString(int seconds) {
|
||||
int hours = seconds/(60*60);
|
||||
int min = seconds/60 - hours*60;
|
||||
int sec = seconds - ((min*60) + (hours*60*60));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if(hours > 0) builder.append(hours).append(I18n.format("replaymod.gui.hours"));
|
||||
if(min > 0 || hours > 0) builder.append(min).append(I18n.format("replaymod.gui.minutes"));
|
||||
builder.append(sec).append(I18n.format("replaymod.gui.seconds"));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String convertSecondsToShortString(int seconds) {
|
||||
int hours = seconds/(60*60);
|
||||
int min = seconds/60 - hours*60;
|
||||
|
||||
@@ -6,7 +6,6 @@ import net.minecraft.client.renderer.WorldRenderer;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class OpenGLUtils {
|
||||
@@ -28,18 +27,6 @@ public class OpenGLUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
public static void openGlBytesToRBG(ByteBuffer buffer, int bufferWidth, int xOffset, int yOffset, ByteBuffer to, int width) {
|
||||
byte[] rowBuf = new byte[bufferWidth * 3];
|
||||
// Copy image flipped vertically to target buffer
|
||||
int rows = buffer.remaining() / 3 / bufferWidth;
|
||||
for (int i = 0; i < rows; i++) {
|
||||
buffer.get(rowBuf);
|
||||
to.position(((yOffset + rows - i - 1) * width + xOffset) * 3);
|
||||
to.put(rowBuf);
|
||||
}
|
||||
to.rewind();
|
||||
}
|
||||
|
||||
public static void drawRotatedRectWithCustomSizedTexture(int x, int y, float rotation, float u, float v, int width, int height, float textureWidth, float textureHeight) {
|
||||
GlStateManager.pushMatrix();
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class ScreenCapture {
|
||||
|
||||
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
private static IntBuffer pixelBuffer;
|
||||
private static int[] pixelValues;
|
||||
|
||||
public static BufferedImage captureScreen() {
|
||||
int width = mc.displayWidth;
|
||||
int height = mc.displayHeight;
|
||||
|
||||
Framebuffer buffer = mc.getFramebuffer();
|
||||
|
||||
if(OpenGlHelper.isFramebufferEnabled()) {
|
||||
width = buffer.framebufferTextureWidth;
|
||||
height = buffer.framebufferTextureHeight;
|
||||
}
|
||||
|
||||
int k = width * height;
|
||||
|
||||
if(pixelBuffer == null || pixelBuffer.capacity() < k) {
|
||||
pixelBuffer = BufferUtils.createIntBuffer(k);
|
||||
pixelValues = new int[k];
|
||||
}
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
pixelBuffer.clear();
|
||||
|
||||
if(OpenGlHelper.isFramebufferEnabled()) {
|
||||
GlStateManager.bindTexture(buffer.framebufferTexture);
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
} else {
|
||||
GL11.glReadPixels(0, 0, width, height, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
|
||||
}
|
||||
|
||||
pixelBuffer.get(pixelValues);
|
||||
TextureUtil.processPixelValues(pixelValues, width, height);
|
||||
BufferedImage bufferedimage;
|
||||
|
||||
|
||||
if(OpenGlHelper.isFramebufferEnabled()) {
|
||||
bufferedimage = new BufferedImage(buffer.framebufferWidth, buffer.framebufferHeight, 1);
|
||||
int l = buffer.framebufferTextureHeight - buffer.framebufferHeight;
|
||||
|
||||
for(int i1 = l; i1 < buffer.framebufferTextureHeight; ++i1) {
|
||||
for(int j1 = 0; j1 < buffer.framebufferWidth; ++j1) {
|
||||
bufferedimage.setRGB(j1, i1 - l, pixelValues[i1 * buffer.framebufferTextureWidth + j1]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bufferedimage = new BufferedImage(width, height, 1);
|
||||
bufferedimage.setRGB(0, 0, width, height, pixelValues, 0, width);
|
||||
}
|
||||
|
||||
|
||||
return bufferedimage;
|
||||
}
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.replay.ReplaySender;
|
||||
import eu.crushedpixel.replaymod.gui.GuiVideoRenderer;
|
||||
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
|
||||
import eu.crushedpixel.replaymod.holders.TimestampValue;
|
||||
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
|
||||
import eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.timer.ReplayTimer;
|
||||
import eu.crushedpixel.replaymod.video.capturer.RenderInfo;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import eu.crushedpixel.replaymod.video.metadata.MetadataInjector;
|
||||
import eu.crushedpixel.replaymod.video.rendering.Pipeline;
|
||||
import eu.crushedpixel.replaymod.video.rendering.Pipelines;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.util.Timer;
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import static net.minecraft.client.renderer.GlStateManager.*;
|
||||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
||||
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
|
||||
|
||||
public class VideoRenderer implements RenderInfo {
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private final ReplaySender replaySender;
|
||||
private final RenderOptions options;
|
||||
private final VideoWriter videoWriter;
|
||||
|
||||
private int fps;
|
||||
private boolean mouseWasGrabbed;
|
||||
|
||||
private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal;
|
||||
|
||||
private int framesDone;
|
||||
private int totalFrames;
|
||||
|
||||
private KeyframeList<AdvancedPosition> positionKeyframes;
|
||||
private KeyframeList<TimestampValue> timeKeyframes;
|
||||
|
||||
private final GuiVideoRenderer gui;
|
||||
private boolean paused;
|
||||
private boolean cancelled;
|
||||
|
||||
private final Pipeline renderingPipeline;
|
||||
|
||||
public VideoRenderer(RenderOptions options) throws IOException {
|
||||
this.gui = new GuiVideoRenderer(this);
|
||||
this.replaySender = ReplayMod.replaySender;
|
||||
this.options = options;
|
||||
this.renderingPipeline = Pipelines.newPipeline(options.getMode(), this, videoWriter = new VideoWriter(options) {
|
||||
@Override
|
||||
public void consume(RGBFrame frame) {
|
||||
gui.updatePreview(frame);
|
||||
super.consume(frame);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this video.
|
||||
* @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed)
|
||||
*/
|
||||
public boolean renderVideo() {
|
||||
setup();
|
||||
|
||||
// Because this might take some time to prepare we'll render the GUI at least once to not confuse the user
|
||||
drawGui();
|
||||
|
||||
Timer timer = mc.timer;
|
||||
|
||||
// Play up to one second before starting to render
|
||||
// This is necessary in order to ensure that all entities have at least two position packets
|
||||
// and their first position in the recording is correct.
|
||||
// Note that it is impossible to also get the interpolation between their latest position
|
||||
// and the one in the recording correct as there's no reliable way to tell when the server ticks
|
||||
// or when we should be done with the interpolation of the entity
|
||||
int videoStart = timeKeyframes.getInterpolatedValueForPathPosition(0, true).asInt();
|
||||
|
||||
if (videoStart > 1000) {
|
||||
int replayTime = videoStart - 1000;
|
||||
timer.elapsedPartialTicks = timer.renderPartialTicks = 0;
|
||||
timer.timerSpeed = 1;
|
||||
while (replayTime < videoStart) {
|
||||
timer.elapsedTicks = 1;
|
||||
replayTime += 50;
|
||||
replaySender.sendPacketsTill(replayTime);
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
mc.renderGlobal.renderEntitiesStartupCounter = 0;
|
||||
|
||||
renderingPipeline.run();
|
||||
|
||||
if(options.isInject360Metadata()) {
|
||||
File outputFile = options.getOutputFile();
|
||||
MetadataInjector.inject360Metadata(new File(outputFile.getParentFile(), outputFile.getName() + ".mp4"));
|
||||
}
|
||||
|
||||
finish();
|
||||
return !cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float updateForNextFrame() {
|
||||
if (!options.isHighPerformance() || framesDone % fps == 0) {
|
||||
drawGui();
|
||||
}
|
||||
|
||||
updateTime(mc.timer, framesDone);
|
||||
|
||||
int elapsedTicks = mc.timer.elapsedTicks;
|
||||
while (elapsedTicks-- > 0) {
|
||||
tick();
|
||||
}
|
||||
|
||||
updateCam();
|
||||
framesDone++;
|
||||
return mc.timer.renderPartialTicks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderOptions getRenderOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
if (Mouse.isGrabbed()) {
|
||||
mouseWasGrabbed = true;
|
||||
}
|
||||
Mouse.setGrabbed(false);
|
||||
ReplayTimer.get(mc).passive = true;
|
||||
mc.timer.timerSpeed = 1;
|
||||
|
||||
fps = options.getFps();
|
||||
|
||||
// TODO
|
||||
// positionKeyframes = ReplayHandler.getPositionKeyframes();
|
||||
// timeKeyframes = ReplayHandler.getTimeKeyframes();
|
||||
|
||||
int duration = Math.max(timeKeyframes.last().getRealTimestamp(), positionKeyframes.last().getRealTimestamp());
|
||||
|
||||
totalFrames = duration*fps/1000;
|
||||
|
||||
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
gui.setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
|
||||
|
||||
if (options.isWaitForChunks()) {
|
||||
chunkLoadingRenderGlobal = new ChunkLoadingRenderGlobal(mc.renderGlobal);
|
||||
}
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
if (mouseWasGrabbed) {
|
||||
mc.mouseHelper.grabMouseCursor();
|
||||
}
|
||||
ReplayTimer.get(mc).passive = false;
|
||||
mc.displayGuiScreen(null);
|
||||
if (chunkLoadingRenderGlobal != null) {
|
||||
chunkLoadingRenderGlobal.uninstall();
|
||||
}
|
||||
|
||||
ReplayMod.soundHandler.playRenderSuccessSound();
|
||||
|
||||
mc.displayGuiScreen(null);
|
||||
}
|
||||
|
||||
private void updateCam() {
|
||||
//TODO
|
||||
// KeyframeList<AdvancedPosition> positionKeyframes = ReplayHandler.getPositionKeyframes();
|
||||
//
|
||||
// if (mc.thePlayer == null) {
|
||||
// return; // Not ready yet
|
||||
// }
|
||||
// int videoTime = framesDone * 1000 / fps;
|
||||
// int posCount = ReplayHandler.getPositionKeyframes().size();
|
||||
//
|
||||
// AdvancedPosition pos = new AdvancedPosition();
|
||||
// Keyframe<AdvancedPosition> lastPos = positionKeyframes.getPreviousKeyframe(videoTime, true);
|
||||
// Keyframe<AdvancedPosition> nextPos = null;
|
||||
//
|
||||
// // Position interpolation
|
||||
// nextPos = positionKeyframes.getNextKeyframe(videoTime, true);
|
||||
//
|
||||
// int lastPosStamp = lastPos.getRealTimestamp();
|
||||
// int nextPosStamp = (nextPos == null ? lastPos : nextPos).getRealTimestamp();
|
||||
//
|
||||
// int diffLength = nextPosStamp - lastPosStamp;
|
||||
// float diffPct = (float) (videoTime - lastPosStamp) / diffLength;
|
||||
// if(Float.isInfinite(diffPct) || Float.isNaN(diffPct)) diffPct = 0;
|
||||
//
|
||||
// float totalPct = (positionKeyframes.indexOf(lastPos) + diffPct) / (posCount-1);
|
||||
// pos = positionKeyframes.getInterpolatedValueForPathPosition(Math.max(0, Math.min(1, totalPct)), ReplayMod.replaySettings.isLinearMovement());
|
||||
//
|
||||
// boolean spectateCamera = true;
|
||||
//
|
||||
// //check whether between two First Person Spectator Keyframes
|
||||
// if(lastPos != null && nextPos != null) {
|
||||
// if(lastPos.getValue() instanceof SpectatorData && nextPos.getValue() instanceof SpectatorData) {
|
||||
// SpectatorData previousSpectatorData = (SpectatorData)lastPos.getValue();
|
||||
// SpectatorData nextSpectatorData = (SpectatorData)nextPos.getValue();
|
||||
// if(previousSpectatorData.getSpectatedEntityID() == nextSpectatorData.getSpectatedEntityID()) {
|
||||
// if(previousSpectatorData.getSpectatingMethod() == SpectatorData.SpectatingMethod.FIRST_PERSON
|
||||
// && nextSpectatorData.getSpectatingMethod() == SpectatorData.SpectatingMethod.FIRST_PERSON) {
|
||||
// spectateCamera = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if(spectateCamera) {
|
||||
// // Make sure we're spectating the camera entity
|
||||
// // We do not use .spectateCamera() as that method sets the position of the camera to the previous
|
||||
// // entity, overriding our calculations
|
||||
// ReplayHandler.spectateEntity(ReplayHandler.getCameraEntity());
|
||||
//
|
||||
// if(pos != null) {
|
||||
// ReplayHandler.setCameraTilt((float)pos.getRoll());
|
||||
// ReplayHandler.getCameraEntity().movePath(pos);
|
||||
// mc.entityRenderer.fovModifierHand = mc.entityRenderer.fovModifierHandPrev = 1;
|
||||
// }
|
||||
// } else {
|
||||
// ReplayHandler.spectateEntity(mc.theWorld.getEntityByID(((SpectatorData)lastPos.getValue()).getSpectatedEntityID()));
|
||||
// }
|
||||
}
|
||||
|
||||
private void updateTime(Timer timer, int framesDone) {
|
||||
// TODO
|
||||
// KeyframeList<TimestampValue> timeKeyframes = ReplayHandler.getTimeKeyframes();
|
||||
//
|
||||
// int videoTime = framesDone * 1000 / fps;
|
||||
// int timeCount = timeKeyframes.size();
|
||||
//
|
||||
// // WARNING: The rest of this method contains some magic for which Marius is responsible
|
||||
// // Time interpolation
|
||||
// Keyframe<TimestampValue> lastTime = timeKeyframes.getPreviousKeyframe(videoTime, true);
|
||||
// Keyframe<TimestampValue> nextTime = timeKeyframes.getNextKeyframe(videoTime, true);
|
||||
//
|
||||
// int lastTimeStamp = 0;
|
||||
// int nextTimeStamp = 0;
|
||||
//
|
||||
// double curSpeed = 0;
|
||||
//
|
||||
// if(nextTime != null || lastTime != null) {
|
||||
// if(nextTime != null && lastTime != null && nextTime.getRealTimestamp() == lastTime.getRealTimestamp()) {
|
||||
// curSpeed = 0;
|
||||
// } else {
|
||||
// if(nextTime != null) {
|
||||
// nextTimeStamp = nextTime.getRealTimestamp();
|
||||
// } else {
|
||||
// nextTimeStamp = lastTime.getRealTimestamp();
|
||||
// }
|
||||
//
|
||||
// if(lastTime != null) {
|
||||
// lastTimeStamp = lastTime.getRealTimestamp();
|
||||
// } else {
|
||||
// lastTimeStamp = nextTime.getRealTimestamp();
|
||||
// }
|
||||
//
|
||||
// if(!(nextTime == null || lastTime == null)) {
|
||||
// curSpeed = ((double)(((int)nextTime.getValue().value-(int)lastTime.getValue().value)))/((double)((nextTimeStamp-lastTimeStamp)));
|
||||
// }
|
||||
//
|
||||
// if(lastTimeStamp == nextTimeStamp) {
|
||||
// curSpeed = 0f;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// int currentTimeDiff = nextTimeStamp - lastTimeStamp;
|
||||
// int currentTime = videoTime - lastTimeStamp;
|
||||
//
|
||||
// float currentTimeStepPerc = (float)currentTime/(float)currentTimeDiff; //The percentage of the travelled path between the current timestamps
|
||||
// if(Float.isInfinite(currentTimeStepPerc) || Float.isNaN(currentTimeStepPerc)) currentTimeStepPerc = 0;
|
||||
//
|
||||
// float timePos = (timeKeyframes.indexOf(lastTime) + currentTimeStepPerc) / (timeCount-1f);
|
||||
//
|
||||
// Integer replayTime = timeKeyframes.getInterpolatedValueForPathPosition(Math.max(0, Math.min(1, timePos)), true).asInt();
|
||||
//
|
||||
// replaySender.sendPacketsTill(replayTime);
|
||||
//
|
||||
// if (curSpeed >= 0) {
|
||||
// replaySender.setReplaySpeed(curSpeed);
|
||||
// }
|
||||
//
|
||||
// // Update Timer
|
||||
// EnchantmentTimer.increaseRecordingTime(1000 / fps);
|
||||
//
|
||||
// timer.elapsedPartialTicks += timer.timerSpeed * 20 / fps;
|
||||
// timer.elapsedTicks = (int) timer.elapsedPartialTicks;
|
||||
// timer.elapsedPartialTicks -= timer.elapsedTicks;
|
||||
// timer.renderPartialTicks = timer.elapsedPartialTicks;
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
synchronized (mc.scheduledTasks) {
|
||||
while (!mc.scheduledTasks.isEmpty()) {
|
||||
((FutureTask) mc.scheduledTasks.poll()).run();
|
||||
}
|
||||
}
|
||||
|
||||
mc.currentScreen = gui;
|
||||
try {
|
||||
mc.runTick();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawGui() {
|
||||
do {
|
||||
pushMatrix();
|
||||
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
enableTexture2D();
|
||||
mc.getFramebuffer().bindFramebuffer(true);
|
||||
mc.entityRenderer.setupOverlayRendering();
|
||||
|
||||
try {
|
||||
gui.handleInput();
|
||||
} catch (IOException e) {
|
||||
// That's a strange exception from this kind of method O_o
|
||||
// It isn't actually thrown here, so we'll deal with it the easy way
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int mouseX = Mouse.getX() * scaled.getScaledWidth() / mc.displayWidth;
|
||||
int mouseY = scaled.getScaledHeight() - Mouse.getY() * scaled.getScaledHeight() / mc.displayHeight - 1;
|
||||
gui.drawScreen(mouseX, mouseY, 0);
|
||||
|
||||
mc.getFramebuffer().unbindFramebuffer();
|
||||
popMatrix();
|
||||
pushMatrix();
|
||||
mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight);
|
||||
popMatrix();
|
||||
|
||||
|
||||
// if not in high performance mode, update the gui size if screen size changed
|
||||
// otherwise just swap the progress gui to screen
|
||||
if (options.isHighPerformance()) {
|
||||
Display.update();
|
||||
} else {
|
||||
mc.updateDisplay();
|
||||
}
|
||||
if (Mouse.isGrabbed()) {
|
||||
Mouse.setGrabbed(false);
|
||||
}
|
||||
if (paused) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (paused);
|
||||
}
|
||||
|
||||
public int getFramesDone() {
|
||||
return framesDone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableDimension getFrameSize() {
|
||||
return new Dimension(options.getWidth(), options.getHeight());
|
||||
}
|
||||
|
||||
public int getTotalFrames() {
|
||||
return totalFrames;
|
||||
}
|
||||
|
||||
public int getVideoTime() { return framesDone * 1000 / fps; }
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
videoWriter.abort();
|
||||
this.cancelled = true;
|
||||
renderingPipeline.cancel();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
public interface CaptureData {
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
public interface Frame {
|
||||
int getFrameId();
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
import eu.crushedpixel.replaymod.opengl.PixelBufferObject;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import eu.crushedpixel.replaymod.video.capturer.*;
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.processor.CubicToRGBProcessor;
|
||||
import eu.crushedpixel.replaymod.video.processor.EquirectangularToRGBProcessor;
|
||||
import eu.crushedpixel.replaymod.video.processor.OpenGlToRGBProcessor;
|
||||
import eu.crushedpixel.replaymod.video.processor.StereoscopicToRGBProcessor;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class Pipelines {
|
||||
public enum Preset {
|
||||
DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR
|
||||
}
|
||||
|
||||
public static Pipeline newPipeline(Preset preset, RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
switch (preset) {
|
||||
case DEFAULT:
|
||||
return newDefaultPipeline(renderInfo, consumer);
|
||||
case STEREOSCOPIC:
|
||||
return newStereoscopicPipeline(renderInfo, consumer);
|
||||
case CUBIC:
|
||||
return newCubicPipeline(renderInfo, consumer);
|
||||
case EQUIRECTANGULAR:
|
||||
return newEquirectangularPipeline(renderInfo, consumer);
|
||||
}
|
||||
throw new UnsupportedOperationException("Unknown preset: " + preset);
|
||||
}
|
||||
|
||||
public static Pipeline<OpenGlFrame, RGBFrame> newDefaultPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderOptions options = renderInfo.getRenderOptions();
|
||||
FrameCapturer<OpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new SimplePboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
|
||||
} else {
|
||||
capturer = new SimpleOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
|
||||
}
|
||||
return new Pipeline<OpenGlFrame, RGBFrame>(capturer, new OpenGlToRGBProcessor(), consumer);
|
||||
}
|
||||
|
||||
public static Pipeline<StereoscopicOpenGlFrame, RGBFrame> newStereoscopicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderOptions options = renderInfo.getRenderOptions();
|
||||
FrameCapturer<StereoscopicOpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new StereoscopicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
|
||||
} else {
|
||||
capturer = new StereoscopicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo);
|
||||
}
|
||||
return new Pipeline<StereoscopicOpenGlFrame, RGBFrame>(capturer, new StereoscopicToRGBProcessor(), consumer);
|
||||
}
|
||||
|
||||
public static Pipeline<CubicOpenGlFrame, RGBFrame> newCubicPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderOptions options = renderInfo.getRenderOptions();
|
||||
FrameCapturer<CubicOpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4);
|
||||
} else {
|
||||
capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4);
|
||||
}
|
||||
return new Pipeline<CubicOpenGlFrame, RGBFrame>(capturer, new CubicToRGBProcessor(), consumer);
|
||||
}
|
||||
|
||||
public static Pipeline<CubicOpenGlFrame, RGBFrame> newEquirectangularPipeline(RenderInfo renderInfo, FrameConsumer<RGBFrame> consumer) {
|
||||
RenderOptions options = renderInfo.getRenderOptions();
|
||||
FrameCapturer<CubicOpenGlFrame> capturer;
|
||||
if (PixelBufferObject.SUPPORTED) {
|
||||
capturer = new CubicPboOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4);
|
||||
} else {
|
||||
capturer = new CubicOpenGlFrameCapturer(new EntityRendererHandler(options), renderInfo, options.getWidth() / 4);
|
||||
}
|
||||
return new Pipeline<CubicOpenGlFrame, RGBFrame>(capturer, new EquirectangularToRGBProcessor(options.getWidth() / 4), consumer);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,23 @@
|
||||
"logoFile": "replaymod_logo.png",
|
||||
"parent": "replaymod",
|
||||
"screenshots": [],
|
||||
"dependencies": ["required-after:replaymod-replay", "required-after:replaymod-render"]
|
||||
},
|
||||
{
|
||||
"modid": "replaymod-render",
|
||||
"name": "Replay Mod - Render",
|
||||
"description": "Render Module of the ReplayMod",
|
||||
"version": "${version}",
|
||||
"mcversion": "${mcversion}",
|
||||
"url": "https://replaymod.com",
|
||||
"updateUrl": "https://replaymod.com/download",
|
||||
"authorList": [
|
||||
"CrushedPixel",
|
||||
"johni0702"
|
||||
],
|
||||
"logoFile": "replaymod_logo.png",
|
||||
"parent": "replaymod",
|
||||
"screenshots": [],
|
||||
"dependencies": ["required-after:replaymod-replay"]
|
||||
},
|
||||
{
|
||||
|
||||
15
src/main/resources/mixins.render.replaymod.json
Normal file
15
src/main/resources/mixins.render.replaymod.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "com.replaymod.render.mixin",
|
||||
"mixins": [
|
||||
"MixinEffectRenderer",
|
||||
"MixinEntityRenderer",
|
||||
"MixinRender",
|
||||
"MixinRendererLivingEntity",
|
||||
"MixinRenderGlobal",
|
||||
"MixinRenderManager"
|
||||
],
|
||||
"server": [],
|
||||
"client": [],
|
||||
"refmap": "mixins.replaymod.refmap.json"
|
||||
}
|
||||
@@ -2,14 +2,11 @@
|
||||
"required": true,
|
||||
"package": "eu.crushedpixel.replaymod.mixin",
|
||||
"mixins": [
|
||||
"MixinEffectRenderer",
|
||||
"MixinEntityRenderer",
|
||||
"MixinGuiSpectator",
|
||||
"MixinMinecraft",
|
||||
"MixinPlayerControllerMP",
|
||||
"MixinRender",
|
||||
"MixinRenderArrow",
|
||||
"MixinRendererLivingEntity",
|
||||
"MixinRenderItem",
|
||||
"MixinRenderManager",
|
||||
"MixinTileEntityEndPortalRenderer",
|
||||
|
||||
Reference in New Issue
Block a user