diff --git a/src/main/java/com/replaymod/pathing/properties/LensProperties.java b/src/main/java/com/replaymod/pathing/properties/LensProperties.java new file mode 100644 index 00000000..7800d7ac --- /dev/null +++ b/src/main/java/com/replaymod/pathing/properties/LensProperties.java @@ -0,0 +1,44 @@ +package com.replaymod.pathing.properties; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.replaymod.replaystudio.pathing.property.AbstractProperty; +import com.replaymod.replaystudio.pathing.property.PropertyPart; +import com.replaymod.replaystudio.pathing.property.PropertyParts; +import de.johni0702.minecraft.gui.utils.NonNull; +import org.apache.commons.lang3.tuple.Triple; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +public class LensProperties extends AbstractProperty> { + + public static final LensProperties FOCAL_DISTANCE = new LensProperties("focalDistance", 5.0f); + public static final LensProperties APERTURE_RADIUS = new LensProperties("apertureRadius", 0.05f); + + public final PropertyPart> VALUE = + new PropertyParts.ForFloatTriple(this, true, PropertyParts.TripleElement.LEFT); + + private LensProperties(String id, float defaultValue) { + super(id, "replaymod.gui.editkeyframe." + id, null, Triple.of(defaultValue, 0f, 0f)); + } + + @Override + public Collection>> getParts() { + return Collections.singletonList(VALUE); + } + + @Override + public void applyToGame(Triple value, @NonNull Object replayHandler) {} + + @Override + public void toJson(JsonWriter writer, Triple value) throws IOException { + writer.value(value.getLeft()); + } + + @Override + public Triple fromJson(JsonReader reader) throws IOException { + return Triple.of((float) reader.nextDouble(), 0f, 0f); + } +} \ No newline at end of file diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index 54217056..964ea2fb 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -155,6 +155,7 @@ public class RenderSettings { private final boolean depthMap; private final boolean cameraPathExport; private final AntiAliasing antiAliasing; + private final int lensBlurSamples; private final String exportCommand; // We switched from rgb24 to bgra for performance at one point, so for backwards compatibility we need to @@ -189,6 +190,7 @@ public class RenderSettings { false, false, RenderSettings.AntiAliasing.NONE, + 16, "", RenderSettings.EncodingPreset.MP4_CUSTOM.getValue(), false @@ -215,6 +217,7 @@ public class RenderSettings { boolean depthMap, boolean cameraPathExport, AntiAliasing antiAliasing, + int lensBlurSamples, String exportCommand, String exportArguments, boolean highPerformance @@ -238,6 +241,7 @@ public class RenderSettings { this.depthMap = depthMap; this.cameraPathExport = cameraPathExport; this.antiAliasing = antiAliasing; + this.lensBlurSamples = lensBlurSamples; this.exportCommand = exportCommand; this.exportArguments = exportArguments; this.highPerformance = highPerformance; @@ -264,6 +268,7 @@ public class RenderSettings { depthMap, cameraPathExport, antiAliasing, + lensBlurSamples, exportCommand, exportArguments, highPerformance @@ -457,6 +462,8 @@ public class RenderSettings { return antiAliasing; } + public int getLensBlurSamples() { return lensBlurSamples; } + public String getExportCommand() { return exportCommand; } diff --git a/src/main/java/com/replaymod/render/capturer/RealLensOpenGlFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/RealLensOpenGlFrameCapturer.java index 29ae9379..19e1145d 100644 --- a/src/main/java/com/replaymod/render/capturer/RealLensOpenGlFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/RealLensOpenGlFrameCapturer.java @@ -4,6 +4,12 @@ import com.replaymod.render.frame.OpenGlFrame; import com.replaymod.render.frame.RealLensOpenGlFrame; import com.replaymod.render.rendering.Channel; +import com.replaymod.replaystudio.pathing.path.Path; +import com.replaymod.simplepathing.ReplayModSimplePathing; +import com.replaymod.pathing.properties.LensProperties; + +import org.apache.commons.lang3.tuple.Triple; + import java.util.Collections; import java.util.Map; @@ -50,14 +56,14 @@ public class RealLensOpenGlFrameCapturer - private static Data[] calculateCameras() { - Data[] cameras = golden_disk(SAMPLES, APERTURE_RADIUS); + private static Data[] calculateCameras(float focal_distance, float aperture_radius, int samples) { + Data[] cameras = golden_disk(samples, aperture_radius); for (Data cam : cameras) { // Направление от смещённой камеры к фокальной точке (0, 0, FOCAL_DISTANCE) float dirX = 0 - cam.dx; float dirY = 0 - cam.dy; - float dirZ = FOCAL_DISTANCE - cam.dz; + float dirZ = focal_distance - cam.dz; // Вычисляем yaw и pitch из направления cam.yaw = (float) -Math.toDegrees(Math.atan2(dirX, dirZ)); @@ -67,11 +73,14 @@ public class RealLensOpenGlFrameCapturer return cameras; } + private final Path positionPath; + public RealLensOpenGlFrameCapturer( WorldRenderer worldRenderer, RenderInfo renderInfo ) { super(worldRenderer, renderInfo); + this.positionPath = ReplayModSimplePathing.instance.getCurrentTimeline().getPositionPath(); } @Override @@ -79,13 +88,24 @@ public class RealLensOpenGlFrameCapturer float partialTicks = renderInfo.updateForNextFrame(); int frameId = framesDone++; - Data[] cameras = calculateCameras(); + int fps = renderInfo.getRenderSettings().getFramesPerSecond(); + long keyframeTime = (long) frameId * 1000 / fps; + + float focalDistance = positionPath.getValue(LensProperties.FOCAL_DISTANCE, keyframeTime).map(Triple::getLeft).orElse(5.0f); + float apertureRadius = positionPath.getValue(LensProperties.APERTURE_RADIUS, keyframeTime).map(Triple::getLeft).orElse(0.05f); + int samples = renderInfo.getRenderSettings().getLensBlurSamples(); + + if (positionPath == null) { + System.err.println("positionPath is null!"); + focalDistance = Float.MAX_VALUE; + apertureRadius = 0.01f; + } + + Data[] cameras = calculateCameras(focalDistance, apertureRadius, samples); OpenGlFrame[] frames = new OpenGlFrame[cameras.length]; for (int i = 0; i < cameras.length; i++) { - // CAMERAS[i] передаётся в renderFrame → worldRenderer.renderWorld(partialTicks, CAMERAS[i]) - // → EntityRendererHandler.data = CAMERAS[i] (автоматически) frames[i] = renderFrame(frameId, partialTicks, cameras[i]); } diff --git a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java index 56f9229f..af1d2560 100644 --- a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java +++ b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java @@ -101,6 +101,7 @@ public class GuiExportFailed extends GuiScreen { oldSettings.isDepthMap(), oldSettings.isCameraPathExport(), oldSettings.getAntiAliasing(), + oldSettings.getLensBlurSamples(), oldSettings.getExportCommand(), oldSettings.getEncodingPreset().getValue(), oldSettings.isHighPerformance() diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 18f4375f..ba05f052 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -103,6 +103,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { public final GuiNumberField videoWidth = new GuiNumberField().setSize(50, 20).setMinValue(1).setValidateOnFocusChange(true); public final GuiNumberField videoHeight = new GuiNumberField().setSize(50, 20).setMinValue(1).setValidateOnFocusChange(true); + public final GuiNumberField lensBlurSamples = new GuiNumberField().setSize(50, 20).setMinValue(1).setMaxValue(512).setValidateOnFocusChange(true); public final GuiSlider frameRateSlider = new GuiSlider().onValueChanged(new Runnable() { @Override public void run() { @@ -199,7 +200,8 @@ public class GuiRenderSettings extends AbstractGuiPopup { injectSphericalMetadata, sphericalFovSlider, depthMap, new GuiLabel(), cameraPathExport, new GuiLabel(), - new GuiLabel().setI18nText("replaymod.gui.rendersettings.antialiasing"), antiAliasingDropdown)); + new GuiLabel().setI18nText("replaymod.gui.rendersettings.antialiasing"), antiAliasingDropdown, + new GuiLabel().setI18nText("replaymod.gui.rendersettings.lensblursamples"), lensBlurSamples)); public final GuiTextField exportCommand = new GuiTextField().setI18nHint("replaymod.gui.rendersettings.command") .setSize(55, 20).setMaxLength(100).onTextChanged((old) -> updateInputs()); @@ -416,6 +418,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { boolean commandChanged = !exportCommand.getText().isEmpty(); boolean argsChanged = !encodingPresetDropdown.getSelectedValue().getValue().equals(exportArguments.getText()); exportReset.setEnabled(commandChanged || argsChanged); + lensBlurSamples.setEnabled(renderMethod == RenderSettings.RenderMethod.REALLENS); } protected String updateResolution() { @@ -542,6 +545,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { depthMap.setChecked(settings.isDepthMap()); cameraPathExport.setChecked(settings.isCameraPathExport()); antiAliasingDropdown.setSelected(settings.getAntiAliasing()); + lensBlurSamples.setValue(settings.getLensBlurSamples()); exportCommand.setText(settings.getExportCommand()); String exportArguments = settings.getExportArguments(); if (exportArguments == null || settings.getEncodingPreset() == null || invalidEncodingPreset) { @@ -575,6 +579,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { depthMap.isChecked() && (serialize || depthMap.isEnabled()), cameraPathExport.isChecked(), serialize || antiAliasingDropdown.isEnabled() ? antiAliasingDropdown.getSelectedValue() : RenderSettings.AntiAliasing.NONE, + lensBlurSamples.getInteger(), exportCommand.getText(), exportArguments.getText(), highPerformance diff --git a/src/main/java/com/replaymod/simplepathing/SPTimeline.java b/src/main/java/com/replaymod/simplepathing/SPTimeline.java index 561122d4..20e46799 100644 --- a/src/main/java/com/replaymod/simplepathing/SPTimeline.java +++ b/src/main/java/com/replaymod/simplepathing/SPTimeline.java @@ -9,6 +9,7 @@ import com.google.gson.stream.JsonWriter; import com.replaymod.pathing.properties.CameraProperties; import com.replaymod.pathing.properties.SpectatorProperty; import com.replaymod.pathing.properties.TimestampProperty; +import com.replaymod.pathing.properties.LensProperties; import com.replaymod.replaystudio.pathing.PathingRegistry; import com.replaymod.replaystudio.pathing.change.AddKeyframe; import com.replaymod.replaystudio.pathing.change.Change; @@ -158,6 +159,12 @@ public class SPTimeline implements PathingRegistry { public void addPositionKeyframe(long time, double posX, double posY, double posZ, float yaw, float pitch, float roll, int spectated) { + addPositionKeyframe(time, posX, posY, posZ, yaw, pitch, roll, spectated, 5.0f, 0.05f); + } + + public void addPositionKeyframe(long time, double posX, double posY, double posZ, + float yaw, float pitch, float roll, int spectated, + float focalDistance, float apertureRadius) { LOGGER.debug("Adding position keyframe at {} pos {}/{}/{} rot {}/{}/{} entId {}", time, posX, posY, posZ, yaw, pitch, roll, spectated); @@ -172,6 +179,8 @@ public class SPTimeline implements PathingRegistry { UpdateKeyframeProperties.Builder builder = UpdateKeyframeProperties.create(path, keyframe); builder.setValue(CameraProperties.POSITION, Triple.of(posX, posY, posZ)); builder.setValue(CameraProperties.ROTATION, Triple.of(yaw, pitch, roll)); + builder.setValue(LensProperties.FOCAL_DISTANCE, Triple.of(focalDistance, 0f, 0f)); + builder.setValue(LensProperties.APERTURE_RADIUS, Triple.of(apertureRadius, 0f, 0f)); if (spectated != -1) { builder.setValue(SpectatorProperty.PROPERTY, spectated); } @@ -201,7 +210,13 @@ public class SPTimeline implements PathingRegistry { } public Change updatePositionKeyframe(long time, double posX, double posY, double posZ, - float yaw, float pitch, float roll) { + float yaw, float pitch, float roll) { + return updatePositionKeyframe(time, posX, posY, posZ, yaw, pitch, roll, 5.0f, 0.05f); + } + + public Change updatePositionKeyframe(long time, double posX, double posY, double posZ, + float yaw, float pitch, float roll, + float focalDistance, float apertureRadius) { LOGGER.debug("Updating position keyframe at {} to pos {}/{}/{} rot {}/{}/{}", time, posX, posY, posZ, yaw, pitch, roll); @@ -213,6 +228,8 @@ public class SPTimeline implements PathingRegistry { Change change = UpdateKeyframeProperties.create(positionPath, keyframe) .setValue(CameraProperties.POSITION, Triple.of(posX, posY, posZ)) .setValue(CameraProperties.ROTATION, Triple.of(yaw, pitch, roll)) + .setValue(LensProperties.FOCAL_DISTANCE, Triple.of(focalDistance, 0f, 0f)) + .setValue(LensProperties.APERTURE_RADIUS, Triple.of(apertureRadius, 0f, 0f)) .done(); change.apply(timeline); return change; @@ -569,6 +586,8 @@ public class SPTimeline implements PathingRegistry { private Interpolator registerPositionInterpolatorProperties(Interpolator interpolator) { interpolator.registerProperty(CameraProperties.POSITION); interpolator.registerProperty(CameraProperties.ROTATION); + interpolator.registerProperty(LensProperties.FOCAL_DISTANCE); + interpolator.registerProperty(LensProperties.APERTURE_RADIUS); return interpolator; } @@ -592,6 +611,8 @@ public class SPTimeline implements PathingRegistry { timeline.registerProperty(CameraProperties.ROTATION); timeline.registerProperty(SpectatorProperty.PROPERTY); timeline.registerProperty(ExplicitInterpolationProperty.PROPERTY); + timeline.registerProperty(LensProperties.FOCAL_DISTANCE); + timeline.registerProperty(LensProperties.APERTURE_RADIUS); return timeline; } diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java b/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java index cd417bcf..7f94b92b 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiEditKeyframe.java @@ -2,6 +2,7 @@ package com.replaymod.simplepathing.gui; import com.replaymod.pathing.properties.CameraProperties; import com.replaymod.pathing.properties.TimestampProperty; +import com.replaymod.pathing.properties.LensProperties; import com.replaymod.replay.ReplayModReplay; import com.replaymod.replaystudio.pathing.change.Change; import com.replaymod.replaystudio.pathing.change.CombinedChange; @@ -214,6 +215,9 @@ public abstract class GuiEditKeyframe> extends Abst public final GuiNumberField pitchField = newGuiNumberField().setSize(60, 20).setPrecision(5); public final GuiNumberField rollField = newGuiNumberField().setSize(60, 20).setPrecision(5); + public final GuiNumberField focalDistanceField = newGuiNumberField().setSize(60, 20).setPrecision(3); + public final GuiNumberField apertureRadiusField = newGuiNumberField().setSize(60, 20).setPrecision(4); + public final InterpolationPanel interpolationPanel = new InterpolationPanel(); { @@ -225,7 +229,10 @@ public abstract class GuiEditKeyframe> extends Abst new GuiLabel().setI18nText("replaymod.gui.editkeyframe.ypos"), yField, new GuiLabel().setI18nText("replaymod.gui.editkeyframe.campitch"), pitchField, new GuiLabel().setI18nText("replaymod.gui.editkeyframe.zpos"), zField, - new GuiLabel().setI18nText("replaymod.gui.editkeyframe.camroll"), rollField); + new GuiLabel().setI18nText("replaymod.gui.editkeyframe.camroll"), rollField, + new GuiLabel().setI18nText("replaymod.gui.editkeyframe.focalDistance"), focalDistanceField, + new GuiLabel().setI18nText("replaymod.gui.editkeyframe.apertureRadius"), apertureRadiusField + ); inputs.setLayout(new VerticalLayout().setSpacing(10)).addElements(new VerticalLayout.Data(0.5, false), positionInputs, interpolationPanel); @@ -245,7 +252,12 @@ public abstract class GuiEditKeyframe> extends Abst rollField.setValue(rot.getRight()); }); - link(xField, yField, zField, yawField, pitchField, rollField, timeMinField, timeSecField, timeMSecField); + this.keyframe.getValue(LensProperties.FOCAL_DISTANCE) + .ifPresent(v -> focalDistanceField.setValue(v.getLeft())); + this.keyframe.getValue(LensProperties.APERTURE_RADIUS) + .ifPresent(v -> apertureRadiusField.setValue(v.getLeft())); + + link(xField, yField, zField, yawField, pitchField, rollField, focalDistanceField, apertureRadiusField, timeMinField, timeSecField, timeMSecField); popup.invokeAll(IGuiLabel.class, e -> e.setColor(Colors.BLACK)); } @@ -255,7 +267,8 @@ public abstract class GuiEditKeyframe> extends Abst SPTimeline timeline = guiPathing.getMod().getCurrentTimeline(); Change positionChange = timeline.updatePositionKeyframe(time, xField.getDouble(), yField.getDouble(), zField.getDouble(), - yawField.getFloat(), pitchField.getFloat(), rollField.getFloat() + yawField.getFloat(), pitchField.getFloat(), rollField.getFloat(), + focalDistanceField.getFloat(), apertureRadiusField.getFloat() ); if (interpolationPanel.getSettingsPanel() == null) { // The last keyframe doesn't have interpolator settings because there is no segment following it diff --git a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java index 560d5d4b..cfa16bc7 100644 --- a/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java +++ b/src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java @@ -13,6 +13,7 @@ import com.replaymod.pathing.player.RealtimeTimelinePlayer; import com.replaymod.pathing.properties.CameraProperties; import com.replaymod.pathing.properties.SpectatorProperty; import com.replaymod.pathing.properties.TimestampProperty; +import com.replaymod.pathing.properties.LensProperties; import com.replaymod.render.gui.GuiRenderQueue; import com.replaymod.render.gui.GuiRenderSettings; import com.replaymod.replay.ReplayHandler; @@ -53,6 +54,7 @@ import net.minecraft.client.resource.language.I18n; import net.minecraft.util.crash.CrashReport; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.commons.lang3.tuple.Triple; //#if MC>=11400 //#else @@ -703,8 +705,14 @@ public class GuiPathing { if (!replayHandler.isCameraView() && !neverSpectator) { spectatedId = replayHandler.getOverlay().getMinecraft().getCameraEntity().getEntityId(); } + + float focalDistance = timeline.getPositionPath() + .getValue(LensProperties.FOCAL_DISTANCE, time).map(Triple::getLeft).orElse(5.0f); + float apertureRadius = timeline.getPositionPath() + .getValue(LensProperties.APERTURE_RADIUS, time).map(Triple::getLeft).orElse(0.05f); + timeline.addPositionKeyframe(time, camera.getX(), camera.getY(), camera.getZ(), - camera.yaw, camera.pitch, camera.roll, spectatedId); + camera.yaw, camera.pitch, camera.roll, spectatedId, focalDistance, apertureRadius); mod.setSelected(path, time); } break;