Rework rendering
Adds default, stereoscopic, tiling, cubic and equirectangular frame rendering
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package eu.crushedpixel.replaymod;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||
import eu.crushedpixel.replaymod.api.ApiClient;
|
||||
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
|
||||
import eu.crushedpixel.replaymod.events.*;
|
||||
import eu.crushedpixel.replaymod.holders.KeyframeSet;
|
||||
import eu.crushedpixel.replaymod.localization.LocalizedResourcePack;
|
||||
import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler;
|
||||
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.renderer.InvisibilityRender;
|
||||
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.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.settings.ReplaySettings;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import eu.crushedpixel.replaymod.video.frame.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
@@ -28,8 +34,10 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
@Mod(modid = ReplayMod.MODID, version = ReplayMod.VERSION)
|
||||
public class ReplayMod {
|
||||
@@ -116,7 +124,7 @@ public class ReplayMod {
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void postInit(FMLPostInitializationEvent event) {
|
||||
public void postInit(FMLPostInitializationEvent event) throws IOException {
|
||||
GameSettings.Options.RENDER_DISTANCE.setValueMax(64f);
|
||||
|
||||
overlay = new GuiReplayOverlay();
|
||||
@@ -164,6 +172,102 @@ public class ReplayMod {
|
||||
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() {
|
||||
|
||||
@@ -30,8 +30,22 @@ public class CameraRollCT implements IClassTransformer {
|
||||
boolean success = false;
|
||||
for (MethodNode m : classNode.methods) {
|
||||
if ("(F)V".equals(m.desc) && name_orientCamera.equals(m.name)) {
|
||||
inject(m.instructions.iterator());
|
||||
success = true;
|
||||
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;
|
||||
break;
|
||||
} else {
|
||||
f = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.crushedpixel.replaymod.events;
|
||||
|
||||
import eu.crushedpixel.replaymod.ReplayMod;
|
||||
import eu.crushedpixel.replaymod.gui.GuiCancelRender;
|
||||
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
||||
import eu.crushedpixel.replaymod.gui.GuiReplaySaving;
|
||||
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.utils.MouseUtils;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import eu.crushedpixel.replaymod.video.VideoWriter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.*;
|
||||
import net.minecraft.client.gui.inventory.GuiInventory;
|
||||
@@ -52,11 +50,6 @@ public class GuiEventHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onGui(GuiOpenEvent event) {
|
||||
if(VideoWriter.isRecording() && !(event.gui instanceof GuiCancelRender)) {
|
||||
event.gui = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.gui instanceof GuiMainMenu) {
|
||||
if(ReplayMod.firstMainMenu) {
|
||||
ReplayMod.firstMainMenu = false;
|
||||
|
||||
@@ -11,8 +11,10 @@ import eu.crushedpixel.replaymod.recording.ConnectionEventHandler;
|
||||
import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry;
|
||||
import eu.crushedpixel.replaymod.replay.ReplayHandler;
|
||||
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
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.gui.Gui;
|
||||
import net.minecraft.client.gui.GuiChat;
|
||||
@@ -123,9 +125,8 @@ public class GuiReplayOverlay extends Gui {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException {
|
||||
|
||||
if(!ReplayHandler.isInReplay() || FMLClientHandler.instance().isGUIOpen(GuiPlayerOverview.class)
|
||||
|| VideoWriter.isRecording() || FMLClientHandler.instance().isGUIOpen(GuiKeyframeRepository.class)) {
|
||||
|| FMLClientHandler.instance().isGUIOpen(GuiKeyframeRepository.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,7 +186,19 @@ public class GuiReplayOverlay extends Gui {
|
||||
if(hover) {
|
||||
playOrPause();
|
||||
} 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) {
|
||||
@@ -743,7 +756,7 @@ public class GuiReplayOverlay extends Gui {
|
||||
if(ReplayHandler.isInPath()) {
|
||||
ReplayHandler.interruptReplay();
|
||||
} 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.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.ReplayProcess;
|
||||
import eu.crushedpixel.replaymod.video.ReplayScreenshot;
|
||||
@@ -40,6 +38,7 @@ public class TickAndRenderListener {
|
||||
public void onRenderWorld(RenderWorldLastEvent event) throws
|
||||
InvocationTargetException, IOException, IllegalAccessException, IllegalArgumentException {
|
||||
if(!ReplayHandler.isInReplay()) return; //If not in Replay, cancel
|
||||
if (ReplayProcess.isVideoRecording()) return; // If recording, cancel
|
||||
|
||||
if(requestScreenshot == 1) {
|
||||
mc.addScheduledTask(new Runnable() {
|
||||
@@ -77,16 +76,12 @@ public class TickAndRenderListener {
|
||||
|
||||
@SubscribeEvent
|
||||
public void tick(TickEvent event) {
|
||||
if(!ReplayHandler.isInReplay()) return;
|
||||
if(!ReplayHandler.isInReplay() || ReplayProcess.isVideoRecording()) return;
|
||||
|
||||
if(ReplayHandler.getCameraEntity() != null)
|
||||
ReplayHandler.getCameraEntity().updateMovement();
|
||||
if(ReplayHandler.isInPath()) {
|
||||
ReplayProcess.unblockAndTick(true);
|
||||
if(ReplayProcess.isVideoRecording() &&
|
||||
!(mc.currentScreen instanceof GuiMouseInput || mc.currentScreen instanceof GuiCancelRender)) {
|
||||
mc.displayGuiScreen(new GuiMouseInput());
|
||||
}
|
||||
} else onMouseMove(new MouseEvent());
|
||||
|
||||
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.List;
|
||||
|
||||
public abstract class LinearInterpolation<K> {
|
||||
public abstract class LinearInterpolation<K> implements Interpolation<K> {
|
||||
|
||||
protected List<K> points = new ArrayList<K>();
|
||||
|
||||
@@ -13,6 +13,11 @@ public abstract class LinearInterpolation<K> {
|
||||
points = new ArrayList<K>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
|
||||
}
|
||||
|
||||
public abstract K getPoint(float position);
|
||||
|
||||
public void addPoint(K point) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
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 Vector<Position> points;
|
||||
private Vector<Cubic> xCubics;
|
||||
@@ -60,7 +60,8 @@ public class SplinePoint extends BasicSpline {
|
||||
return points;
|
||||
}
|
||||
|
||||
public void calcSpline() {
|
||||
@Override
|
||||
public void prepare() {
|
||||
try {
|
||||
calcNaturalCubic(points, vectorX, xCubics);
|
||||
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.holders.*;
|
||||
import eu.crushedpixel.replaymod.registry.PlayerHandler;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFile;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -86,8 +87,8 @@ public class ReplayHandler {
|
||||
return currentEntity == cameraEntity;
|
||||
}
|
||||
|
||||
public static void startPath(boolean save) {
|
||||
if(!ReplayHandler.isInPath()) ReplayProcess.startReplayProcess(save);
|
||||
public static void startPath(RenderOptions renderOptions) {
|
||||
if(!ReplayHandler.isInPath()) ReplayProcess.startReplayProcess(renderOptions);
|
||||
}
|
||||
|
||||
public static void interruptReplay() {
|
||||
@@ -322,7 +323,11 @@ public class ReplayHandler {
|
||||
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();
|
||||
mc.ingameGUI.getChatGUI().clearChatMessages();
|
||||
@@ -358,7 +363,7 @@ public class ReplayHandler {
|
||||
PlayerVisibility visibility = currentReplayFile.visibility().get();
|
||||
PlayerHandler.loadPlayerVisibilityConfiguration(visibility);
|
||||
|
||||
ReplayMod.replaySender = new ReplaySender(currentReplayFile, true);
|
||||
ReplayMod.replaySender = new ReplaySender(currentReplayFile, asyncMode);
|
||||
channel.pipeline().addFirst(ReplayMod.replaySender);
|
||||
channel.pipeline().fireChannelActive();
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package eu.crushedpixel.replaymod.replay;
|
||||
|
||||
import eu.crushedpixel.replaymod.ReplayMod;
|
||||
import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType;
|
||||
import eu.crushedpixel.replaymod.gui.GuiCancelRender;
|
||||
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||
import eu.crushedpixel.replaymod.holders.Position;
|
||||
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.LinearTimestamp;
|
||||
import eu.crushedpixel.replaymod.interpolation.SplinePoint;
|
||||
import eu.crushedpixel.replaymod.settings.RenderOptions;
|
||||
import eu.crushedpixel.replaymod.timer.EnchantmentTimer;
|
||||
import eu.crushedpixel.replaymod.timer.MCTimerHandler;
|
||||
import eu.crushedpixel.replaymod.video.ScreenCapture;
|
||||
import eu.crushedpixel.replaymod.video.VideoWriter;
|
||||
import eu.crushedpixel.replaymod.video.VideoRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.chunk.RenderChunk;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ReplayProcess {
|
||||
|
||||
@@ -79,11 +77,11 @@ public class ReplayProcess {
|
||||
EnchantmentTimer.resetRecordingTime();
|
||||
}
|
||||
|
||||
public static void startReplayProcess(boolean record) {
|
||||
public static void startReplayProcess(RenderOptions renderOptions) {
|
||||
ReplayHandler.selectKeyframe(null);
|
||||
resetProcess();
|
||||
|
||||
isVideoRecording = record;
|
||||
isVideoRecording = renderOptions != null;
|
||||
|
||||
ReplayMod.chatMessageHandler.initialize();
|
||||
|
||||
@@ -97,24 +95,28 @@ public class ReplayProcess {
|
||||
ReplayHandler.setInPath(true);
|
||||
ReplayMod.replaySender.setAsyncMode(false);
|
||||
|
||||
//gets first Timestamp and sets Replay Time to it
|
||||
TimeKeyframe tf = ReplayHandler.getFirstTimeKeyframe();
|
||||
if(tf != null) {
|
||||
int ts = tf.getTimestamp();
|
||||
if(ts < ReplayMod.replaySender.currentTimeStamp()) {
|
||||
mc.displayGuiScreen(null);
|
||||
if (renderOptions == null) {
|
||||
//gets first Timestamp and sets Replay Time to it
|
||||
TimeKeyframe tf = ReplayHandler.getFirstTimeKeyframe();
|
||||
if (tf != null) {
|
||||
int ts = tf.getTimestamp();
|
||||
if (ts < ReplayMod.replaySender.currentTimeStamp()) {
|
||||
mc.displayGuiScreen(null);
|
||||
}
|
||||
ReplayMod.replaySender.sendPacketsTill(ts);
|
||||
}
|
||||
ReplayMod.replaySender.sendPacketsTill(ts);
|
||||
}
|
||||
|
||||
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathstarted", ChatMessageType.INFORMATION);
|
||||
|
||||
//if video is recording, the Replay Process takes control over the Minecraft Timer
|
||||
if(isVideoRecording()) {
|
||||
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathstarted", ChatMessageType.INFORMATION);
|
||||
MCTimerHandler.setTimerSpeed(1f);
|
||||
MCTimerHandler.setPassiveTimer();
|
||||
} 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);
|
||||
else {
|
||||
ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.pathinterrupted", ChatMessageType.INFORMATION);
|
||||
if(isVideoRecording()) {
|
||||
VideoWriter.abortRecording();
|
||||
}
|
||||
}
|
||||
|
||||
ReplayHandler.setInPath(false);
|
||||
@@ -143,14 +142,17 @@ public class ReplayProcess {
|
||||
public static void unblockAndTick(boolean justCheck) {
|
||||
if(!deepBlock) blocked = false;
|
||||
if(!blocked || !isVideoRecording())
|
||||
pathTick(isVideoRecording(), justCheck);
|
||||
ReplayProcess.tickReplay(justCheck);
|
||||
}
|
||||
|
||||
//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
|
||||
//are called if the Timer speed is set to 0, leading to this method never being
|
||||
//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()) {
|
||||
lastRealTime = System.currentTimeMillis();
|
||||
return;
|
||||
@@ -166,35 +168,8 @@ public class ReplayProcess {
|
||||
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(recording) {
|
||||
if(blocked) return;
|
||||
|
||||
deepBlock = true;
|
||||
blocked = true;
|
||||
}
|
||||
|
||||
int posCount = ReplayHandler.getPosKeyframeCount();
|
||||
int timeCount = ReplayHandler.getTimeKeyframeCount();
|
||||
|
||||
@@ -233,16 +208,11 @@ public class ReplayProcess {
|
||||
if(!calculated) {
|
||||
calculated = true;
|
||||
if(posCount > 1 && motionSpline != null)
|
||||
motionSpline.calcSpline();
|
||||
motionSpline.prepare();
|
||||
}
|
||||
|
||||
long curTime = System.currentTimeMillis();
|
||||
long timeStep;
|
||||
if(recording) {
|
||||
timeStep = 1000 / ReplayMod.replaySettings.getVideoFramerate();
|
||||
} else {
|
||||
timeStep = curTime - lastRealTime;
|
||||
}
|
||||
long timeStep = curTime - lastRealTime;
|
||||
|
||||
int curRealReplayTime = (int) (lastRealReplayTime + timeStep);
|
||||
|
||||
@@ -333,14 +303,13 @@ public class ReplayProcess {
|
||||
}
|
||||
}
|
||||
|
||||
ReplayHandler.setCameraTilt(pos.getRoll());
|
||||
|
||||
Integer curTimestamp = null;
|
||||
if(timeLinear != null && timeCount > 1) {
|
||||
curTimestamp = timeLinear.getPoint(Math.max(0, Math.min(1, timePos)));
|
||||
}
|
||||
|
||||
if(pos != null) {
|
||||
ReplayHandler.setCameraTilt(pos.getRoll());
|
||||
ReplayHandler.getCameraEntity().movePath(pos);
|
||||
}
|
||||
|
||||
@@ -348,11 +317,6 @@ public class ReplayProcess {
|
||||
//if(curSpeed > 0)
|
||||
lastSpeed = curSpeed;
|
||||
|
||||
if(recording) {
|
||||
MCTimerHandler.updateTimer((1f / ReplayMod.replaySettings.getVideoFramerate()));
|
||||
EnchantmentTimer.increaseRecordingTime((1000 / ReplayMod.replaySettings.getVideoFramerate()));
|
||||
}
|
||||
|
||||
lastPartialTicks = MCTimerHandler.getPartialTicks();
|
||||
lastRenderPartialTicks = MCTimerHandler.getRenderTicks();
|
||||
lastTicks = MCTimerHandler.getTicks();
|
||||
@@ -365,33 +329,13 @@ public class ReplayProcess {
|
||||
lastRealReplayTime = curRealReplayTime;
|
||||
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) {
|
||||
stopReplayProcess(true);
|
||||
requestFinish = false;
|
||||
if(recording) {
|
||||
VideoWriter.endRecording();
|
||||
}
|
||||
}
|
||||
|
||||
if((splinePos >= 1 || posCount <= 1) && (timePos >= 1 || timeCount <= 1)) {
|
||||
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;
|
||||
}
|
||||
|
||||
public static Timer getTimer() {
|
||||
return mc.timer;
|
||||
}
|
||||
|
||||
public static void advanceTicks(int 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;
|
||||
|
||||
import eu.crushedpixel.replaymod.replay.ReplayProcess;
|
||||
import net.minecraft.util.Timer;
|
||||
|
||||
public class ReplayTimer extends Timer {
|
||||
@@ -11,7 +10,5 @@ public class ReplayTimer extends Timer {
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
import eu.crushedpixel.replaymod.ReplayMod;
|
||||
import com.google.common.base.Preconditions;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import org.monte.media.*;
|
||||
import org.monte.media.FormatKeys.MediaType;
|
||||
@@ -9,132 +9,170 @@ import org.monte.media.math.Rational;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.LinkedList;
|
||||
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 {
|
||||
|
||||
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 MovieWriter out;
|
||||
private static File file;
|
||||
private static boolean isRecording = false;
|
||||
private static boolean requestFinish = false;
|
||||
private static boolean abort = false;
|
||||
private final File file;
|
||||
private final MovieWriter out;
|
||||
private final Buffer buf;
|
||||
private final int track;
|
||||
|
||||
private static Buffer buf;
|
||||
private static int track;
|
||||
private static Queue<BufferedImage> toWrite = new LinkedBlockingQueue<BufferedImage>();
|
||||
private volatile boolean active = true;
|
||||
private volatile boolean cancelled = false;
|
||||
|
||||
public static boolean isRecording() {
|
||||
return isRecording;
|
||||
private int queueLimit;
|
||||
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) {
|
||||
if(isRecording) {
|
||||
IllegalStateException up = new IllegalStateException("VideoWriter is already recording!");
|
||||
throw up; //lolololo
|
||||
}
|
||||
isRecording = true;
|
||||
public VideoWriter(int width, int height, int fps, float quality, int queueLimit) throws IOException {
|
||||
this.queueLimit = queueLimit;
|
||||
this.toWrite = new LinkedList<BufferedImage>();
|
||||
|
||||
toWrite = new LinkedBlockingQueue<BufferedImage>();
|
||||
File folder = ReplayFileIO.getRenderFolder();
|
||||
String fileName = FILE_FORMAT.format(Calendar.getInstance().getTime());
|
||||
file = new File(folder, fileName + VIDEO_EXTENSION);
|
||||
Files.createFile(file.toPath());
|
||||
|
||||
try {
|
||||
File folder = ReplayFileIO.getRenderFolder();
|
||||
out = Registry.getInstance().getWriter(file);
|
||||
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
||||
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
||||
FormatKeys.FrameRateKey, new Rational(fps, 1),
|
||||
VideoFormatKeys.WidthKey, width,
|
||||
VideoFormatKeys.HeightKey, height,
|
||||
VideoFormatKeys.DepthKey, 24,
|
||||
VideoFormatKeys.QualityKey, quality);
|
||||
|
||||
String fileName = sdf.format(Calendar.getInstance().getTime());
|
||||
track = out.addTrack(format);
|
||||
|
||||
file = new File(folder, fileName + VIDEO_EXTENSION);
|
||||
file.createNewFile();
|
||||
buf = new Buffer();
|
||||
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
||||
buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse();
|
||||
|
||||
out = Registry.getInstance().getWriter(file);
|
||||
Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO,
|
||||
FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG,
|
||||
FormatKeys.FrameRateKey, new Rational(ReplayMod.replaySettings.getVideoFramerate(), 1),
|
||||
VideoFormatKeys.WidthKey, width,
|
||||
VideoFormatKeys.HeightKey, height,
|
||||
VideoFormatKeys.DepthKey, 24,
|
||||
VideoFormatKeys.QualityKey, (float) ReplayMod.replaySettings.getVideoQuality());
|
||||
|
||||
|
||||
track = out.addTrack(format);
|
||||
|
||||
buf = new Buffer();
|
||||
buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class);
|
||||
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
|
||||
public void run() {
|
||||
while(true) {
|
||||
if(toWrite.isEmpty() || abort) {
|
||||
if(requestFinish) {
|
||||
requestFinish = false;
|
||||
isRecording = false;
|
||||
try {
|
||||
out.close();
|
||||
if(abort) {
|
||||
file.delete();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
abort = false;
|
||||
toWrite = new LinkedBlockingQueue<BufferedImage>();
|
||||
return;
|
||||
}
|
||||
while(!cancelled && (active || !toWrite.isEmpty())) {
|
||||
try {
|
||||
lock.lockInterruptibly();
|
||||
BufferedImage img;
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch(Exception e) {
|
||||
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();
|
||||
}
|
||||
} else {
|
||||
write();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
toWrite.clear();
|
||||
out.close();
|
||||
if (cancelled) {
|
||||
Files.delete(file.toPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
writerThread.start();
|
||||
}
|
||||
|
||||
public static void writeImage(BufferedImage image) {
|
||||
if(requestFinish || !isRecording) {
|
||||
IllegalStateException up = new IllegalStateException(
|
||||
"The VideoWriter is currently not available. Please try again later.");
|
||||
throw up; //lolololo^2
|
||||
}
|
||||
|
||||
toWrite.add(image);
|
||||
public void setQueueLimit(int limit) {
|
||||
this.queueLimit = limit;
|
||||
}
|
||||
|
||||
public static void endRecording() {
|
||||
if(!isRecording) return;
|
||||
requestFinish = true;
|
||||
}
|
||||
/**
|
||||
* 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.");
|
||||
|
||||
private static void write() {
|
||||
lock.lock();
|
||||
try {
|
||||
BufferedImage img = toWrite.poll();
|
||||
if(img != null) {
|
||||
buf.data = img;
|
||||
out.write(track, buf);
|
||||
while (toWrite.size() >= queueLimit) {
|
||||
if (waitIfFull) {
|
||||
noLongerFullCondition.await();
|
||||
} 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();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static void abortRecording() {
|
||||
requestFinish = true;
|
||||
abort = true;
|
||||
public void waitForFinish() throws InterruptedException {
|
||||
Preconditions.checkState(!active, "Video writer still active.");
|
||||
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
|
||||
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
|
||||
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