Rework rendering
Adds default, stereoscopic, tiling, cubic and equirectangular frame rendering
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
package eu.crushedpixel.replaymod;
|
package eu.crushedpixel.replaymod;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||||
import eu.crushedpixel.replaymod.api.ApiClient;
|
import eu.crushedpixel.replaymod.api.ApiClient;
|
||||||
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
||||||
import eu.crushedpixel.replaymod.events.*;
|
import eu.crushedpixel.replaymod.events.*;
|
||||||
|
import eu.crushedpixel.replaymod.holders.KeyframeSet;
|
||||||
import eu.crushedpixel.replaymod.localization.LocalizedResourcePack;
|
import eu.crushedpixel.replaymod.localization.LocalizedResourcePack;
|
||||||
import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler;
|
import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler;
|
||||||
import eu.crushedpixel.replaymod.recording.ConnectionEventHandler;
|
import eu.crushedpixel.replaymod.recording.ConnectionEventHandler;
|
||||||
@@ -10,10 +12,14 @@ import eu.crushedpixel.replaymod.reflection.MCPNames;
|
|||||||
import eu.crushedpixel.replaymod.registry.*;
|
import eu.crushedpixel.replaymod.registry.*;
|
||||||
import eu.crushedpixel.replaymod.renderer.InvisibilityRender;
|
import eu.crushedpixel.replaymod.renderer.InvisibilityRender;
|
||||||
import eu.crushedpixel.replaymod.renderer.SafeEntityRenderer;
|
import eu.crushedpixel.replaymod.renderer.SafeEntityRenderer;
|
||||||
|
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||||
|
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
||||||
import eu.crushedpixel.replaymod.replay.ReplaySender;
|
import eu.crushedpixel.replaymod.replay.ReplaySender;
|
||||||
|
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||||
import eu.crushedpixel.replaymod.settings.ReplaySettings;
|
import eu.crushedpixel.replaymod.settings.ReplaySettings;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||||
|
import eu.crushedpixel.replaymod.video.frame.*;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.settings.GameSettings;
|
import net.minecraft.client.settings.GameSettings;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
@@ -28,8 +34,10 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
|||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
@Mod(modid = ReplayMod.MODID, version = ReplayMod.VERSION)
|
@Mod(modid = ReplayMod.MODID, version = ReplayMod.VERSION)
|
||||||
public class ReplayMod {
|
public class ReplayMod {
|
||||||
@@ -116,7 +124,7 @@ public class ReplayMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void postInit(FMLPostInitializationEvent event) {
|
public void postInit(FMLPostInitializationEvent event) throws IOException {
|
||||||
GameSettings.Options.RENDER_DISTANCE.setValueMax(64f);
|
GameSettings.Options.RENDER_DISTANCE.setValueMax(64f);
|
||||||
|
|
||||||
overlay = new GuiReplayOverlay();
|
overlay = new GuiReplayOverlay();
|
||||||
@@ -164,6 +172,102 @@ public class ReplayMod {
|
|||||||
FMLCommonHandler.instance().exitJava(0, false);
|
FMLCommonHandler.instance().exitJava(0, false);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (System.getProperty("replaymod.render.file") != null) {
|
||||||
|
final File file = new File(System.getProperty("replaymod.render.file"));
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new IOException("File \"" + file.getPath() + "\" not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String renderDistance = System.getProperty("mc.renderdistance");
|
||||||
|
if (renderDistance != null) {
|
||||||
|
mc.gameSettings.renderDistanceChunks = Integer.parseInt(renderDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String path = System.getProperty("replaymod.render.path");
|
||||||
|
String type = System.getProperty("replaymod.render.type");
|
||||||
|
String quality = System.getProperty("replaymod.render.quality");
|
||||||
|
String fps = System.getProperty("replaymod.render.fps");
|
||||||
|
String waitForChunks = System.getProperty("replaymod.render.waitforchunks");
|
||||||
|
String linearMovement = System.getProperty("replaymod.render.linearmovement");
|
||||||
|
FrameRenderer renderer;
|
||||||
|
if (type != null) {
|
||||||
|
String[] parts = type.split(":");
|
||||||
|
type = parts[0].toUpperCase();
|
||||||
|
if ("NORMAL".equals(type) || "DEFAULT".equals(type)) {
|
||||||
|
renderer = new DefaultFrameRenderer();
|
||||||
|
} else if ("TILED".equals(type)) {
|
||||||
|
if (parts.length < 3) {
|
||||||
|
throw new IllegalArgumentException("Tiled renderer requires width and height.");
|
||||||
|
}
|
||||||
|
int width = Integer.parseInt(parts[1]);
|
||||||
|
int height = Integer.parseInt(parts[2]);
|
||||||
|
renderer = new TilingFrameRenderer(width, height);
|
||||||
|
} else if ("STEREO".equals(type) || "STEREOSCOPIC".equals(type)) {
|
||||||
|
renderer = new StereoscopicFrameRenderer();
|
||||||
|
} else if ("CUBIC".equals(type)) {
|
||||||
|
if (parts.length < 2) {
|
||||||
|
throw new IllegalArgumentException("Cubic renderer requires boolean for whether it's stable.");
|
||||||
|
}
|
||||||
|
renderer = new CubicFrameRenderer(Boolean.parseBoolean(parts[1]));
|
||||||
|
} else if ("EQUIRECTANGULAR".equals(type)) {
|
||||||
|
if (parts.length < 2) {
|
||||||
|
throw new IllegalArgumentException("Equirectangular renderer requires boolean for whether it's stable.");
|
||||||
|
}
|
||||||
|
renderer = new EquirectangularFrameRenderer(Boolean.parseBoolean(parts[1]));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown type: " + parts[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderer = new DefaultFrameRenderer();
|
||||||
|
}
|
||||||
|
final RenderOptions options = new RenderOptions(renderer);
|
||||||
|
if (quality != null) {
|
||||||
|
options.setQuality(Float.parseFloat(quality));
|
||||||
|
}
|
||||||
|
if (fps != null) {
|
||||||
|
options.setFps(Integer.parseInt(fps));
|
||||||
|
}
|
||||||
|
if (waitForChunks != null) {
|
||||||
|
options.setWaitForChunks(Boolean.parseBoolean(waitForChunks));
|
||||||
|
}
|
||||||
|
if (linearMovement != null) {
|
||||||
|
options.setLinearMovement(Boolean.parseBoolean(linearMovement));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Queue<ListenableFutureTask> tasks = mc.scheduledTasks;
|
||||||
|
synchronized (mc.scheduledTasks) {
|
||||||
|
tasks.add(ListenableFutureTask.create(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Loading replay...");
|
||||||
|
ReplayHandler.startReplay(file, false);
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
if (path != null) {
|
||||||
|
for (KeyframeSet set : ReplayHandler.getKeyframeRepository()) {
|
||||||
|
if (set.getName().equals(path)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (index >= ReplayHandler.getKeyframeRepository().length) {
|
||||||
|
throw new IllegalArgumentException("No path named \"" + path + "\".");
|
||||||
|
}
|
||||||
|
} else if (ReplayHandler.getKeyframeRepository().length == 0) {
|
||||||
|
throw new IllegalArgumentException("Replay file has no paths defined.");
|
||||||
|
}
|
||||||
|
ReplayHandler.useKeyframePresetFromRepository(index);
|
||||||
|
|
||||||
|
System.out.println("Rendering started...");
|
||||||
|
ReplayProcess.startReplayProcess(options);
|
||||||
|
System.out.println("Rendering done. Shutting down...");
|
||||||
|
mc.shutdown();
|
||||||
|
}
|
||||||
|
}, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeTmcprFiles() {
|
private void removeTmcprFiles() {
|
||||||
|
|||||||
@@ -30,8 +30,22 @@ public class CameraRollCT implements IClassTransformer {
|
|||||||
boolean success = false;
|
boolean success = false;
|
||||||
for (MethodNode m : classNode.methods) {
|
for (MethodNode m : classNode.methods) {
|
||||||
if ("(F)V".equals(m.desc) && name_orientCamera.equals(m.name)) {
|
if ("(F)V".equals(m.desc) && name_orientCamera.equals(m.name)) {
|
||||||
inject(m.instructions.iterator());
|
ListIterator<AbstractInsnNode> iter = m.instructions.iterator();
|
||||||
|
int f = 0;
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
AbstractInsnNode node = iter.next();
|
||||||
|
if ((f == 0 || f == 1) && node.getOpcode() == FCONST_0) {
|
||||||
|
f++;
|
||||||
|
} else if ((f == 2) && node instanceof LdcInsnNode && ((LdcInsnNode) node).cst.equals(-0.1f)) {
|
||||||
|
f++;
|
||||||
|
} else if (f == 3) {
|
||||||
|
inject(iter);
|
||||||
success = true;
|
success = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
f = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package eu.crushedpixel.replaymod.events;
|
package eu.crushedpixel.replaymod.events;
|
||||||
|
|
||||||
import eu.crushedpixel.replaymod.ReplayMod;
|
import eu.crushedpixel.replaymod.ReplayMod;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiCancelRender;
|
|
||||||
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiReplaySaving;
|
import eu.crushedpixel.replaymod.gui.GuiReplaySaving;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiReplaySettings;
|
import eu.crushedpixel.replaymod.gui.GuiReplaySettings;
|
||||||
@@ -18,7 +17,6 @@ import eu.crushedpixel.replaymod.studio.VersionValidator;
|
|||||||
import eu.crushedpixel.replaymod.timer.MCTimerHandler;
|
import eu.crushedpixel.replaymod.timer.MCTimerHandler;
|
||||||
import eu.crushedpixel.replaymod.utils.MouseUtils;
|
import eu.crushedpixel.replaymod.utils.MouseUtils;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||||
import eu.crushedpixel.replaymod.video.VideoWriter;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.*;
|
import net.minecraft.client.gui.*;
|
||||||
import net.minecraft.client.gui.inventory.GuiInventory;
|
import net.minecraft.client.gui.inventory.GuiInventory;
|
||||||
@@ -52,11 +50,6 @@ public class GuiEventHandler {
|
|||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public void onGui(GuiOpenEvent event) {
|
public void onGui(GuiOpenEvent event) {
|
||||||
if(VideoWriter.isRecording() && !(event.gui instanceof GuiCancelRender)) {
|
|
||||||
event.gui = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(event.gui instanceof GuiMainMenu) {
|
if(event.gui instanceof GuiMainMenu) {
|
||||||
if(ReplayMod.firstMainMenu) {
|
if(ReplayMod.firstMainMenu) {
|
||||||
ReplayMod.firstMainMenu = false;
|
ReplayMod.firstMainMenu = false;
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import eu.crushedpixel.replaymod.recording.ConnectionEventHandler;
|
|||||||
import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry;
|
import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry;
|
||||||
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||||
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
||||||
|
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||||
import eu.crushedpixel.replaymod.utils.MouseUtils;
|
import eu.crushedpixel.replaymod.utils.MouseUtils;
|
||||||
import eu.crushedpixel.replaymod.video.VideoWriter;
|
import eu.crushedpixel.replaymod.video.frame.FrameRenderer;
|
||||||
|
import eu.crushedpixel.replaymod.video.frame.DefaultFrameRenderer;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.Gui;
|
import net.minecraft.client.gui.Gui;
|
||||||
import net.minecraft.client.gui.GuiChat;
|
import net.minecraft.client.gui.GuiChat;
|
||||||
@@ -123,9 +125,8 @@ public class GuiReplayOverlay extends Gui {
|
|||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException {
|
public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
|
||||||
if(!ReplayHandler.isInReplay() || FMLClientHandler.instance().isGUIOpen(GuiPlayerOverview.class)
|
if(!ReplayHandler.isInReplay() || FMLClientHandler.instance().isGUIOpen(GuiPlayerOverview.class)
|
||||||
|| VideoWriter.isRecording() || FMLClientHandler.instance().isGUIOpen(GuiKeyframeRepository.class)) {
|
|| FMLClientHandler.instance().isGUIOpen(GuiKeyframeRepository.class)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +186,19 @@ public class GuiReplayOverlay extends Gui {
|
|||||||
if(hover) {
|
if(hover) {
|
||||||
playOrPause();
|
playOrPause();
|
||||||
} else if(mouseX >= exportButtonX && mouseX <= exportButtonX + 20 && mouseY >= exportButtonY && exportButtonY <= exportButtonY + 20) {
|
} else if(mouseX >= exportButtonX && mouseX <= exportButtonX + 20 && mouseY >= exportButtonY && exportButtonY <= exportButtonY + 20) {
|
||||||
ReplayHandler.startPath(true);
|
// TODO Put all those settings into a nice little GUI
|
||||||
|
FrameRenderer renderer = new DefaultFrameRenderer();
|
||||||
|
// FrameRenderer renderer = new TilingReadPixelFrameRenderer(mc.displayWidth*3, mc.displayHeight*2);
|
||||||
|
// FrameRenderer renderer = new TilingReadPixelFrameRenderer(3840, 2160);
|
||||||
|
// FrameRenderer renderer = new StereoscopicFrameRenderer();
|
||||||
|
// FrameRenderer renderer = new CubicFrameRenderer(false);
|
||||||
|
// FrameRenderer renderer = new EquirectangularFrameRenderer(false);
|
||||||
|
RenderOptions options = new RenderOptions(renderer);
|
||||||
|
options.setLinearMovement(ReplayMod.replaySettings.isLinearMovement());
|
||||||
|
options.setWaitForChunks(ReplayMod.replaySettings.getWaitForChunks());
|
||||||
|
options.setFps(ReplayMod.replaySettings.getVideoFramerate());
|
||||||
|
options.setQuality((float) ReplayMod.replaySettings.getVideoQuality());
|
||||||
|
ReplayHandler.startPath(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mouseX >= timelineX + 4 && mouseX <= width - 18 && mouseY >= 11 && mouseY <= 29) {
|
if(mouseX >= timelineX + 4 && mouseX <= width - 18 && mouseY >= 11 && mouseY <= 29) {
|
||||||
@@ -743,7 +756,7 @@ public class GuiReplayOverlay extends Gui {
|
|||||||
if(ReplayHandler.isInPath()) {
|
if(ReplayHandler.isInPath()) {
|
||||||
ReplayHandler.interruptReplay();
|
ReplayHandler.interruptReplay();
|
||||||
} else {
|
} else {
|
||||||
ReplayHandler.startPath(false);
|
ReplayHandler.startPath(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package eu.crushedpixel.replaymod.events;
|
|||||||
|
|
||||||
import eu.crushedpixel.replaymod.ReplayMod;
|
import eu.crushedpixel.replaymod.ReplayMod;
|
||||||
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiCancelRender;
|
|
||||||
import eu.crushedpixel.replaymod.gui.GuiMouseInput;
|
|
||||||
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||||
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
||||||
import eu.crushedpixel.replaymod.video.ReplayScreenshot;
|
import eu.crushedpixel.replaymod.video.ReplayScreenshot;
|
||||||
@@ -40,6 +38,7 @@ public class TickAndRenderListener {
|
|||||||
public void onRenderWorld(RenderWorldLastEvent event) throws
|
public void onRenderWorld(RenderWorldLastEvent event) throws
|
||||||
InvocationTargetException, IOException, IllegalAccessException, IllegalArgumentException {
|
InvocationTargetException, IOException, IllegalAccessException, IllegalArgumentException {
|
||||||
if(!ReplayHandler.isInReplay()) return; //If not in Replay, cancel
|
if(!ReplayHandler.isInReplay()) return; //If not in Replay, cancel
|
||||||
|
if (ReplayProcess.isVideoRecording()) return; // If recording, cancel
|
||||||
|
|
||||||
if(requestScreenshot == 1) {
|
if(requestScreenshot == 1) {
|
||||||
mc.addScheduledTask(new Runnable() {
|
mc.addScheduledTask(new Runnable() {
|
||||||
@@ -77,16 +76,12 @@ public class TickAndRenderListener {
|
|||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public void tick(TickEvent event) {
|
public void tick(TickEvent event) {
|
||||||
if(!ReplayHandler.isInReplay()) return;
|
if(!ReplayHandler.isInReplay() || ReplayProcess.isVideoRecording()) return;
|
||||||
|
|
||||||
if(ReplayHandler.getCameraEntity() != null)
|
if(ReplayHandler.getCameraEntity() != null)
|
||||||
ReplayHandler.getCameraEntity().updateMovement();
|
ReplayHandler.getCameraEntity().updateMovement();
|
||||||
if(ReplayHandler.isInPath()) {
|
if(ReplayHandler.isInPath()) {
|
||||||
ReplayProcess.unblockAndTick(true);
|
ReplayProcess.unblockAndTick(true);
|
||||||
if(ReplayProcess.isVideoRecording() &&
|
|
||||||
!(mc.currentScreen instanceof GuiMouseInput || mc.currentScreen instanceof GuiCancelRender)) {
|
|
||||||
mc.displayGuiScreen(new GuiMouseInput());
|
|
||||||
}
|
|
||||||
} else onMouseMove(new MouseEvent());
|
} else onMouseMove(new MouseEvent());
|
||||||
|
|
||||||
FMLCommonHandler.instance().bus().post(new InputEvent.KeyInputEvent());
|
FMLCommonHandler.instance().bus().post(new InputEvent.KeyInputEvent());
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package eu.crushedpixel.replaymod.gui;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.VideoRenderer;
|
||||||
|
import eu.crushedpixel.replaymod.video.frame.FrameRenderer;
|
||||||
|
import net.minecraft.client.gui.GuiButton;
|
||||||
|
import net.minecraft.client.gui.GuiScreen;
|
||||||
|
import net.minecraftforge.fml.client.config.GuiCheckBox;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class GuiVideoRenderer extends GuiScreen {
|
||||||
|
private final VideoRenderer renderer;
|
||||||
|
|
||||||
|
private final String PAUSE_RENDERING = "Pause Rendering";
|
||||||
|
private final String RESUME_RENDERING = "Resume Rendering";
|
||||||
|
private final String CANCEL = "Cancel Rendering";
|
||||||
|
private final String CANCEL_CONFIRM = "Sure?";
|
||||||
|
|
||||||
|
private GuiButton pauseButton;
|
||||||
|
private GuiButton cancelButton;
|
||||||
|
private GuiCheckBox previewCheckBox;
|
||||||
|
|
||||||
|
public GuiVideoRenderer(VideoRenderer renderer) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked") // I blame forge for not re-adding generics to that list
|
||||||
|
public void initGui() {
|
||||||
|
FrameRenderer frameRenderer = renderer.getFrameRenderer();
|
||||||
|
|
||||||
|
String text = "Show Preview (Performance might suffer)";
|
||||||
|
buttonList.add(previewCheckBox = new GuiCheckBox(0, (width - fontRendererObj.getStringWidth(text)) / 2 - 8,
|
||||||
|
frameRenderer.getPreviewHeight(this), text, false));
|
||||||
|
|
||||||
|
text = PAUSE_RENDERING;
|
||||||
|
buttonList.add(pauseButton = new GuiButton(1, width / 2 - 202, height / 2 - 23, text));
|
||||||
|
|
||||||
|
text = CANCEL;
|
||||||
|
buttonList.add(cancelButton = new GuiButton(1, width / 2 + 2, height / 2 - 23, text));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
} else if (button == previewCheckBox) {
|
||||||
|
renderer.getFrameRenderer().setPreviewActive(previewCheckBox.isChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||||
|
FrameRenderer frameRenderer = renderer.getFrameRenderer();
|
||||||
|
int centerX = width / 2;
|
||||||
|
int centerY = height / 2;
|
||||||
|
|
||||||
|
drawBackground(0);
|
||||||
|
|
||||||
|
String framesProgress = String.format("Frames rendered: %d / %d", renderer.getFramesDone(), renderer.getTotalFrames());
|
||||||
|
drawCenteredString(fontRendererObj, framesProgress, centerX, centerY - 45, 0xffffffff);
|
||||||
|
|
||||||
|
if (previewCheckBox.isChecked()) {
|
||||||
|
frameRenderer.renderPreview(this);
|
||||||
|
} else {
|
||||||
|
int previewHeight = frameRenderer.getPreviewHeight(this) - height / 2 - 10;
|
||||||
|
FrameRenderer.renderNoPreview(width, height, previewHeight);
|
||||||
|
}
|
||||||
|
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package eu.crushedpixel.replaymod.interpolation;
|
||||||
|
|
||||||
|
public interface Interpolation<T> {
|
||||||
|
void prepare();
|
||||||
|
T getPoint(float position);
|
||||||
|
void addPoint(T pos);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import akka.japi.Pair;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class LinearInterpolation<K> {
|
public abstract class LinearInterpolation<K> implements Interpolation<K> {
|
||||||
|
|
||||||
protected List<K> points = new ArrayList<K>();
|
protected List<K> points = new ArrayList<K>();
|
||||||
|
|
||||||
@@ -13,6 +13,11 @@ public abstract class LinearInterpolation<K> {
|
|||||||
points = new ArrayList<K>();
|
points = new ArrayList<K>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepare() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public abstract K getPoint(float position);
|
public abstract K getPoint(float position);
|
||||||
|
|
||||||
public void addPoint(K point) {
|
public void addPoint(K point) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
public class SplinePoint extends BasicSpline {
|
public class SplinePoint extends BasicSpline implements Interpolation<Position> {
|
||||||
private static final Object[] EMPTYOBJ = new Object[]{};
|
private static final Object[] EMPTYOBJ = new Object[]{};
|
||||||
private Vector<Position> points;
|
private Vector<Position> points;
|
||||||
private Vector<Cubic> xCubics;
|
private Vector<Cubic> xCubics;
|
||||||
@@ -60,7 +60,8 @@ public class SplinePoint extends BasicSpline {
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void calcSpline() {
|
@Override
|
||||||
|
public void prepare() {
|
||||||
try {
|
try {
|
||||||
calcNaturalCubic(points, vectorX, xCubics);
|
calcNaturalCubic(points, vectorX, xCubics);
|
||||||
calcNaturalCubic(points, vectorY, yCubics);
|
calcNaturalCubic(points, vectorY, yCubics);
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package eu.crushedpixel.replaymod.renderer;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.utils.JailingQueue;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
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.util.Iterator;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
|
public class ChunkLoadingRenderGlobal extends RenderGlobal {
|
||||||
|
public static void install(Minecraft mc) {
|
||||||
|
RenderGlobal org = mc.renderGlobal;
|
||||||
|
if (org instanceof ChunkLoadingRenderGlobal) {
|
||||||
|
((ChunkLoadingRenderGlobal) org).uninstall();
|
||||||
|
}
|
||||||
|
mc.renderGlobal = new ChunkLoadingRenderGlobal(mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final RenderGlobal originalRenderGlobal;
|
||||||
|
private final JailingQueue<?> workerJailingQueue;
|
||||||
|
private final CustomChunkRenderWorker renderWorker;
|
||||||
|
private int frame;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private ChunkLoadingRenderGlobal(Minecraft mc) {
|
||||||
|
super(mc);
|
||||||
|
this.originalRenderGlobal = mc.renderGlobal;
|
||||||
|
this.renderWorker = new CustomChunkRenderWorker(renderDispatcher, new RegionRenderCacheBuilder());
|
||||||
|
if (mc.theWorld != null) {
|
||||||
|
setWorldAndLoadRenderers(mc.theWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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 {
|
||||||
|
super.setupTerrain(viewEntity, partialTicks, camera, frame++, playerSpectator);
|
||||||
|
} while (displayListEntitiesDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPositionInRenderChunk(BlockPos p_174983_1_, RenderChunk p_174983_2_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void updateChunks(long finishTimeNano) {
|
||||||
|
while (renderDispatcher.runChunkUploads(0)) {
|
||||||
|
displayListEntitiesDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!renderDispatcher.queueChunkUpdates.isEmpty()) {
|
||||||
|
try {
|
||||||
|
renderWorker.processTask((ChunkCompileTaskGenerator) renderDispatcher.queueChunkUpdates.poll());
|
||||||
|
} catch (InterruptedException ignored) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<RenderChunk> iterator = chunksToUpdate.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
RenderChunk renderchunk = iterator.next();
|
||||||
|
|
||||||
|
renderDispatcher.updateChunkNow(renderchunk);
|
||||||
|
|
||||||
|
renderchunk.setNeedsUpdate(false);
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uninstall() {
|
||||||
|
workerJailingQueue.freeAll();
|
||||||
|
Minecraft.getMinecraft().renderGlobal = originalRenderGlobal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import eu.crushedpixel.replaymod.ReplayMod;
|
|||||||
import eu.crushedpixel.replaymod.entities.CameraEntity;
|
import eu.crushedpixel.replaymod.entities.CameraEntity;
|
||||||
import eu.crushedpixel.replaymod.holders.*;
|
import eu.crushedpixel.replaymod.holders.*;
|
||||||
import eu.crushedpixel.replaymod.registry.PlayerHandler;
|
import eu.crushedpixel.replaymod.registry.PlayerHandler;
|
||||||
|
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
@@ -86,8 +87,8 @@ public class ReplayHandler {
|
|||||||
return currentEntity == cameraEntity;
|
return currentEntity == cameraEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startPath(boolean save) {
|
public static void startPath(RenderOptions renderOptions) {
|
||||||
if(!ReplayHandler.isInPath()) ReplayProcess.startReplayProcess(save);
|
if(!ReplayHandler.isInPath()) ReplayProcess.startReplayProcess(renderOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void interruptReplay() {
|
public static void interruptReplay() {
|
||||||
@@ -322,7 +323,11 @@ public class ReplayHandler {
|
|||||||
return inReplay;
|
return inReplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startReplay(File file) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
|
public static void startReplay(File file) {
|
||||||
|
startReplay(file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startReplay(File file, boolean asyncMode) {
|
||||||
|
|
||||||
ReplayMod.chatMessageHandler.initialize();
|
ReplayMod.chatMessageHandler.initialize();
|
||||||
mc.ingameGUI.getChatGUI().clearChatMessages();
|
mc.ingameGUI.getChatGUI().clearChatMessages();
|
||||||
@@ -358,7 +363,7 @@ public class ReplayHandler {
|
|||||||
PlayerVisibility visibility = currentReplayFile.visibility().get();
|
PlayerVisibility visibility = currentReplayFile.visibility().get();
|
||||||
PlayerHandler.loadPlayerVisibilityConfiguration(visibility);
|
PlayerHandler.loadPlayerVisibilityConfiguration(visibility);
|
||||||
|
|
||||||
ReplayMod.replaySender = new ReplaySender(currentReplayFile, true);
|
ReplayMod.replaySender = new ReplaySender(currentReplayFile, asyncMode);
|
||||||
channel.pipeline().addFirst(ReplayMod.replaySender);
|
channel.pipeline().addFirst(ReplayMod.replaySender);
|
||||||
channel.pipeline().fireChannelActive();
|
channel.pipeline().fireChannelActive();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package eu.crushedpixel.replaymod.replay;
|
|||||||
|
|
||||||
import eu.crushedpixel.replaymod.ReplayMod;
|
import eu.crushedpixel.replaymod.ReplayMod;
|
||||||
import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType;
|
import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiCancelRender;
|
|
||||||
import eu.crushedpixel.replaymod.holders.Keyframe;
|
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||||
import eu.crushedpixel.replaymod.holders.Position;
|
import eu.crushedpixel.replaymod.holders.Position;
|
||||||
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
|
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
|
||||||
@@ -10,14 +9,13 @@ import eu.crushedpixel.replaymod.holders.TimeKeyframe;
|
|||||||
import eu.crushedpixel.replaymod.interpolation.LinearPoint;
|
import eu.crushedpixel.replaymod.interpolation.LinearPoint;
|
||||||
import eu.crushedpixel.replaymod.interpolation.LinearTimestamp;
|
import eu.crushedpixel.replaymod.interpolation.LinearTimestamp;
|
||||||
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
|
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
|
||||||
|
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||||
import eu.crushedpixel.replaymod.timer.EnchantmentTimer;
|
import eu.crushedpixel.replaymod.timer.EnchantmentTimer;
|
||||||
import eu.crushedpixel.replaymod.timer.MCTimerHandler;
|
import eu.crushedpixel.replaymod.timer.MCTimerHandler;
|
||||||
import eu.crushedpixel.replaymod.video.ScreenCapture;
|
import eu.crushedpixel.replaymod.video.VideoRenderer;
|
||||||
import eu.crushedpixel.replaymod.video.VideoWriter;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.chunk.RenderChunk;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class ReplayProcess {
|
public class ReplayProcess {
|
||||||
|
|
||||||
@@ -79,11 +77,11 @@ public class ReplayProcess {
|
|||||||
EnchantmentTimer.resetRecordingTime();
|
EnchantmentTimer.resetRecordingTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startReplayProcess(boolean record) {
|
public static void startReplayProcess(RenderOptions renderOptions) {
|
||||||
ReplayHandler.selectKeyframe(null);
|
ReplayHandler.selectKeyframe(null);
|
||||||
resetProcess();
|
resetProcess();
|
||||||
|
|
||||||
isVideoRecording = record;
|
isVideoRecording = renderOptions != null;
|
||||||
|
|
||||||
ReplayMod.chatMessageHandler.initialize();
|
ReplayMod.chatMessageHandler.initialize();
|
||||||
|
|
||||||
@@ -97,24 +95,28 @@ public class ReplayProcess {
|
|||||||
ReplayHandler.setInPath(true);
|
ReplayHandler.setInPath(true);
|
||||||
ReplayMod.replaySender.setAsyncMode(false);
|
ReplayMod.replaySender.setAsyncMode(false);
|
||||||
|
|
||||||
|
if (renderOptions == null) {
|
||||||
//gets first Timestamp and sets Replay Time to it
|
//gets first Timestamp and sets Replay Time to it
|
||||||
TimeKeyframe tf = ReplayHandler.getFirstTimeKeyframe();
|
TimeKeyframe tf = ReplayHandler.getFirstTimeKeyframe();
|
||||||
if(tf != null) {
|
if (tf != null) {
|
||||||
int ts = tf.getTimestamp();
|
int ts = tf.getTimestamp();
|
||||||
if(ts < ReplayMod.replaySender.currentTimeStamp()) {
|
if (ts < ReplayMod.replaySender.currentTimeStamp()) {
|
||||||
mc.displayGuiScreen(null);
|
mc.displayGuiScreen(null);
|
||||||
}
|
}
|
||||||
ReplayMod.replaySender.sendPacketsTill(ts);
|
ReplayMod.replaySender.sendPacketsTill(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathstarted", ChatMessageType.INFORMATION);
|
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathstarted", ChatMessageType.INFORMATION);
|
||||||
|
|
||||||
//if video is recording, the Replay Process takes control over the Minecraft Timer
|
|
||||||
if(isVideoRecording()) {
|
|
||||||
MCTimerHandler.setTimerSpeed(1f);
|
MCTimerHandler.setTimerSpeed(1f);
|
||||||
MCTimerHandler.setPassiveTimer();
|
|
||||||
} else {
|
} else {
|
||||||
MCTimerHandler.setTimerSpeed(1f);
|
try {
|
||||||
|
isVideoRecording = true;
|
||||||
|
boolean success = new VideoRenderer(renderOptions).renderVideo();
|
||||||
|
isVideoRecording = false;
|
||||||
|
stopReplayProcess(success);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,9 +127,6 @@ public class ReplayProcess {
|
|||||||
if(finished) ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathfinished", ChatMessageType.INFORMATION);
|
if(finished) ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathfinished", ChatMessageType.INFORMATION);
|
||||||
else {
|
else {
|
||||||
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathinterrupted", ChatMessageType.INFORMATION);
|
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathinterrupted", ChatMessageType.INFORMATION);
|
||||||
if(isVideoRecording()) {
|
|
||||||
VideoWriter.abortRecording();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplayHandler.setInPath(false);
|
ReplayHandler.setInPath(false);
|
||||||
@@ -143,14 +142,17 @@ public class ReplayProcess {
|
|||||||
public static void unblockAndTick(boolean justCheck) {
|
public static void unblockAndTick(boolean justCheck) {
|
||||||
if(!deepBlock) blocked = false;
|
if(!deepBlock) blocked = false;
|
||||||
if(!blocked || !isVideoRecording())
|
if(!blocked || !isVideoRecording())
|
||||||
pathTick(isVideoRecording(), justCheck);
|
ReplayProcess.tickReplay(justCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
//if justCheck is true, no Screenshot will be taken, it will only be checked
|
//if justCheck is true, no Screenshot will be taken, it will only be checked
|
||||||
//whether all chunks have been rendered. This is necessary because no Render ticks
|
//whether all chunks have been rendered. This is necessary because no Render ticks
|
||||||
//are called if the Timer speed is set to 0, leading to this method never being
|
//are called if the Timer speed is set to 0, leading to this method never being
|
||||||
//called from the RenderWorldLastEvent handlers.
|
//called from the RenderWorldLastEvent handlers.
|
||||||
private static void pathTick(boolean recording, boolean justCheck) {
|
public static void tickReplay(boolean justCheck) {
|
||||||
|
if (isVideoRecording) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(ReplayMod.replaySender.isHurrying()) {
|
if(ReplayMod.replaySender.isHurrying()) {
|
||||||
lastRealTime = System.currentTimeMillis();
|
lastRealTime = System.currentTimeMillis();
|
||||||
return;
|
return;
|
||||||
@@ -166,35 +168,8 @@ public class ReplayProcess {
|
|||||||
MCTimerHandler.setTicks(100);
|
MCTimerHandler.setTicks(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(recording && ((ReplayMod.replaySettings.getWaitForChunks() && RenderChunk.renderChunksUpdated != 0) || mc.currentScreen instanceof GuiCancelRender)) {
|
|
||||||
if(!firstTime) {
|
|
||||||
MCTimerHandler.setTimerSpeed(0f);
|
|
||||||
MCTimerHandler.setPartialTicks(0f);
|
|
||||||
MCTimerHandler.setRenderPartialTicks(0f);
|
|
||||||
MCTimerHandler.setTicks(0);
|
|
||||||
resetTimer = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else if(recording && ReplayMod.replaySettings.getWaitForChunks()) {
|
|
||||||
MCTimerHandler.setTimerSpeed((float) lastSpeed);
|
|
||||||
//MCTimerHandler.setRenderPartialTicks(lastRenderPartialTicks);
|
|
||||||
if(resetTimer) {
|
|
||||||
MCTimerHandler.setPartialTicks(lastPartialTicks);
|
|
||||||
MCTimerHandler.setRenderPartialTicks(lastRenderPartialTicks);
|
|
||||||
MCTimerHandler.setTicks(lastTicks);
|
|
||||||
resetTimer = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(justCheck) return;
|
if(justCheck) return;
|
||||||
|
|
||||||
if(recording) {
|
|
||||||
if(blocked) return;
|
|
||||||
|
|
||||||
deepBlock = true;
|
|
||||||
blocked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int posCount = ReplayHandler.getPosKeyframeCount();
|
int posCount = ReplayHandler.getPosKeyframeCount();
|
||||||
int timeCount = ReplayHandler.getTimeKeyframeCount();
|
int timeCount = ReplayHandler.getTimeKeyframeCount();
|
||||||
|
|
||||||
@@ -233,16 +208,11 @@ public class ReplayProcess {
|
|||||||
if(!calculated) {
|
if(!calculated) {
|
||||||
calculated = true;
|
calculated = true;
|
||||||
if(posCount > 1 && motionSpline != null)
|
if(posCount > 1 && motionSpline != null)
|
||||||
motionSpline.calcSpline();
|
motionSpline.prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
long curTime = System.currentTimeMillis();
|
long curTime = System.currentTimeMillis();
|
||||||
long timeStep;
|
long timeStep = curTime - lastRealTime;
|
||||||
if(recording) {
|
|
||||||
timeStep = 1000 / ReplayMod.replaySettings.getVideoFramerate();
|
|
||||||
} else {
|
|
||||||
timeStep = curTime - lastRealTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
int curRealReplayTime = (int) (lastRealReplayTime + timeStep);
|
int curRealReplayTime = (int) (lastRealReplayTime + timeStep);
|
||||||
|
|
||||||
@@ -333,14 +303,13 @@ public class ReplayProcess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplayHandler.setCameraTilt(pos.getRoll());
|
|
||||||
|
|
||||||
Integer curTimestamp = null;
|
Integer curTimestamp = null;
|
||||||
if(timeLinear != null && timeCount > 1) {
|
if(timeLinear != null && timeCount > 1) {
|
||||||
curTimestamp = timeLinear.getPoint(Math.max(0, Math.min(1, timePos)));
|
curTimestamp = timeLinear.getPoint(Math.max(0, Math.min(1, timePos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pos != null) {
|
if(pos != null) {
|
||||||
|
ReplayHandler.setCameraTilt(pos.getRoll());
|
||||||
ReplayHandler.getCameraEntity().movePath(pos);
|
ReplayHandler.getCameraEntity().movePath(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,11 +317,6 @@ public class ReplayProcess {
|
|||||||
//if(curSpeed > 0)
|
//if(curSpeed > 0)
|
||||||
lastSpeed = curSpeed;
|
lastSpeed = curSpeed;
|
||||||
|
|
||||||
if(recording) {
|
|
||||||
MCTimerHandler.updateTimer((1f / ReplayMod.replaySettings.getVideoFramerate()));
|
|
||||||
EnchantmentTimer.increaseRecordingTime((1000 / ReplayMod.replaySettings.getVideoFramerate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPartialTicks = MCTimerHandler.getPartialTicks();
|
lastPartialTicks = MCTimerHandler.getPartialTicks();
|
||||||
lastRenderPartialTicks = MCTimerHandler.getRenderTicks();
|
lastRenderPartialTicks = MCTimerHandler.getRenderTicks();
|
||||||
lastTicks = MCTimerHandler.getTicks();
|
lastTicks = MCTimerHandler.getTicks();
|
||||||
@@ -365,33 +329,13 @@ public class ReplayProcess {
|
|||||||
lastRealReplayTime = curRealReplayTime;
|
lastRealReplayTime = curRealReplayTime;
|
||||||
lastRealTime = curTime;
|
lastRealTime = curTime;
|
||||||
|
|
||||||
if(isVideoRecording()) {
|
|
||||||
try {
|
|
||||||
if(!VideoWriter.isRecording() && ReplayHandler.isInPath()) {
|
|
||||||
VideoWriter.startRecording(mc.displayWidth, mc.displayHeight);
|
|
||||||
} else {
|
|
||||||
final BufferedImage screen = ScreenCapture.captureScreen();
|
|
||||||
VideoWriter.writeImage(screen);
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(requestFinish) {
|
if(requestFinish) {
|
||||||
stopReplayProcess(true);
|
stopReplayProcess(true);
|
||||||
requestFinish = false;
|
requestFinish = false;
|
||||||
if(recording) {
|
|
||||||
VideoWriter.endRecording();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if((splinePos >= 1 || posCount <= 1) && (timePos >= 1 || timeCount <= 1)) {
|
if((splinePos >= 1 || posCount <= 1) && (timePos >= 1 || timeCount <= 1)) {
|
||||||
requestFinish = true;
|
requestFinish = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(recording) {
|
|
||||||
deepBlock = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package eu.crushedpixel.replaymod.settings;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.frame.FrameRenderer;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.Validate.*;
|
||||||
|
|
||||||
|
public final class RenderOptions {
|
||||||
|
private FrameRenderer renderer;
|
||||||
|
private float quality = 0.5f;
|
||||||
|
private int fps = 30;
|
||||||
|
private boolean waitForChunks = true;
|
||||||
|
private boolean isLinearMovement = false;
|
||||||
|
|
||||||
|
public RenderOptions(FrameRenderer renderer) {
|
||||||
|
this.renderer = notNull(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameRenderer getRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenderer(FrameRenderer renderer) {
|
||||||
|
this.renderer = notNull(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getQuality() {
|
||||||
|
return quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuality(float quality) {
|
||||||
|
inclusiveBetween(0f, 1f, quality);
|
||||||
|
this.quality = quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFps() {
|
||||||
|
return fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFps(int fps) {
|
||||||
|
isTrue(fps > 0, "Fps must be positive.");
|
||||||
|
this.fps = fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWaitForChunks() {
|
||||||
|
return waitForChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWaitForChunks(boolean waitForChunks) {
|
||||||
|
this.waitForChunks = waitForChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLinearMovement() {
|
||||||
|
return isLinearMovement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLinearMovement(boolean isLinearMovement) {
|
||||||
|
this.isLinearMovement = isLinearMovement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderOptions copy() {
|
||||||
|
RenderOptions copy = new RenderOptions(renderer);
|
||||||
|
copy.quality = this.quality;
|
||||||
|
copy.fps = this.fps;
|
||||||
|
copy.waitForChunks = this.waitForChunks;
|
||||||
|
copy.isLinearMovement = this.isLinearMovement;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,10 @@ public class MCTimerHandler {
|
|||||||
return mc.timer.renderPartialTicks;
|
return mc.timer.renderPartialTicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Timer getTimer() {
|
||||||
|
return mc.timer;
|
||||||
|
}
|
||||||
|
|
||||||
public static void advanceTicks(int ticks) {
|
public static void advanceTicks(int ticks) {
|
||||||
mc.timer.elapsedTicks += ticks;
|
mc.timer.elapsedTicks += ticks;
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/main/java/eu/crushedpixel/replaymod/utils/JailingQueue.java
Normal file
120
src/main/java/eu/crushedpixel/replaymod/utils/JailingQueue.java
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package eu.crushedpixel.replaymod.utils;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class JailingQueue<T> extends AbstractQueue<T> implements BlockingQueue<T> {
|
||||||
|
private final BlockingQueue<T> delegate;
|
||||||
|
private final Set<Thread> jailed = new HashSet<Thread>();
|
||||||
|
|
||||||
|
public JailingQueue(BlockingQueue<T> delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void jail(int atLeast) {
|
||||||
|
while (jailed.size() < atLeast) {
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.interrupted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void free(Thread thread) {
|
||||||
|
Preconditions.checkState(jailed.remove(thread), "Thread is not jailed.");
|
||||||
|
thread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void freeAll() {
|
||||||
|
jailed.clear();
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void tryAccess() {
|
||||||
|
jailed.add(Thread.currentThread());
|
||||||
|
notifyAll();
|
||||||
|
while (jailed.contains(Thread.currentThread())) {
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.interrupted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(T t) throws InterruptedException {
|
||||||
|
tryAccess();
|
||||||
|
delegate.put(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean offer(T t, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.offer(t, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T take() throws InterruptedException {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T poll(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.poll(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int remainingCapacity() {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.remainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int drainTo(Collection<? super T> c) {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.drainTo(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int drainTo(Collection<? super T> c, int maxElements) {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.drainTo(c, maxElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean offer(T t) {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.offer(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T poll() {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T peek() {
|
||||||
|
tryAccess();
|
||||||
|
return delegate.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package eu.crushedpixel.replaymod.video;
|
package eu.crushedpixel.replaymod.video;
|
||||||
|
|
||||||
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
|
||||||
import net.minecraft.util.Timer;
|
import net.minecraft.util.Timer;
|
||||||
|
|
||||||
public class ReplayTimer extends Timer {
|
public class ReplayTimer extends Timer {
|
||||||
@@ -11,7 +10,5 @@ public class ReplayTimer extends Timer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTimer() {
|
public void updateTimer() {
|
||||||
if(ReplayProcess.isVideoRecording()) return;
|
|
||||||
super.updateTimer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
391
src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java
Normal file
391
src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.ReplayMod;
|
||||||
|
import eu.crushedpixel.replaymod.entities.CameraEntity;
|
||||||
|
import eu.crushedpixel.replaymod.gui.GuiVideoRenderer;
|
||||||
|
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||||
|
import eu.crushedpixel.replaymod.holders.Position;
|
||||||
|
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
|
||||||
|
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
|
||||||
|
import eu.crushedpixel.replaymod.interpolation.Interpolation;
|
||||||
|
import eu.crushedpixel.replaymod.interpolation.LinearPoint;
|
||||||
|
import eu.crushedpixel.replaymod.interpolation.LinearTimestamp;
|
||||||
|
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
|
||||||
|
import eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal;
|
||||||
|
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||||
|
import eu.crushedpixel.replaymod.replay.ReplaySender;
|
||||||
|
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||||
|
import eu.crushedpixel.replaymod.timer.EnchantmentTimer;
|
||||||
|
import eu.crushedpixel.replaymod.timer.MCTimerHandler;
|
||||||
|
import eu.crushedpixel.replaymod.video.frame.FrameRenderer;
|
||||||
|
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 java.awt.image.BufferedImage;
|
||||||
|
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 {
|
||||||
|
private final Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
private final FrameRenderer frameRenderer;
|
||||||
|
private final VideoWriter videoWriter;
|
||||||
|
private final ReplaySender replaySender;
|
||||||
|
private final RenderOptions options;
|
||||||
|
|
||||||
|
private int fps;
|
||||||
|
private boolean mouseWasGrabbed;
|
||||||
|
|
||||||
|
private Interpolation<Position> movement;
|
||||||
|
private Interpolation<Integer> time;
|
||||||
|
|
||||||
|
private int framesDone;
|
||||||
|
private int totalFrames;
|
||||||
|
|
||||||
|
private final GuiVideoRenderer gui;
|
||||||
|
private boolean frameRendererUpdatesGui;
|
||||||
|
private boolean paused;
|
||||||
|
private boolean cancelled;
|
||||||
|
|
||||||
|
public VideoRenderer(RenderOptions options) throws IOException {
|
||||||
|
this.frameRenderer = options.getRenderer();
|
||||||
|
this.videoWriter = new VideoWriter(frameRenderer.getVideoWidth(), frameRenderer.getVideoHeight(),
|
||||||
|
options.getFps(), options.getQuality(), 10);
|
||||||
|
this.gui = new GuiVideoRenderer(this);
|
||||||
|
this.replaySender = ReplayMod.replaySender;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this video.
|
||||||
|
* @return {@code true} if rendering was successful, {@code false} if the user aborted rendering (or the window was closed)
|
||||||
|
* @throws IOException {@link Minecraft#runTick()} can apparently throw these, don't question it!
|
||||||
|
*/
|
||||||
|
public boolean renderVideo() throws IOException {
|
||||||
|
setup();
|
||||||
|
|
||||||
|
Timer timer = MCTimerHandler.getTimer();
|
||||||
|
|
||||||
|
replaySender.sendPacketsTill(time.getPoint(0));
|
||||||
|
// Pre-tick twice, once to process all the packets
|
||||||
|
tick();
|
||||||
|
// the second time to prevent interpolation of the player position
|
||||||
|
tick();
|
||||||
|
|
||||||
|
|
||||||
|
framesDone = 0;
|
||||||
|
while (framesDone < totalFrames && !cancelled) {
|
||||||
|
pauseIfNeeded();
|
||||||
|
|
||||||
|
if (Display.isActive() && Display.isCloseRequested()) {
|
||||||
|
mc.shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTime(timer, framesDone);
|
||||||
|
|
||||||
|
int elapsedTicks = timer.elapsedTicks;
|
||||||
|
while (elapsedTicks-- > 0) {
|
||||||
|
tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
frame(timer);
|
||||||
|
framesDone++;
|
||||||
|
|
||||||
|
if (!frameRendererUpdatesGui) {
|
||||||
|
drawGui();
|
||||||
|
}
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish();
|
||||||
|
return !cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup() {
|
||||||
|
if (Mouse.isGrabbed()) {
|
||||||
|
mouseWasGrabbed = true;
|
||||||
|
}
|
||||||
|
Mouse.setGrabbed(false);
|
||||||
|
MCTimerHandler.setTimerSpeed(1f);
|
||||||
|
MCTimerHandler.setPassiveTimer();
|
||||||
|
|
||||||
|
fps = options.getFps();
|
||||||
|
if (options.isLinearMovement()) {
|
||||||
|
movement = new LinearPoint();
|
||||||
|
} else {
|
||||||
|
movement = new SplinePoint();
|
||||||
|
}
|
||||||
|
time = new LinearTimestamp();
|
||||||
|
|
||||||
|
int duration = 0;
|
||||||
|
int posKeyframes = 0;
|
||||||
|
for (Keyframe keyframe : ReplayHandler.getKeyframes()) {
|
||||||
|
if (keyframe.getRealTimestamp() > duration) {
|
||||||
|
duration = keyframe.getRealTimestamp();
|
||||||
|
}
|
||||||
|
if(keyframe instanceof PositionKeyframe) {
|
||||||
|
movement.addPoint(((PositionKeyframe) keyframe).getPosition());
|
||||||
|
posKeyframes++;
|
||||||
|
}
|
||||||
|
if (keyframe instanceof TimeKeyframe) {
|
||||||
|
time.addPoint(((TimeKeyframe) keyframe).getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalFrames = duration*fps/1000;
|
||||||
|
|
||||||
|
if (posKeyframes >= 2) {
|
||||||
|
movement.prepare();
|
||||||
|
} else {
|
||||||
|
movement = null; // No movement occurring
|
||||||
|
}
|
||||||
|
time.prepare();
|
||||||
|
|
||||||
|
frameRendererUpdatesGui = frameRenderer.setRenderPreviewCallback(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
drawGui();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
frameRenderer.setup();
|
||||||
|
|
||||||
|
ScaledResolution scaled = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||||
|
gui.setWorldAndResolution(mc, scaled.getScaledWidth(), scaled.getScaledHeight());
|
||||||
|
|
||||||
|
if (options.isWaitForChunks()) {
|
||||||
|
ChunkLoadingRenderGlobal.install(mc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finish() {
|
||||||
|
if (cancelled) {
|
||||||
|
videoWriter.abortRecording();
|
||||||
|
} else {
|
||||||
|
videoWriter.endRecording();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
videoWriter.waitForFinish();
|
||||||
|
} catch (InterruptedException ignored) { }
|
||||||
|
|
||||||
|
frameRenderer.tearDown();
|
||||||
|
if (mouseWasGrabbed) {
|
||||||
|
Mouse.setGrabbed(true);
|
||||||
|
}
|
||||||
|
mc.displayGuiScreen(null);
|
||||||
|
if (mc.renderGlobal instanceof ChunkLoadingRenderGlobal) {
|
||||||
|
((ChunkLoadingRenderGlobal) mc.renderGlobal).uninstall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCam() {
|
||||||
|
if (ReplayHandler.getCameraEntity() == null) {
|
||||||
|
if (mc.theWorld == null) {
|
||||||
|
return; // World hasn't been sent yet
|
||||||
|
}
|
||||||
|
ReplayHandler.setCameraEntity(new CameraEntity(mc.theWorld));
|
||||||
|
}
|
||||||
|
int videoTime = framesDone * 1000 / fps;
|
||||||
|
int posCount = ReplayHandler.getPosKeyframeCount();
|
||||||
|
|
||||||
|
Position pos;
|
||||||
|
PositionKeyframe lastPos = ReplayHandler.getPreviousPositionKeyframe(videoTime);
|
||||||
|
if (movement == null || lastPos == null) {
|
||||||
|
// Stay at one position, no movement
|
||||||
|
pos = ReplayHandler.getNextPositionKeyframe(-1).getPosition();
|
||||||
|
} else {
|
||||||
|
// Position interpolation
|
||||||
|
PositionKeyframe nextPos = ReplayHandler.getNextPositionKeyframe(videoTime);
|
||||||
|
|
||||||
|
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 = (ReplayHandler.getKeyframeIndex(lastPos) + diffPct) / (posCount-1);
|
||||||
|
pos = movement.getPoint(Math.max(0, Math.min(1, totalPct)));
|
||||||
|
}
|
||||||
|
ReplayHandler.setCameraTilt(pos.getRoll());
|
||||||
|
ReplayHandler.getCameraEntity().movePath(pos);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTime(Timer timer, int framesDone) {
|
||||||
|
int videoTime = framesDone * 1000 / fps;
|
||||||
|
int timeCount = ReplayHandler.getTimeKeyframeCount();
|
||||||
|
|
||||||
|
// WARNING: The rest of this method contains some magic for which Marius is responsible
|
||||||
|
// Time interpolation
|
||||||
|
TimeKeyframe lastTime = ReplayHandler.getPreviousTimeKeyframe(videoTime);
|
||||||
|
TimeKeyframe nextTime = ReplayHandler.getNextTimeKeyframe(videoTime);
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
if(lastTimeStamp == nextTimeStamp) curSpeed = 0f;
|
||||||
|
else curSpeed = ((double)((nextTime.getTimestamp()-lastTime.getTimestamp())))/((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 = (ReplayHandler.getKeyframeIndex(lastTime) + currentTimeStepPerc) / (timeCount-1f);
|
||||||
|
|
||||||
|
Integer replayTime = time.getPoint(Math.max(0, Math.min(1, timePos)));
|
||||||
|
if(replayTime != null) {
|
||||||
|
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() throws IOException {
|
||||||
|
synchronized (mc.scheduledTasks) {
|
||||||
|
while (!mc.scheduledTasks.isEmpty()) {
|
||||||
|
((FutureTask) mc.scheduledTasks.poll()).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.currentScreen = gui;
|
||||||
|
mc.runTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void frame(Timer timer) {
|
||||||
|
updateCam();
|
||||||
|
|
||||||
|
BufferedImage frame = null;
|
||||||
|
while (frame == null) {
|
||||||
|
try {
|
||||||
|
frame = frameRenderer.captureFrame(timer);
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
System.out.println("Caught oom error-> calling garbage collector, decreasing queue size and waiting for video writer");
|
||||||
|
System.gc();
|
||||||
|
videoWriter.waitTillQueueEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!videoWriter.writeImage(frame, false)) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drawGui();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pauseIfNeeded() {
|
||||||
|
while (paused && !cancelled && !Display.isCloseRequested()) {
|
||||||
|
drawGui();
|
||||||
|
try {
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawGui() {
|
||||||
|
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();
|
||||||
|
|
||||||
|
Display.update();
|
||||||
|
if (Mouse.isGrabbed()) {
|
||||||
|
Mouse.setGrabbed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFramesDone() {
|
||||||
|
return framesDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalFrames() {
|
||||||
|
return totalFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameRenderer getFrameRenderer() {
|
||||||
|
return frameRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaused(boolean paused) {
|
||||||
|
this.paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPaused() {
|
||||||
|
return paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
this.cancelled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.crushedpixel.replaymod.video;
|
package eu.crushedpixel.replaymod.video;
|
||||||
|
|
||||||
import eu.crushedpixel.replaymod.ReplayMod;
|
import com.google.common.base.Preconditions;
|
||||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||||
import org.monte.media.*;
|
import org.monte.media.*;
|
||||||
import org.monte.media.FormatKeys.MediaType;
|
import org.monte.media.FormatKeys.MediaType;
|
||||||
@@ -9,132 +9,170 @@ import org.monte.media.math.Rational;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public class VideoWriter {
|
public class VideoWriter {
|
||||||
|
|
||||||
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
|
private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss";
|
||||||
private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
private static final SimpleDateFormat FILE_FORMAT = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
|
||||||
private static final String VIDEO_EXTENSION = ".avi";
|
private static final String VIDEO_EXTENSION = ".avi";
|
||||||
|
|
||||||
private static MovieWriter out;
|
private final File file;
|
||||||
private static File file;
|
private final MovieWriter out;
|
||||||
private static boolean isRecording = false;
|
private final Buffer buf;
|
||||||
private static boolean requestFinish = false;
|
private final int track;
|
||||||
private static boolean abort = false;
|
|
||||||
|
|
||||||
private static Buffer buf;
|
private volatile boolean active = true;
|
||||||
private static int track;
|
private volatile boolean cancelled = false;
|
||||||
private static Queue<BufferedImage> toWrite = new LinkedBlockingQueue<BufferedImage>();
|
|
||||||
|
|
||||||
public static boolean isRecording() {
|
private int queueLimit;
|
||||||
return isRecording;
|
private final Queue<BufferedImage> toWrite;
|
||||||
|
private final Thread writerThread;
|
||||||
|
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
private final Condition emptyCondition = lock.newCondition();
|
||||||
|
private final Condition noLongerEmptyCondition = lock.newCondition();
|
||||||
|
private final Condition noLongerFullCondition = lock.newCondition();
|
||||||
|
|
||||||
|
public VideoWriter(int width, int height, int fps, float quality) throws IOException {
|
||||||
|
this(width, height, fps, quality, Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startRecording(int width, int height) {
|
public VideoWriter(int width, int height, int fps, float quality, int queueLimit) throws IOException {
|
||||||
if(isRecording) {
|
this.queueLimit = queueLimit;
|
||||||
IllegalStateException up = new IllegalStateException("VideoWriter is already recording!");
|
this.toWrite = new LinkedList<BufferedImage>();
|
||||||
throw up; //lolololo
|
|
||||||
}
|
|
||||||
isRecording = true;
|
|
||||||
|
|
||||||
toWrite = new LinkedBlockingQueue<BufferedImage>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
File folder = ReplayFileIO.getRenderFolder();
|
File folder = ReplayFileIO.getRenderFolder();
|
||||||
|
String fileName = FILE_FORMAT.format(Calendar.getInstance().getTime());
|
||||||
String fileName = sdf.format(Calendar.getInstance().getTime());
|
|
||||||
|
|
||||||
file = new File(folder, fileName + VIDEO_EXTENSION);
|
file = new File(folder, fileName + VIDEO_EXTENSION);
|
||||||
file.createNewFile();
|
Files.createFile(file.toPath());
|
||||||
|
|
||||||
out = Registry.getInstance().getWriter(file);
|
out = Registry.getInstance().getWriter(file);
|
||||||
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
||||||
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
||||||
FormatKeys.FrameRateKey, new Rational(ReplayMod.replaySettings.getVideoFramerate(), 1),
|
FormatKeys.FrameRateKey, new Rational(fps, 1),
|
||||||
VideoFormatKeys.WidthKey, width,
|
VideoFormatKeys.WidthKey, width,
|
||||||
VideoFormatKeys.HeightKey, height,
|
VideoFormatKeys.HeightKey, height,
|
||||||
VideoFormatKeys.DepthKey, 24,
|
VideoFormatKeys.DepthKey, 24,
|
||||||
VideoFormatKeys.QualityKey, (float) ReplayMod.replaySettings.getVideoQuality());
|
VideoFormatKeys.QualityKey, quality);
|
||||||
|
|
||||||
|
|
||||||
track = out.addTrack(format);
|
track = out.addTrack(format);
|
||||||
|
|
||||||
buf = new Buffer();
|
buf = new Buffer();
|
||||||
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
||||||
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
|
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
|
||||||
} catch(IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread t = new Thread(new Runnable() {
|
writerThread = new Thread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
while(true) {
|
while(!cancelled && (active || !toWrite.isEmpty())) {
|
||||||
if(toWrite.isEmpty() || abort) {
|
|
||||||
if(requestFinish) {
|
|
||||||
requestFinish = false;
|
|
||||||
isRecording = false;
|
|
||||||
try {
|
try {
|
||||||
|
lock.lockInterruptibly();
|
||||||
|
BufferedImage img;
|
||||||
|
try {
|
||||||
|
img = toWrite.poll();
|
||||||
|
if (img == null) {
|
||||||
|
noLongerEmptyCondition.await();
|
||||||
|
img = toWrite.poll();
|
||||||
|
}
|
||||||
|
noLongerFullCondition.signal();
|
||||||
|
if (toWrite.isEmpty()) {
|
||||||
|
emptyCondition.signalAll();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
buf.data = img;
|
||||||
|
try {
|
||||||
|
out.write(track, buf);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
toWrite.clear();
|
||||||
out.close();
|
out.close();
|
||||||
if(abort) {
|
if (cancelled) {
|
||||||
file.delete();
|
Files.delete(file.toPath());
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
abort = false;
|
|
||||||
toWrite = new LinkedBlockingQueue<BufferedImage>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Thread.sleep(10);
|
|
||||||
} catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
write();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
t.start();
|
writerThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeImage(BufferedImage image) {
|
public void setQueueLimit(int limit) {
|
||||||
if(requestFinish || !isRecording) {
|
this.queueLimit = limit;
|
||||||
IllegalStateException up = new IllegalStateException(
|
|
||||||
"The VideoWriter is currently not available. Please try again later.");
|
|
||||||
throw up; //lolololo^2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toWrite.add(image);
|
/**
|
||||||
}
|
* Add the image to the writer queue.
|
||||||
|
* @param image The image
|
||||||
|
* @param waitIfFull Whether to wait if the queue is full or to return immediately
|
||||||
|
* @return {@code} true if the image was added, {@code false} if it could not be added due to the queue being full
|
||||||
|
* and {@code waitIfFull} being {@code false}
|
||||||
|
*/
|
||||||
|
public boolean writeImage(BufferedImage image, boolean waitIfFull) {
|
||||||
|
Preconditions.checkState(active, "This VideoWriter has already been closed.");
|
||||||
|
|
||||||
public static void endRecording() {
|
lock.lock();
|
||||||
if(!isRecording) return;
|
|
||||||
requestFinish = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void write() {
|
|
||||||
try {
|
try {
|
||||||
BufferedImage img = toWrite.poll();
|
while (toWrite.size() >= queueLimit) {
|
||||||
if(img != null) {
|
if (waitIfFull) {
|
||||||
buf.data = img;
|
noLongerFullCondition.await();
|
||||||
out.write(track, buf);
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
}
|
||||||
|
toWrite.offer(image);
|
||||||
|
noLongerEmptyCondition.signal();
|
||||||
|
return true;
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endRecording() {
|
||||||
|
active = false;
|
||||||
|
writerThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void abortRecording() {
|
||||||
|
cancelled = true;
|
||||||
|
active = false;
|
||||||
|
writerThread.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitTillQueueEmpty() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (!toWrite.isEmpty()) {
|
||||||
|
emptyCondition.await();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void abortRecording() {
|
public void waitForFinish() throws InterruptedException {
|
||||||
requestFinish = true;
|
Preconditions.checkState(!active, "Video writer still active.");
|
||||||
abort = true;
|
writerThread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.entity;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||||
|
import net.minecraft.client.renderer.GlStateManager;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
|
||||||
|
public class CubicEntityRenderer extends CustomEntityRenderer {
|
||||||
|
|
||||||
|
public static enum Direction {
|
||||||
|
TOP(1), BOTTOM(9), LEFT(4), FRONT(5), RIGHT(6), BACK(7);
|
||||||
|
|
||||||
|
private final int frame;
|
||||||
|
|
||||||
|
Direction(int frame) {
|
||||||
|
this.frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Direction forFrame(int frame) {
|
||||||
|
for (Direction d : values()) {
|
||||||
|
if (d.frame == frame) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCubicFrame() {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean stable;
|
||||||
|
private Direction direction;
|
||||||
|
|
||||||
|
public CubicEntityRenderer(boolean stable) {
|
||||||
|
this.stable = stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrame(int id) {
|
||||||
|
setDirection(Direction.forFrame(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirection(Direction direction) {
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void gluPerspective(float fovY, float aspect, float zNear, float zFar) {
|
||||||
|
super.gluPerspective(90, 1, zNear, zFar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupCameraTransform(float partialTicks) {
|
||||||
|
Entity entity = mc.getRenderViewEntity();
|
||||||
|
float orgYaw = entity.rotationYaw;
|
||||||
|
float orgPitch = entity.rotationPitch;
|
||||||
|
float orgPrevYaw = entity.prevRotationYaw;
|
||||||
|
float orgPrevPitch = entity.prevRotationPitch;
|
||||||
|
float orgRoll = ReplayHandler.getCameraTilt();
|
||||||
|
|
||||||
|
super.setupCameraTransform(partialTicks);
|
||||||
|
|
||||||
|
entity.rotationYaw = orgYaw;
|
||||||
|
entity.rotationPitch = orgPitch;
|
||||||
|
entity.prevRotationYaw = orgPrevYaw;
|
||||||
|
entity.prevRotationPitch = orgPrevPitch;
|
||||||
|
if (stable) {
|
||||||
|
ReplayHandler.setCameraTilt(orgRoll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void orientCamera(float partialTicks) {
|
||||||
|
Entity entity = mc.getRenderViewEntity();
|
||||||
|
|
||||||
|
// Rotate
|
||||||
|
switch(direction) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stable) {
|
||||||
|
// Stop the minecraft code from doing any rotation
|
||||||
|
entity.prevRotationPitch = entity.rotationPitch = 0;
|
||||||
|
entity.prevRotationYaw = entity.rotationYaw = 0;
|
||||||
|
ReplayHandler.setCameraTilt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minecraft goes back a little so we have to invert that
|
||||||
|
GlStateManager.translate(0.0F, 0.0F, 0.1F);
|
||||||
|
super.orientCamera(partialTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.entity;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.particle.EffectRenderer;
|
||||||
|
import net.minecraft.client.renderer.*;
|
||||||
|
import net.minecraft.client.renderer.culling.ClippingHelperImpl;
|
||||||
|
import net.minecraft.client.renderer.culling.ICamera;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureMap;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.util.AxisAlignedBB;
|
||||||
|
import net.minecraft.util.EnumWorldBlockLayer;
|
||||||
|
import net.minecraft.util.MathHelper;
|
||||||
|
import org.lwjgl.util.glu.Project;
|
||||||
|
|
||||||
|
import static net.minecraft.client.renderer.GlStateManager.*;
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
|
||||||
|
public abstract class CustomEntityRenderer {
|
||||||
|
protected final Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
protected final EntityRenderer proxied = mc.entityRenderer;
|
||||||
|
protected int frameCount;
|
||||||
|
|
||||||
|
protected void gluPerspective(float fovY, float aspect, float zNear, float zFar) {
|
||||||
|
Project.gluPerspective(fovY, aspect, zNear, zFar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCameraAndRender(float renderPartialTicks) {
|
||||||
|
if (mc.theWorld != null) {
|
||||||
|
renderWorld(renderPartialTicks, 0);
|
||||||
|
|
||||||
|
if (OpenGlHelper.shadersSupported) {
|
||||||
|
mc.renderGlobal.renderEntityOutlineFramebuffer();
|
||||||
|
|
||||||
|
if (proxied.theShaderGroup != null && proxied.useShader) {
|
||||||
|
matrixMode(GL_TEXTURE);
|
||||||
|
pushMatrix();
|
||||||
|
loadIdentity();
|
||||||
|
proxied.theShaderGroup.loadShaderGroup(renderPartialTicks);
|
||||||
|
popMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.getFramebuffer().bindFramebuffer(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderWorld(float partialTicks, long finishTimeNano) {
|
||||||
|
proxied.updateLightmap(partialTicks);
|
||||||
|
|
||||||
|
enableDepth();
|
||||||
|
enableAlpha();
|
||||||
|
alphaFunc(516, 0.5F);
|
||||||
|
|
||||||
|
renderWorldPass(partialTicks, finishTimeNano, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderWorldPass(float partialTicks, long finishTimeNano, int renderPass) {
|
||||||
|
RenderGlobal renderglobal = mc.renderGlobal;
|
||||||
|
EffectRenderer effectrenderer = mc.effectRenderer;
|
||||||
|
Entity entity = mc.getRenderViewEntity();
|
||||||
|
enableCull();
|
||||||
|
viewport(0, 0, mc.displayWidth, mc.displayHeight);
|
||||||
|
proxied.updateFogColor(partialTicks);
|
||||||
|
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
setupCameraTransform(partialTicks);
|
||||||
|
ActiveRenderInfo.updateRenderInfo(mc.thePlayer, mc.gameSettings.thirdPersonView == 2);
|
||||||
|
ClippingHelperImpl.getInstance();
|
||||||
|
ICamera camera = new NoCullingCamera();
|
||||||
|
|
||||||
|
if (this.mc.gameSettings.renderDistanceChunks >= 4) {
|
||||||
|
proxied.setupFog(-1, partialTicks);
|
||||||
|
matrixMode(GL_PROJECTION);
|
||||||
|
loadIdentity();
|
||||||
|
gluPerspective(proxied.getFOVModifier(partialTicks, true), (float) mc.displayWidth / mc.displayHeight, 0.05F, proxied.farPlaneDistance * 2.0F);
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
renderglobal.renderSky(partialTicks, renderPass);
|
||||||
|
matrixMode(GL_PROJECTION);
|
||||||
|
loadIdentity();
|
||||||
|
gluPerspective(proxied.getFOVModifier(partialTicks, true), (float) mc.displayWidth / mc.displayHeight, 0.05F, proxied.farPlaneDistance * MathHelper.SQRT_2);
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
proxied.setupFog(0, partialTicks);
|
||||||
|
shadeModel(GL_SMOOTH);
|
||||||
|
|
||||||
|
if (entity.posY + entity.getEyeHeight() < 128) {
|
||||||
|
renderCloudsCheck(renderglobal, partialTicks, renderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
proxied.setupFog(0, partialTicks);
|
||||||
|
mc.getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
|
||||||
|
RenderHelper.disableStandardItemLighting();
|
||||||
|
renderglobal.setupTerrain(entity, partialTicks, camera, frameCount++, mc.thePlayer.isSpectator());
|
||||||
|
|
||||||
|
renderglobal.updateChunks(finishTimeNano);
|
||||||
|
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
pushMatrix();
|
||||||
|
disableAlpha();
|
||||||
|
renderglobal.renderBlockLayer(EnumWorldBlockLayer.SOLID, partialTicks, renderPass, entity);
|
||||||
|
enableAlpha();
|
||||||
|
renderglobal.renderBlockLayer(EnumWorldBlockLayer.CUTOUT_MIPPED, partialTicks, renderPass, entity);
|
||||||
|
mc.getTextureManager().getTexture(TextureMap.locationBlocksTexture).setBlurMipmap(false, false);
|
||||||
|
renderglobal.renderBlockLayer(EnumWorldBlockLayer.CUTOUT, partialTicks, renderPass, entity);
|
||||||
|
mc.getTextureManager().getTexture(TextureMap.locationBlocksTexture).restoreLastBlurMipmap();
|
||||||
|
shadeModel(GL_FLAT);
|
||||||
|
alphaFunc(516, 0.1F);
|
||||||
|
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
popMatrix();
|
||||||
|
pushMatrix();
|
||||||
|
RenderHelper.enableStandardItemLighting();
|
||||||
|
|
||||||
|
net.minecraftforge.client.ForgeHooksClient.setRenderPass(0);
|
||||||
|
renderglobal.renderEntities(entity, camera, partialTicks);
|
||||||
|
net.minecraftforge.client.ForgeHooksClient.setRenderPass(0);
|
||||||
|
RenderHelper.disableStandardItemLighting();
|
||||||
|
proxied.disableLightmap();
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
popMatrix();
|
||||||
|
|
||||||
|
enableBlend();
|
||||||
|
tryBlendFuncSeparate(770, 1, 1, 0);
|
||||||
|
renderglobal.drawBlockDamageTexture(Tessellator.getInstance(), Tessellator.getInstance().getWorldRenderer(), entity, partialTicks);
|
||||||
|
disableBlend();
|
||||||
|
|
||||||
|
proxied.enableLightmap();
|
||||||
|
effectrenderer.renderLitParticles(entity, partialTicks);
|
||||||
|
RenderHelper.disableStandardItemLighting();
|
||||||
|
proxied.setupFog(0, partialTicks);
|
||||||
|
effectrenderer.renderParticles(entity, partialTicks);
|
||||||
|
proxied.disableLightmap();
|
||||||
|
|
||||||
|
depthMask(false);
|
||||||
|
enableCull();
|
||||||
|
proxied.renderRainSnow(partialTicks);
|
||||||
|
depthMask(true);
|
||||||
|
renderglobal.renderWorldBorder(entity, partialTicks);
|
||||||
|
disableBlend();
|
||||||
|
enableCull();
|
||||||
|
tryBlendFuncSeparate(770, 771, 1, 0);
|
||||||
|
alphaFunc(516, 0.1F);
|
||||||
|
proxied.setupFog(0, partialTicks);
|
||||||
|
enableBlend();
|
||||||
|
depthMask(false);
|
||||||
|
mc.getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
|
||||||
|
shadeModel(GL_SMOOTH);
|
||||||
|
|
||||||
|
if (mc.gameSettings.fancyGraphics) {
|
||||||
|
enableBlend();
|
||||||
|
tryBlendFuncSeparate(770, 771, 1, 0);
|
||||||
|
renderglobal.renderBlockLayer(EnumWorldBlockLayer.TRANSLUCENT, partialTicks, renderPass, entity);
|
||||||
|
disableBlend();
|
||||||
|
} else {
|
||||||
|
renderglobal.renderBlockLayer(EnumWorldBlockLayer.TRANSLUCENT, partialTicks, renderPass, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderHelper.enableStandardItemLighting();
|
||||||
|
net.minecraftforge.client.ForgeHooksClient.setRenderPass(1);
|
||||||
|
renderglobal.renderEntities(entity, camera, partialTicks);
|
||||||
|
net.minecraftforge.client.ForgeHooksClient.setRenderPass(-1);
|
||||||
|
RenderHelper.disableStandardItemLighting();
|
||||||
|
|
||||||
|
shadeModel(GL_FLAT);
|
||||||
|
depthMask(true);
|
||||||
|
enableCull();
|
||||||
|
disableBlend();
|
||||||
|
disableFog();
|
||||||
|
|
||||||
|
if (entity.posY + entity.getEyeHeight() >= 128) {
|
||||||
|
renderCloudsCheck(renderglobal, partialTicks, renderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
net.minecraftforge.client.ForgeHooksClient.dispatchRenderLast(renderglobal, partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupCameraTransform(float partialTicks) {
|
||||||
|
proxied.farPlaneDistance = (float)(this.mc.gameSettings.renderDistanceChunks * 16);
|
||||||
|
|
||||||
|
matrixMode(GL_PROJECTION);
|
||||||
|
loadIdentity();
|
||||||
|
|
||||||
|
gluPerspective(proxied.getFOVModifier(partialTicks, true), (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, proxied.farPlaneDistance * MathHelper.SQRT_2);
|
||||||
|
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
loadIdentity();
|
||||||
|
|
||||||
|
orientCamera(partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void orientCamera(float partialTicks) {
|
||||||
|
proxied.orientCamera(partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderCloudsCheck(RenderGlobal renderglobal, float partialTicks, int renderPass) {
|
||||||
|
if (this.mc.gameSettings.shouldRenderClouds()) {
|
||||||
|
matrixMode(GL_PROJECTION);
|
||||||
|
loadIdentity();
|
||||||
|
gluPerspective(proxied.getFOVModifier(partialTicks, true), (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, proxied.farPlaneDistance * 4.0F);
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
pushMatrix();
|
||||||
|
proxied.setupFog(0, partialTicks);
|
||||||
|
renderglobal.renderClouds(partialTicks, renderPass);
|
||||||
|
disableFog();
|
||||||
|
popMatrix();
|
||||||
|
matrixMode(GL_PROJECTION);
|
||||||
|
loadIdentity();
|
||||||
|
gluPerspective(proxied.getFOVModifier(partialTicks, true), (float)this.mc.displayWidth / (float)this.mc.displayHeight, 0.05F, proxied.farPlaneDistance * MathHelper.SQRT_2);
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class NoCullingCamera implements ICamera {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBoundingBoxInFrustum(AxisAlignedBB p_78546_1_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPosition(double p_78547_1_, double p_78547_3_, double p_78547_5_) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.entity;
|
||||||
|
|
||||||
|
import net.minecraft.client.renderer.GlStateManager;
|
||||||
|
import net.minecraft.util.MathHelper;
|
||||||
|
|
||||||
|
import static net.minecraft.client.renderer.GlStateManager.loadIdentity;
|
||||||
|
import static net.minecraft.client.renderer.GlStateManager.matrixMode;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
|
||||||
|
|
||||||
|
public class StereoscopicEntityRenderer extends CustomEntityRenderer {
|
||||||
|
|
||||||
|
private boolean leftEye;
|
||||||
|
|
||||||
|
public void setEye(boolean leftEye) {
|
||||||
|
this.leftEye = leftEye;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void translateStereoscopic() {
|
||||||
|
GlStateManager.translate(leftEye ? 0.07 : -0.07, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setupCameraTransform(float partialTicks) {
|
||||||
|
proxied.farPlaneDistance = (float)(this.mc.gameSettings.renderDistanceChunks * 16);
|
||||||
|
|
||||||
|
matrixMode(GL_PROJECTION);
|
||||||
|
loadIdentity();
|
||||||
|
translateStereoscopic();
|
||||||
|
|
||||||
|
gluPerspective(proxied.getFOVModifier(partialTicks, true), (float) this.mc.displayWidth / (float) this.mc.displayHeight, 0.05F, proxied.farPlaneDistance * MathHelper.SQRT_2);
|
||||||
|
|
||||||
|
matrixMode(GL_MODELVIEW);
|
||||||
|
loadIdentity();
|
||||||
|
translateStereoscopic();
|
||||||
|
|
||||||
|
orientCamera(partialTicks);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.entity;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.frame.TilingFrameRenderer;
|
||||||
|
|
||||||
|
import static net.minecraft.client.renderer.GlStateManager.scale;
|
||||||
|
import static net.minecraft.client.renderer.GlStateManager.translate;
|
||||||
|
import static org.lwjgl.opengl.GL11.glFrustum;
|
||||||
|
|
||||||
|
public class TilingEntityRenderer extends CustomEntityRenderer {
|
||||||
|
private final TilingFrameRenderer renderer;
|
||||||
|
private int tileX, tileY;
|
||||||
|
|
||||||
|
public TilingEntityRenderer(TilingFrameRenderer renderer) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTile(int tileX, int tileY) {
|
||||||
|
this.tileX = tileX;
|
||||||
|
this.tileY = tileY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void gluPerspective(float fovY, float aspect, float zNear, float zFar) {
|
||||||
|
double height = Math.tan(fovY / 360 * Math.PI) * zNear;
|
||||||
|
double width = height * aspect;
|
||||||
|
|
||||||
|
int videoWidth = renderer.getVideoWidth();
|
||||||
|
int videoHeight = renderer.getVideoHeight();
|
||||||
|
int tileWidth = renderer.getTileWidth();
|
||||||
|
int tileHeight = renderer.getTileHeight();
|
||||||
|
double tilesX = (double) videoWidth / tileWidth;
|
||||||
|
double tilesY = (double) videoHeight / tileHeight;
|
||||||
|
double tilesMin = Math.min(tilesX, tilesY);
|
||||||
|
scale(tilesMin, tilesMin, 1);
|
||||||
|
double xOffset = (2 * tileX - tilesX + 1) / tilesMin;
|
||||||
|
double yOffset = (2 * tileY - tilesY + 1) / tilesMin;
|
||||||
|
translate(-xOffset, yOffset, 0);
|
||||||
|
|
||||||
|
glFrustum(-width, width, -height, height, zNear, zFar);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.frame;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.entity.CubicEntityRenderer;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.crash.CrashReport;
|
||||||
|
import net.minecraft.util.ReportedException;
|
||||||
|
import net.minecraft.util.Timer;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class CubicFrameRenderer extends FrameRenderer {
|
||||||
|
protected static int getDisplaySize() {
|
||||||
|
Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
return Math.min(mc.displayWidth, mc.displayHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final CubicEntityRenderer entityRenderer;
|
||||||
|
protected final int displaySize;
|
||||||
|
|
||||||
|
protected CubicFrameRenderer(int videoWidth, int videoHeight, boolean stable) {
|
||||||
|
super(videoWidth, videoHeight, getDisplaySize() * getDisplaySize() * 3);
|
||||||
|
this.displaySize = getDisplaySize();
|
||||||
|
this.entityRenderer = new CubicEntityRenderer(stable);
|
||||||
|
setCustomEntityRenderer(entityRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CubicFrameRenderer(boolean stable) {
|
||||||
|
this(getDisplaySize() * 4, getDisplaySize() * 3, stable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage captureFrame(Timer timer) {
|
||||||
|
BufferedImage image = new BufferedImage(getVideoWidth(), getVideoHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
|
for (CubicEntityRenderer.Direction direction : CubicEntityRenderer.Direction.values()) {
|
||||||
|
try {
|
||||||
|
captureFrame(timer, image, direction);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReport crash = CrashReport.makeCrashReport(t, "Rendering frame " + direction + ".");
|
||||||
|
throw new ReportedException(crash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDefaultPreview(image);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void captureFrame(Timer timer, BufferedImage into, CubicEntityRenderer.Direction direction) {
|
||||||
|
renderFrameToBuffer(timer, direction);
|
||||||
|
|
||||||
|
// Copy to image
|
||||||
|
int frame = direction.getCubicFrame();
|
||||||
|
int xVideo = frame % 4 * displaySize;
|
||||||
|
int yVideo = frame / 4 * displaySize;
|
||||||
|
copyPixelsToImage(buffer, displaySize, into, xVideo, yVideo);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderFrameToBuffer(Timer timer, CubicEntityRenderer.Direction direction) {
|
||||||
|
// Setup aspect ratio
|
||||||
|
int originalWidth = mc.displayWidth;
|
||||||
|
int originalHeight = mc.displayHeight;
|
||||||
|
mc.displayWidth = mc.displayHeight = displaySize;
|
||||||
|
updateFrameBufferSize();
|
||||||
|
|
||||||
|
// Render frame
|
||||||
|
entityRenderer.setDirection(direction);
|
||||||
|
renderFrame(timer);
|
||||||
|
|
||||||
|
// Read pixels
|
||||||
|
readPixels(buffer);
|
||||||
|
|
||||||
|
mc.displayWidth = originalWidth;
|
||||||
|
mc.displayHeight = originalHeight;
|
||||||
|
updateFrameBufferSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFrameBufferSize() {
|
||||||
|
mc.getFramebuffer().createBindFramebuffer(mc.displayWidth, mc.displayHeight);
|
||||||
|
mc.entityRenderer.updateShaderGroupSize(mc.displayWidth, mc.displayHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.frame;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.util.Timer;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class DefaultFrameRenderer extends FrameRenderer {
|
||||||
|
|
||||||
|
public DefaultFrameRenderer() {
|
||||||
|
super(Minecraft.getMinecraft().displayWidth, Minecraft.getMinecraft().displayHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage captureFrame(Timer timer) {
|
||||||
|
renderFrame(timer);
|
||||||
|
|
||||||
|
int width = getVideoWidth();
|
||||||
|
int height = getVideoHeight();
|
||||||
|
|
||||||
|
readPixels(buffer);
|
||||||
|
|
||||||
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||||
|
copyPixelsToImage(buffer, width, image, 0, 0);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.frame;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.entity.CubicEntityRenderer;
|
||||||
|
import net.minecraft.crash.CrashReport;
|
||||||
|
import net.minecraft.util.ReportedException;
|
||||||
|
import net.minecraft.util.Timer;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferInt;
|
||||||
|
|
||||||
|
import static java.lang.Math.PI;
|
||||||
|
|
||||||
|
public class EquirectangularFrameRenderer extends CubicFrameRenderer {
|
||||||
|
|
||||||
|
public EquirectangularFrameRenderer(boolean stable) {
|
||||||
|
super(getDisplaySize() * 4, getDisplaySize() * 2, stable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage captureFrame(Timer timer) {
|
||||||
|
BufferedImage top = captureFrame(timer, CubicEntityRenderer.Direction.TOP);
|
||||||
|
BufferedImage bottom = captureFrame(timer, CubicEntityRenderer.Direction.BOTTOM);
|
||||||
|
BufferedImage front = captureFrame(timer, CubicEntityRenderer.Direction.FRONT);
|
||||||
|
BufferedImage back = captureFrame(timer, CubicEntityRenderer.Direction.BACK);
|
||||||
|
BufferedImage left = captureFrame(timer, CubicEntityRenderer.Direction.LEFT);
|
||||||
|
BufferedImage right = captureFrame(timer, CubicEntityRenderer.Direction.RIGHT);
|
||||||
|
try {
|
||||||
|
BufferedImage result = cubicToEquirectangular(top, bottom, front, back, left, right);
|
||||||
|
updateDefaultPreview(result);
|
||||||
|
return result;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReport crash = CrashReport.makeCrashReport(t, "Transforming cubic to equirectangular image.");
|
||||||
|
throw new ReportedException(crash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BufferedImage cubicToEquirectangular(BufferedImage top, BufferedImage bottom, BufferedImage front, BufferedImage back, BufferedImage left, BufferedImage right) {
|
||||||
|
int rWidth = getVideoWidth();
|
||||||
|
int rHeight = getVideoHeight();
|
||||||
|
BufferedImage result = new BufferedImage(rWidth, rHeight, BufferedImage.TYPE_INT_RGB);
|
||||||
|
int[] resultPixels = ((DataBufferInt) result.getRaster().getDataBuffer()).getData();
|
||||||
|
for (int i = 0; i < rWidth; i++) {
|
||||||
|
double yaw = PI * 2 * i / rWidth;
|
||||||
|
int piQuarter = 8 * i / rWidth - 4;
|
||||||
|
BufferedImage target;
|
||||||
|
if (piQuarter < -3) {
|
||||||
|
target = back;
|
||||||
|
} else if (piQuarter < -1) {
|
||||||
|
target = left;
|
||||||
|
} else if (piQuarter < 1) {
|
||||||
|
target = front;
|
||||||
|
} else if (piQuarter < 3) {
|
||||||
|
target = right;
|
||||||
|
} else {
|
||||||
|
target = 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 < rHeight; j++) {
|
||||||
|
double cXN = gcXN;
|
||||||
|
BufferedImage pt = target;
|
||||||
|
double pitch = PI * j / rHeight - 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 = 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 = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cX = Math.min(displaySize - 1, (int) (cXN * displaySize));
|
||||||
|
int cY = Math.min(displaySize - 1, (int) (cYN * displaySize));
|
||||||
|
resultPixels[i + j * rWidth] = 0xff000000 | pt.getRGB(cX, cY); // Make sure we got alpha value as well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BufferedImage captureFrame(Timer timer, CubicEntityRenderer.Direction direction) {
|
||||||
|
try {
|
||||||
|
renderFrameToBuffer(timer, direction);
|
||||||
|
|
||||||
|
// Copy to image
|
||||||
|
BufferedImage image = new BufferedImage(displaySize, displaySize, BufferedImage.TYPE_INT_RGB);
|
||||||
|
copyPixelsToImage(buffer, displaySize, image, displaySize, 0, 0);
|
||||||
|
return image;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReport crash = CrashReport.makeCrashReport(t, "Rendering frame " + direction);
|
||||||
|
throw new ReportedException(crash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.frame;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.util.concurrent.Runnables;
|
||||||
|
import eu.crushedpixel.replaymod.gui.GuiVideoRenderer;
|
||||||
|
import eu.crushedpixel.replaymod.video.entity.CustomEntityRenderer;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.Gui;
|
||||||
|
import net.minecraft.client.gui.GuiScreen;
|
||||||
|
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraft.util.Timer;
|
||||||
|
import org.lwjgl.BufferUtils;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferInt;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
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 FrameRenderer {
|
||||||
|
private static final ResourceLocation noPreviewTexture = new ResourceLocation("replaymod", "logo.jpg");
|
||||||
|
protected final Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
private final int videoWidth;
|
||||||
|
private final int videoHeight;
|
||||||
|
private Optional<CustomEntityRenderer> customEntityRenderer;
|
||||||
|
private DynamicTexture previewTexture;
|
||||||
|
private Runnable renderPreviewCallback;
|
||||||
|
private boolean previewActive = false;
|
||||||
|
protected final ByteBuffer buffer;
|
||||||
|
|
||||||
|
public FrameRenderer(int videoWidth, int videoHeight) {
|
||||||
|
this.videoWidth = videoWidth;
|
||||||
|
this.videoHeight = videoHeight;
|
||||||
|
this.buffer = BufferUtils.createByteBuffer(mc.displayWidth * mc.displayHeight * 3);
|
||||||
|
this.customEntityRenderer = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameRenderer(int videoWidth, int videoHeight, int bufferSize) {
|
||||||
|
this.videoWidth = videoWidth;
|
||||||
|
this.videoHeight = videoHeight;
|
||||||
|
this.buffer = BufferUtils.createByteBuffer(bufferSize);
|
||||||
|
this.customEntityRenderer = Optional.absent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getVideoWidth() {
|
||||||
|
return videoWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getVideoHeight() {
|
||||||
|
return videoHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreviewActive(boolean previewActive) {
|
||||||
|
this.previewActive = previewActive;
|
||||||
|
if (previewActive) {
|
||||||
|
Arrays.fill(previewTexture.getTextureData(), 0xff000000);
|
||||||
|
previewTexture.updateDynamicTexture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPreviewActive() {
|
||||||
|
return previewActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setRenderPreviewCallback(Runnable renderPreviewCallback) {
|
||||||
|
Preconditions.checkState(this.renderPreviewCallback == null, "Render preview callback already set.");
|
||||||
|
this.renderPreviewCallback = renderPreviewCallback;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Runnable getRenderPreviewCallback() {
|
||||||
|
return renderPreviewCallback != null ? renderPreviewCallback : Runnables.doNothing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomEntityRenderer(CustomEntityRenderer customEntityRenderer) {
|
||||||
|
this.customEntityRenderer = Optional.fromNullable(customEntityRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract BufferedImage captureFrame(Timer timer);
|
||||||
|
|
||||||
|
protected void readPixels(ByteBuffer buffer) {
|
||||||
|
buffer.clear();
|
||||||
|
GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
GL11.glReadPixels(0, 0, mc.displayWidth, mc.displayHeight, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
|
||||||
|
buffer.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateDefaultPreview(BufferedImage image) {
|
||||||
|
if (isPreviewActive()) {
|
||||||
|
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||||
|
updateDefaultPreview(pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateDefaultPreview(int[] pixels) {
|
||||||
|
if (isPreviewActive()) {
|
||||||
|
System.arraycopy(pixels, 0, getPreviewTexture().getTextureData(), 0, pixels.length);
|
||||||
|
getPreviewTexture().updateDynamicTexture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, BufferedImage image, int offsetX, int offsetY) {
|
||||||
|
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||||
|
copyPixelsToImage(buffer, bufferWidth, pixels, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, BufferedImage image, int imageWidth, int offsetX, int offsetY) {
|
||||||
|
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||||
|
copyPixelsToImage(buffer, bufferWidth, pixels, imageWidth, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, int[] image, int offsetX, int offsetY) {
|
||||||
|
copyPixelsToImage(buffer, bufferWidth, image, videoWidth, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void copyPixelsToImage(ByteBuffer buffer, int bufferWidth, int[] image, int imageWidth, int offsetX, int offsetY) {
|
||||||
|
int bufferSize = buffer.remaining() / 3;
|
||||||
|
// Read the OpenGL image row by row from right to left (flipped horizontally)
|
||||||
|
for (int i = bufferSize - 1; i >= 0; i--) {
|
||||||
|
// Coordinates in the final image
|
||||||
|
int x = offsetX + bufferWidth - i % bufferWidth - 1; // X coord of OpenGL image has to be flipped first
|
||||||
|
int y = offsetY + i / bufferWidth;
|
||||||
|
if (x >= imageWidth || y * imageWidth >= image.length) {
|
||||||
|
buffer.position(buffer.position() + 3); // Pixel would end up outside of image
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Write to image (row by row, left to right)
|
||||||
|
image[y * imageWidth + x] = 0xff << 24 | (buffer.get() & 0xff) << 16 | (buffer.get() & 0xff) << 8 | buffer.get() & 0xff;
|
||||||
|
}
|
||||||
|
buffer.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderFrame(Timer timer) {
|
||||||
|
pushMatrix();
|
||||||
|
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
mc.getFramebuffer().bindFramebuffer(true);
|
||||||
|
|
||||||
|
enableTexture2D();
|
||||||
|
|
||||||
|
if (customEntityRenderer.isPresent()) {
|
||||||
|
customEntityRenderer.get().updateCameraAndRender(timer.renderPartialTicks);
|
||||||
|
} else {
|
||||||
|
mc.entityRenderer.updateCameraAndRender(timer.renderPartialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.getFramebuffer().unbindFramebuffer();
|
||||||
|
popMatrix();
|
||||||
|
|
||||||
|
pushMatrix();
|
||||||
|
mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight);
|
||||||
|
popMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setup() {
|
||||||
|
this.previewTexture = new DynamicTexture(videoWidth, videoHeight) {
|
||||||
|
@Override
|
||||||
|
public void updateDynamicTexture() {
|
||||||
|
bindTexture(getGlTextureId());
|
||||||
|
TextureUtil.uploadTextureSub(0, getTextureData(), getVideoWidth(), getVideoHeight(), 0, 0, true, false, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tearDown() {
|
||||||
|
this.previewTexture.deleteGlTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicTexture getPreviewTexture() {
|
||||||
|
return previewTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the height including any borders of the preview.
|
||||||
|
* No rendering should be performed by {@link #renderPreview(GuiVideoRenderer)} below this height.
|
||||||
|
* @param guiScreen The gui screen
|
||||||
|
* @return Bottom of the preview
|
||||||
|
*/
|
||||||
|
public int getPreviewHeight(GuiScreen guiScreen) {
|
||||||
|
int width = guiScreen.width / 2;
|
||||||
|
int height = guiScreen.height / 3;
|
||||||
|
if (width / height < getVideoWidth() / getVideoHeight()) {
|
||||||
|
height = width * getVideoHeight() / getVideoWidth();
|
||||||
|
}
|
||||||
|
return guiScreen.height / 2 + height + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a preview of the current scene on the lower half of the screen.
|
||||||
|
* @param guiScreen The gui screen
|
||||||
|
*/
|
||||||
|
public void renderPreview(GuiVideoRenderer guiScreen) {
|
||||||
|
int width = guiScreen.width / 2;
|
||||||
|
int height = guiScreen.height / 3;
|
||||||
|
if (width / height > getVideoWidth() / getVideoHeight()) {
|
||||||
|
width = height * getVideoWidth() / getVideoHeight();
|
||||||
|
} else {
|
||||||
|
height = width * getVideoHeight() / getVideoWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = guiScreen.width / 2 - width / 2;
|
||||||
|
int y = guiScreen.height / 2 + 5;
|
||||||
|
|
||||||
|
bindTexture(previewTexture.getGlTextureId());
|
||||||
|
color(1, 1, 1, 1);
|
||||||
|
Gui.drawScaledCustomSizeModalRect(x, y, 0, 0, videoWidth, videoHeight, width, height, videoWidth, videoHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void renderNoPreview(int guiWidth, int guiHeight, int height) {
|
||||||
|
int width = height * 1280 / 720;
|
||||||
|
int x = guiWidth / 2 - width / 2;
|
||||||
|
int y = guiHeight / 2 + 5;
|
||||||
|
|
||||||
|
Minecraft.getMinecraft().getTextureManager().bindTexture(noPreviewTexture);
|
||||||
|
color(1, 1, 1, 1);
|
||||||
|
Gui.drawScaledCustomSizeModalRect(x, y, 0, 0, 1280, 720, width, height, 1280, 720);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.frame;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.entity.StereoscopicEntityRenderer;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.crash.CrashReport;
|
||||||
|
import net.minecraft.util.ReportedException;
|
||||||
|
import net.minecraft.util.Timer;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class StereoscopicFrameRenderer extends FrameRenderer {
|
||||||
|
private final StereoscopicEntityRenderer entityRenderer = new StereoscopicEntityRenderer();
|
||||||
|
|
||||||
|
public StereoscopicFrameRenderer() {
|
||||||
|
super(Minecraft.getMinecraft().displayWidth * 2, Minecraft.getMinecraft().displayHeight);
|
||||||
|
setCustomEntityRenderer(entityRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage captureFrame(Timer timer) {
|
||||||
|
BufferedImage image = new BufferedImage(getVideoWidth(), getVideoHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
|
try {
|
||||||
|
captureFrame(timer, image, true);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReport crash = CrashReport.makeCrashReport(t, "Rendering left eye.");
|
||||||
|
throw new ReportedException(crash);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
captureFrame(timer, image, false);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReport crash = CrashReport.makeCrashReport(t, "Rendering right eye.");
|
||||||
|
throw new ReportedException(crash);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void captureFrame(Timer timer, BufferedImage into, boolean leftEye) {
|
||||||
|
int displayWidth = getVideoWidth() / 2;
|
||||||
|
|
||||||
|
// Render frame
|
||||||
|
entityRenderer.setEye(leftEye);
|
||||||
|
renderFrame(timer);
|
||||||
|
|
||||||
|
// Read pixels
|
||||||
|
readPixels(buffer);
|
||||||
|
|
||||||
|
// Copy to image
|
||||||
|
copyPixelsToImage(buffer, displayWidth, into, leftEye ? 0 : displayWidth, 0);
|
||||||
|
updateDefaultPreview(into);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package eu.crushedpixel.replaymod.video.frame;
|
||||||
|
|
||||||
|
import eu.crushedpixel.replaymod.video.entity.TilingEntityRenderer;
|
||||||
|
import net.minecraft.crash.CrashReport;
|
||||||
|
import net.minecraft.util.ReportedException;
|
||||||
|
import net.minecraft.util.Timer;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class TilingFrameRenderer extends FrameRenderer {
|
||||||
|
private final TilingEntityRenderer entityRenderer = new TilingEntityRenderer(this);
|
||||||
|
private final int tileWidth;
|
||||||
|
private final int tileHeight;
|
||||||
|
|
||||||
|
public TilingFrameRenderer(int videoWidth, int videoHeight) {
|
||||||
|
super(videoWidth, videoHeight);
|
||||||
|
this.tileWidth = mc.displayWidth;
|
||||||
|
this.tileHeight = mc.displayHeight;
|
||||||
|
setCustomEntityRenderer(entityRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setRenderPreviewCallback(Runnable renderPreviewCallback) {
|
||||||
|
super.setRenderPreviewCallback(renderPreviewCallback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage captureFrame(Timer timer) {
|
||||||
|
BufferedImage image = new BufferedImage(getVideoWidth(), getVideoHeight(), BufferedImage.TYPE_INT_RGB);
|
||||||
|
int tilesX = getTilesX();
|
||||||
|
int tilesY = getTilesY();
|
||||||
|
for (int x = 0; x < tilesX; x++) {
|
||||||
|
for (int y = 0; y < tilesY; y++) {
|
||||||
|
try {
|
||||||
|
captureTile(timer, x, y, image);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
CrashReport crash = CrashReport.makeCrashReport(t, "Rendering tile " + x + "/" + y);
|
||||||
|
throw new ReportedException(crash);
|
||||||
|
}
|
||||||
|
getRenderPreviewCallback().run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void captureTile(Timer timer, int tileX, int tileY, BufferedImage into) {
|
||||||
|
// Render frame
|
||||||
|
entityRenderer.setTile(tileX, tileY);
|
||||||
|
renderFrame(timer);
|
||||||
|
|
||||||
|
// Read pixels
|
||||||
|
readPixels(buffer);
|
||||||
|
|
||||||
|
// Copy to image
|
||||||
|
copyPixelsToImage(buffer, tileWidth, into, tileX * tileWidth, tileY * tileHeight);
|
||||||
|
if (isPreviewActive()) {
|
||||||
|
int[] pixels = getPreviewTexture().getTextureData();
|
||||||
|
copyPixelsToImage(buffer, tileWidth, pixels, tileX * tileWidth, tileY * tileHeight);
|
||||||
|
|
||||||
|
// Draw red rectangle around next tile
|
||||||
|
tileY = (tileY + 1) % getTilesY();
|
||||||
|
if (tileY == 0) {
|
||||||
|
tileX = (tileX + 1) % getTilesX();
|
||||||
|
}
|
||||||
|
drawPreviewRectangle(pixels, tileX, tileY);
|
||||||
|
|
||||||
|
getPreviewTexture().updateDynamicTexture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPreviewRectangle(int[] pixels, int tileX, int tileY) {
|
||||||
|
final int RED = 0xffff0000;
|
||||||
|
|
||||||
|
int videoWidth = getVideoWidth();
|
||||||
|
int videoHeight = getVideoHeight();
|
||||||
|
int border = videoWidth / tileWidth * 5;
|
||||||
|
int left = Math.min(tileX * tileWidth, videoWidth - border - 1);
|
||||||
|
int right = Math.min((left + tileWidth), videoWidth) - 1;
|
||||||
|
int top = Math.min(tileY * tileHeight, videoHeight - border - 1);
|
||||||
|
int bottom = Math.min((top + tileHeight), videoHeight - border) - 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < border; i++) {
|
||||||
|
// Top border
|
||||||
|
int offset = (top + i) * videoWidth;
|
||||||
|
Arrays.fill(pixels, offset + left, offset + right + 1, RED);
|
||||||
|
|
||||||
|
// Bottom border
|
||||||
|
offset = (bottom - i) * videoWidth;
|
||||||
|
Arrays.fill(pixels, offset + left, offset + right + 1, RED);
|
||||||
|
|
||||||
|
// Left border
|
||||||
|
for (int j = top; j <= bottom; j++) {
|
||||||
|
pixels[j * videoWidth + left + i] = RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right border
|
||||||
|
for (int j = top; j <= bottom; j++) {
|
||||||
|
pixels[j * videoWidth + right - i] = RED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTilesX() {
|
||||||
|
int videoWidth = getVideoWidth();
|
||||||
|
if (videoWidth % tileWidth == 0) {
|
||||||
|
return videoWidth / tileWidth;
|
||||||
|
} else {
|
||||||
|
return videoWidth / tileWidth + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTilesY() {
|
||||||
|
int videoHeight = getVideoHeight();
|
||||||
|
if (videoHeight % tileHeight == 0) {
|
||||||
|
return videoHeight / tileHeight;
|
||||||
|
} else {
|
||||||
|
return videoHeight / tileHeight + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTileWidth() {
|
||||||
|
return tileWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTileHeight() {
|
||||||
|
return tileHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,35 @@ public net.minecraft.client.resources.ResourcePackRepository field_148534_e # di
|
|||||||
|
|
||||||
# EntityRenderer
|
# EntityRenderer
|
||||||
public net.minecraft.client.renderer.EntityRenderer field_147711_ac # resourceManager
|
public net.minecraft.client.renderer.EntityRenderer field_147711_ac # resourceManager
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer field_147707_d # theShaderGroup
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer field_175083_ad # useShader
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer field_78530_s # farPlaneDistance
|
||||||
|
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer func_78467_g(F)V # orientCamera
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer func_78474_d(F)V # renderRainSnow
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer func_78481_a(FZ)F # getFOVModifier
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer func_78468_a(IF)V # setupFog
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer func_78466_h(F)V # updateFogColor
|
||||||
|
public net.minecraft.client.renderer.EntityRenderer func_78472_g(F)V # updateLightmap
|
||||||
|
|
||||||
|
# RenderGlobal
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal field_174995_M # renderDispatcher
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal field_147595_R # displayListEntitiesDirty
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal field_72755_R # renderInfos
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal field_175009_l # chunksToUpdate
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal func_174983_a(Lnet.minecraft.util.BlockPos;Lnet.minecraft.client.renderer.chunk.RenderChunk;)Z # isPositionInRenderChunk
|
||||||
|
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal$ContainerLocalRenderInformation
|
||||||
|
public net.minecraft.client.renderer.RenderGlobal$ContainerLocalRenderInformation field_178553_a # renderChunk
|
||||||
|
|
||||||
|
# ChunkRenderDispatcher
|
||||||
|
public-f net.minecraft.client.renderer.chunk.ChunkRenderDispatcher field_178519_d # queueChunkUpdates
|
||||||
|
public net.minecraft.client.renderer.chunk.ChunkRenderDispatcher field_178524_h # queueChunkUploads
|
||||||
|
public net.minecraft.client.renderer.chunk.ChunkRenderDispatcher field_178522_c # listThreadedWorkers
|
||||||
|
|
||||||
|
# TextureUtil
|
||||||
|
public net.minecraft.client.renderer.texture.TextureUtil func_147947_a(I[IIIIIZZZ)V # uploadTextureSub
|
||||||
|
|
||||||
#RenderManager
|
#RenderManager
|
||||||
public net.minecraft.client.renderer.entity.RenderManager field_178636_l # skinMap
|
public net.minecraft.client.renderer.entity.RenderManager field_178636_l # skinMap
|
||||||
|
|||||||
BIN
src/main/resources/assets/replaymod/logo.jpg
Executable file
BIN
src/main/resources/assets/replaymod/logo.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Reference in New Issue
Block a user