Separate rendering into its own module
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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.particle.EffectRenderer;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(EffectRenderer.class)
|
||||
public 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) {
|
||||
renderParticle(fx, worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
}
|
||||
|
||||
@Redirect(method = "renderLitParticles", 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 renderLitParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks,
|
||||
float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
|
||||
renderParticle(fx, worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
double dy = fx.prevPosY + (fx.posY - fx.prevPosY) * partialTicks - view.posY;
|
||||
double dz = fx.prevPosZ + (fx.posZ - fx.prevPosZ) * partialTicks - view.posZ;
|
||||
double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz));
|
||||
double yaw = -Math.atan2(dx, dz);
|
||||
|
||||
rotX = (float) Math.cos(yaw);
|
||||
rotZ = (float) Math.sin(yaw);
|
||||
rotXZ = (float) Math.cos(pitch);
|
||||
|
||||
rotYZ = (float) (-rotZ * Math.sin(pitch));
|
||||
rotXY = (float) (rotX * Math.sin(pitch));
|
||||
}
|
||||
fx.func_180434_a(worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY);
|
||||
}
|
||||
}
|
||||
@@ -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,22 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.mixin;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.Render;
|
||||
import net.minecraft.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
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()) {
|
||||
ci.setReturnValue(false);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,38 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.mixin;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.EntityRendererHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
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.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()) {
|
||||
ci.setReturnValue(false); //this calls the cancel method
|
||||
}
|
||||
|
||||
// TODO
|
||||
// if(ReplayHandler.isInReplay() && entity.isInvisible()
|
||||
// && ReplaySettings.ReplayOptions.renderInvisible.getValue() == Boolean.FALSE) {
|
||||
// ci.setReturnValue(false);
|
||||
// }
|
||||
}
|
||||
|
||||
@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) {
|
||||
// TODO
|
||||
// if(ReplaySettings.ReplayOptions.renderInvisible.getValue() == Boolean.TRUE|| !ReplayHandler.isInReplay()) {
|
||||
// return entity.isInvisibleToPlayer(thePlayer);
|
||||
// }
|
||||
return true; //the original method inverts the return value
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.opengl;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.Api;
|
||||
import eu.crushedpixel.replaymod.utils.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.lwjgl.opengl.ARBBufferObject;
|
||||
import org.lwjgl.opengl.GLContext;
|
||||
import org.lwjgl.opengl.Util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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 {
|
||||
COPY(GL_STREAM_COPY_ARB, GL_STREAM_COPY),
|
||||
DRAW(GL_STREAM_DRAW_ARB, GL_STREAM_DRAW),
|
||||
READ(GL_STREAM_READ_ARB, GL_STREAM_READ);
|
||||
|
||||
private final int arb, gl15;
|
||||
}
|
||||
|
||||
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 final long size;
|
||||
private long handle;
|
||||
|
||||
public PixelBufferObject(long size, Usage usage) {
|
||||
if (!SUPPORTED) {
|
||||
throw new UnsupportedOperationException("PBOs not supported.");
|
||||
}
|
||||
|
||||
this.size = size;
|
||||
this.handle = arb ? ARBBufferObject.glGenBuffersARB() : glGenBuffers();
|
||||
|
||||
bind();
|
||||
|
||||
if (arb) {
|
||||
ARBBufferObject.glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, size, usage.arb);
|
||||
} else {
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, size, usage.gl15);
|
||||
}
|
||||
}
|
||||
|
||||
@Api
|
||||
private int getHandle() {
|
||||
if (handle == -1) {
|
||||
throw new IllegalStateException("PBO not allocated.");
|
||||
}
|
||||
return (int) handle;
|
||||
}
|
||||
|
||||
@Api
|
||||
public void bind() {
|
||||
if (arb) {
|
||||
ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, getHandle());
|
||||
} else {
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, getHandle());
|
||||
}
|
||||
bound.set(getHandle());
|
||||
}
|
||||
|
||||
@Api
|
||||
public void unbind() {
|
||||
checkBound();
|
||||
if (arb) {
|
||||
ARBBufferObject.glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
|
||||
} else {
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
}
|
||||
bound.set(0);
|
||||
}
|
||||
|
||||
private void checkBound() {
|
||||
if (!Objects.equals(getHandle(), bound.get())) {
|
||||
throw new IllegalStateException("Buffer not bound.");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNotMapped() {
|
||||
if (Objects.equals(getHandle(), mapped.get())) {
|
||||
throw new IllegalStateException("Buffer already mapped.");
|
||||
}
|
||||
}
|
||||
|
||||
@Api
|
||||
public ByteBuffer mapReadOnly() {
|
||||
checkBound();
|
||||
checkNotMapped();
|
||||
ByteBuffer buffer;
|
||||
if (arb) {
|
||||
buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB, size, null);
|
||||
} else {
|
||||
buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY, size, null);
|
||||
}
|
||||
if (buffer == null) {
|
||||
Util.checkGLError();
|
||||
}
|
||||
mapped.set(getHandle());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Api
|
||||
public ByteBuffer mapWriteOnly() {
|
||||
checkBound();
|
||||
checkNotMapped();
|
||||
ByteBuffer buffer;
|
||||
if (arb) {
|
||||
buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_WRITE_ONLY_ARB, size, null);
|
||||
} else {
|
||||
buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_WRITE_ONLY, size, null);
|
||||
}
|
||||
if (buffer == null) {
|
||||
Util.checkGLError();
|
||||
}
|
||||
mapped.set(getHandle());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Api
|
||||
public ByteBuffer mapReadWrite() {
|
||||
checkBound();
|
||||
checkNotMapped();
|
||||
ByteBuffer buffer;
|
||||
if (arb) {
|
||||
buffer = ARBBufferObject.glMapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_WRITE_ARB, size, null);
|
||||
} else {
|
||||
buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_WRITE, size, null);
|
||||
}
|
||||
if (buffer == null) {
|
||||
Util.checkGLError();
|
||||
}
|
||||
mapped.set(getHandle());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Api
|
||||
public void unmap() {
|
||||
checkBound();
|
||||
if (!Objects.equals(mapped.get(), getHandle())) {
|
||||
throw new IllegalStateException("Buffer not mapped.");
|
||||
}
|
||||
if (arb) {
|
||||
ARBBufferObject.glUnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);
|
||||
} else {
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
}
|
||||
mapped.set(0);
|
||||
}
|
||||
|
||||
@Api
|
||||
public void delete() {
|
||||
if (handle != -1) {
|
||||
if (arb) {
|
||||
ARBBufferObject.glDeleteBuffersARB(getHandle());
|
||||
} else {
|
||||
glDeleteBuffers(getHandle());
|
||||
}
|
||||
handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
if (handle != -1) {
|
||||
LogManager.getLogger().warn("PBO garbage collected before deleted!");
|
||||
Minecraft.getMinecraft().addScheduledTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.renderer;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.JailingQueue;
|
||||
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
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;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
public class ChunkLoadingRenderGlobal {
|
||||
|
||||
private final RenderGlobal hooked;
|
||||
private final ChunkRenderDispatcher renderDispatcher;
|
||||
private final JailingQueue<?> workerJailingQueue;
|
||||
private final CustomChunkRenderWorker renderWorker;
|
||||
private int frame;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ChunkLoadingRenderGlobal(RenderGlobal renderGlobal) {
|
||||
this.hooked = renderGlobal;
|
||||
this.renderDispatcher = renderGlobal.renderDispatcher;
|
||||
this.renderWorker = new CustomChunkRenderWorker(renderDispatcher, new RegionRenderCacheBuilder());
|
||||
|
||||
int workerThreads = renderDispatcher.listThreadedWorkers.size();
|
||||
BlockingQueue<Object> queueChunkUpdates = renderDispatcher.queueChunkUpdates;
|
||||
workerJailingQueue = new JailingQueue<Object>(queueChunkUpdates);
|
||||
renderDispatcher.queueChunkUpdates = workerJailingQueue;
|
||||
ChunkCompileTaskGenerator element = new ChunkCompileTaskGenerator(null, null);
|
||||
element.finish();
|
||||
for (int i = 0; i < workerThreads; i++) {
|
||||
queueChunkUpdates.add(element);
|
||||
}
|
||||
workerJailingQueue.jail(workerThreads);
|
||||
renderDispatcher.queueChunkUpdates = queueChunkUpdates;
|
||||
|
||||
try {
|
||||
Field hookField = RenderGlobal.class.getField("hook");
|
||||
hookField.set(hooked, this);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new Error(e);
|
||||
} catch (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) {
|
||||
while (renderDispatcher.runChunkUploads(0)) {
|
||||
hooked.displayListEntitiesDirty = true;
|
||||
}
|
||||
|
||||
while (!renderDispatcher.queueChunkUpdates.isEmpty()) {
|
||||
try {
|
||||
renderWorker.processTask((ChunkCompileTaskGenerator) renderDispatcher.queueChunkUpdates.poll());
|
||||
} catch (InterruptedException ignored) { }
|
||||
}
|
||||
|
||||
Iterator<RenderChunk> iterator = hooked.chunksToUpdate.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
RenderChunk renderchunk = iterator.next();
|
||||
|
||||
renderDispatcher.updateChunkNow(renderchunk);
|
||||
|
||||
renderchunk.setNeedsUpdate(false);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
hookField.set(hooked, null);
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new Error(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom ChunkRenderWorker class providing access to the protected processTask method
|
||||
*/
|
||||
private static class CustomChunkRenderWorker extends ChunkRenderWorker {
|
||||
public CustomChunkRenderWorker(ChunkRenderDispatcher p_i46202_1_, RegionRenderCacheBuilder p_i46202_2_) {
|
||||
super(p_i46202_1_, p_i46202_2_);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processTask(ChunkCompileTaskGenerator p_178474_1_) throws InterruptedException {
|
||||
super.processTask(p_178474_1_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,48 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.utils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ByteBufferPool {
|
||||
private static Map<Integer, List<SoftReference<ByteBuffer>>> bufferPool = Maps.newHashMap();
|
||||
|
||||
public static synchronized ByteBuffer allocate(int size) {
|
||||
List<SoftReference<ByteBuffer>> available = bufferPool.get(size);
|
||||
if (available != null) {
|
||||
Iterator<SoftReference<ByteBuffer>> iter = available.iterator();
|
||||
try {
|
||||
while (iter.hasNext()) {
|
||||
SoftReference<ByteBuffer> reference = iter.next();
|
||||
ByteBuffer buffer = reference.get();
|
||||
iter.remove();
|
||||
if (buffer != null) {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!iter.hasNext()) {
|
||||
bufferPool.remove(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
return BufferUtils.createByteBuffer(size);
|
||||
}
|
||||
|
||||
public static synchronized void release(ByteBuffer buffer) {
|
||||
buffer.clear();
|
||||
int size = buffer.capacity();
|
||||
List<SoftReference<ByteBuffer>> available = bufferPool.get(size);
|
||||
if (available == null) {
|
||||
available = new LinkedList<SoftReference<ByteBuffer>>();
|
||||
bufferPool.put(size, available);
|
||||
}
|
||||
available.add(new SoftReference<ByteBuffer>(buffer));
|
||||
}
|
||||
}
|
||||
@@ -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,82 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.video.capturer.CaptureData;
|
||||
import eu.crushedpixel.replaymod.video.capturer.WorldRenderer;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.culling.ClippingHelper;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.minecraft.client.renderer.GlStateManager.*;
|
||||
|
||||
public class EntityRendererHandler implements WorldRenderer {
|
||||
public final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
@Getter
|
||||
protected final RenderOptions options;
|
||||
|
||||
public CaptureData data;
|
||||
|
||||
public EntityRendererHandler(RenderOptions options) {
|
||||
this.options = options;
|
||||
|
||||
((IEntityRenderer) mc.entityRenderer).setHandler(this);
|
||||
}
|
||||
|
||||
public void withDisplaySize(int displayWidth, int displayHeight, Runnable runnable) {
|
||||
final int prevWidth = mc.displayWidth;
|
||||
final int prevHeight = mc.displayHeight;
|
||||
mc.displayWidth = displayWidth;
|
||||
mc.displayHeight = displayHeight;
|
||||
|
||||
runnable.run();
|
||||
|
||||
mc.displayWidth = prevWidth;
|
||||
mc.displayHeight = prevHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderWorld(ReadableDimension displaySize, final float partialTicks, CaptureData data) {
|
||||
this.data = data;
|
||||
withDisplaySize(displaySize.getWidth(), displaySize.getHeight(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
renderWorld(partialTicks, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void renderWorld(float partialTicks, long finishTimeNano) {
|
||||
mc.entityRenderer.updateLightmap(partialTicks);
|
||||
|
||||
enableDepth();
|
||||
enableAlpha();
|
||||
alphaFunc(516, 0.5F);
|
||||
|
||||
mc.entityRenderer.renderWorldPass(2, partialTicks, finishTimeNano);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
((IEntityRenderer) mc.entityRenderer).setHandler(null);
|
||||
}
|
||||
|
||||
public static final class NoCullingClippingHelper extends ClippingHelper {
|
||||
@Override
|
||||
public boolean isBoxInFrustum(double p_78553_1_, double p_78553_3_, double p_78553_5_, double p_78553_7_, double p_78553_9_, double p_78553_11_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public interface GluPerspective {
|
||||
void gluPerspective(float fovY, float aspect, float zNear, float zFar);
|
||||
}
|
||||
|
||||
public interface IEntityRenderer {
|
||||
void setHandler(EntityRendererHandler handler);
|
||||
EntityRendererHandler getHandler();
|
||||
}
|
||||
}
|
||||
@@ -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,117 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video;
|
||||
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.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;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.apache.commons.lang3.Validate.isTrue;
|
||||
|
||||
public class VideoWriter implements FrameConsumer<RGBFrame> {
|
||||
|
||||
private final RenderOptions options;
|
||||
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();
|
||||
|
||||
File outputFolder = options.getOutputFile().getParentFile();
|
||||
String fileName = options.getOutputFile().getName();
|
||||
|
||||
commandArgs = options.getExportCommandArgs()
|
||||
.replace("%WIDTH%", String.valueOf(options.getWidth()))
|
||||
.replace("%HEIGHT%", String.valueOf(options.getHeight()))
|
||||
.replace("%FPS%", String.valueOf(options.getFps()))
|
||||
.replace("%FILENAME%", fileName)
|
||||
.replace("%BITRATE%", options.getBitrate());
|
||||
|
||||
List<String> command = new ArrayList<String>();
|
||||
command.add(options.getExportCommand());
|
||||
command.addAll(StringUtils.translateCommandline(commandArgs));
|
||||
System.out.println("Starting " + options.getExportCommand() + " with args: " + commandArgs);
|
||||
process = new ProcessBuilder(command).directory(outputFolder).start();
|
||||
OutputStream exportLogOut = new FileOutputStream("export.log");
|
||||
new StreamPipe(process.getInputStream(), exportLogOut).start();
|
||||
new StreamPipe(process.getErrorStream(), exportLogOut).start();
|
||||
outputStream = process.getOutputStream();
|
||||
channel = Channels.newChannel(outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
IOUtils.closeQuietly(outputStream);
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
long rem = TimeUnit.SECONDS.toNanos(30);
|
||||
do {
|
||||
try {
|
||||
process.exitValue();
|
||||
break;
|
||||
} catch(IllegalThreadStateException ex) {
|
||||
if (rem > 0) {
|
||||
try {
|
||||
Thread.sleep(Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
rem = TimeUnit.SECONDS.toNanos(30) - (System.nanoTime() - startTime);
|
||||
} while (rem > 0);
|
||||
|
||||
process.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(RGBFrame frame) {
|
||||
try {
|
||||
checkSize(frame.getSize());
|
||||
channel.write(frame.getByteBuffer());
|
||||
ByteBufferPool.release(frame.getByteBuffer());
|
||||
} catch (Throwable t) {
|
||||
if (aborted) {
|
||||
return;
|
||||
}
|
||||
CrashReport report = CrashReport.makeCrashReport(t, "Exporting frame");
|
||||
CrashReportCategory exportDetails = report.makeCategory("Export details");
|
||||
exportDetails.addCrashSection("Export command", options.getExportCommand());
|
||||
exportDetails.addCrashSection("Export args", commandArgs);
|
||||
Minecraft.getMinecraft().crashed(report);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSize(ReadableDimension size) {
|
||||
checkSize(size.getWidth(), size.getHeight());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void abort() {
|
||||
aborted = true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
public interface CaptureData {
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
|
||||
public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
||||
public enum Data implements CaptureData {
|
||||
LEFT, RIGHT, FRONT, BACK, TOP, BOTTOM
|
||||
}
|
||||
|
||||
private final int frameSize;
|
||||
public CubicOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
|
||||
super(worldRenderer, renderInfo);
|
||||
this.frameSize = frameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrameWidth() {
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrameHeight() {
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CubicOpenGlFrame process() {
|
||||
float partialTicks = renderInfo.updateForNextFrame();
|
||||
int frameId = framesDone++;
|
||||
return new CubicOpenGlFrame(renderFrame(frameId, partialTicks, Data.LEFT),
|
||||
renderFrame(frameId, partialTicks, Data.RIGHT),
|
||||
renderFrame(frameId, partialTicks, Data.FRONT),
|
||||
renderFrame(frameId, partialTicks, Data.BACK),
|
||||
renderFrame(frameId, partialTicks, Data.TOP),
|
||||
renderFrame(frameId, partialTicks, Data.BOTTOM));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
|
||||
public class CubicPboOpenGlFrameCapturer extends
|
||||
MultiFramePboOpenGlFrameCapturer<CubicOpenGlFrame, CubicOpenGlFrameCapturer.Data> {
|
||||
|
||||
private final int frameSize;
|
||||
public CubicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) {
|
||||
super(worldRenderer, renderInfo, CubicOpenGlFrameCapturer.Data.class, frameSize * frameSize);
|
||||
this.frameSize = frameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrameWidth() {
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrameHeight() {
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CubicOpenGlFrame create(OpenGlFrame[] from) {
|
||||
return new CubicOpenGlFrame(from[0], from[1], from[2], from[3], from[4], from[5]);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.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 net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class MultiFramePboOpenGlFrameCapturer<F extends Frame, D extends Enum<D> & CaptureData>
|
||||
extends OpenGlFrameCapturer<F, D> {
|
||||
private final D[] data;
|
||||
private PixelBufferObject pbo, otherPBO;
|
||||
|
||||
public MultiFramePboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, Class<D> type, int framePixels) {
|
||||
super(worldRenderer, renderInfo);
|
||||
|
||||
data = type.getEnumConstants();
|
||||
int bufferSize = framePixels * 3 * data.length;
|
||||
pbo = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
||||
otherPBO = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
||||
}
|
||||
|
||||
protected abstract F create(OpenGlFrame[] from);
|
||||
|
||||
private void swapPBOs() {
|
||||
PixelBufferObject old = pbo;
|
||||
pbo = otherPBO;
|
||||
otherPBO = old;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return framesDone >= renderInfo.getTotalFrames() + 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public F process() {
|
||||
F frame = null;
|
||||
|
||||
if (framesDone > 1) {
|
||||
// Read pbo to memory
|
||||
pbo.bind();
|
||||
ByteBuffer pboBuffer = pbo.mapReadOnly();
|
||||
|
||||
OpenGlFrame[] frames = new OpenGlFrame[data.length];
|
||||
int frameBufferSize = getFrameWidth() * getFrameHeight() * 3;
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
ByteBuffer frameBuffer = ByteBufferPool.allocate(frameBufferSize);
|
||||
pboBuffer.limit(pboBuffer.position() + frameBufferSize);
|
||||
frameBuffer.put(pboBuffer);
|
||||
frameBuffer.rewind();
|
||||
frames[i] = new OpenGlFrame(framesDone - 2, frameSize, frameBuffer);
|
||||
}
|
||||
|
||||
pbo.unmap();
|
||||
pbo.unbind();
|
||||
|
||||
frame = create(frames);
|
||||
}
|
||||
|
||||
if (framesDone < renderInfo.getTotalFrames()) {
|
||||
float partialTicks = renderInfo.updateForNextFrame();
|
||||
// Then fill it again
|
||||
for (D data : this.data) {
|
||||
renderFrame(framesDone, partialTicks, data);
|
||||
}
|
||||
}
|
||||
|
||||
framesDone++;
|
||||
swapPBOs();
|
||||
return frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OpenGlFrame captureFrame(int frameId, D captureData) {
|
||||
pbo.bind();
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
int offset = captureData.ordinal() * getFrameWidth() * getFrameHeight() * 3;
|
||||
if (OpenGlHelper.isFramebufferEnabled()) {
|
||||
frameBuffer().bindFramebufferTexture();
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, offset);
|
||||
frameBuffer().unbindFramebufferTexture();
|
||||
} else {
|
||||
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, offset);
|
||||
}
|
||||
|
||||
pbo.unbind();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
pbo.delete();
|
||||
otherPBO.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.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 net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
import org.lwjgl.util.WritableDimension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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 abstract class OpenGlFrameCapturer<F extends Frame, D extends CaptureData> implements FrameCapturer<F> {
|
||||
protected final WorldRenderer worldRenderer;
|
||||
protected final RenderInfo renderInfo;
|
||||
protected int framesDone;
|
||||
private Framebuffer frameBuffer;
|
||||
|
||||
public OpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||
this.worldRenderer = worldRenderer;
|
||||
this.renderInfo = renderInfo;
|
||||
}
|
||||
|
||||
protected final ReadableDimension frameSize = new ReadableDimension() {
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return getFrameWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return getFrameHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSize(WritableDimension dest) {
|
||||
dest.setSize(getWidth(), getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
protected int getFrameWidth() {
|
||||
return renderInfo.getFrameSize().getWidth();
|
||||
}
|
||||
|
||||
protected int getFrameHeight() {
|
||||
return renderInfo.getFrameSize().getHeight();
|
||||
}
|
||||
|
||||
protected Framebuffer frameBuffer() {
|
||||
if (frameBuffer == null) {
|
||||
frameBuffer = new Framebuffer(getFrameWidth(), getFrameHeight(), true);
|
||||
}
|
||||
return frameBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return framesDone >= renderInfo.getTotalFrames();
|
||||
}
|
||||
|
||||
protected OpenGlFrame renderFrame(int frameId, float partialTicks) {
|
||||
return renderFrame(frameId, partialTicks, null);
|
||||
}
|
||||
|
||||
protected OpenGlFrame renderFrame(int frameId, float partialTicks, D captureData) {
|
||||
pushMatrix();
|
||||
frameBuffer().bindFramebuffer(true);
|
||||
|
||||
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
enableTexture2D();
|
||||
|
||||
worldRenderer.renderWorld(frameSize, partialTicks, captureData);
|
||||
|
||||
frameBuffer().unbindFramebuffer();
|
||||
popMatrix();
|
||||
|
||||
return captureFrame(frameId, captureData);
|
||||
}
|
||||
|
||||
protected OpenGlFrame captureFrame(int frameId, D captureData) {
|
||||
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
ByteBuffer buffer = ByteBufferPool.allocate(getFrameWidth() * getFrameHeight() * 3);
|
||||
if (OpenGlHelper.isFramebufferEnabled()) {
|
||||
frameBuffer().bindFramebufferTexture();
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
|
||||
frameBuffer().unbindFramebufferTexture();
|
||||
} else {
|
||||
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
|
||||
}
|
||||
buffer.rewind();
|
||||
|
||||
return new OpenGlFrame(frameId, new Dimension(getFrameWidth(), getFrameHeight()), buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
worldRenderer.close();
|
||||
if (frameBuffer != null) {
|
||||
frameBuffer.deleteFramebuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
public interface RenderInfo {
|
||||
ReadableDimension getFrameSize();
|
||||
|
||||
int getTotalFrames();
|
||||
|
||||
float updateForNextFrame();
|
||||
|
||||
RenderOptions getRenderOptions();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
|
||||
public class SimpleOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
|
||||
|
||||
public SimpleOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||
super(worldRenderer, renderInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenGlFrame process() {
|
||||
float partialTicks = renderInfo.updateForNextFrame();
|
||||
return renderFrame(framesDone++, partialTicks);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.opengl.PixelBufferObject;
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class SimplePboOpenGlFrameCapturer extends OpenGlFrameCapturer<OpenGlFrame, CaptureData> {
|
||||
private final int bufferSize;
|
||||
private PixelBufferObject pbo, otherPBO;
|
||||
|
||||
public SimplePboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||
super(worldRenderer, renderInfo);
|
||||
|
||||
ReadableDimension size = renderInfo.getFrameSize();
|
||||
bufferSize = size.getHeight() * size.getWidth() * 3;
|
||||
pbo = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
||||
otherPBO = new PixelBufferObject(bufferSize, PixelBufferObject.Usage.READ);
|
||||
}
|
||||
|
||||
private void swapPBOs() {
|
||||
PixelBufferObject old = pbo;
|
||||
pbo = otherPBO;
|
||||
otherPBO = old;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return framesDone >= renderInfo.getTotalFrames() + 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenGlFrame process() {
|
||||
OpenGlFrame frame = null;
|
||||
|
||||
if (framesDone > 1) {
|
||||
// Read pbo to memory
|
||||
pbo.bind();
|
||||
ByteBuffer pboBuffer = pbo.mapReadOnly();
|
||||
ByteBuffer buffer = ByteBufferPool.allocate(bufferSize);
|
||||
buffer.put(pboBuffer);
|
||||
buffer.rewind();
|
||||
pbo.unmap();
|
||||
pbo.unbind();
|
||||
frame = new OpenGlFrame(framesDone - 2, frameSize, buffer);
|
||||
}
|
||||
|
||||
if (framesDone < renderInfo.getTotalFrames()) {
|
||||
// Then fill it again
|
||||
renderFrame(framesDone, renderInfo.updateForNextFrame());
|
||||
}
|
||||
|
||||
framesDone++;
|
||||
swapPBOs();
|
||||
return frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OpenGlFrame captureFrame(int frameId, CaptureData data) {
|
||||
pbo.bind();
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
if (OpenGlHelper.isFramebufferEnabled()) {
|
||||
frameBuffer().bindFramebufferTexture();
|
||||
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, 0);
|
||||
frameBuffer().unbindFramebufferTexture();
|
||||
} else {
|
||||
GL11.glReadPixels(0, 0, getFrameWidth(), getFrameHeight(), GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, 0);
|
||||
}
|
||||
|
||||
pbo.unbind();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
pbo.delete();
|
||||
otherPBO.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
|
||||
public class StereoscopicOpenGlFrameCapturer
|
||||
extends OpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
||||
public enum Data implements CaptureData {
|
||||
LEFT_EYE, RIGHT_EYE
|
||||
}
|
||||
|
||||
public StereoscopicOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||
super(worldRenderer, renderInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrameWidth() {
|
||||
return super.getFrameWidth() / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StereoscopicOpenGlFrame process() {
|
||||
float partialTicks = renderInfo.updateForNextFrame();
|
||||
int frameId = framesDone++;
|
||||
OpenGlFrame left = renderFrame(frameId, partialTicks, Data.LEFT_EYE);
|
||||
OpenGlFrame right = renderFrame(frameId, partialTicks, Data.RIGHT_EYE);
|
||||
return new StereoscopicOpenGlFrame(left, right);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
|
||||
public class StereoscopicPboOpenGlFrameCapturer
|
||||
extends MultiFramePboOpenGlFrameCapturer<StereoscopicOpenGlFrame, StereoscopicOpenGlFrameCapturer.Data> {
|
||||
|
||||
public StereoscopicPboOpenGlFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo) {
|
||||
super(worldRenderer, renderInfo, StereoscopicOpenGlFrameCapturer.Data.class,
|
||||
renderInfo.getFrameSize().getWidth() / 2 * renderInfo.getFrameSize().getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFrameWidth() {
|
||||
return super.getFrameWidth() / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StereoscopicOpenGlFrame create(OpenGlFrame[] from) {
|
||||
return new StereoscopicOpenGlFrame(from[0], from[1]);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.capturer;
|
||||
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface WorldRenderer extends Closeable {
|
||||
void renderWorld(ReadableDimension displaySize, float partialTicks, CaptureData data);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class CubicOpenGlFrame implements Frame {
|
||||
@Getter
|
||||
private final OpenGlFrame left, right, front, back, top, bottom;
|
||||
|
||||
public CubicOpenGlFrame(OpenGlFrame left, OpenGlFrame right, OpenGlFrame front, OpenGlFrame back, OpenGlFrame top, OpenGlFrame bottom) {
|
||||
Validate.isTrue(left.getFrameId() == right.getFrameId()
|
||||
&& right.getFrameId() == front.getFrameId()
|
||||
&& front.getFrameId() == back.getFrameId()
|
||||
&& back.getFrameId() == top.getFrameId()
|
||||
&& top.getFrameId() == bottom.getFrameId(), "Frame ids do not match.");
|
||||
Validate.isTrue(left.getByteBuffer().remaining() == right.getByteBuffer().remaining()
|
||||
&& right.getByteBuffer().remaining() == front.getByteBuffer().remaining()
|
||||
&& front.getByteBuffer().remaining() == back.getByteBuffer().remaining()
|
||||
&& back.getByteBuffer().remaining() == top.getByteBuffer().remaining()
|
||||
&& top.getByteBuffer().remaining() == bottom.getByteBuffer().remaining(), "Buffer size does not match.");
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.front = front;
|
||||
this.back = back;
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrameId() {
|
||||
return left.getFrameId();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class OpenGlFrame implements Frame {
|
||||
@Getter
|
||||
private final int frameId;
|
||||
|
||||
@Getter
|
||||
private final ReadableDimension size;
|
||||
|
||||
@Getter
|
||||
private final ByteBuffer byteBuffer;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class RGBFrame implements Frame {
|
||||
@Getter
|
||||
private final int frameId;
|
||||
|
||||
@Getter
|
||||
private final ReadableDimension size;
|
||||
|
||||
@Getter
|
||||
private final ByteBuffer byteBuffer;
|
||||
|
||||
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);
|
||||
this.frameId = frameId;
|
||||
this.size = size;
|
||||
this.byteBuffer = byteBuffer;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.frame;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
public class StereoscopicOpenGlFrame implements Frame {
|
||||
@Getter
|
||||
private final OpenGlFrame left, right;
|
||||
|
||||
public StereoscopicOpenGlFrame(OpenGlFrame left, OpenGlFrame right) {
|
||||
Validate.isTrue(left.getFrameId() == right.getFrameId(), "Frame ids do not match.");
|
||||
Validate.isTrue(left.getByteBuffer().remaining() == right.getByteBuffer().remaining(), "Buffer size does not match.");
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrameId() {
|
||||
return left.getFrameId();
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.metadata;
|
||||
|
||||
import com.coremedia.iso.IsoFile;
|
||||
import com.coremedia.iso.boxes.*;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.googlecode.mp4parser.BasicContainer;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class MetadataInjector {
|
||||
|
||||
private static final String STITCHING_SOFTWARE = "Minecraft ReplayMod";
|
||||
|
||||
/*
|
||||
* The Spherical XML is a modified version of https://github.com/google/spatial-media/tree/master/360-Videos-Metadata
|
||||
*/
|
||||
private static final String SPHERICAL_XML_HEADER =
|
||||
"<?xml version=\"1.0\"?> " +
|
||||
"<rdf:SphericalVideo xmlns:rdf=" +
|
||||
"\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:GSpherical=\"http://ns.google.com/videos/1.0/spherical/\"> ";
|
||||
private static final String SPHERICAL_XML_CONTENTS =
|
||||
"<GSpherical:Spherical>true</GSpherical:Spherical> " +
|
||||
"<GSpherical:Stitched>true</GSpherical:Stitched> " +
|
||||
"<GSpherical:StitchingSoftware>"+STITCHING_SOFTWARE+"</GSpherical:StitchingSoftware> " +
|
||||
"<GSpherical:ProjectionType>equirectangular</GSpherical:ProjectionType> ";
|
||||
private static final String SPHERICAL_XML_FOOTER = "</rdf:SphericalVideo>";
|
||||
|
||||
private static final String XML_360_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + SPHERICAL_XML_FOOTER;
|
||||
|
||||
/**
|
||||
* These bytes are taken from the variable 'spherical_uuid_id'
|
||||
* in https://github.com/google/spatial-media/tree/master/360-Videos-Metadata
|
||||
*/
|
||||
private static final byte[] UUID_BYTES = new byte[] {
|
||||
(byte)0xff, (byte)0xcc, (byte)0x82, (byte)0x63,
|
||||
(byte)0xf8, (byte)0x55, (byte)0x4a, (byte)0x93,
|
||||
(byte)0x88, (byte)0x14, (byte)0x58, (byte)0x7a,
|
||||
(byte)0x02, (byte)0x52, (byte)0x1f, (byte)0xdd
|
||||
};
|
||||
|
||||
private static final byte[] BYTES_360_METADATA = Bytes.concat(UUID_BYTES, XML_360_METADATA.getBytes());
|
||||
|
||||
public static void inject360Metadata(File videoFile) {
|
||||
writeMetadata(videoFile, BYTES_360_METADATA);
|
||||
}
|
||||
|
||||
private static void writeMetadata(File videoFile, byte[] metadata) {
|
||||
File tempFile = null;
|
||||
FileOutputStream videoFileOutputStream = null;
|
||||
IsoFile tempIsoFile = null;
|
||||
|
||||
try {
|
||||
tempFile = File.createTempFile("videoCopy", "mp4");
|
||||
FileUtils.copyFile(videoFile, tempFile);
|
||||
|
||||
tempIsoFile = new IsoFile(tempFile.getAbsolutePath());
|
||||
|
||||
//first, get the "moov" box from the mp4
|
||||
MovieBox moovBox = (MovieBox)getBoxByName(tempIsoFile, "moov");
|
||||
if(moovBox == null) throw new IOException("Could not find moov box inside IsoFile");
|
||||
|
||||
//get the Movie Track to which we will add the Metadata
|
||||
TrackBox trackBox = (TrackBox)getBoxByName(moovBox, "trak");
|
||||
if(trackBox == null) throw new IOException("Could not find trak box inside moov box");
|
||||
|
||||
//create a new UserBox, which actually contains the Metadata bytes
|
||||
UserBox metadataBox = new UserBox(new byte[0]);
|
||||
metadataBox.setData(metadata);
|
||||
|
||||
//add the Metadata UserBox to the Movie Track
|
||||
trackBox.addBox(metadataBox);
|
||||
|
||||
//finally, reduce the Video's FreeBox, whose size we will need
|
||||
//to reduce by the Metadata Box's size to preserve the Video's File size
|
||||
FreeBox freeBox = (FreeBox)getBoxByName(tempIsoFile, "free");
|
||||
if(freeBox == null) throw new IOException("Could not find free box inside IsoFile");
|
||||
|
||||
int freeSize = Math.max(0, freeBox.getData().capacity() - (int)metadataBox.getSize());
|
||||
freeBox.setData(ByteBuffer.allocate(freeSize));
|
||||
|
||||
//save the ISO file to disk
|
||||
videoFileOutputStream = new FileOutputStream(videoFile);
|
||||
tempIsoFile.getBox(videoFileOutputStream.getChannel());
|
||||
} catch(Exception e) {
|
||||
FMLLog.getLogger().error("360 Degree Metadata couldn't be injected", e);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(tempIsoFile);
|
||||
IOUtils.closeQuietly(videoFileOutputStream);
|
||||
FileUtils.deleteQuietly(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static Box getBoxByName(BasicContainer container, String boxName) {
|
||||
for (Box box : container.getBoxes()) {
|
||||
if(box.getType().equals(boxName)) return box;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.rendering.Frame;
|
||||
import eu.crushedpixel.replaymod.video.rendering.FrameProcessor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class AbstractFrameProcessor<R extends Frame, P extends Frame> implements FrameProcessor<R, P> {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import org.lwjgl.util.Dimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static eu.crushedpixel.replaymod.utils.OpenGLUtils.openGlBytesToRBG;
|
||||
|
||||
public class CubicToRGBProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, RGBFrame> {
|
||||
@Override
|
||||
public RGBFrame process(CubicOpenGlFrame rawFrame) {
|
||||
int size = rawFrame.getLeft().getSize().getWidth();
|
||||
int width = size * 4;
|
||||
int height = size * 3;
|
||||
ByteBuffer result = ByteBufferPool.allocate(width * height * 3);
|
||||
openGlBytesToRBG(rawFrame.getLeft().getByteBuffer(), size, 0, size, result, width);
|
||||
openGlBytesToRBG(rawFrame.getFront().getByteBuffer(), size, size, size, result, width);
|
||||
openGlBytesToRBG(rawFrame.getRight().getByteBuffer(), size, size * 2, size, result, width);
|
||||
openGlBytesToRBG(rawFrame.getBack().getByteBuffer(), size, size * 3, size, result, width);
|
||||
openGlBytesToRBG(rawFrame.getTop().getByteBuffer(), size, size, 0, result, width);
|
||||
openGlBytesToRBG(rawFrame.getBottom().getByteBuffer(), size, size, size * 2, result, width);
|
||||
ByteBufferPool.release(rawFrame.getLeft().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getRight().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getFront().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getBack().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getTop().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getBottom().getByteBuffer());
|
||||
return new RGBFrame(rawFrame.getFrameId(), new Dimension(width, height), result);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.CubicOpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.lwjgl.util.Dimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static java.lang.Math.PI;
|
||||
|
||||
public class EquirectangularToRGBProcessor extends AbstractFrameProcessor<CubicOpenGlFrame, RGBFrame> {
|
||||
private static final byte IMAGE_BACK = 0;
|
||||
private static final byte IMAGE_FRONT = 1;
|
||||
private static final byte IMAGE_LEFT = 2;
|
||||
private static final byte IMAGE_RIGHT = 3;
|
||||
private static final byte IMAGE_TOP = 4;
|
||||
private static final byte IMAGE_BOTTOM = 5;
|
||||
|
||||
private final int frameSize;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private final byte[][] image;
|
||||
private final int[][] imageX;
|
||||
private final int[][] imageY;
|
||||
|
||||
|
||||
public EquirectangularToRGBProcessor(int frameSize) {
|
||||
this.frameSize = frameSize;
|
||||
|
||||
width = frameSize * 4;
|
||||
height = frameSize * 2;
|
||||
image = new byte[height][width];
|
||||
imageX = new int[height][width];
|
||||
imageY = new int[height][width];
|
||||
for (int i = 0; i < width; i++) {
|
||||
double yaw = PI * 2 * i / width;
|
||||
int piQuarter = 8 * i / width - 4;
|
||||
byte target;
|
||||
if (piQuarter < -3) {
|
||||
target = IMAGE_BACK;
|
||||
} else if (piQuarter < -1) {
|
||||
target = IMAGE_LEFT;
|
||||
} else if (piQuarter < 1) {
|
||||
target = IMAGE_FRONT;
|
||||
} else if (piQuarter < 3) {
|
||||
target = IMAGE_RIGHT;
|
||||
} else {
|
||||
target = IMAGE_BACK;
|
||||
}
|
||||
double fYaw = (yaw + PI/4) % (PI / 2) - PI/4;
|
||||
double d = 1 / Math.cos(fYaw);
|
||||
double gcXN = (Math.tan(fYaw) + 1) / 2;
|
||||
for (int j = 0; j < height; j++) {
|
||||
double cXN = gcXN;
|
||||
byte pt = target;
|
||||
double pitch = PI * j / height - PI / 2;
|
||||
double cYN = (Math.tan(pitch) * d + 1) / 2;
|
||||
|
||||
if (cYN >= 1) {
|
||||
double pd = Math.tan(PI/2 - pitch);
|
||||
cXN = (-Math.sin(yaw) * pd + 1) / 2;
|
||||
cYN = (Math.cos(yaw) * pd + 1) / 2;
|
||||
pt = IMAGE_BOTTOM;
|
||||
}
|
||||
if (cYN < 0) {
|
||||
double pd = Math.tan(PI/2 - pitch);
|
||||
cXN = (Math.sin(yaw) * pd + 1) / 2;
|
||||
cYN = (Math.cos(yaw) * pd + 1) / 2;
|
||||
pt = IMAGE_TOP;
|
||||
}
|
||||
|
||||
int imgX = (int) Math.min(frameSize - 1, (cXN * frameSize));
|
||||
int imgY = (int) Math.min(frameSize - 1, (cYN * frameSize));
|
||||
image[j][i] = pt;
|
||||
imageX[j][i] = imgX;
|
||||
imageY[j][i] = frameSize - imgY - 1; // The OpenGl buffer contains data flipped vertically
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RGBFrame process(CubicOpenGlFrame rawFrame) {
|
||||
Validate.isTrue(rawFrame.getLeft().getSize().getWidth() == frameSize, "Frame size must be %d but was %d",
|
||||
frameSize, rawFrame.getLeft().getSize().getWidth());
|
||||
ByteBuffer result = ByteBufferPool.allocate(width * height * 3);
|
||||
ByteBuffer[] images = {
|
||||
rawFrame.getBack().getByteBuffer(), rawFrame.getFront().getByteBuffer(),
|
||||
rawFrame.getLeft().getByteBuffer(), rawFrame.getRight().getByteBuffer(),
|
||||
rawFrame.getTop().getByteBuffer(), rawFrame.getBottom().getByteBuffer()
|
||||
};
|
||||
byte[] pixel = new byte[3];
|
||||
byte[] image;
|
||||
int[] imageX, imageY;
|
||||
for (int y = 0; y < height; y++) {
|
||||
image = this.image[y];
|
||||
imageX = this.imageX[y];
|
||||
imageY = this.imageY[y];
|
||||
for (int x = 0; x < width; x++) {
|
||||
ByteBuffer source = images[image[x]];
|
||||
source.position((imageX[x] + imageY[x] * frameSize) * 3);
|
||||
source.get(pixel);
|
||||
result.put(pixel);
|
||||
}
|
||||
}
|
||||
result.rewind();
|
||||
|
||||
ByteBufferPool.release(rawFrame.getLeft().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getRight().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getFront().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getBack().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getTop().getByteBuffer());
|
||||
ByteBufferPool.release(rawFrame.getBottom().getByteBuffer());
|
||||
return new RGBFrame(rawFrame.getFrameId(), new Dimension(width, height), result);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.video.frame.OpenGlFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class OpenGlToRGBProcessor extends AbstractFrameProcessor<OpenGlFrame, RGBFrame> {
|
||||
|
||||
private byte[] row, rowSwap;
|
||||
|
||||
@Override
|
||||
public RGBFrame process(OpenGlFrame rawFrame) {
|
||||
// Flip whole image in place
|
||||
|
||||
ReadableDimension size = rawFrame.getSize();
|
||||
int rowSize = size.getWidth() * 3;
|
||||
if (row == null || row.length < rowSize) {
|
||||
row = new byte[rowSize];
|
||||
rowSwap = new byte[rowSize];
|
||||
}
|
||||
ByteBuffer buffer = rawFrame.getByteBuffer();
|
||||
int rows = size.getHeight();
|
||||
byte[] row = this.row;
|
||||
byte[] rowSwap = this.rowSwap;
|
||||
for (int i = 0; i < rows / 2; i++) {
|
||||
int from = rowSize * i;
|
||||
int to = rowSize * (rows - i - 1);
|
||||
buffer.position(from);
|
||||
buffer.get(row);
|
||||
buffer.position(to);
|
||||
buffer.get(rowSwap);
|
||||
buffer.position(to);
|
||||
buffer.put(row);
|
||||
buffer.position(from);
|
||||
buffer.put(rowSwap);
|
||||
}
|
||||
buffer.rewind();
|
||||
return new RGBFrame(rawFrame.getFrameId(), size, buffer);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.processor;
|
||||
|
||||
import eu.crushedpixel.replaymod.utils.ByteBufferPool;
|
||||
import eu.crushedpixel.replaymod.video.frame.RGBFrame;
|
||||
import eu.crushedpixel.replaymod.video.frame.StereoscopicOpenGlFrame;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static eu.crushedpixel.replaymod.utils.OpenGLUtils.openGlBytesToRBG;
|
||||
|
||||
public class StereoscopicToRGBProcessor extends AbstractFrameProcessor<StereoscopicOpenGlFrame, RGBFrame> {
|
||||
@Override
|
||||
public RGBFrame process(StereoscopicOpenGlFrame rawFrame) {
|
||||
ReadableDimension size = rawFrame.getLeft().getSize();
|
||||
int width = size.getWidth();
|
||||
ByteBuffer leftBuffer = rawFrame.getLeft().getByteBuffer();
|
||||
ByteBuffer rightBuffer = rawFrame.getRight().getByteBuffer();
|
||||
ByteBuffer result = ByteBufferPool.allocate(width * 2 * size.getHeight() * 3);
|
||||
openGlBytesToRBG(leftBuffer, width, 0, 0, result, width * 2);
|
||||
openGlBytesToRBG(rightBuffer, width, size.getWidth(), 0, result, width * 2);
|
||||
ByteBufferPool.release(leftBuffer);
|
||||
ByteBufferPool.release(rightBuffer);
|
||||
return new RGBFrame(rawFrame.getFrameId(), new Dimension(width * 2, size.getHeight()), result);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
public interface Frame {
|
||||
int getFrameId();
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface FrameCapturer<R extends Frame> extends Closeable {
|
||||
|
||||
boolean isDone();
|
||||
|
||||
R process();
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface FrameConsumer<P extends Frame> extends Closeable {
|
||||
|
||||
void consume(P frame);
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface FrameProcessor<R extends Frame, P extends Frame> extends Closeable {
|
||||
|
||||
P process(R rawFrame);
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.video.rendering;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.crash.CrashReport;
|
||||
import net.minecraft.util.ReportedException;
|
||||
import org.lwjgl.opengl.Display;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Pipeline<R extends Frame, P extends Frame> implements Runnable {
|
||||
|
||||
private final FrameCapturer<R> capturer;
|
||||
private final FrameProcessor<R, P> processor;
|
||||
private int consumerNextFrame;
|
||||
private final Object consumerLock = new Object();
|
||||
private final FrameConsumer<P> consumer;
|
||||
|
||||
private Thread runningThread;
|
||||
|
||||
public Pipeline(FrameCapturer<R> capturer, FrameProcessor<R, P> processor, FrameConsumer<P> consumer) {
|
||||
this.capturer = capturer;
|
||||
this.processor = processor;
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
runningThread = Thread.currentThread();
|
||||
consumerNextFrame = 0;
|
||||
int processors = Runtime.getRuntime().availableProcessors();
|
||||
int processThreads = Math.max(1, processors - 2); // One processor for the main thread and one for ffmpeg, sorry OS :(
|
||||
ExecutorService processService = new ThreadPoolExecutor(processThreads, processThreads,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(2) {
|
||||
@Override
|
||||
public boolean offer(Runnable runnable) {
|
||||
try {
|
||||
put(runnable);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, new ThreadPoolExecutor.DiscardPolicy());
|
||||
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
while (!capturer.isDone() && !Thread.currentThread().isInterrupted()) {
|
||||
if (Display.isCloseRequested() || mc.hasCrashed) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
R rawFrame = capturer.process();
|
||||
if (rawFrame != null) {
|
||||
processService.submit(new ProcessTask(rawFrame));
|
||||
}
|
||||
}
|
||||
|
||||
processService.shutdown();
|
||||
try {
|
||||
processService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
try {
|
||||
capturer.close();
|
||||
processor.close();
|
||||
consumer.close();
|
||||
} catch (Throwable t) {
|
||||
CrashReport crashReport = CrashReport.makeCrashReport(t, "Cleaning up rendering pipeline");
|
||||
throw new ReportedException(crashReport);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (runningThread != null) {
|
||||
runningThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private class ProcessTask implements Runnable {
|
||||
private final R rawFrame;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
P processedFrame = processor.process(rawFrame);
|
||||
synchronized (consumerLock) {
|
||||
while (consumerNextFrame != processedFrame.getFrameId()) {
|
||||
try {
|
||||
consumerLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
consumer.consume(processedFrame);
|
||||
consumerNextFrame++;
|
||||
consumerLock.notifyAll();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
CrashReport crashReport = CrashReport.makeCrashReport(t, "Processing frame");
|
||||
Minecraft.getMinecraft().crashed(crashReport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user