diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index 2248c24b..7f5926de 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -196,6 +196,11 @@ public class ReplayMod { throw new IllegalArgumentException("Equirectangular renderer requires boolean for whether it's stable."); } pipelinePreset = Pipelines.Preset.EQUIRECTANGULAR; + } else if ("ODS".equals(type)) { + if (parts.length < 2) { + throw new IllegalArgumentException("ODS renderer requires boolean for whether it's stable."); + } + pipelinePreset = Pipelines.Preset.ODS; } else { throw new IllegalArgumentException("Unknown type: " + parts[0]); } diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index f87339be..6ff08de6 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -9,7 +9,7 @@ import java.io.File; @Data public class RenderSettings { public enum RenderMethod { - DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR; + DEFAULT, STEREOSCOPIC, CUBIC, EQUIRECTANGULAR, ODS; @Override public String toString() { diff --git a/src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java index dc01c374..7631f30d 100644 --- a/src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/CubicOpenGlFrameCapturer.java @@ -11,6 +11,7 @@ public class CubicOpenGlFrameCapturer extends OpenGlFrameCapturer { + private static final ResourceLocation vertexResource = new ResourceLocation("replaymod", "shader/ods.vert"); + private static final ResourceLocation fragmentResource = new ResourceLocation("replaymod", "shader/ods.frag"); + + private final CubicPboOpenGlFrameCapturer left, right; + private final Program shaderProgram; + private final Program.Uniform directionVariable; + private final Program.Uniform leftEyeVariable; + + private final BooleanState[] previousStates = new BooleanState[3]; + private final BooleanState previousFogState; + + public ODSFrameCapturer(WorldRenderer worldRenderer, final RenderInfo renderInfo, int frameSize) { + RenderInfo fakeInfo = new RenderInfo() { + private int call; + private float partialTicks; + @Override + public ReadableDimension getFrameSize() { + return renderInfo.getFrameSize(); + } + + @Override + public int getTotalFrames() { + return renderInfo.getTotalFrames(); + } + + @Override + public float updateForNextFrame() { + if (call++ % 2 == 0) { + shaderProgram.stopUsing(); + partialTicks = renderInfo.updateForNextFrame(); + shaderProgram.use(); + } + return partialTicks; + } + + @Override + public RenderSettings getRenderSettings() { + return renderInfo.getRenderSettings(); + } + }; + left = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize); + right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize); + try { + shaderProgram = new Program(vertexResource, fragmentResource); + shaderProgram.use(); + leftEyeVariable = shaderProgram.getUniformVariable("leftEye"); + directionVariable = shaderProgram.getUniformVariable("direction"); + setTexture("texture", 0); + setTexture("lightMap", 1); + linkState(0, "textureEnabled"); + linkState(1, "lightMapEnabled"); + linkState(2, "hurtTextureEnabled"); + final Program.Uniform uniform = shaderProgram.getUniformVariable("fogEnabled"); + previousFogState = GlStateManager.fogState.field_179049_a; + uniform.set(previousFogState.currentState); + GlStateManager.fogState.field_179049_a = new BooleanState(previousFogState.capability) { + @Override + public void setState(boolean state) { + super.setState(state); + uniform.set(state); + } + }; + shaderProgram.stopUsing(); + } catch (Exception e) { + throw new ReportedException(CrashReport.makeCrashReport(e, "Creating ODS shaders")); + } + } + + private void setTexture(String texture, int i) { + shaderProgram.getUniformVariable(texture).set(i); + } + + private void linkState(int id, String var) { + final Program.Uniform uniform = shaderProgram.getUniformVariable(var); + previousStates[id] = GlStateManager.textureState[id].texture2DState; + uniform.set(previousStates[id].currentState); + GlStateManager.textureState[id].texture2DState = new BooleanState(previousStates[id].capability) { + @Override + public void setState(boolean state) { + super.setState(state); + uniform.set(state); + } + }; + } + + @Override + public boolean isDone() { + return left.isDone() && right.isDone(); + } + + @Override + public ODSOpenGlFrame process() { + shaderProgram.use(); + leftEyeVariable.set(true); + CubicOpenGlFrame leftFrame = left.process(); + leftEyeVariable.set(false); + CubicOpenGlFrame rightFrame = right.process(); + shaderProgram.stopUsing(); + + if (leftFrame != null && rightFrame != null) { + return new ODSOpenGlFrame(leftFrame, rightFrame); + } + return null; + } + + @Override + public void close() throws IOException { + left.close(); + right.close(); + shaderProgram.delete(); + for (int i = 0; i < 3; i++) { + GlStateManager.textureState[i].texture2DState = previousStates[i]; + } + GlStateManager.fogState.field_179049_a = previousFogState; + } + + private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer { + public CubicStereoFrameCapturer(WorldRenderer worldRenderer, RenderInfo renderInfo, int frameSize) { + super(worldRenderer, renderInfo, frameSize); + } + + @Override + protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { + pushMatrix(); + frameBuffer().bindFramebuffer(true); + + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + + directionVariable.set(captureData.ordinal()); + worldRenderer.renderWorld(new Dimension(getFrameWidth(), getFrameHeight()), partialTicks, null); + + frameBuffer().unbindFramebuffer(); + popMatrix(); + + return captureFrame(frameId, captureData); + } + } +} diff --git a/src/main/java/com/replaymod/render/capturer/WorldRenderer.java b/src/main/java/com/replaymod/render/capturer/WorldRenderer.java index f1f9d020..445eecc7 100644 --- a/src/main/java/com/replaymod/render/capturer/WorldRenderer.java +++ b/src/main/java/com/replaymod/render/capturer/WorldRenderer.java @@ -6,4 +6,5 @@ import java.io.Closeable; public interface WorldRenderer extends Closeable { void renderWorld(ReadableDimension displaySize, float partialTicks, CaptureData data); + void setOmnidirectional(boolean omnidirectional); } diff --git a/src/main/java/com/replaymod/render/frame/ODSOpenGlFrame.java b/src/main/java/com/replaymod/render/frame/ODSOpenGlFrame.java new file mode 100644 index 00000000..1a595fe8 --- /dev/null +++ b/src/main/java/com/replaymod/render/frame/ODSOpenGlFrame.java @@ -0,0 +1,21 @@ +package com.replaymod.render.frame; + +import com.replaymod.render.rendering.Frame; +import lombok.Getter; +import org.apache.commons.lang3.Validate; + +public class ODSOpenGlFrame implements Frame { + @Getter + private final CubicOpenGlFrame left, right; + + public ODSOpenGlFrame(CubicOpenGlFrame left, CubicOpenGlFrame right) { + Validate.isTrue(left.getFrameId() == right.getFrameId(), "Frame ids do not match."); + this.left = left; + this.right = right; + } + + @Override + public int getFrameId() { + return left.getFrameId(); + } +} diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 3df011a2..75e62838 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -286,6 +286,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { switch (renderMethodDropdown.getSelectedValue()) { case CUBIC: case EQUIRECTANGULAR: + case ODS: stabilizePanel.forEach(IGuiCheckbox.class).setEnabled(); break; default: @@ -294,7 +295,8 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { // Enable/Disable inject metadata checkbox if (encodingPresetDropdown.getSelectedValue().getFileExtension().equals("mp4") - && renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR) { + && (renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.EQUIRECTANGULAR + || renderMethodDropdown.getSelectedValue() == RenderSettings.RenderMethod.ODS)) { inject360Metadata.setEnabled().setTooltip(null); } else { inject360Metadata.setDisabled().setTooltip(new GuiTooltip().setColor(Colors.RED) @@ -325,6 +327,10 @@ public class GuiRenderSettings extends GuiScreen implements Closeable { && (videoWidth / 2 != videoHeight || videoWidth % 2 != 0)) { return "replaymod.gui.rendersettings.customresolution.warning.equirectangular"; } + if (method == RenderSettings.RenderMethod.ODS + && videoWidth != videoHeight) { + return "replaymod.gui.rendersettings.customresolution.warning.ods"; + } return null; } diff --git a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java index 1bbe9038..6b5c7701 100644 --- a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java +++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java @@ -20,6 +20,8 @@ public class EntityRendererHandler implements WorldRenderer { public CaptureData data; + public boolean omnidirectional; + public EntityRendererHandler(RenderSettings settings) { this.settings = settings; @@ -64,6 +66,11 @@ public class EntityRendererHandler implements WorldRenderer { ((IEntityRenderer) mc.entityRenderer).replayModRender_setHandler(null); } + @Override + public void setOmnidirectional(boolean omnidirectional) { + this.omnidirectional = omnidirectional; + } + public static final class NoCullingClippingHelper extends ClippingHelper { @Override public boolean isBoxInFrustum(double p_78553_1_, double p_78553_3_, double p_78553_5_, double p_78553_7_, double p_78553_9_, double p_78553_11_) { diff --git a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java index e8ac41ca..eebb92d7 100644 --- a/src/main/java/com/replaymod/render/metadata/MetadataInjector.java +++ b/src/main/java/com/replaymod/render/metadata/MetadataInjector.java @@ -29,9 +29,12 @@ public class MetadataInjector { "true " + ""+STITCHING_SOFTWARE+" " + "equirectangular "; + private static final String STEREO_XML_CONTENTS = "top-bottom"; private static final String SPHERICAL_XML_FOOTER = ""; private static final String XML_360_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + SPHERICAL_XML_FOOTER; + private static final String XML_ODS_METADATA = SPHERICAL_XML_HEADER + SPHERICAL_XML_CONTENTS + + STEREO_XML_CONTENTS + SPHERICAL_XML_FOOTER; /** * These bytes are taken from the variable 'spherical_uuid_id' @@ -45,11 +48,16 @@ public class MetadataInjector { }; private static final byte[] BYTES_360_METADATA = Bytes.concat(UUID_BYTES, XML_360_METADATA.getBytes()); + private static final byte[] BYTES_ODS_METADATA = Bytes.concat(UUID_BYTES, XML_ODS_METADATA.getBytes()); public static void inject360Metadata(File videoFile) { writeMetadata(videoFile, BYTES_360_METADATA); } + public static void injectODSMetadata(File videoFile) { + writeMetadata(videoFile, BYTES_ODS_METADATA); + } + private static void writeMetadata(File videoFile, byte[] metadata) { File tempFile = null; FileOutputStream videoFileOutputStream = null; diff --git a/src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java index 5ae8f2d1..eb9deb08 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEffectRenderer.java @@ -1,6 +1,5 @@ package com.replaymod.render.mixin; -import com.replaymod.render.capturer.CubicOpenGlFrameCapturer; import com.replaymod.render.hooks.EntityRendererHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.particle.EffectRenderer; @@ -28,7 +27,7 @@ public abstract class MixinEffectRenderer { private void renderParticle(EntityFX fx, WorldRenderer worldRenderer, Entity view, float partialTicks, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) { EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); - if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) { + if (handler != null && handler.omnidirectional) { // Align all particles towards the camera double dx = fx.prevPosX + (fx.posX - fx.prevPosX) * partialTicks - view.posX; double dy = fx.prevPosY + (fx.posY - fx.prevPosY) * partialTicks - view.posY; diff --git a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java index f7efabd5..dace69cb 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java +++ b/src/main/java/com/replaymod/render/mixin/MixinEntityRenderer.java @@ -109,7 +109,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) private void replayModRender_renderSpectatorHand(float partialTicks, int renderPass, CallbackInfo ci) { if (replayModRender_handler != null) { - if (replayModRender_handler.data instanceof CubicOpenGlFrameCapturer.Data) { + if (replayModRender_handler.omnidirectional) { ci.cancel(); return; // No spectator hands during 360° view, we wouldn't even know where to put it } @@ -204,7 +204,7 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti @Override public void replayModRender_gluPerspective(float fovY, float aspect, float zNear, float zFar) { - if (replayModRender_getHandler() != null && replayModRender_getHandler().data instanceof CubicOpenGlFrameCapturer.Data) { + if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { fovY = 90; aspect = 1; } @@ -234,7 +234,8 @@ public abstract class MixinEntityRenderer implements EntityRendererHandler.IEnti GlStateManager.rotate(-90, 1.0F, 0.0F, 0.0F); break; } - + } + if (replayModRender_getHandler() != null && replayModRender_getHandler().omnidirectional) { // Minecraft goes back a little so we have to revert that GlStateManager.translate(0.0F, 0.0F, 0.1F); } diff --git a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java index cbf3dfb2..f12637f5 100644 --- a/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java +++ b/src/main/java/com/replaymod/render/mixin/MixinRenderManager.java @@ -1,6 +1,5 @@ package com.replaymod.render.mixin; -import com.replaymod.render.capturer.CubicOpenGlFrameCapturer; import com.replaymod.render.hooks.EntityRendererHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.RenderManager; @@ -22,7 +21,7 @@ public abstract class MixinRenderManager { @Inject(method = "doRenderEntity", at = @At("HEAD")) private void replayModRender_reorientForCubicRendering(Entity entity, double dx, double dy, double dz, float iDoNotKnow, float partialTicks, boolean iDoNotCare, CallbackInfoReturnable ci) { EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) Minecraft.getMinecraft().entityRenderer).replayModRender_getHandler(); - if (handler != null && handler.data instanceof CubicOpenGlFrameCapturer.Data) { + if (handler != null && handler.omnidirectional) { double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)); double yaw = -Math.atan2(dx, dz); playerViewX = (float) Math.toDegrees(pitch); diff --git a/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java b/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java new file mode 100644 index 00000000..b39bd984 --- /dev/null +++ b/src/main/java/com/replaymod/render/processor/ODSToRGBProcessor.java @@ -0,0 +1,37 @@ +package com.replaymod.render.processor; + +import com.replaymod.render.frame.ODSOpenGlFrame; +import com.replaymod.render.frame.RGBFrame; +import com.replaymod.render.utils.ByteBufferPool; +import org.lwjgl.util.Dimension; +import org.lwjgl.util.ReadableDimension; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class ODSToRGBProcessor extends AbstractFrameProcessor { + private final EquirectangularToRGBProcessor processor; + + public ODSToRGBProcessor(int frameSize) { + processor = new EquirectangularToRGBProcessor(frameSize); + } + + @Override + public RGBFrame process(ODSOpenGlFrame rawFrame) { + RGBFrame leftFrame = processor.process(rawFrame.getLeft()); + RGBFrame rightFrame = processor.process(rawFrame.getRight()); + ReadableDimension size = new Dimension(leftFrame.getSize().getWidth(), leftFrame.getSize().getHeight() * 2); + ByteBuffer result = ByteBufferPool.allocate(size.getWidth() * size.getHeight() * 3); + result.put(leftFrame.getByteBuffer()); + result.put(rightFrame.getByteBuffer()); + result.rewind(); + ByteBufferPool.release(leftFrame.getByteBuffer()); + ByteBufferPool.release(rightFrame.getByteBuffer()); + return new RGBFrame(rawFrame.getFrameId(), size, result); + } + + @Override + public void close() throws IOException { + processor.close(); + } +} diff --git a/src/main/java/com/replaymod/render/rendering/Pipelines.java b/src/main/java/com/replaymod/render/rendering/Pipelines.java index fed5ca42..f16ba7df 100644 --- a/src/main/java/com/replaymod/render/rendering/Pipelines.java +++ b/src/main/java/com/replaymod/render/rendering/Pipelines.java @@ -2,15 +2,9 @@ package com.replaymod.render.rendering; import com.replaymod.render.RenderSettings; import com.replaymod.render.capturer.*; -import com.replaymod.render.frame.CubicOpenGlFrame; -import com.replaymod.render.frame.OpenGlFrame; -import com.replaymod.render.frame.RGBFrame; -import com.replaymod.render.frame.StereoscopicOpenGlFrame; +import com.replaymod.render.frame.*; import com.replaymod.render.hooks.EntityRendererHandler; -import com.replaymod.render.processor.CubicToRGBProcessor; -import com.replaymod.render.processor.EquirectangularToRGBProcessor; -import com.replaymod.render.processor.OpenGlToRGBProcessor; -import com.replaymod.render.processor.StereoscopicToRGBProcessor; +import com.replaymod.render.processor.*; import com.replaymod.render.utils.PixelBufferObject; import lombok.experimental.UtilityClass; @@ -26,6 +20,8 @@ public class Pipelines { return newCubicPipeline(renderInfo, consumer); case EQUIRECTANGULAR: return newEquirectangularPipeline(renderInfo, consumer); + case ODS: + return newODSPipeline(renderInfo, consumer); } throw new UnsupportedOperationException("Unknown method: " + method); } @@ -73,4 +69,11 @@ public class Pipelines { } return new Pipeline<>(capturer, new EquirectangularToRGBProcessor(settings.getVideoWidth() / 4), consumer); } + + public static Pipeline newODSPipeline(RenderInfo renderInfo, FrameConsumer consumer) { + RenderSettings settings = renderInfo.getRenderSettings(); + FrameCapturer capturer = + new ODSFrameCapturer(new EntityRendererHandler(settings), renderInfo, settings.getVideoWidth() / 4); + return new Pipeline<>(capturer, new ODSToRGBProcessor(settings.getVideoWidth() / 4), consumer); + } } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 60f231f3..c63dcfa4 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -113,7 +113,11 @@ public class VideoRenderer implements RenderInfo { renderingPipeline.run(); if (settings.isInject360Metadata()) { - MetadataInjector.inject360Metadata(settings.getOutputFile()); + if (settings.getRenderMethod() == RenderSettings.RenderMethod.ODS) { + MetadataInjector.injectODSMetadata(settings.getOutputFile()); + } else { + MetadataInjector.inject360Metadata(settings.getOutputFile()); + } } finish(); diff --git a/src/main/java/com/replaymod/render/shader/Program.java b/src/main/java/com/replaymod/render/shader/Program.java new file mode 100644 index 00000000..b4d2c8e8 --- /dev/null +++ b/src/main/java/com/replaymod/render/shader/Program.java @@ -0,0 +1,102 @@ +package com.replaymod.render.shader; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IResource; +import net.minecraft.util.ResourceLocation; +import org.apache.commons.io.IOUtils; +import org.lwjgl.opengl.ARBFragmentShader; +import org.lwjgl.opengl.ARBShaderObjects; +import org.lwjgl.opengl.ARBVertexShader; +import org.lwjgl.opengl.GL11; + +import java.io.InputStream; + +import static org.lwjgl.opengl.ARBShaderObjects.*; + +public class Program { + private final int program; + + public Program(ResourceLocation vertexShader, ResourceLocation fragmentShader) throws Exception { + int vertShader = createShader(vertexShader, ARBVertexShader.GL_VERTEX_SHADER_ARB); + int fragShader = createShader(fragmentShader, ARBFragmentShader.GL_FRAGMENT_SHADER_ARB); + + program = glCreateProgramObjectARB(); + if (program == 0) { + throw new Exception("glCreateProgramObjectARB failed"); + } + + glAttachObjectARB(program, vertShader); + glAttachObjectARB(program, fragShader); + + glLinkProgramARB(program); + if (glGetObjectParameteriARB(program, GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) { + throw new Exception("Error linking: " + getLogInfo(program)); + } + + glValidateProgramARB(program); + if (glGetObjectParameteriARB(program, GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) { + throw new Exception("Error validating: " + getLogInfo(program)); + } + } + + private int createShader(ResourceLocation resourceLocation, int shaderType) throws Exception { + int shader = 0; + try { + shader = glCreateShaderObjectARB(shaderType); + + if(shader == 0) + throw new Exception("glCreateShaderObjectARB failed"); + + IResource resource = Minecraft.getMinecraft().getResourceManager().getResource(resourceLocation); + try (InputStream is = resource.getInputStream()) { + glShaderSourceARB(shader, IOUtils.toString(is)); + } + glCompileShaderARB(shader); + + if (glGetObjectParameteriARB(shader, GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error creating shader: " + getLogInfo(shader)); + + return shader; + } + catch(Exception exc) { + glDeleteObjectARB(shader); + throw exc; + } + } + + private static String getLogInfo(int obj) { + return glGetInfoLogARB(obj, glGetObjectParameteriARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB)); + } + + public void use() { + ARBShaderObjects.glUseProgramObjectARB(program); + } + + public void stopUsing() { + ARBShaderObjects.glUseProgramObjectARB(0); + } + + public void delete() { + ARBShaderObjects.glDeleteObjectARB(program); + } + + public Uniform getUniformVariable(String name) { + return new Uniform(ARBShaderObjects.glGetUniformLocationARB(program, name)); + } + + public class Uniform { + private final int location; + + public Uniform(int location) { + this.location = location; + } + + public void set(boolean bool) { + ARBShaderObjects.glUniform1iARB(location, bool ? GL11.GL_TRUE : GL11.GL_FALSE); + } + + public void set(int integer) { + ARBShaderObjects.glUniform1iARB(location, integer); + } + } +} diff --git a/src/main/resources/META-INF/replaymod_at.cfg b/src/main/resources/META-INF/replaymod_at.cfg index dc1acbc0..59d3b95e 100644 --- a/src/main/resources/META-INF/replaymod_at.cfg +++ b/src/main/resources/META-INF/replaymod_at.cfg @@ -110,5 +110,12 @@ public net.minecraft.client.settings.KeyBinding field_151474_i # pressTime # Timer public net.minecraft.util.Timer * +# GlStateManager +public net.minecraft.client.renderer.GlStateManager * +public net.minecraft.client.renderer.GlStateManager$TextureState +public net.minecraft.client.renderer.GlStateManager$FogState +public net.minecraft.client.renderer.GlStateManager$BooleanState +public net.minecraft.client.renderer.GlStateManager$BooleanState * + # Example # public net.minecraft.package.ClassName func_some_id(Ljava/lang/Class;IZS)V # methodName diff --git a/src/main/resources/assets/replaymod/shader/ods.frag b/src/main/resources/assets/replaymod/shader/ods.frag new file mode 100644 index 00000000..ef46047b --- /dev/null +++ b/src/main/resources/assets/replaymod/shader/ods.frag @@ -0,0 +1,31 @@ +#version 110 + +varying vec4 vertColor; + +varying vec4 textureCoord; +varying vec4 lightMapCoord; + +uniform sampler2D texture; +uniform sampler2D lightMap; + +uniform bool textureEnabled; +uniform bool lightMapEnabled; +uniform bool hurtTextureEnabled; +uniform bool fogEnabled; + +void main() { + vec4 color = vertColor; + if (textureEnabled) { + color *= texture2D(texture, textureCoord.st); + } + if (lightMapEnabled) { + color *= texture2D(lightMap, lightMapCoord.st); + } + if (hurtTextureEnabled) { + color = vec4(mix(color.rgb, vec3(1, 0, 0), 0.3), color.a); + } + if (fogEnabled) { + color.rgb = mix(color.rgb, gl_Fog.color.rgb, clamp((gl_FogFragCoord - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0)); + } + gl_FragColor = color; +} \ No newline at end of file diff --git a/src/main/resources/assets/replaymod/shader/ods.vert b/src/main/resources/assets/replaymod/shader/ods.vert new file mode 100644 index 00000000..61fa8450 --- /dev/null +++ b/src/main/resources/assets/replaymod/shader/ods.vert @@ -0,0 +1,79 @@ +#version 110 + +varying vec4 vertColor; +varying float vertId; + +varying vec4 textureCoord; +varying vec4 lightMapCoord; + +uniform bool leftEye; +uniform int direction; + +const float eyeDistance = 0.14; + +void main() { + // Transform to view space + vec4 position = gl_ModelViewMatrix * gl_Vertex; + + // Distort for ODS + // O := The origin + // P := The current vertex/point + // T := Point of tangency with the tangent going through P + // Distance between P and O + float distPO = sqrt(position.x * position.x + position.z * position.z); + float distTO = eyeDistance * 0.5; + // Angle between PO and PT + float angP = acos(distTO / distPO); + // Angle between PO and TO (angle at the origin) + float angO = 90.0 - angP; + if (!leftEye) { + angO = -angO; + } + // The angel of OP within the circle, that is between OP and O(0,1) + float angOP = atan(position.x, position.z); + // The angle of OT within the circle, that is between OT and O(0,1) + float angOT = angO + angOP; + // Calculate the vector between O and T and finally move the vertex by that vector + position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0); + + // Rotate for different cubic views + float z; + switch (direction) { + case 0: // LEFT + z = position.z; + position.z = position.x; + position.x = -z; + break; + case 1: // RIGHT + z = position.z; + position.z = -position.x; + position.x = z; + break; + case 2: // FRONT + // No changes required + break; + case 3: // BACK + position.x = -position.x; + position.z = -position.z; + break; + case 4: // TOP + z = position.z; + position.z = -position.y; + position.y = z; + break; + case 5: // BOTTOM + z = position.z; + position.z = position.y; + position.y = -z; + break; + } + + // Transform to screen space + gl_Position = gl_ProjectionMatrix * position; + + // Misc. + textureCoord = gl_TextureMatrix[0] * gl_MultiTexCoord0; + lightMapCoord = gl_TextureMatrix[1] * gl_MultiTexCoord1; + vertColor = gl_Color; + gl_FogFragCoord = sqrt(position.x * position.x + position.y * position.y + position.z * position.z); +} \ No newline at end of file