From 0ad829189ca7972dbcea61381d0573b5088b68aa Mon Sep 17 00:00:00 2001 From: yugecin Date: Sat, 4 Mar 2017 00:31:01 +0100 Subject: [PATCH 1/7] add slider style from yugecin/opsu-dance --- src/itdelatrisu/opsu/Container.java | 2 + src/itdelatrisu/opsu/GameData.java | 12 +- src/itdelatrisu/opsu/GameImage.java | 1 + src/itdelatrisu/opsu/Utils.java | 15 + src/itdelatrisu/opsu/beatmap/HitObject.java | 13 + src/itdelatrisu/opsu/objects/Circle.java | 2 +- src/itdelatrisu/opsu/objects/Slider.java | 65 +- .../opsu/objects/curves/Curve.java | 38 +- .../objects/curves/FakeCombinedCurve.java | 44 ++ src/itdelatrisu/opsu/options/OptionGroup.java | 6 + src/itdelatrisu/opsu/options/Options.java | 9 + .../opsu/render/LegacyCurveRenderState.java | 569 ++++++++++++++++++ src/itdelatrisu/opsu/states/Game.java | 76 ++- 13 files changed, 837 insertions(+), 15 deletions(-) create mode 100644 src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java create mode 100644 src/itdelatrisu/opsu/render/LegacyCurveRenderState.java diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index 617386f7..a95d34a8 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -29,6 +29,7 @@ import itdelatrisu.opsu.downloads.Updater; import itdelatrisu.opsu.options.Options; import itdelatrisu.opsu.render.CurveRenderState; +import itdelatrisu.opsu.render.LegacyCurveRenderState; import itdelatrisu.opsu.ui.UI; import org.lwjgl.opengl.Display; @@ -179,6 +180,7 @@ private void close_sub() { // delete OpenGL objects involved in the Curve rendering CurveRenderState.shutdown(); + LegacyCurveRenderState.shutdown(); // destroy watch service if (!Options.isWatchServiceEnabled()) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 842cf42e..619406eb 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1088,14 +1088,18 @@ else if (Options.isHitLightingEnabled() && !hitResult.hideResult && hitResult.re */ private void drawHitAnimations(HitObjectResult hitResult, int trackPosition) { // fade out slider curve - if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.curve != null) { + if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.curve != null && !(Options.isExperimentalSliderStyle() && Options.isShrinkingSliders())) { float progress = AnimationEquation.OUT_CUBIC.calc( (float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME); float alpha = 1f - progress; float oldWhiteAlpha = Colors.WHITE_FADE.a; float oldColorAlpha = hitResult.color.a; Colors.WHITE_FADE.a = hitResult.color.a = alpha; - hitResult.curve.draw(hitResult.color); + if (Options.isExperimentalSliderStyle()) { + hitResult.curve.draw(hitResult.color, Options.isMergingSliders() ? 1 : 0, hitResult.curve.getCurvePoints().length); + } else { + hitResult.curve.draw(hitResult.color); + } Colors.WHITE_FADE.a = oldWhiteAlpha; hitResult.color.a = oldColorAlpha; } @@ -1125,6 +1129,10 @@ private void drawHitAnimations(HitObjectResult hitResult, int trackPosition) { fc.drawCentered(hitResult.x, hitResult.y); } + if (hitResult.result != HIT_SLIDER_REPEAT && hitResult.curve != null && !Options.isDrawSliderEndCircles()) { + return; + } + // hit circles float progress = AnimationEquation.OUT_CUBIC.calc( (float) Utils.clamp(trackPosition - hitResult.time, 0, HITCIRCLE_FADE_TIME) / HITCIRCLE_FADE_TIME); diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index deb5e402..49ed118b 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -105,6 +105,7 @@ protected Image process_sub(Image img, int w, int h) { // Slider SLIDER_GRADIENT ("slidergradient", "png"), + SLIDER_GRADIENT_EX ("slidergradient_ex", "png"), SLIDER_BALL ("sliderb", "sliderb%d", "png"), SLIDER_FOLLOWCIRCLE ("sliderfollowcircle", "png"), REVERSEARROW ("reversearrow", "png"), diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 209d82a7..f4b816e2 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -235,6 +235,21 @@ public static float clamp(float val, float low, float high) { return val; } + /** + * Clamps a value between a lower and upper bound. + * @param val the value to clamp + * @param low the lower bound + * @param high the upper bound + * @return the clamped value + */ + public static double clamp(double val, double low, double high) { + if (val < low) + return low; + if (val > high) + return high; + return val; + } + /** * Returns the distance between two points. * @param x1 the x-component of the first point diff --git a/src/itdelatrisu/opsu/beatmap/HitObject.java b/src/itdelatrisu/opsu/beatmap/HitObject.java index ca5f5790..07be1b5a 100644 --- a/src/itdelatrisu/opsu/beatmap/HitObject.java +++ b/src/itdelatrisu/opsu/beatmap/HitObject.java @@ -271,6 +271,19 @@ else if ((type & HitObject.TYPE_SLIDER) > 0) { } } + /** + * Constructor to make fake/temp hitobj + * @param x xpos + * @param y ypos + * @param time time + */ + public HitObject( float x, float y, int time) { + this.x = x; + this.y = y; + this.time = time; + this.type = HitObject.TYPE_CIRCLE; + } + /** * Returns the raw starting x coordinate. */ diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index 05e29237..14946983 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -38,7 +38,7 @@ */ public class Circle implements GameObject { /** The diameter of hit circles. */ - private static float diameter; + public static float diameter; /** The associated HitObject. */ private HitObject hitObject; diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 96a75acd..e2dc241a 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -117,6 +117,12 @@ public class Slider implements GameObject { /** Container dimensions. */ private static int containerWidth, containerHeight; + /** Curve color for experimental style sliders. */ + private static Color curveColor = new Color(0, 0, 0, 20); + + /** Start index of this slider in the merged slider. */ + public int baseSliderFrom; + /** * Initializes the Slider data type with images and dimensions. * @param container the game container @@ -212,11 +218,18 @@ public void draw(Graphics g, int trackPosition) { Math.max(0f, 1f - ((float) (trackPosition - hitObject.getTime()) / (getEndTime() - hitObject.getTime())) * 1.05f); } - float curveInterval = Options.isSliderSnaking() ? alpha : 1f; - curve.draw(color,curveInterval); + boolean isCurveCompletelyDrawn; + if (!Options.isExperimentalSliderStyle()) { + isCurveCompletelyDrawn = Options.isSliderSnaking() || alpha == 1f; + curve.draw(color, isCurveCompletelyDrawn ? 1f : alpha); + } else { + curveColor.a = sliderAlpha; + isCurveCompletelyDrawn = drawSliderTrack(trackPosition, Utils.clamp(1d - (double) (timeDiff - approachTime + fadeInTime) / fadeInTime, 0d, 1d)); + color.a = alpha; + } // end circle (only draw if ball still has to go there) - if (curveInterval == 1f && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) { + if (isCurveCompletelyDrawn && (!Options.isExperimentalSliderStyle() || Options.isDrawSliderEndCircles()) && currentRepeats < repeatCount - (repeatCount % 2 == 0 ? 1 : 0)) { Color circleColor = new Color(color); Color overlayColor = new Color(Colors.WHITE_FADE); if (currentRepeats == 0) { @@ -228,7 +241,7 @@ public void draw(Graphics g, int trackPosition) { // fade in end circle after repeats circleColor.a = overlayColor.a = sliderAlpha * getCircleAlphaAfterRepeat(trackPosition, true); } - Vec2f endCircPos = curve.pointAt(curveInterval); + Vec2f endCircPos = curve.pointAt(1f); hitCircle.drawCentered(endCircPos.x, endCircPos.y, circleColor); hitCircleOverlay.drawCentered(endCircPos.x, endCircPos.y, overlayColor); } @@ -277,7 +290,7 @@ public void draw(Graphics g, int trackPosition) { } // repeats - if (curveInterval == 1.0f) { + if (isCurveCompletelyDrawn) { for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1 && tcurRepeat < repeatCount - 1; tcurRepeat++) { Image arrow = GameImage.REVERSEARROW.getImage(); // bouncing animation @@ -406,6 +419,41 @@ private void drawSliderTicks(int trackPosition, float curveAlpha, float decorati } } + /** + * Draws the slider track for the experimental style sliders. + * @param trackPosition position of the song + * @param snakingSliderProgress progress of the snaking sliders [0, 1] + * @return true if the track was completely drawn + */ + private boolean drawSliderTrack(int trackPosition, double snakingSliderProgress) { + double curveIntervalTo = Options.isSliderSnaking() ? snakingSliderProgress : 1d; + double curveIntervalFrom = 0d; + if (Options.isShrinkingSliders()) { + double sliderprogress = (trackPosition - hitObject.getTime() - ((double) sliderTime * (hitObject.getRepeatCount() - 1))) / (double) sliderTime; + if (sliderprogress > 0) { + curveIntervalFrom = sliderprogress; + } + } + int curvelen = curve.getCurvePoints().length; + if (Options.isMergingSliders()) { + if (Options.isShrinkingSliders() && curveIntervalFrom > 0) { + if (hitObject.getRepeatCount() % 2 == 0) { + game.spliceSliderCurve(baseSliderFrom + (int) ((1d - curveIntervalFrom) * curvelen) - 1, baseSliderFrom + curvelen); + } else { + game.setSlidercurveFrom(baseSliderFrom + (int) (curveIntervalFrom * curvelen) + 1); + } + } + game.setSlidercurveTo(baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); + } else { + if (Options.isShrinkingSliders() && curveIntervalFrom > 0 && hitObject.getRepeatCount() % 2 == 0) { + curve.splice((int) ((1d - curveIntervalFrom) * curvelen), curvelen); + curveIntervalFrom = 0d; + } + curve.draw(curveColor, (int) (curveIntervalFrom * curvelen), (int) (curveIntervalTo * curvelen)); + } + return curveIntervalTo == 1d; + } + /** * Get the alpha level used to fade in circles & reversearrows after repeat * @param trackPosition current trackposition, in ms @@ -707,6 +755,9 @@ else if (GameMod.RELAX.isActive() && trackPosition >= time) // calculate and send slider result hitResult(); + if (Options.isMergingSliders()) { + game.setSlidercurveFrom(baseSliderFrom + curve.getCurvePoints().length + 1); + } return true; } @@ -753,6 +804,10 @@ private float getT(int trackPosition, boolean raw) { } } + public Curve getCurve() { + return curve; + } + @Override public void reset() { sliderClickedInitial = false; diff --git a/src/itdelatrisu/opsu/objects/curves/Curve.java b/src/itdelatrisu/opsu/objects/curves/Curve.java index e4fd34c4..63fe5949 100644 --- a/src/itdelatrisu/opsu/objects/curves/Curve.java +++ b/src/itdelatrisu/opsu/objects/curves/Curve.java @@ -23,6 +23,7 @@ import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.options.Options; import itdelatrisu.opsu.render.CurveRenderState; +import itdelatrisu.opsu.render.LegacyCurveRenderState; import itdelatrisu.opsu.skins.Skin; import itdelatrisu.opsu.ui.Colors; @@ -59,9 +60,20 @@ public abstract class Curve { /** Per-curve render-state used for the new style curve renders. */ private CurveRenderState renderState; + /** Per-curve render-state used for the legacy style curve renders. */ + private LegacyCurveRenderState legacyRenderState; + /** Points along the curve (set by inherited classes). */ protected Vec2f[] curve; + /** + * Get the curve points + * @return curve points + */ + public Vec2f[] getCurvePoints() { + return curve; + } + /** * Constructor. * @param hitObject the associated HitObject @@ -96,9 +108,10 @@ public static void init(int width, int height, float circleDiameter, Color borde ContextCapabilities capabilities = GLContext.getCapabilities(); mmsliderSupported = capabilities.OpenGL30; - if (mmsliderSupported) + if (mmsliderSupported) { CurveRenderState.init(width, height, circleDiameter); - else { + LegacyCurveRenderState.init(width, height, circleDiameter); + } else { if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER) Log.warn("New slider style requires OpenGL 3.0."); } @@ -147,6 +160,25 @@ public void draw(Color color, float t) { } } + public void draw(Color color, int from, int to) { + if (curve == null) + return; + if (legacyRenderState == null) + legacyRenderState = new LegacyCurveRenderState(hitObject, curve); + legacyRenderState.draw(color, borderColor, from, to); + } + + /** + * Splice the curve to draw (= flag a part of it as 'do not draw'). Bases on the curve point indices. + * @param from start index to splice + * @param to end index to splice + */ + public void splice(int from, int to) { + if (legacyRenderState == null) + return; + legacyRenderState.splice(from, to); + } + /** * Returns the angle of the first control point. */ @@ -175,5 +207,7 @@ public void draw(Color color, float t) { public void discardGeometry() { if (renderState != null) renderState.discardGeometry(); + if (legacyRenderState != null) + legacyRenderState.discardGeometry(); } } diff --git a/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java b/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java new file mode 100644 index 00000000..085fe6da --- /dev/null +++ b/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java @@ -0,0 +1,44 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014-2017 Jeffrey Han + * + * opsu! is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu! is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!. If not, see . + */ +package itdelatrisu.opsu.objects.curves; + +import itdelatrisu.opsu.beatmap.HitObject; + +public class FakeCombinedCurve extends Curve { + + public FakeCombinedCurve(Vec2f[] points) { + super(new HitObject(0, 0, 0), false); + this.curve = points; + } + + @Override + public Vec2f pointAt(float t) { + return null; + } + + @Override + public float getEndAngle() { + return 0; + } + + @Override + public float getStartAngle() { + return 0; + } + +} diff --git a/src/itdelatrisu/opsu/options/OptionGroup.java b/src/itdelatrisu/opsu/options/OptionGroup.java index 07a3e244..cc5f8a95 100644 --- a/src/itdelatrisu/opsu/options/OptionGroup.java +++ b/src/itdelatrisu/opsu/options/OptionGroup.java @@ -51,6 +51,12 @@ public class OptionGroup { GameOption.SHOW_FOLLOW_POINTS, GameOption.SCREENSHOT_FORMAT, }), + new OptionGroup("EXPERIMENTAL SLIDERS", new GameOption[] { + GameOption.EXPERIMENTAL_SLIDERS, + GameOption.EXPERIMENTAL_SLIDERS_MERGE, + GameOption.EXPERIMENTAL_SLIDERS_SHRINK, + GameOption.EXPERIMENTAL_SLIDERS_DRAW_ENDCIRCLES, + }), new OptionGroup("MAIN MENU", new GameOption[] { GameOption.DYNAMIC_BACKGROUND, GameOption.PARALLAX, diff --git a/src/itdelatrisu/opsu/options/Options.java b/src/itdelatrisu/opsu/options/Options.java index f907b5f6..0a25a247 100644 --- a/src/itdelatrisu/opsu/options/Options.java +++ b/src/itdelatrisu/opsu/options/Options.java @@ -567,6 +567,10 @@ public void setValue(int value) { IGNORE_BEATMAP_SKINS ("Ignore all beatmap skins", "IgnoreBeatmapSkins", "Defaults game settings to never use skin element overrides provided by beatmaps.", false), FORCE_SKIN_CURSOR ("Always use skin cursor", "UseSkinCursor", "The selected skin's cursor will override any beatmap-specific cursor modifications.", false), SNAKING_SLIDERS ("Snaking sliders", "SnakingSliders", "Sliders gradually snake out from their starting point.", true), + EXPERIMENTAL_SLIDERS ("Enable", "ExperimentalSliders", "Enable experimental slider style", false), + EXPERIMENTAL_SLIDERS_DRAW_ENDCIRCLES ("Draw slider endcircles", "ExSlDrawEnd", "Only for experimental sliders", false), + EXPERIMENTAL_SLIDERS_SHRINK ("Shrink sliders", "ExSlShrink", "Only for experimental sliders", true), + EXPERIMENTAL_SLIDERS_MERGE ("Merge sliders", "ExSlMerge", "Only for experimental sliders", true), SHOW_HIT_LIGHTING ("Hit lighting", "HitLighting", "Adds a subtle glow behind hit explosions which lights the playfield.", true), SHOW_COMBO_BURSTS ("Combo bursts", "ComboBurst", "A character image bursts from the side of the screen at combo milestones.", true), SHOW_PERFECT_HIT ("Perfect hits", "PerfectHit", "Shows perfect hit result bursts (300s, slider ticks).", true), @@ -1176,6 +1180,11 @@ public static void setDisplayMode(Container app) { */ public static boolean isSliderSnaking() { return GameOption.SNAKING_SLIDERS.getBooleanValue(); } + public static boolean isExperimentalSliderStyle() { return GameOption.EXPERIMENTAL_SLIDERS.getBooleanValue(); } + public static boolean isDrawSliderEndCircles() { return GameOption.EXPERIMENTAL_SLIDERS_DRAW_ENDCIRCLES.getBooleanValue(); } + public static boolean isShrinkingSliders() { return GameOption.EXPERIMENTAL_SLIDERS_SHRINK.getBooleanValue(); } + public static boolean isMergingSliders() { return GameOption.EXPERIMENTAL_SLIDERS_MERGE.getBooleanValue(); } + /** * Returns the fixed circle size override, if any. * @return the CS value (0, 10], 0f if disabled diff --git a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java new file mode 100644 index 00000000..d47b1045 --- /dev/null +++ b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java @@ -0,0 +1,569 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014, 2015 Jeffrey Han + * + * opsu! is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu! is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!. If not, see . + */ +package itdelatrisu.opsu.render; + +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.beatmap.HitObject; +import itdelatrisu.opsu.objects.Circle; +import itdelatrisu.opsu.objects.curves.Vec2f; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.*; +import org.newdawn.slick.Color; +import org.newdawn.slick.Image; +import org.newdawn.slick.util.Log; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +/** + * Hold the temporary render state that needs to be restored again after the new + * style curves are drawn. + * + * @author Bigpet {@literal } + */ +public class LegacyCurveRenderState { + /** The width and height of the display container this curve gets drawn into. */ + protected static int containerWidth, containerHeight; + + /** Thickness of the curve. */ + protected static int scale; + + /** Static state that's needed to draw the new style curves. */ + private static final NewCurveStyleState staticState = new NewCurveStyleState(); + + /** Cached drawn slider, only used if new style sliders are activated. */ + public Rendertarget fbo; + + /** The HitObject associated with the curve to be drawn. */ + protected HitObject hitObject; + + protected Vec2f[] curve; + + /** The point to which the curve has last been rendered into the texture (as an index into {@code curve}). */ + private int lastPointDrawn; + + /** The point from which the curve has last been rendered into the texture (as an index into {@code curve}). */ + private int firstPointDrawn; + + /** Index of the curve array that indicates the starting position of the slider part that should not be drawn. */ + private int spliceFrom; + + /** Index of the curve array that indicates the end position of the slider part that should not be drawn. */ + private int spliceTo; + + /** + * Set the width and height of the container that Curves get drawn into. + * Should be called before any curves are drawn. + * @param width the container width + * @param height the container height + * @param circleDiameter the circle diameter + */ + public static void init(int width, int height, float circleDiameter) { + containerWidth = width; + containerHeight = height; + + // equivalent to what happens in Slider.init() + scale = (int) (circleDiameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480) + //scale = scale * 118 / 128; //for curves exactly as big as the sliderball + FrameBufferCache.init(width, height); + NewCurveStyleState.initUnitCone(); + } + + /** + * Undo the static state. Static state setup caused by calls to + * {@link #draw(org.newdawn.slick.Color, org.newdawn.slick.Color, int, int)} + * are undone. + */ + public static void shutdown() { + staticState.shutdown(); + //FrameBufferCache.shutdown(); + } + + /** + * Creates an object to hold the render state that's necessary to draw a curve. + * @param hitObject the HitObject that represents this curve, just used as a unique ID + * @param curve the points along the curve to be drawn + */ + public LegacyCurveRenderState(HitObject hitObject, Vec2f[] curve) { + this.hitObject = hitObject; + this.curve = curve; + initFBO(); + } + + + private void initFBO() { + FrameBufferCache cache = FrameBufferCache.getInstance(); + Rendertarget mapping = cache.get(hitObject); + if(mapping == null) + mapping = cache.insert(hitObject); + fbo = mapping; + + createVertexBuffer(fbo.getVbo() + + ); + //write impossible value to make sure the fbo is cleared + lastPointDrawn=-1; + spliceFrom=spliceTo=-1; + } + + /** + * Splice the curve + * @param from start index to splice + * @param to end index to splice + */ + public void splice(int from, int to) { + spliceFrom = from * 2; + spliceTo = to * 2; + firstPointDrawn = -1; // force redraw + lastPointDrawn = -1; // force redraw + } + + /** + * Draw a curve to the screen that's tinted with `color`. The first time + * this is called this caches the image result of the curve and on subsequent + * runs it just draws the cached copy to the screen. + * @param color tint of the curve + * @param borderColor the curve border color + * @param from index to draw from + * @param to index to draw to (exclusive) + */ + public void draw(Color color, Color borderColor, int from, int to) { + float alpha = color.a; + + if (fbo == null) { + // this should not be null, but issue #106 claims it is possible, at least 3 people had this... + // debugging shows that the draw was called after discardGeometry was, which does not really make sense + initFBO(); + } + + if (lastPointDrawn != to || firstPointDrawn != from) { + int oldFb = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT); + int oldTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); + //glGetInteger requires a buffer of size 16, even though just 4 + //values are returned in this specific case + IntBuffer oldViewport = BufferUtils.createIntBuffer(16); + GL11.glGetInteger(GL11.GL_VIEWPORT, oldViewport); + EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo.getID()); + GL11.glViewport(0, 0, fbo.width, fbo.height); + GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + this.renderCurve(color, borderColor, from, to, firstPointDrawn != from); + lastPointDrawn = to; + firstPointDrawn = from; + color.a = 1f; + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, oldTex); + EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFb); + GL11.glViewport(oldViewport.get(0), oldViewport.get(1), oldViewport.get(2), oldViewport.get(3)); + } + + // draw a fullscreen quad with the texture that contains the curve + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(GL11.GL_TEXTURE_1D); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo.getTextureID()); + GL11.glBegin(GL11.GL_QUADS); + GL11.glColor4f(1.0f, 1.0f, 1.0f, alpha); + GL11.glTexCoord2f(1.0f, 1.0f); + GL11.glVertex2i(fbo.width, 0); + GL11.glTexCoord2f(0.0f, 1.0f); + GL11.glVertex2i(0, 0); + GL11.glTexCoord2f(0.0f, 0.0f); + GL11.glVertex2i(0, fbo.height); + GL11.glTexCoord2f(1.0f, 0.0f); + GL11.glVertex2i(fbo.width, fbo.height); + GL11.glEnd(); + } + + /** + * Discard the cache mapping for this curve object. + */ + public void discardGeometry() { + fbo = null; + FrameBufferCache.getInstance().freeMappingFor(hitObject); + } + + /** + * A structure to hold all the important OpenGL state that needs to be + * changed to draw the curve. This is used to backup and restore the state + * so that the code outside of this (mainly Slick2D) doesn't break. + */ + private class RenderState { + boolean smoothedPoly; + boolean blendEnabled; + boolean depthEnabled; + boolean depthWriteEnabled; + boolean texEnabled; + int texUnit; + int oldProgram; + int oldArrayBuffer; + } + + /** + * Backup the current state of the relevant OpenGL state and change it to + * what's needed to draw the curve. + */ + private RenderState saveRenderState() { + RenderState state = new RenderState(); + state.smoothedPoly = GL11.glGetBoolean(GL11.GL_POLYGON_SMOOTH); + state.blendEnabled = GL11.glGetBoolean(GL11.GL_BLEND); + state.depthEnabled = GL11.glGetBoolean(GL11.GL_DEPTH_TEST); + state.depthWriteEnabled = GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK); + state.texEnabled = GL11.glGetBoolean(GL11.GL_TEXTURE_2D); + state.texUnit = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE); + state.oldProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM); + state.oldArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING); + GL11.glDisable(GL11.GL_POLYGON_SMOOTH); + GL11.glDisable(GL11.GL_BLEND); + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glDepthMask(true); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glEnable(GL11.GL_TEXTURE_1D); + GL11.glBindTexture(GL11.GL_TEXTURE_1D, staticState.gradientTexture); + GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR); + GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP); + + GL20.glUseProgram(0); + + GL11.glMatrixMode(GL11.GL_PROJECTION); + GL11.glPushMatrix(); + GL11.glLoadIdentity(); + GL11.glMatrixMode(GL11.GL_MODELVIEW); + GL11.glPushMatrix(); + GL11.glLoadIdentity(); + + return state; + } + + /** + * Restore the old OpenGL state that's backed up in {@code state}. + * @param state the old state to restore + */ + private void restoreRenderState(RenderState state) { + GL11.glMatrixMode(GL11.GL_PROJECTION); + GL11.glPopMatrix(); + GL11.glMatrixMode(GL11.GL_MODELVIEW); + GL11.glPopMatrix(); + GL11.glEnable(GL11.GL_BLEND); + GL20.glUseProgram(state.oldProgram); + GL13.glActiveTexture(state.texUnit); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, state.oldArrayBuffer); + if (!state.depthWriteEnabled) + GL11.glDepthMask(false); + if (!state.depthEnabled) + GL11.glDisable(GL11.GL_DEPTH_TEST); + if (state.texEnabled) + GL11.glEnable(GL11.GL_TEXTURE_2D); + if (state.smoothedPoly) + GL11.glEnable(GL11.GL_POLYGON_SMOOTH); + if (!state.blendEnabled) + GL11.glDisable(GL11.GL_BLEND); + } + + /** + * Write the vertices and (with position and texture coordinates) for the full + * curve into the OpenGL buffer with the ID specified by {@code bufferID} + * @param bufferID the buffer ID for the OpenGL buffer the vertices should be written into + */ + private void createVertexBuffer(int bufferID) { + int arrayBufferBinding = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING); + FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * (2 * curve.length - 1) * (NewCurveStyleState.DIVIDES + 2)).asFloatBuffer(); + if (curve.length > 0) { + fillCone(buff, curve[0].x, curve[0].y); + } + + for (int i = 1; i < curve.length; ++i) { + float x = curve[i].x; + float y = curve[i].y; + fillCone(buff, x, y); + float last_x = curve[i - 1].x; + float last_y = curve[i - 1].y; + double diff_x = x - last_x; + double diff_y = y - last_y; + float dist = Utils.distance(x, y, last_x, last_y); + if (dist < Circle.diameter / 8) { + x = (float) (x - diff_x / 2); + y = (float) (y - diff_y / 2); + } else { + // don't mind me + x = -100f; + y = -100f; + } + fillCone(buff, x, y); + } + buff.flip(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buff, GL15.GL_STATIC_DRAW); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, arrayBufferBinding); + } + + /** + * Do the actual drawing of the curve into the currently bound framebuffer. + * @param color the color of the curve + * @param borderColor the curve border color + */ + private void renderCurve(Color color, Color borderColor, int from, int to, boolean clearFirst) { + staticState.initGradient(); + RenderState state = saveRenderState(); + staticState.initShaderProgram(); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, fbo.getVbo()); + GL20.glUseProgram(staticState.program); + GL20.glEnableVertexAttribArray(staticState.attribLoc); + GL20.glEnableVertexAttribArray(staticState.texCoordLoc); + GL20.glUniform1i(staticState.texLoc, 0); + GL20.glUniform3f(staticState.colLoc, color.r, color.g, color.b); + GL20.glUniform4f(staticState.colBorderLoc, borderColor.r, borderColor.g, borderColor.b, borderColor.a); + //stride is 6*4 for the floats (4 bytes) (u,v)(x,y,z,w) + //2*4 is for skipping the first 2 floats (u,v) + GL20.glVertexAttribPointer(staticState.attribLoc, 4, GL11.GL_FLOAT, false, 6 * 4, 2 * 4); + GL20.glVertexAttribPointer(staticState.texCoordLoc, 2, GL11.GL_FLOAT, false, 6 * 4, 0); + if (clearFirst) { + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + } + renderCurve(from, to); + GL11.glFlush(); + GL20.glDisableVertexAttribArray(staticState.texCoordLoc); + GL20.glDisableVertexAttribArray(staticState.attribLoc); + restoreRenderState(state); + } + + private void renderCurve(int from, int to) { + for (int i = from * 2; i < to * 2 - 1; ++i) { + if (spliceFrom <= i && i <= spliceTo) { + continue; + } + GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2); + } + } + + /** + * Fill {@code buff} with the texture coordinates and positions for a cone + * that has its center at the coordinates {@code (x1,y1)}. + * @param buff the buffer to be filled + * @param x1 x-coordinate of the cone + * @param y1 y-coordinate of the cone + */ + protected void fillCone(FloatBuffer buff, float x1, float y1) { + float divx = containerWidth / 2.0f; + float divy = containerHeight / 2.0f; + float offx = -1.0f; + float offy = 1.0f; + float radius = scale / 2; + + for (int i = 0; i < NewCurveStyleState.unitCone.length / 6; ++i) { + buff.put(NewCurveStyleState.unitCone[i * 6 + 0]); + buff.put(NewCurveStyleState.unitCone[i * 6 + 1]); + buff.put(offx + (x1 + radius * NewCurveStyleState.unitCone[i * 6 + 2]) / divx); + buff.put(offy - (y1 + radius * NewCurveStyleState.unitCone[i * 6 + 3]) / divy); + buff.put(NewCurveStyleState.unitCone[i * 6 + 4]); + buff.put(NewCurveStyleState.unitCone[i * 6 + 5]); + } + } + + /** + * Contains all the necessary state that needs to be tracked to draw curves + * in the new style and not re-create the shader each time. + * + * @author Bigpet {@literal } + */ + private static class NewCurveStyleState { + /** + * Used for new style Slider rendering, defines how many vertices the + * base of the cone has that is used to draw the curve. + */ + protected static final int DIVIDES = 30; + + /** + * Array to hold the dummy vertex data (texture coordinates and position) + * of a cone with DIVIDES vertices at its base, that is centered around + * (0,0) and has a radius of 1 (so that it can be translated and scaled easily). + */ + protected static float[] unitCone = new float[(DIVIDES + 2) * 6]; + + /** OpenGL shader program ID used to draw and recolor the curve. */ + protected int program = 0; + + /** OpenGL shader attribute location of the vertex position attribute. */ + protected int attribLoc = 0; + + /** OpenGL shader attribute location of the texture coordinate attribute. */ + protected int texCoordLoc = 0; + + /** OpenGL shader uniform location of the color attribute. */ + protected int colLoc = 0; + + /** OpenGL shader uniform location of the border color attribute. */ + protected int colBorderLoc = 0; + + /** OpenGL shader uniform location of the texture sampler attribute. */ + protected int texLoc = 0; + + /** OpenGL texture id for the gradient texture for the curve. */ + protected int gradientTexture = 0; + + /** + * Reads the first row of the slider gradient texture and upload it as + * a 1D texture to OpenGL if it hasn't already been done. + */ + public void initGradient() { + if (gradientTexture == 0) { + Image slider = GameImage.SLIDER_GRADIENT_EX.getImage().getScaledCopy(1.0f / GameImage.getUIscale()); + staticState.gradientTexture = GL11.glGenTextures(); + ByteBuffer buff = BufferUtils.createByteBuffer(slider.getWidth() * 4); + for (int i = 0; i < slider.getWidth(); ++i) { + Color col = slider.getColor(i, 0); + buff.put((byte) (255 * col.r)); + buff.put((byte) (255 * col.g)); + buff.put((byte) (255 * col.b)); + buff.put((byte) (255 * col.a)); + } + buff.flip(); + GL11.glBindTexture(GL11.GL_TEXTURE_1D, gradientTexture); + GL11.glTexImage1D(GL11.GL_TEXTURE_1D, 0, GL11.GL_RGBA, slider.getWidth(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buff); + EXTFramebufferObject.glGenerateMipmapEXT(GL11.GL_TEXTURE_1D); + } + } + + /** + * Write the data into {@code unitCone} if it hasn't already been initialized. + */ + public static void initUnitCone() { + int index = 0; + //check if initialization has already happened + if (unitCone[0] == 0.0f) { + //tip of the cone + //vec2 texture coordinates + unitCone[index++] = 1.0f; + unitCone[index++] = 0.5f; + + //vec4 position + unitCone[index++] = 0.0f; + unitCone[index++] = 0.0f; + unitCone[index++] = 0.0f; + unitCone[index++] = 1.0f; + for (int j = 0; j < NewCurveStyleState.DIVIDES; ++j) { + double phase = j * (float) Math.PI * 2 / NewCurveStyleState.DIVIDES; + //vec2 texture coordinates + unitCone[index++] = 0.0f; + unitCone[index++] = 0.5f; + //vec4 positon + unitCone[index++] = (float) Math.sin(phase); + unitCone[index++] = (float) Math.cos(phase); + unitCone[index++] = 1.0f; + unitCone[index++] = 1.0f; + } + //vec2 texture coordinates + unitCone[index++] = 0.0f; + unitCone[index++] = 0.5f; + //vec4 positon + unitCone[index++] = (float) Math.sin(0.0f); + unitCone[index++] = (float) Math.cos(0.0f); + unitCone[index++] = 1.0f; + unitCone[index++] = 1.0f; + } + } + + /** + * Compiles and links the shader program for the new style curve objects + * if it hasn't already been compiled and linked. + */ + public void initShaderProgram() { + if (program == 0) { + program = GL20.glCreateProgram(); + int vtxShdr = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); + int frgShdr = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); + GL20.glShaderSource(vtxShdr, "#version 110\n" + + "\n" + + "attribute vec4 in_position;\n" + + "attribute vec2 in_tex_coord;\n" + + "\n" + + "varying vec2 tex_coord;\n" + + "void main()\n" + + "{\n" + + " gl_Position = in_position;\n" + + " tex_coord = in_tex_coord;\n" + + "}"); + GL20.glCompileShader(vtxShdr); + int res = GL20.glGetShaderi(vtxShdr, GL20.GL_COMPILE_STATUS); + if (res != GL11.GL_TRUE) { + String error = GL20.glGetShaderInfoLog(vtxShdr, 1024); + Log.error("Vertex Shader compilation failed.", new Exception(error)); + } + GL20.glShaderSource(frgShdr, "#version 110\n" + + "\n" + + "uniform sampler1D tex;\n" + + "uniform vec2 tex_size;\n" + + "uniform vec3 col_tint;\n" + + "uniform vec4 col_border;\n" + + "\n" + + "varying vec2 tex_coord;\n" + + "\n" + + "void main()\n" + + "{\n" + + " vec4 in_color = texture1D(tex, tex_coord.x);\n" + + " float blend_factor = in_color.r-in_color.b;\n" + + " vec4 new_color = vec4(mix(in_color.xyz*col_border.xyz,col_tint,blend_factor),in_color.w);\n" + + " gl_FragColor = new_color;\n" + + "}"); + GL20.glCompileShader(frgShdr); + res = GL20.glGetShaderi(frgShdr, GL20.GL_COMPILE_STATUS); + if (res != GL11.GL_TRUE) { + String error = GL20.glGetShaderInfoLog(frgShdr, 1024); + Log.error("Fragment Shader compilation failed.", new Exception(error)); + } + GL20.glAttachShader(program, vtxShdr); + GL20.glAttachShader(program, frgShdr); + GL20.glLinkProgram(program); + res = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS); + if (res != GL11.GL_TRUE) { + String error = GL20.glGetProgramInfoLog(program, 1024); + Log.error("Program linking failed.", new Exception(error)); + } + GL20.glDeleteShader(vtxShdr); + GL20.glDeleteShader(frgShdr); + attribLoc = GL20.glGetAttribLocation(program, "in_position"); + texCoordLoc = GL20.glGetAttribLocation(program, "in_tex_coord"); + texLoc = GL20.glGetUniformLocation(program, "tex"); + colLoc = GL20.glGetUniformLocation(program, "col_tint"); + colBorderLoc = GL20.glGetUniformLocation(program, "col_border"); + } + } + + /** + * Cleanup any OpenGL objects that may have been initialized. + */ + private void shutdown() { + if (gradientTexture != 0) { + GL11.glDeleteTextures(gradientTexture); + gradientTexture = 0; + } + + if (program != 0) { + GL20.glDeleteProgram(program); + program = 0; + attribLoc = 0; + texCoordLoc = 0; + colLoc = 0; + colBorderLoc = 0; + texLoc = 0; + } + } + } +} diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 83220e61..404f56cc 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -42,6 +42,7 @@ import itdelatrisu.opsu.objects.Slider; import itdelatrisu.opsu.objects.Spinner; import itdelatrisu.opsu.objects.curves.Curve; +import itdelatrisu.opsu.objects.curves.FakeCombinedCurve; import itdelatrisu.opsu.objects.curves.Vec2f; import itdelatrisu.opsu.options.Options; import itdelatrisu.opsu.render.FrameBufferCache; @@ -63,11 +64,7 @@ import java.io.File; import java.io.IOException; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Stack; +import java.util.*; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.Display; @@ -319,6 +316,9 @@ public enum Restart { /** The video seek time (if any). */ private int videoSeekTime; + /** The merged slider. */ + private FakeCombinedCurve mergedslider; + /** Music position bar background colors. */ private static final Color MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f), @@ -1201,6 +1201,19 @@ else if (key == Options.getGameKeyRight()) beatmap.objects[objectIndex++].getTime() <= checkpoint) ; objectIndex--; + + if (Options.isExperimentalSliderStyle() && Options.isMergingSliders()) { + int obj = objectIndex; + while (obj < gameObjects.length) { + if (gameObjects[obj] instanceof Slider) { + slidercurveFrom = slidercurveTo = ((Slider) gameObjects[obj]).baseSliderFrom; + break; + } + obj++; + } + spliceSliderCurve(-1, -1); + } + lastReplayTime = beatmap.objects[objectIndex].getTime(); lastTrackPosition = checkpoint; } catch (SlickException e) { @@ -1586,8 +1599,37 @@ else if (hitObject.isSpinner()) MusicController.pause(); SoundController.mute(false); + + if (Options.isMergingSliders()) { + if (!Options.isShrinkingSliders()) { + mergedslider = null; // workaround for yugecin/opsu-dance#130 + } + if (mergedslider == null) { + List curvepoints = new ArrayList<>(); + for (GameObject gameObject : gameObjects) { + if (gameObject instanceof Slider) { + ((Slider) gameObject).baseSliderFrom = curvepoints.size(); + curvepoints.addAll(Arrays.asList(((Slider) gameObject).getCurve().getCurvePoints())); + } + } + if (curvepoints.size() > 0) { + mergedslider = new FakeCombinedCurve(curvepoints.toArray(new Vec2f[curvepoints.size()])); + } + } else { + int base = 0; + for (GameObject gameObject : gameObjects) { + if (gameObject instanceof Slider) { + ((Slider) gameObject).baseSliderFrom = base; + base += ((Slider) gameObject).getCurve().getCurvePoints().length; + } + } + } + } } + slidercurveFrom = 0; + slidercurveTo = 0; + skipButton.resetHover(); if (isReplay || GameMod.AUTO.isActive()) playbackSpeed.getButton().resetHover(); @@ -1603,11 +1645,31 @@ public void leave(GameContainer container, StateBasedGame game) if (GameMod.AUTO.isActive() || isReplay) UI.getCursor().hide(); + mergedslider = null; + // replays if (isReplay) GameMod.loadModState(previousMods); } + /** Index from which to draw the experminental merged slider. */ + private int slidercurveFrom; + + /** Index to draw the experminental merged slider. */ + private int slidercurveTo; + + public void setSlidercurveFrom(int slidercurveFrom) { + this.slidercurveFrom = Math.max(slidercurveFrom, this.slidercurveFrom); + } + + public void setSlidercurveTo(int slidercurveTo) { + this.slidercurveTo = Math.max(slidercurveTo, this.slidercurveTo); + } + + public void spliceSliderCurve(int from, int to) { + this.mergedslider.splice(from, to); + } + /** * Draws hit objects, hit results, and follow points. * @param g the graphics context @@ -1617,6 +1679,10 @@ private void drawHitObjects(Graphics g, int trackPosition) { // draw result objects (under) data.drawHitResults(trackPosition, false); + if (Options.isMergingSliders() && mergedslider != null) { + mergedslider.draw(Color.white, this.slidercurveFrom, this.slidercurveTo); + } + // include previous object in follow points int lastObjectIndex = -1; if (objectIndex > 0 && objectIndex < beatmap.objects.length && From 3db9be9bbbfada52f380e0f9af278f3bf572a97a Mon Sep 17 00:00:00 2001 From: yugecin Date: Sat, 4 Mar 2017 11:26:19 +0100 Subject: [PATCH 2/7] add slidergradient_ex.png --- res/slidergradient_ex.png | Bin 0 -> 4719 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/slidergradient_ex.png diff --git a/res/slidergradient_ex.png b/res/slidergradient_ex.png new file mode 100644 index 0000000000000000000000000000000000000000..363fa41847974f260ec337204f066672f9946d8d GIT binary patch literal 4719 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 Date: Sat, 4 Mar 2017 11:33:26 +0100 Subject: [PATCH 3/7] don't use black reversearrows when the experimental slider style is used --- src/itdelatrisu/opsu/objects/Slider.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index e2dc241a..7412762f 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -295,8 +295,10 @@ public void draw(Graphics g, int trackPosition) { Image arrow = GameImage.REVERSEARROW.getImage(); // bouncing animation //arrow = arrow.getScaledCopy((float) (1 + 0.2d * ((trackPosition + sliderTime * tcurRepeat) % 292) / 292)); - float colorLuminance = Utils.getLuminance(color); - Color arrowColor = colorLuminance < 0.8f ? Color.white : Color.black; + Color arrowColor = Color.white; + if (!Options.isExperimentalSliderStyle() && Utils.getLuminance(color) >= 0.8f) { + arrowColor = Color.black; + } if (tcurRepeat == 0) { arrow.setAlpha(Options.isSliderSnaking() ? decorationsAlpha : 1f); } else { From bb94617e2487c4386ed0d4b61a84eb2de1d0a312 Mon Sep 17 00:00:00 2001 From: yugecin Date: Sat, 4 Mar 2017 11:45:38 +0100 Subject: [PATCH 4/7] Fix incorrect slider rendering after restart --- src/itdelatrisu/opsu/render/LegacyCurveRenderState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java index d47b1045..77c12580 100644 --- a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java +++ b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java @@ -93,7 +93,7 @@ public static void init(int width, int height, float circleDiameter) { */ public static void shutdown() { staticState.shutdown(); - //FrameBufferCache.shutdown(); + FrameBufferCache.shutdown(); } /** From d89eb8525bd931ab5b1adacef94516c596b957b5 Mon Sep 17 00:00:00 2001 From: yugecin Date: Sun, 5 Mar 2017 11:43:25 +0100 Subject: [PATCH 5/7] fix sliders disappearing when multiple sliders are active or when later sliders is done earlier (2b maps etc) --- src/itdelatrisu/opsu/Utils.java | 10 ++++++ src/itdelatrisu/opsu/objects/Slider.java | 11 +++--- .../opsu/objects/curves/Curve.java | 4 +-- .../objects/curves/FakeCombinedCurve.java | 23 ++++++++++++ .../opsu/render/LegacyCurveRenderState.java | 25 ++++++++++++- src/itdelatrisu/opsu/states/Game.java | 36 +++---------------- 6 files changed, 67 insertions(+), 42 deletions(-) diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index f4b816e2..67385c2b 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -756,4 +756,14 @@ public static long getUsedMemory() { Runtime r = Runtime.getRuntime(); return r.totalMemory() - r.freeMemory(); } + + public static class Pair { + public F first; + public S second; + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + } + } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 7412762f..25ed5f41 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -440,12 +440,13 @@ private boolean drawSliderTrack(int trackPosition, double snakingSliderProgress) if (Options.isMergingSliders()) { if (Options.isShrinkingSliders() && curveIntervalFrom > 0) { if (hitObject.getRepeatCount() % 2 == 0) { - game.spliceSliderCurve(baseSliderFrom + (int) ((1d - curveIntervalFrom) * curvelen) - 1, baseSliderFrom + curvelen); + game.addMergedSliderPointsToRender(baseSliderFrom, baseSliderFrom + (int) ((1d - curveIntervalFrom) * curvelen)); } else { - game.setSlidercurveFrom(baseSliderFrom + (int) (curveIntervalFrom * curvelen) + 1); + game.addMergedSliderPointsToRender(baseSliderFrom + (int) (curveIntervalFrom * curvelen) + 1, baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); } + } else { + game.addMergedSliderPointsToRender(baseSliderFrom, baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); } - game.setSlidercurveTo(baseSliderFrom + (int) (curveIntervalTo * curve.getCurvePoints().length)); } else { if (Options.isShrinkingSliders() && curveIntervalFrom > 0 && hitObject.getRepeatCount() % 2 == 0) { curve.splice((int) ((1d - curveIntervalFrom) * curvelen), curvelen); @@ -757,10 +758,6 @@ else if (GameMod.RELAX.isActive() && trackPosition >= time) // calculate and send slider result hitResult(); - if (Options.isMergingSliders()) { - game.setSlidercurveFrom(baseSliderFrom + curve.getCurvePoints().length + 1); - } - return true; } diff --git a/src/itdelatrisu/opsu/objects/curves/Curve.java b/src/itdelatrisu/opsu/objects/curves/Curve.java index 63fe5949..0afb7bd6 100644 --- a/src/itdelatrisu/opsu/objects/curves/Curve.java +++ b/src/itdelatrisu/opsu/objects/curves/Curve.java @@ -43,7 +43,7 @@ public abstract class Curve { protected static float CURVE_POINTS_SEPERATION = 5; /** The curve border color. */ - private static Color borderColor; + protected static Color borderColor; /** Whether mmsliders are supported. */ private static boolean mmsliderSupported = false; @@ -61,7 +61,7 @@ public abstract class Curve { private CurveRenderState renderState; /** Per-curve render-state used for the legacy style curve renders. */ - private LegacyCurveRenderState legacyRenderState; + protected LegacyCurveRenderState legacyRenderState; /** Points along the curve (set by inherited classes). */ protected Vec2f[] curve; diff --git a/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java b/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java index 085fe6da..38437054 100644 --- a/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java +++ b/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java @@ -17,13 +17,36 @@ */ package itdelatrisu.opsu.objects.curves; +import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; +import itdelatrisu.opsu.render.LegacyCurveRenderState; +import org.newdawn.slick.Color; + +import java.util.LinkedList; public class FakeCombinedCurve extends Curve { + private LinkedList> pointsToRender; + public FakeCombinedCurve(Vec2f[] points) { super(new HitObject(0, 0, 0), false); this.curve = points; + pointsToRender = new LinkedList<>(); + } + + public void initForFrame() { + pointsToRender.clear(); + } + + public void addRange(int from, int to) { + pointsToRender.add(new Utils.Pair<>(from, to)); + } + + @Override + public void draw(Color color) { + if (legacyRenderState == null) + legacyRenderState = new LegacyCurveRenderState(hitObject, curve); + legacyRenderState.draw(color, borderColor, pointsToRender); } @Override diff --git a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java index 77c12580..e7e6e22d 100644 --- a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java +++ b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java @@ -31,6 +31,7 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.LinkedList; /** * Hold the temporary render state that needs to be restored again after the new @@ -68,6 +69,8 @@ public class LegacyCurveRenderState { /** Index of the curve array that indicates the end position of the slider part that should not be drawn. */ private int spliceTo; + protected LinkedList> pointsToRender; + /** * Set the width and height of the container that Curves get drawn into. * Should be called before any curves are drawn. @@ -135,6 +138,14 @@ public void splice(int from, int to) { lastPointDrawn = -1; // force redraw } + public void draw(Color color, Color borderColor, LinkedList> pointsToRender) { + lastPointDrawn = -1; + firstPointDrawn = -1; + this.pointsToRender = pointsToRender; + draw(color, borderColor, 0, curve.length); + this.pointsToRender = null; + } + /** * Draw a curve to the screen that's tinted with `color`. The first time * this is called this caches the image result of the curve and on subsequent @@ -337,7 +348,11 @@ private void renderCurve(Color color, Color borderColor, int from, int to, boole if (clearFirst) { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); } - renderCurve(from, to); + if (pointsToRender == null) { + renderCurve(from, to); + } else { + renderCurve(); + } GL11.glFlush(); GL20.glDisableVertexAttribArray(staticState.texCoordLoc); GL20.glDisableVertexAttribArray(staticState.attribLoc); @@ -353,6 +368,14 @@ private void renderCurve(int from, int to) { } } + private void renderCurve() { + for (Utils.Pair point : pointsToRender) { + for (int i = point.first * 2; i < point.second * 2 - 1; ++i) { + GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2); + } + } + } + /** * Fill {@code buff} with the texture coordinates and positions for a cone * that has its center at the coordinates {@code (x1,y1)}. diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 404f56cc..811f3db0 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -1202,18 +1202,6 @@ else if (key == Options.getGameKeyRight()) ; objectIndex--; - if (Options.isExperimentalSliderStyle() && Options.isMergingSliders()) { - int obj = objectIndex; - while (obj < gameObjects.length) { - if (gameObjects[obj] instanceof Slider) { - slidercurveFrom = slidercurveTo = ((Slider) gameObjects[obj]).baseSliderFrom; - break; - } - obj++; - } - spliceSliderCurve(-1, -1); - } - lastReplayTime = beatmap.objects[objectIndex].getTime(); lastTrackPosition = checkpoint; } catch (SlickException e) { @@ -1627,9 +1615,6 @@ else if (hitObject.isSpinner()) } } - slidercurveFrom = 0; - slidercurveTo = 0; - skipButton.resetHover(); if (isReplay || GameMod.AUTO.isActive()) playbackSpeed.getButton().resetHover(); @@ -1652,22 +1637,8 @@ public void leave(GameContainer container, StateBasedGame game) GameMod.loadModState(previousMods); } - /** Index from which to draw the experminental merged slider. */ - private int slidercurveFrom; - - /** Index to draw the experminental merged slider. */ - private int slidercurveTo; - - public void setSlidercurveFrom(int slidercurveFrom) { - this.slidercurveFrom = Math.max(slidercurveFrom, this.slidercurveFrom); - } - - public void setSlidercurveTo(int slidercurveTo) { - this.slidercurveTo = Math.max(slidercurveTo, this.slidercurveTo); - } - - public void spliceSliderCurve(int from, int to) { - this.mergedslider.splice(from, to); + public void addMergedSliderPointsToRender(int from, int to) { + mergedslider.addRange(from, to); } /** @@ -1680,7 +1651,8 @@ private void drawHitObjects(Graphics g, int trackPosition) { data.drawHitResults(trackPosition, false); if (Options.isMergingSliders() && mergedslider != null) { - mergedslider.draw(Color.white, this.slidercurveFrom, this.slidercurveTo); + mergedslider.draw(Color.white); + mergedslider.initForFrame(); } // include previous object in follow points From 7471231b34b42e8beb10133b50f09ac5b07ae1e2 Mon Sep 17 00:00:00 2001 From: yugecin Date: Sun, 5 Mar 2017 20:11:18 +0100 Subject: [PATCH 6/7] remove the pair utilclass --- src/itdelatrisu/opsu/Utils.java | 10 ---------- .../opsu/objects/curves/FakeCombinedCurve.java | 11 ++++++----- .../opsu/render/LegacyCurveRenderState.java | 13 ++++++++----- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 67385c2b..f4b816e2 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -756,14 +756,4 @@ public static long getUsedMemory() { Runtime r = Runtime.getRuntime(); return r.totalMemory() - r.freeMemory(); } - - public static class Pair { - public F first; - public S second; - public Pair(F first, S second) { - this.first = first; - this.second = second; - } - } - } diff --git a/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java b/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java index 38437054..9050de23 100644 --- a/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java +++ b/src/itdelatrisu/opsu/objects/curves/FakeCombinedCurve.java @@ -17,21 +17,21 @@ */ package itdelatrisu.opsu.objects.curves; -import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.beatmap.HitObject; import itdelatrisu.opsu.render.LegacyCurveRenderState; import org.newdawn.slick.Color; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.List; public class FakeCombinedCurve extends Curve { - private LinkedList> pointsToRender; + private List pointsToRender; public FakeCombinedCurve(Vec2f[] points) { super(new HitObject(0, 0, 0), false); this.curve = points; - pointsToRender = new LinkedList<>(); + pointsToRender = new ArrayList<>(); } public void initForFrame() { @@ -39,7 +39,8 @@ public void initForFrame() { } public void addRange(int from, int to) { - pointsToRender.add(new Utils.Pair<>(from, to)); + pointsToRender.add(from); + pointsToRender.add(to); } @Override diff --git a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java index e7e6e22d..45bd0232 100644 --- a/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java +++ b/src/itdelatrisu/opsu/render/LegacyCurveRenderState.java @@ -31,7 +31,8 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; -import java.util.LinkedList; +import java.util.Iterator; +import java.util.List; /** * Hold the temporary render state that needs to be restored again after the new @@ -69,7 +70,8 @@ public class LegacyCurveRenderState { /** Index of the curve array that indicates the end position of the slider part that should not be drawn. */ private int spliceTo; - protected LinkedList> pointsToRender; + /** List holding pairs of 2 numbers in a sequential order, indicating the start- and endindex of the curvearray to render. */ + protected List pointsToRender; /** * Set the width and height of the container that Curves get drawn into. @@ -138,7 +140,7 @@ public void splice(int from, int to) { lastPointDrawn = -1; // force redraw } - public void draw(Color color, Color borderColor, LinkedList> pointsToRender) { + public void draw(Color color, Color borderColor, List pointsToRender) { lastPointDrawn = -1; firstPointDrawn = -1; this.pointsToRender = pointsToRender; @@ -369,8 +371,9 @@ private void renderCurve(int from, int to) { } private void renderCurve() { - for (Utils.Pair point : pointsToRender) { - for (int i = point.first * 2; i < point.second * 2 - 1; ++i) { + Iterator iter = pointsToRender.iterator(); + while (iter.hasNext()) { + for (int i = iter.next() * 2, end = iter.next() * 2 - 1; i < end; ++i) { GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2); } } From ff3a22e142597455ad4ad56254ea704efdf88d58 Mon Sep 17 00:00:00 2001 From: yugecin Date: Sun, 5 Mar 2017 20:15:20 +0100 Subject: [PATCH 7/7] more descriptive descriptions --- src/itdelatrisu/opsu/options/Options.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/itdelatrisu/opsu/options/Options.java b/src/itdelatrisu/opsu/options/Options.java index 0a25a247..8a1e913f 100644 --- a/src/itdelatrisu/opsu/options/Options.java +++ b/src/itdelatrisu/opsu/options/Options.java @@ -569,8 +569,8 @@ public void setValue(int value) { SNAKING_SLIDERS ("Snaking sliders", "SnakingSliders", "Sliders gradually snake out from their starting point.", true), EXPERIMENTAL_SLIDERS ("Enable", "ExperimentalSliders", "Enable experimental slider style", false), EXPERIMENTAL_SLIDERS_DRAW_ENDCIRCLES ("Draw slider endcircles", "ExSlDrawEnd", "Only for experimental sliders", false), - EXPERIMENTAL_SLIDERS_SHRINK ("Shrink sliders", "ExSlShrink", "Only for experimental sliders", true), - EXPERIMENTAL_SLIDERS_MERGE ("Merge sliders", "ExSlMerge", "Only for experimental sliders", true), + EXPERIMENTAL_SLIDERS_SHRINK ("Shrinking sliders", "ExSlShrink", "Sliders shrink toward their ending point when the ball passes.", true), + EXPERIMENTAL_SLIDERS_MERGE ("Merging sliders", "ExSlMerge", "Don't draw edges and combine slider tracks where sliders overlap.", true), SHOW_HIT_LIGHTING ("Hit lighting", "HitLighting", "Adds a subtle glow behind hit explosions which lights the playfield.", true), SHOW_COMBO_BURSTS ("Combo bursts", "ComboBurst", "A character image bursts from the side of the screen at combo milestones.", true), SHOW_PERFECT_HIT ("Perfect hits", "PerfectHit", "Shows perfect hit result bursts (300s, slider ticks).", true),