From 6f685cf5c7f8b7735e67e9f3ca79c6b42af6b671 Mon Sep 17 00:00:00 2001 From: MatteoS Date: Sun, 29 Nov 2015 09:56:10 +0100 Subject: [PATCH 01/16] Ingame scoreboard initial implementation --- src/itdelatrisu/opsu/GameData.java | 6 +-- src/itdelatrisu/opsu/ScoreData.java | 34 +++++++++++++++ src/itdelatrisu/opsu/states/Game.java | 60 +++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index 1dd3de06..a69020d1 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1396,10 +1396,9 @@ public class GameData { * @return the ScoreData object */ public ScoreData getScoreData(Beatmap beatmap) { - if (scoreData != null) - return scoreData; + if (scoreData == null) + scoreData = new ScoreData(); - scoreData = new ScoreData(); scoreData.timestamp = System.currentTimeMillis() / 1000L; scoreData.MID = beatmap.beatmapID; scoreData.MSID = beatmap.beatmapSetID; @@ -1419,6 +1418,7 @@ public class GameData { scoreData.mods = GameMod.getModState(); scoreData.replayString = (replay == null) ? null : replay.getReplayFilename(); scoreData.playerName = null; // TODO + return scoreData; } diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index bb14c88e..13b634e8 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -328,6 +328,40 @@ public class ScoreData implements Comparable { c.a = oldAlpha; } + /** + * Draws the score ingame (smaller and with less information). + * @param g the current graphics context + * @param vPos the base y position of the scoreboard + * @param rank the current rank of this score + * @param position the animated position offset + * @param data an instance of GameData to draw rank number + * @param alpha the transparancy of the score + * @param isActive if this score is the one currently played + */ + public void drawSmall(Graphics g, int vPos, int rank, float position, GameData data, float alpha, boolean isActive) { + + int rectHeight = data.getScoreSymbolImage('0').getHeight(); + int vertDistance = rectHeight + 10; + int yPos = (int)(vPos + position * vertDistance - rectHeight/2); + String scoreString = String.format("%d (%dx)", score, combo); + String rankString = String.format("%d", rank); + int rectWidth = (int) (170 * GameImage.getUIscale()); + + Color rectColor = isActive ? Colors.YELLOW_ALPHA : Colors.BLACK_ALPHA; + rectColor.a = 0.5f * alpha; + + g.setColor(rectColor); + g.fillRect(0, yPos, rectWidth, rectHeight); + data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, 0.5f*alpha, true); + if (playerName != null) { + Colors.WHITE_ALPHA.a = 0.5f * alpha; + Fonts.MEDIUM.drawString(0, yPos, playerName, Colors.WHITE_ALPHA); + } + Colors.WHITE_ALPHA.a = alpha; + Fonts.MEDIUMBOLD.drawString(0, yPos + rectHeight - Fonts.MEDIUM.getLineHeight(), scoreString, Colors.WHITE_ALPHA); + + } + /** * Returns the tooltip string for this score. */ diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 476a62c7..08d73856 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -101,6 +101,12 @@ public class Game extends BasicGameState { /** Maximum rotation, in degrees, over fade out upon death. */ private static final float MAX_ROTATION = 90f; + /** The duration of the score changing animation */ + private static final float SCOREBOARD_ANIMATION_TIME = 500f; + + /** The time the scoreboard takes to fade in */ + private static final float SCOREBOARD_FADE_IN_TIME = 500f; + /** Minimum time before start of song, in milliseconds, to process skip-related actions. */ private static final int SKIP_OFFSET = 2000; @@ -256,6 +262,18 @@ public class Game extends BasicGameState { /** Music position bar coordinates and dimensions (for replay seeking). */ private float musicBarX, musicBarY, musicBarWidth, musicBarHeight; + /** The previous scores. */ + private ScoreData[] previousScores; + + /** The current rank in the scores. */ + private int currentRank; + + /** The time the rank was last updated. */ + private int lastRankUpdateTime; + + /** Is the scoreboard visible? */ + private boolean scoreboardVisible; + /** Music position bar background colors. */ private static final Color MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f), @@ -482,6 +500,7 @@ public class Game extends BasicGameState { arrow.draw(width * 0.75f, height * 0.75f); } } + } // non-break @@ -563,6 +582,38 @@ public class Game extends BasicGameState { drawHitObjects(g, trackPosition); } + if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && scoreboardVisible) { + ScoreData currentScore = data.getScoreData(beatmap); + while (currentRank > 0 && previousScores[currentRank-1].score < currentScore.score) { + currentRank--; + lastRankUpdateTime = trackPosition; + } + + float animation = AnimationEquation.IN_OUT_QUAD.calc(Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f)); + float fadeIn = Utils.clamp((trackPosition - firstObjectTime) / SCOREBOARD_FADE_IN_TIME, 0f, 1f); + int scoreboardPosition = 2 * container.getHeight() / 3; + + if (currentRank < 4) { + //draw the (new) top 5 ranks + for (int i = 0; i < 4; i++) { + int ii = i + (i>=currentRank ? 1 : 0); + if (i < previousScores.length) + previousScores[i].drawSmall(g, scoreboardPosition, ii + 1, ii + (i==currentRank ? animation-3f : -2f), data, fadeIn, false); + } + currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, fadeIn, true); + } else { + //draw the top 2 and next 2 ranks + previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, fadeIn, false); + previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, fadeIn, false); + previousScores[currentRank-2].drawSmall(g, scoreboardPosition, currentRank - 1, animation - 1f, data, fadeIn*animation, false); + previousScores[currentRank-1].drawSmall(g, scoreboardPosition, currentRank, animation, data, fadeIn, false); + currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, fadeIn, true); + if (animation < 1.0f && currentRank < previousScores.length) { + previousScores[currentRank].drawSmall(g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, fadeIn*(1f - animation), false); + } + } + } + if (GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); @@ -986,6 +1037,9 @@ public class Game extends BasicGameState { case Input.KEY_F12: Utils.takeScreenShot(); break; + case Input.KEY_TAB: + scoreboardVisible = ! scoreboardVisible; + break; } } @@ -1138,6 +1192,12 @@ public class Game extends BasicGameState { if (beatmap == null || beatmap.objects == null) throw new RuntimeException("Running game with no beatmap loaded."); + // fetch previous results + previousScores = ScoreDB.getMapScores(beatmap); + lastRankUpdateTime = -1000; + if (previousScores != null) + currentRank = previousScores.length; + scoreboardVisible = true; // free all previously cached hitobject to framebuffer mappings if some still exist FrameBufferCache.getInstance().freeMap(); From 4b76d2c3cf3a91dce84646f427d03f2ba494f355 Mon Sep 17 00:00:00 2001 From: MatteoS Date: Mon, 30 Nov 2015 16:19:25 +0100 Subject: [PATCH 02/16] Scoreboard improvements --- src/itdelatrisu/opsu/ScoreData.java | 9 ++++--- src/itdelatrisu/opsu/states/Game.java | 38 ++++++++++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 13b634e8..71971160 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -30,6 +30,7 @@ import java.sql.SQLException; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; @@ -343,7 +344,8 @@ public class ScoreData implements Comparable { int rectHeight = data.getScoreSymbolImage('0').getHeight(); int vertDistance = rectHeight + 10; int yPos = (int)(vPos + position * vertDistance - rectHeight/2); - String scoreString = String.format("%d (%dx)", score, combo); + String scoreString = String.format(Locale.US, "%,d", score); + String comboString = String.format("%dx", combo); String rankString = String.format("%d", rank); int rectWidth = (int) (170 * GameImage.getUIscale()); @@ -352,13 +354,14 @@ public class ScoreData implements Comparable { g.setColor(rectColor); g.fillRect(0, yPos, rectWidth, rectHeight); - data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, 0.5f*alpha, true); + data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, 0.25f*alpha, true); if (playerName != null) { Colors.WHITE_ALPHA.a = 0.5f * alpha; Fonts.MEDIUM.drawString(0, yPos, playerName, Colors.WHITE_ALPHA); } Colors.WHITE_ALPHA.a = alpha; - Fonts.MEDIUMBOLD.drawString(0, yPos + rectHeight - Fonts.MEDIUM.getLineHeight(), scoreString, Colors.WHITE_ALPHA); + Fonts.MEDIUMBOLD.drawString(0, yPos + rectHeight - Fonts.MEDIUMBOLD.getLineHeight(), scoreString, Colors.WHITE_ALPHA); + Fonts.MEDIUMBOLD.drawString(rectWidth - Fonts.MEDIUMBOLD.getWidth(comboString), yPos + rectHeight - Fonts.MEDIUMBOLD.getLineHeight(), comboString, Colors.WHITE_ALPHA); } diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 08d73856..df6d9f10 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -274,6 +274,9 @@ public class Game extends BasicGameState { /** Is the scoreboard visible? */ private boolean scoreboardVisible; + /** The current aplha of the scoreboard. */ + private float currentScoreboardAlpha; + /** Music position bar background colors. */ private static final Color MUSICBAR_NORMAL = new Color(12, 9, 10, 0.25f), @@ -582,7 +585,7 @@ public class Game extends BasicGameState { drawHitObjects(g, trackPosition); } - if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive() && scoreboardVisible) { + if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive()) { ScoreData currentScore = data.getScoreData(beatmap); while (currentRank > 0 && previousScores[currentRank-1].score < currentScore.score) { currentRank--; @@ -590,7 +593,6 @@ public class Game extends BasicGameState { } float animation = AnimationEquation.IN_OUT_QUAD.calc(Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f)); - float fadeIn = Utils.clamp((trackPosition - firstObjectTime) / SCOREBOARD_FADE_IN_TIME, 0f, 1f); int scoreboardPosition = 2 * container.getHeight() / 3; if (currentRank < 4) { @@ -598,18 +600,18 @@ public class Game extends BasicGameState { for (int i = 0; i < 4; i++) { int ii = i + (i>=currentRank ? 1 : 0); if (i < previousScores.length) - previousScores[i].drawSmall(g, scoreboardPosition, ii + 1, ii + (i==currentRank ? animation-3f : -2f), data, fadeIn, false); + previousScores[i].drawSmall(g, scoreboardPosition, ii + 1, ii + (i==currentRank ? animation-3f : -2f), data, currentScoreboardAlpha, false); } - currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, fadeIn, true); + currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, currentScoreboardAlpha, true); } else { //draw the top 2 and next 2 ranks - previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, fadeIn, false); - previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, fadeIn, false); - previousScores[currentRank-2].drawSmall(g, scoreboardPosition, currentRank - 1, animation - 1f, data, fadeIn*animation, false); - previousScores[currentRank-1].drawSmall(g, scoreboardPosition, currentRank, animation, data, fadeIn, false); - currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, fadeIn, true); + previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, currentScoreboardAlpha, false); + previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, currentScoreboardAlpha, false); + previousScores[currentRank-2].drawSmall(g, scoreboardPosition, currentRank - 1, animation - 1f, data, currentScoreboardAlpha*animation, false); + previousScores[currentRank-1].drawSmall(g, scoreboardPosition, currentRank, animation, data, currentScoreboardAlpha, false); + currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, currentScoreboardAlpha, true); if (animation < 1.0f && currentRank < previousScores.length) { - previousScores[currentRank].drawSmall(g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, fadeIn*(1f - animation), false); + previousScores[currentRank].drawSmall(g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, currentScoreboardAlpha*(1f - animation), false); } } } @@ -668,6 +670,21 @@ public class Game extends BasicGameState { if (isReplay || GameMod.AUTO.isActive()) playbackSpeed.getButton().hoverUpdate(delta, mouseX, mouseY); int trackPosition = MusicController.getPosition(); + int firstObjectTime = beatmap.objects[0].getTime(); + + + if (previousScores != null && trackPosition > firstObjectTime) { + // show scoreboard when in break + if (scoreboardVisible || breakTime > 0) { + currentScoreboardAlpha += 1f/SCOREBOARD_FADE_IN_TIME * delta; + if (currentScoreboardAlpha > 1f) + currentScoreboardAlpha = 1f; + } else { + currentScoreboardAlpha -= 1f/SCOREBOARD_FADE_IN_TIME * delta; + if (currentScoreboardAlpha < 0f) + currentScoreboardAlpha = 0f; + } + } // returning from pause screen: must click previous mouse position if (pauseTime > -1) { @@ -1198,6 +1215,7 @@ public class Game extends BasicGameState { if (previousScores != null) currentRank = previousScores.length; scoreboardVisible = true; + currentScoreboardAlpha = 0f; // free all previously cached hitobject to framebuffer mappings if some still exist FrameBufferCache.getInstance().freeMap(); From d7c980e0a46804bb3990cbe618b8df87d8266716 Mon Sep 17 00:00:00 2001 From: Matteo Signer Date: Mon, 4 Apr 2016 10:59:50 +0200 Subject: [PATCH 03/16] Misc. bugfixes --- src/itdelatrisu/opsu/Opsu.java | 1 + src/itdelatrisu/opsu/db/ScoreDB.java | 17 +++++++++++++++-- src/itdelatrisu/opsu/states/Game.java | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index 82e9f553..4a7a3abd 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -230,6 +230,7 @@ public class Opsu extends StateBasedGame { } else { if (id == STATE_GAME) { MusicController.pause(); + MusicController.setPitch(1.0f); MusicController.resume(); } else songMenu.resetTrackOnLoad(); diff --git a/src/itdelatrisu/opsu/db/ScoreDB.java b/src/itdelatrisu/opsu/db/ScoreDB.java index 34891987..e6f27aef 100644 --- a/src/itdelatrisu/opsu/db/ScoreDB.java +++ b/src/itdelatrisu/opsu/db/ScoreDB.java @@ -304,6 +304,16 @@ public class ScoreDB { * @return all scores for the beatmap, or null if any error occurred */ public static ScoreData[] getMapScores(Beatmap beatmap) { + return getMapScores(beatmap, null); + } + + /** + * Retrieves the game scores for a beatmap while excluding a score. + * @param beatmap the beatmap + * @param exclude the filename of the score to exclude + * @return all scores for the beatmap except for exclude, or null if any error occurred + */ + public static ScoreData[] getMapScores(Beatmap beatmap, String exclude) { if (connection == null) return null; @@ -317,7 +327,11 @@ public class ScoreDB { ResultSet rs = selectMapStmt.executeQuery(); while (rs.next()) { ScoreData s = new ScoreData(rs); - list.add(s); + if (s.replayString != null && s.replayString.equals(exclude)) { + // dont return this score + } else { + list.add(s); + } } rs.close(); } catch (SQLException e) { @@ -326,7 +340,6 @@ public class ScoreDB { } return getSortedArray(list); } - /** * Retrieves the game scores for a beatmap set. * @param beatmap the beatmap diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index df6d9f10..2ad24db9 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -1210,7 +1210,7 @@ public class Game extends BasicGameState { if (beatmap == null || beatmap.objects == null) throw new RuntimeException("Running game with no beatmap loaded."); // fetch previous results - previousScores = ScoreDB.getMapScores(beatmap); + previousScores = ScoreDB.getMapScores(beatmap, replay == null ? null : replay.getReplayFilename()); lastRankUpdateTime = -1000; if (previousScores != null) currentRank = previousScores.length; From db36ec390d9679919fd6cda5cdfe052368613ae9 Mon Sep 17 00:00:00 2001 From: yugecin Date: Mon, 6 Jun 2016 11:43:00 +0200 Subject: [PATCH 04/16] Fix #177 --- src/itdelatrisu/opsu/states/Game.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 1ab7b64f..96daf0b7 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -1297,6 +1297,9 @@ public class Game extends BasicGameState { // replays if (isReplay) GameMod.loadModState(previousMods); + + // reset the playback speed + MusicController.setPitch(1f); } /** From 76c8efb0c2db6646da40caf843473f79bd5f7268 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 01:17:59 -0400 Subject: [PATCH 05/16] Disable SSL validation for YaSOnlineServer. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/Utils.java | 29 +++++++++++++++++++ .../downloads/servers/YaSOnlineServer.java | 8 +++++ 2 files changed, 37 insertions(+) diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 6b8b6829..9b7e03de 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -43,6 +43,7 @@ import java.net.URL; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; @@ -50,6 +51,10 @@ import java.util.Scanner; import java.util.jar.JarFile; import javax.imageio.ImageIO; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import org.json.JSONArray; import org.json.JSONException; @@ -561,4 +566,28 @@ public class Utils { return null; } } + + /** + * Switches validation of SSL certificates on or off by installing a default + * all-trusting {@link TrustManager}. + * @param enabled whether to validate SSL certificates + * @author neu242 (http://stackoverflow.com/a/876785) + */ + public static void setSSLCertValidation(boolean enabled) { + // create a trust manager that does not validate certificate chains + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + @Override public void checkClientTrusted(X509Certificate[] certs, String authType) {} + @Override public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + + // install the all-trusting trust manager + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, enabled ? null : trustAllCerts, null); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) {} + } } diff --git a/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java b/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java index 95846bfd..3d25459a 100644 --- a/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/YaSOnlineServer.java @@ -93,6 +93,8 @@ public class YaSOnlineServer extends DownloadServer { */ private String getDownloadURLFromMapData(int beatmapSetID) throws IOException { try { + Utils.setSSLCertValidation(false); + // read JSON String search = String.format(DOWNLOAD_URL, beatmapSetID); JSONObject json = Utils.readJsonObjectFromUrl(new URL(search)); @@ -114,6 +116,8 @@ public class YaSOnlineServer extends DownloadServer { } catch (MalformedURLException | UnsupportedEncodingException e) { ErrorHandler.error(String.format("Problem retrieving download URL for beatmap '%d'.", beatmapSetID), e, true); return null; + } finally { + Utils.setSSLCertValidation(true); } } @@ -121,6 +125,8 @@ public class YaSOnlineServer extends DownloadServer { public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException { DownloadNode[] nodes = null; try { + Utils.setSSLCertValidation(false); + // read JSON String search; boolean isSearch; @@ -181,6 +187,8 @@ public class YaSOnlineServer extends DownloadServer { this.totalResults = maxServerID; } catch (MalformedURLException | UnsupportedEncodingException e) { ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true); + } finally { + Utils.setSSLCertValidation(true); } return nodes; } From f3918b7b604abcef0f2cb8409c092e351902e33b Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 02:07:45 -0400 Subject: [PATCH 06/16] New MengSkyServer API. Signed-off-by: Jeffrey Han --- .../opsu/downloads/servers/MengSkyServer.java | 166 ++++-------------- 1 file changed, 39 insertions(+), 127 deletions(-) diff --git a/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java b/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java index ce4647be..1c72dff5 100644 --- a/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java +++ b/src/itdelatrisu/opsu/downloads/servers/MengSkyServer.java @@ -27,8 +27,9 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; + +import org.json.JSONArray; +import org.json.JSONObject; /** * Download server: http://osu.mengsky.net/ @@ -38,13 +39,10 @@ public class MengSkyServer extends DownloadServer { private static final String SERVER_NAME = "MengSky"; /** Formatted download URL: {@code beatmapSetID} */ - private static final String DOWNLOAD_URL = "http://osu.mengsky.net/d.php?id=%d"; + private static final String DOWNLOAD_URL = "http://osu.mengsky.net/api/download/%d"; - /** Formatted search URL: {@code query} */ - private static final String SEARCH_URL = "http://osu.mengsky.net/index.php?search_keywords=%s"; - - /** Formatted home URL: {@code page} */ - private static final String HOME_URL = "http://osu.mengsky.net/index.php?next=1&page=%d"; + /** Formatted search URL: {@code query,page,unranked,approved,qualified} */ + private static final String SEARCH_URL = "http://osu.mengsky.net/api/beatmapinfo?query=%s&page=%d&ranked=1&unrank=%d&approved=%d&qualified=%d"; /** Maximum beatmaps displayed per page. */ private static final int PAGE_LIMIT = 20; @@ -67,127 +65,41 @@ public class MengSkyServer extends DownloadServer { public DownloadNode[] resultList(String query, int page, boolean rankedOnly) throws IOException { DownloadNode[] nodes = null; try { - // read HTML - String search; - boolean isSearch; - if (query.isEmpty()) { - isSearch = false; - search = String.format(HOME_URL, page - 1); - } else { - isSearch = true; - search = String.format(SEARCH_URL, URLEncoder.encode(query, "UTF-8")); + // read JSON + int rankedOnlyFlag = rankedOnly ? 0 : 1; + String search = String.format( + SEARCH_URL, URLEncoder.encode(query, "UTF-8"), page, + rankedOnlyFlag, rankedOnlyFlag, rankedOnlyFlag + ); + JSONObject json = Utils.readJsonObjectFromUrl(new URL(search)); + + // parse result list + JSONArray arr = json.getJSONArray("data"); + nodes = new DownloadNode[arr.length()]; + for (int i = 0; i < nodes.length; i++) { + JSONObject item = arr.getJSONObject(i); + String + title = item.getString("title"), titleU = item.getString("titleU"), + artist = item.getString("artist"), artistU = item.getString("artistU"), + creator = item.getString("creator"); + // bug with v1.x API (as of 10-13-16): + // sometimes titleU is artistU instead of the proper title + if (titleU.equals(artistU) && !titleU.equals(title)) + titleU = title; + nodes[i] = new DownloadNode( + item.getInt("id"), item.getString("syncedDateTime"), + title, titleU, artist, artistU, creator + ); } - String html = Utils.readDataFromUrl(new URL(search)); - if (html == null) { - this.totalResults = -1; - return null; - } - - // parse results - // NOTE: Maybe an HTML parser would be better for this... - // FORMAT: - //
- //
- // - //
- // Creator: {{creator}}
- // MaxBpm: {{bpm}}
- // Title: {{titleUnicode}}
- // Artist: {{artistUnicode}}
- // Status: {{"Ranked?" || "Unranked"}}
- //
- //

- // Fork: bloodcat
- // UpdateTime: {{yyyy}}/{{mm}}/{{dd}} {{hh}}:{{mm}}:{{ss}}
- // Mode: {{...}} - //
- //
- // Osu.ppy - //
- //
- // DownLoad - //
- //
- List nodeList = new ArrayList(); - final String - START_TAG = "
", - CREATOR_TAG = "Creator: ", TITLE_TAG = "Title: ", ARTIST_TAG = "Artist: ", - TIMESTAMP_TAG = "UpdateTime: ", DOWNLOAD_TAG = "
", - BR_TAG = "
", HREF_TAG = "
n) continue; - j = html.indexOf(HREF_TAG_END, i + 1); - if (j == -1 || j > n) continue; - String beatmap = html.substring(i + NAME_TAG.length(), j); - String[] beatmapTokens = beatmap.split(" - ", 2); - if (beatmapTokens.length < 2) - continue; - String artist = beatmapTokens[0]; - String title = beatmapTokens[1]; - - // find other beatmap details - i = html.indexOf(CREATOR_TAG, j + HREF_TAG_END.length()); - if (i == -1 || i > n) continue; - j = html.indexOf(BR_TAG, i + CREATOR_TAG.length()); - if (j == -1 || j > n) continue; - String creator = html.substring(i + CREATOR_TAG.length(), j); - i = html.indexOf(TITLE_TAG, j + BR_TAG.length()); - if (i == -1 || i > n) continue; - j = html.indexOf(BR_TAG, i + TITLE_TAG.length()); - if (j == -1 || j > n) continue; - String titleUnicode = html.substring(i + TITLE_TAG.length(), j); - i = html.indexOf(ARTIST_TAG, j + BR_TAG.length()); - if (i == -1 || i > n) continue; - j = html.indexOf(BR_TAG, i + ARTIST_TAG.length()); - if (j == -1 || j > n) continue; - String artistUnicode = html.substring(i + ARTIST_TAG.length(), j); - i = html.indexOf(TIMESTAMP_TAG, j + BR_TAG.length()); - if (i == -1 || i >= n) continue; - j = html.indexOf(BR_TAG, i + TIMESTAMP_TAG.length()); - if (j == -1 || j > n) continue; - String date = html.substring(i + TIMESTAMP_TAG.length(), j); - - // find beatmap ID - i = html.indexOf(DOWNLOAD_TAG, j + BR_TAG.length()); - if (i == -1 || i >= n) continue; - i = html.indexOf(HREF_TAG, i + DOWNLOAD_TAG.length()); - if (i == -1 || i > n) continue; - j = html.indexOf('"', i + HREF_TAG.length()); - if (j == -1 || j > n) continue; - String downloadURL = html.substring(i + HREF_TAG.length(), j); - String[] downloadTokens = downloadURL.split("(?=\\d*$)", 2); - if (downloadTokens[1].isEmpty()) continue; - int id; - try { - id = Integer.parseInt(downloadTokens[1]); - } catch (NumberFormatException e) { - continue; - } - - nodeList.add(new DownloadNode(id, date, title, titleUnicode, artist, artistUnicode, creator)); - } - - nodes = nodeList.toArray(new DownloadNode[nodeList.size()]); // store total result count - if (isSearch) - this.totalResults = nodes.length; - else { - int resultCount = nodes.length + (page - 1) * PAGE_LIMIT; - if (divCount == PAGE_LIMIT) - resultCount++; - this.totalResults = resultCount; - } + int pageTotal = json.getInt("pageTotal"); + int resultCount = nodes.length; + if (page == pageTotal) + resultCount = nodes.length + (pageTotal - 1) * PAGE_LIMIT; + else + resultCount = 1 + (pageTotal - 1) * PAGE_LIMIT; + this.totalResults = resultCount; } catch (MalformedURLException | UnsupportedEncodingException e) { ErrorHandler.error(String.format("Problem loading result list for query '%s'.", query), e, true); } @@ -195,7 +107,7 @@ public class MengSkyServer extends DownloadServer { } @Override - public int minQueryLength() { return 2; } + public int minQueryLength() { return 1; } @Override public int totalResults() { return totalResults; } From dabfb827ee55d6991577def5c0f25624022cad72 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 04:12:47 -0400 Subject: [PATCH 07/16] Use Download class to download track previews into a Temp/ directory. AudioSystem.getAudioInputStream(URL) was causing issues, so just download the files using the more robust Download class. Signed-off-by: Jeffrey Han --- .gitignore | 1 + README.md | 1 + src/itdelatrisu/opsu/Container.java | 13 ++++ src/itdelatrisu/opsu/Options.java | 3 + .../opsu/audio/SoundController.java | 59 +++++++++++++++---- src/itdelatrisu/opsu/downloads/Download.java | 11 ++-- .../opsu/states/DownloadsMenu.java | 41 +++++++------ 7 files changed, 93 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 4ba1441d..6348415d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /Skins/ /SongPacks/ /Songs/ +/Temp/ /.opsu.log /.opsu.cfg /.opsu.db* diff --git a/README.md b/README.md index cc1455a2..d21f1585 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ The following files and folders will be created by opsu! as needed: files within this directory to the replay directory and saves the scores in the scores database. Replays can be imported from osu! as well as opsu!. * `Natives/`: The native libraries directory. +* `Temp/`: The temporary files directory. Deleted when opsu! exits. ## Building opsu! is distributed as both a [Maven](https://maven.apache.org/) and diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index 5fbb774b..67a4daec 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -27,11 +27,15 @@ import itdelatrisu.opsu.downloads.Updater; import itdelatrisu.opsu.render.CurveRenderState; import itdelatrisu.opsu.ui.UI; +import java.io.IOException; + +import org.codehaus.plexus.util.FileUtils; import org.lwjgl.opengl.Display; import org.newdawn.slick.AppGameContainer; import org.newdawn.slick.Game; import org.newdawn.slick.SlickException; import org.newdawn.slick.opengl.InternalTextureLoader; +import org.newdawn.slick.util.Log; /** * AppGameContainer extension that sends critical errors to ErrorHandler. @@ -142,6 +146,15 @@ public class Container extends AppGameContainer { if (!Options.isWatchServiceEnabled()) BeatmapWatchService.destroy(); BeatmapWatchService.removeListeners(); + + // delete temporary directory + if (Options.TEMP_DIR.isDirectory()) { + try { + FileUtils.deleteDirectory(Options.TEMP_DIR); + } catch (IOException e) { + Log.warn(String.format("Failed to delete temp dir: %s", Options.TEMP_DIR.getAbsolutePath()), e); + } + } } @Override diff --git a/src/itdelatrisu/opsu/Options.java b/src/itdelatrisu/opsu/Options.java index 9bf79fac..1ec253c4 100644 --- a/src/itdelatrisu/opsu/Options.java +++ b/src/itdelatrisu/opsu/Options.java @@ -94,6 +94,9 @@ public class Options { /** Directory where natives are unpacked. */ public static final File NATIVE_DIR = new File(CACHE_DIR, "Natives/"); + /** Directory where temporary files are stored (deleted on exit). */ + public static final File TEMP_DIR = new File(CACHE_DIR, "Temp/"); + /** Font file name. */ public static final String FONT_NAME = "DroidSansFallback.ttf"; diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index 7f42f144..8a0cbe0e 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -22,6 +22,9 @@ import itdelatrisu.opsu.ErrorHandler; import itdelatrisu.opsu.Options; import itdelatrisu.opsu.audio.HitSound.SampleSet; import itdelatrisu.opsu.beatmap.HitObject; +import itdelatrisu.opsu.downloads.Download; +import itdelatrisu.opsu.downloads.Download.DownloadListener; +import itdelatrisu.opsu.ui.UI; import java.io.File; import java.io.IOException; @@ -345,24 +348,58 @@ public class SoundController { } /** - * Plays a track from a URL. + * Plays a track from a remote URL. * If a track is currently playing, it will be stopped. - * @param url the resource URL + * @param url the remote URL + * @param name the track file name * @param isMP3 true if MP3, false if WAV * @param listener the line listener - * @return the MultiClip being played + * @return true if playing, false otherwise * @throws SlickException if any error occurred */ - public static synchronized MultiClip playTrack(URL url, boolean isMP3, LineListener listener) throws SlickException { + public static synchronized boolean playTrack(String url, String name, boolean isMP3, LineListener listener) + throws SlickException { + // stop previous track stopTrack(); - try { - AudioInputStream audioIn = AudioSystem.getAudioInputStream(url); - currentTrack = loadClip(url.getFile(), audioIn, isMP3); - playClip(currentTrack, Options.getMusicVolume() * Options.getMasterVolume(), listener); - return currentTrack; - } catch (Exception e) { - throw new SlickException(String.format("Failed to load clip '%s'.", url.getFile(), e)); + + // download new track + File dir = Options.TEMP_DIR; + if (!dir.isDirectory()) + dir.mkdir(); + String filename = String.format("%s.%s", name, isMP3 ? "mp3" : "wav"); + final File downloadFile = new File(dir, filename); + boolean complete; + if (downloadFile.isFile()) { + complete = true; // file already downloaded + } else { + Download download = new Download(url, downloadFile.getAbsolutePath()); + download.setListener(new DownloadListener() { + @Override + public void completed() {} + + @Override + public void error() { + UI.sendBarNotification("Failed to download track preview."); + } + }); + try { + download.start().join(); + } catch (InterruptedException e) {} + complete = (download.getStatus() == Download.Status.COMPLETE); } + + // play the track + if (complete) { + try { + AudioInputStream audioIn = AudioSystem.getAudioInputStream(downloadFile); + currentTrack = loadClip(filename, audioIn, isMP3); + playClip(currentTrack, Options.getMusicVolume() * Options.getMasterVolume(), listener); + return true; + } catch (Exception e) { + throw new SlickException(String.format("Failed to load clip '%s'.", url)); + } + } + return false; } /** diff --git a/src/itdelatrisu/opsu/downloads/Download.java b/src/itdelatrisu/opsu/downloads/Download.java index 09ebe292..58818490 100644 --- a/src/itdelatrisu/opsu/downloads/Download.java +++ b/src/itdelatrisu/opsu/downloads/Download.java @@ -167,12 +167,13 @@ public class Download { /** * Starts the download from the "waiting" status. + * @return the started download thread, or {@code null} if none started */ - public void start() { + public Thread start() { if (status != Status.WAITING) - return; + return null; - new Thread() { + Thread t = new Thread() { @Override public void run() { // open connection @@ -274,7 +275,9 @@ public class Download { listener.error(); } } - }.start(); + }; + t.start(); + return t; } /** diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java index a9c5b7cf..62f0eff6 100644 --- a/src/itdelatrisu/opsu/states/DownloadsMenu.java +++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java @@ -47,8 +47,6 @@ import itdelatrisu.opsu.ui.UI; import java.io.File; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineListener; @@ -663,15 +661,18 @@ public class DownloadsMenu extends BasicGameState { SoundController.stopTrack(); } else { // play preview - try { - final URL url = new URL(serverMenu.getSelectedItem().getPreviewURL(node.getID())); - MusicController.pause(); - new Thread() { - @Override - public void run() { - try { - previewID = -1; - SoundController.playTrack(url, true, new LineListener() { + final String url = serverMenu.getSelectedItem().getPreviewURL(node.getID()); + MusicController.pause(); + new Thread() { + @Override + public void run() { + try { + previewID = -1; + boolean playing = SoundController.playTrack( + url, + Integer.toString(node.getID()), + true, + new LineListener() { @Override public void update(LineEvent event) { if (event.getType() == LineEvent.Type.STOP) { @@ -681,18 +682,16 @@ public class DownloadsMenu extends BasicGameState { } } } - }); + } + ); + if (playing) previewID = node.getID(); - } catch (SlickException e) { - UI.sendBarNotification("Failed to load track preview. See log for details."); - Log.error(e); - } + } catch (SlickException e) { + UI.sendBarNotification("Failed to load track preview. See log for details."); + Log.error(e); } - }.start(); - } catch (MalformedURLException e) { - UI.sendBarNotification("Could not load track preview (bad URL)."); - Log.error(e); - } + } + }.start(); } return; } From 2d150c993944878eae8262a41ce54a894197c9b7 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 04:20:03 -0400 Subject: [PATCH 08/16] Follow-up to dabfb82. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/Container.java | 13 ++----------- src/itdelatrisu/opsu/Utils.java | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index 67a4daec..bdabaf42 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -27,15 +27,11 @@ import itdelatrisu.opsu.downloads.Updater; import itdelatrisu.opsu.render.CurveRenderState; import itdelatrisu.opsu.ui.UI; -import java.io.IOException; - -import org.codehaus.plexus.util.FileUtils; import org.lwjgl.opengl.Display; import org.newdawn.slick.AppGameContainer; import org.newdawn.slick.Game; import org.newdawn.slick.SlickException; import org.newdawn.slick.opengl.InternalTextureLoader; -import org.newdawn.slick.util.Log; /** * AppGameContainer extension that sends critical errors to ErrorHandler. @@ -148,13 +144,8 @@ public class Container extends AppGameContainer { BeatmapWatchService.removeListeners(); // delete temporary directory - if (Options.TEMP_DIR.isDirectory()) { - try { - FileUtils.deleteDirectory(Options.TEMP_DIR); - } catch (IOException e) { - Log.warn(String.format("Failed to delete temp dir: %s", Options.TEMP_DIR.getAbsolutePath()), e); - } - } + if (Options.TEMP_DIR.isDirectory()) + Utils.deleteDirectory(Options.TEMP_DIR); } @Override diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 9b7e03de..b77bd903 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -346,7 +346,7 @@ public class Utils { * deletes the directory itself. * @param dir the directory to delete */ - private static void deleteDirectory(File dir) { + public static void deleteDirectory(File dir) { if (dir == null || !dir.isDirectory()) return; From 410e6d6765d73c5b53fc06ef9678d75e6523d761 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 04:52:03 -0400 Subject: [PATCH 09/16] Destroy SoundController resources before exit. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/Container.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index bdabaf42..fd0300ee 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -19,6 +19,7 @@ package itdelatrisu.opsu; import itdelatrisu.opsu.audio.MusicController; +import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.beatmap.Beatmap; import itdelatrisu.opsu.beatmap.BeatmapSetList; import itdelatrisu.opsu.beatmap.BeatmapWatchService; @@ -131,6 +132,9 @@ public class Container extends AppGameContainer { // prevent loading tracks from re-initializing OpenAL MusicController.reset(); + // stop any playing track + SoundController.stopTrack(); + // reset BeatmapSetList data if (BeatmapSetList.get() != null) BeatmapSetList.get().reset(); @@ -144,8 +148,7 @@ public class Container extends AppGameContainer { BeatmapWatchService.removeListeners(); // delete temporary directory - if (Options.TEMP_DIR.isDirectory()) - Utils.deleteDirectory(Options.TEMP_DIR); + Utils.deleteDirectory(Options.TEMP_DIR); } @Override From 3b847d088d2a7d385a2b76efbaa0d81e265571cb Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 14:41:58 -0400 Subject: [PATCH 10/16] Follow-up to #155. Some code cleanup and tweaks to the scoreboard display. --- src/itdelatrisu/opsu/GameData.java | 55 ++++++++++------- src/itdelatrisu/opsu/Opsu.java | 1 - src/itdelatrisu/opsu/ScoreData.java | 59 +++++++++++++----- src/itdelatrisu/opsu/db/ScoreDB.java | 9 +-- src/itdelatrisu/opsu/states/Game.java | 87 +++++++++++++++------------ src/itdelatrisu/opsu/ui/Colors.java | 1 + 6 files changed, 130 insertions(+), 82 deletions(-) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index a69020d1..a9c5f54e 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -1394,34 +1394,45 @@ public class GameData { * (i.e. this will not overwrite existing data). * @param beatmap the beatmap * @return the ScoreData object + * @see #getCurrentScoreData(Beatmap, boolean) */ public ScoreData getScoreData(Beatmap beatmap) { if (scoreData == null) - scoreData = new ScoreData(); - - scoreData.timestamp = System.currentTimeMillis() / 1000L; - scoreData.MID = beatmap.beatmapID; - scoreData.MSID = beatmap.beatmapSetID; - scoreData.title = beatmap.title; - scoreData.artist = beatmap.artist; - scoreData.creator = beatmap.creator; - scoreData.version = beatmap.version; - scoreData.hit300 = hitResultCount[HIT_300]; - scoreData.hit100 = hitResultCount[HIT_100]; - scoreData.hit50 = hitResultCount[HIT_50]; - scoreData.geki = hitResultCount[HIT_300G]; - scoreData.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K]; - scoreData.miss = hitResultCount[HIT_MISS]; - scoreData.score = score; - scoreData.combo = comboMax; - scoreData.perfect = (comboMax == fullObjectCount); - scoreData.mods = GameMod.getModState(); - scoreData.replayString = (replay == null) ? null : replay.getReplayFilename(); - scoreData.playerName = null; // TODO - + scoreData = getCurrentScoreData(beatmap, false); return scoreData; } + /** + * Returns a ScoreData object encapsulating all current game data. + * @param beatmap the beatmap + * @param slidingScore if true, use the display score (might not be actual score) + * @return the ScoreData object + * @see #getScoreData(Beatmap) + */ + public ScoreData getCurrentScoreData(Beatmap beatmap, boolean slidingScore) { + ScoreData sd = new ScoreData(); + sd.timestamp = System.currentTimeMillis() / 1000L; + sd.MID = beatmap.beatmapID; + sd.MSID = beatmap.beatmapSetID; + sd.title = beatmap.title; + sd.artist = beatmap.artist; + sd.creator = beatmap.creator; + sd.version = beatmap.version; + sd.hit300 = hitResultCount[HIT_300]; + sd.hit100 = hitResultCount[HIT_100]; + sd.hit50 = hitResultCount[HIT_50]; + sd.geki = hitResultCount[HIT_300G]; + sd.katu = hitResultCount[HIT_300K] + hitResultCount[HIT_100K]; + sd.miss = hitResultCount[HIT_MISS]; + sd.score = slidingScore ? scoreDisplay : score; + sd.combo = comboMax; + sd.perfect = (comboMax == fullObjectCount); + sd.mods = GameMod.getModState(); + sd.replayString = (replay == null) ? null : replay.getReplayFilename(); + sd.playerName = null; // TODO + return sd; + } + /** * Returns a Replay object encapsulating all game data. * If a replay already exists and frames is null, the existing object will be returned. diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index 4a7a3abd..82e9f553 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -230,7 +230,6 @@ public class Opsu extends StateBasedGame { } else { if (id == STATE_GAME) { MusicController.pause(); - MusicController.setPitch(1.0f); MusicController.resume(); } else songMenu.resetTrackOnLoad(); diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 71971160..b873672f 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -330,39 +330,66 @@ public class ScoreData implements Comparable { } /** - * Draws the score ingame (smaller and with less information). + * Draws the score in-game (smaller and with less information). * @param g the current graphics context * @param vPos the base y position of the scoreboard * @param rank the current rank of this score * @param position the animated position offset * @param data an instance of GameData to draw rank number - * @param alpha the transparancy of the score + * @param alpha the transparency of the score * @param isActive if this score is the one currently played */ public void drawSmall(Graphics g, int vPos, int rank, float position, GameData data, float alpha, boolean isActive) { - + int rectWidth = (int) (145 * GameImage.getUIscale()); //135 int rectHeight = data.getScoreSymbolImage('0').getHeight(); int vertDistance = rectHeight + 10; - int yPos = (int)(vPos + position * vertDistance - rectHeight/2); + int yPos = (int) (vPos + position * vertDistance - rectHeight / 2); + int xPaddingLeft = Math.max(4, (int) (rectWidth * 0.04f)); + int xPaddingRight = Math.max(2, (int) (rectWidth * 0.02f)); + int yPadding = Math.max(2, (int) (rectHeight * 0.02f)); String scoreString = String.format(Locale.US, "%,d", score); String comboString = String.format("%dx", combo); String rankString = String.format("%d", rank); - int rectWidth = (int) (170 * GameImage.getUIscale()); - Color rectColor = isActive ? Colors.YELLOW_ALPHA : Colors.BLACK_ALPHA; - rectColor.a = 0.5f * alpha; - + Color white = Colors.WHITE_ALPHA, blue = Colors.BLUE_SCOREBOARD, black = Colors.BLACK_ALPHA; + float oldAlphaWhite = white.a, oldAlphaBlue = blue.a, oldAlphaBlack = black.a; + + // rectangle background + Color rectColor = isActive ? white : blue; + rectColor.a = alpha * 0.2f; g.setColor(rectColor); g.fillRect(0, yPos, rectWidth, rectHeight); - data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, 0.25f*alpha, true); - if (playerName != null) { - Colors.WHITE_ALPHA.a = 0.5f * alpha; - Fonts.MEDIUM.drawString(0, yPos, playerName, Colors.WHITE_ALPHA); - } - Colors.WHITE_ALPHA.a = alpha; - Fonts.MEDIUMBOLD.drawString(0, yPos + rectHeight - Fonts.MEDIUMBOLD.getLineHeight(), scoreString, Colors.WHITE_ALPHA); - Fonts.MEDIUMBOLD.drawString(rectWidth - Fonts.MEDIUMBOLD.getWidth(comboString), yPos + rectHeight - Fonts.MEDIUMBOLD.getLineHeight(), comboString, Colors.WHITE_ALPHA); + black.a = alpha * 0.2f; + g.setColor(black); + float oldLineWidth = g.getLineWidth(); + g.setLineWidth(1f); + g.drawRect(0, yPos, rectWidth, rectHeight); + g.setLineWidth(oldLineWidth); + // rank + data.drawSymbolString(rankString, rectWidth, yPos, 1.0f, alpha * 0.2f, true); + + white.a = blue.a = alpha * 0.75f; + + // player name + if (playerName != null) + Fonts.MEDIUMBOLD.drawString(xPaddingLeft, yPos + yPadding, playerName, white); + + // score + Fonts.DEFAULT.drawString( + xPaddingLeft, yPos + rectHeight - Fonts.DEFAULT.getLineHeight() - yPadding, scoreString, white + ); + + // combo + Fonts.DEFAULT.drawString( + rectWidth - Fonts.DEFAULT.getWidth(comboString) - xPaddingRight, + yPos + rectHeight - Fonts.DEFAULT.getLineHeight() - yPadding, + comboString, blue + ); + + white.a = oldAlphaWhite; + blue.a = oldAlphaBlue; + black.a = oldAlphaBlack; } /** diff --git a/src/itdelatrisu/opsu/db/ScoreDB.java b/src/itdelatrisu/opsu/db/ScoreDB.java index e6f27aef..8ae37f8c 100644 --- a/src/itdelatrisu/opsu/db/ScoreDB.java +++ b/src/itdelatrisu/opsu/db/ScoreDB.java @@ -304,16 +304,16 @@ public class ScoreDB { * @return all scores for the beatmap, or null if any error occurred */ public static ScoreData[] getMapScores(Beatmap beatmap) { - return getMapScores(beatmap, null); + return getMapScoresExcluding(beatmap, null); } /** * Retrieves the game scores for a beatmap while excluding a score. * @param beatmap the beatmap - * @param exclude the filename of the score to exclude + * @param exclude the filename (replay string) of the score to exclude * @return all scores for the beatmap except for exclude, or null if any error occurred */ - public static ScoreData[] getMapScores(Beatmap beatmap, String exclude) { + public static ScoreData[] getMapScoresExcluding(Beatmap beatmap, String exclude) { if (connection == null) return null; @@ -328,7 +328,7 @@ public class ScoreDB { while (rs.next()) { ScoreData s = new ScoreData(rs); if (s.replayString != null && s.replayString.equals(exclude)) { - // dont return this score + // don't return this score } else { list.add(s); } @@ -340,6 +340,7 @@ public class ScoreDB { } return getSortedArray(list); } + /** * Retrieves the game scores for a beatmap set. * @param beatmap the beatmap diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 2ce8a3a8..7a5b1c27 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -101,11 +101,11 @@ public class Game extends BasicGameState { /** Maximum rotation, in degrees, over fade out upon death. */ private static final float MAX_ROTATION = 90f; - /** The duration of the score changing animation */ + /** The duration of the score changing animation. */ private static final float SCOREBOARD_ANIMATION_TIME = 500f; - /** The time the scoreboard takes to fade in */ - private static final float SCOREBOARD_FADE_IN_TIME = 500f; + /** The time the scoreboard takes to fade in. */ + private static final float SCOREBOARD_FADE_IN_TIME = 300f; /** Minimum time before start of song, in milliseconds, to process skip-related actions. */ private static final int SKIP_OFFSET = 2000; @@ -271,10 +271,10 @@ public class Game extends BasicGameState { /** The time the rank was last updated. */ private int lastRankUpdateTime; - /** Is the scoreboard visible? */ + /** Whether the scoreboard is visible. */ private boolean scoreboardVisible; - /** The current aplha of the scoreboard. */ + /** The current alpha of the scoreboard. */ private float currentScoreboardAlpha; /** Music position bar background colors. */ @@ -503,7 +503,6 @@ public class Game extends BasicGameState { arrow.draw(width * 0.75f, height * 0.75f); } } - } // non-break @@ -585,33 +584,42 @@ public class Game extends BasicGameState { drawHitObjects(g, trackPosition); } + // in-game scoreboard if (previousScores != null && trackPosition >= firstObjectTime && !GameMod.RELAX.isActive() && !GameMod.AUTOPILOT.isActive()) { - ScoreData currentScore = data.getScoreData(beatmap); - while (currentRank > 0 && previousScores[currentRank-1].score < currentScore.score) { + ScoreData currentScore = data.getCurrentScoreData(beatmap, true); + while (currentRank > 0 && previousScores[currentRank - 1].score < currentScore.score) { currentRank--; lastRankUpdateTime = trackPosition; } - float animation = AnimationEquation.IN_OUT_QUAD.calc(Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f)); + float animation = AnimationEquation.IN_OUT_QUAD.calc( + Utils.clamp((trackPosition - lastRankUpdateTime) / SCOREBOARD_ANIMATION_TIME, 0f, 1f) + ); int scoreboardPosition = 2 * container.getHeight() / 3; if (currentRank < 4) { - //draw the (new) top 5 ranks + // draw the (new) top 5 ranks for (int i = 0; i < 4; i++) { - int ii = i + (i>=currentRank ? 1 : 0); - if (i < previousScores.length) - previousScores[i].drawSmall(g, scoreboardPosition, ii + 1, ii + (i==currentRank ? animation-3f : -2f), data, currentScoreboardAlpha, false); + int index = i + (i >= currentRank ? 1 : 0); + if (i < previousScores.length) { + float position = index + (i == currentRank ? animation - 3f : -2f); + previousScores[i].drawSmall(g, scoreboardPosition, index + 1, position, data, currentScoreboardAlpha, false); + } } currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, currentRank - 1f - animation, data, currentScoreboardAlpha, true); } else { - //draw the top 2 and next 2 ranks + // draw the top 2 and next 2 ranks previousScores[0].drawSmall(g, scoreboardPosition, 1, -2f, data, currentScoreboardAlpha, false); previousScores[1].drawSmall(g, scoreboardPosition, 2, -1f, data, currentScoreboardAlpha, false); - previousScores[currentRank-2].drawSmall(g, scoreboardPosition, currentRank - 1, animation - 1f, data, currentScoreboardAlpha*animation, false); - previousScores[currentRank-1].drawSmall(g, scoreboardPosition, currentRank, animation, data, currentScoreboardAlpha, false); + previousScores[currentRank - 2].drawSmall( + g, scoreboardPosition, currentRank - 1, animation - 1f, data, currentScoreboardAlpha * animation, false + ); + previousScores[currentRank - 1].drawSmall(g, scoreboardPosition, currentRank, animation, data, currentScoreboardAlpha, false); currentScore.drawSmall(g, scoreboardPosition, currentRank + 1, 2f, data, currentScoreboardAlpha, true); if (animation < 1.0f && currentRank < previousScores.length) { - previousScores[currentRank].drawSmall(g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, currentScoreboardAlpha*(1f - animation), false); + previousScores[currentRank].drawSmall( + g, scoreboardPosition, currentRank + 2, 1f + 5 * animation, data, currentScoreboardAlpha * (1f - animation), false + ); } } } @@ -672,20 +680,6 @@ public class Game extends BasicGameState { int trackPosition = MusicController.getPosition(); int firstObjectTime = beatmap.objects[0].getTime(); - - if (previousScores != null && trackPosition > firstObjectTime) { - // show scoreboard when in break - if (scoreboardVisible || breakTime > 0) { - currentScoreboardAlpha += 1f/SCOREBOARD_FADE_IN_TIME * delta; - if (currentScoreboardAlpha > 1f) - currentScoreboardAlpha = 1f; - } else { - currentScoreboardAlpha -= 1f/SCOREBOARD_FADE_IN_TIME * delta; - if (currentScoreboardAlpha < 0f) - currentScoreboardAlpha = 0f; - } - } - // returning from pause screen: must click previous mouse position if (pauseTime > -1) { // paused during lead-in or break, or "relax" or "autopilot": continue immediately @@ -790,6 +784,20 @@ public class Game extends BasicGameState { } } + // update in-game scoreboard + if (previousScores != null && trackPosition > firstObjectTime) { + // show scoreboard if selected, and always in break + if (scoreboardVisible || breakTime > 0) { + currentScoreboardAlpha += 1f / SCOREBOARD_FADE_IN_TIME * delta; + if (currentScoreboardAlpha > 1f) + currentScoreboardAlpha = 1f; + } else { + currentScoreboardAlpha -= 1f / SCOREBOARD_FADE_IN_TIME * delta; + if (currentScoreboardAlpha < 0f) + currentScoreboardAlpha = 0f; + } + } + data.updateDisplays(delta); } @@ -1055,7 +1063,7 @@ public class Game extends BasicGameState { Utils.takeScreenShot(); break; case Input.KEY_TAB: - scoreboardVisible = ! scoreboardVisible; + scoreboardVisible = !scoreboardVisible; break; } } @@ -1209,13 +1217,6 @@ public class Game extends BasicGameState { if (beatmap == null || beatmap.objects == null) throw new RuntimeException("Running game with no beatmap loaded."); - // fetch previous results - previousScores = ScoreDB.getMapScores(beatmap, replay == null ? null : replay.getReplayFilename()); - lastRankUpdateTime = -1000; - if (previousScores != null) - currentRank = previousScores.length; - scoreboardVisible = true; - currentScoreboardAlpha = 0f; // free all previously cached hitobject to framebuffer mappings if some still exist FrameBufferCache.getInstance().freeMap(); @@ -1348,6 +1349,14 @@ public class Game extends BasicGameState { leadInTime = beatmap.audioLeadIn + approachTime; restart = Restart.FALSE; + // fetch previous scores + previousScores = ScoreDB.getMapScoresExcluding(beatmap, replay == null ? null : replay.getReplayFilename()); + lastRankUpdateTime = -1000; + if (previousScores != null) + currentRank = previousScores.length; + scoreboardVisible = true; + currentScoreboardAlpha = 0f; + // needs to play before setting position to resume without lag later MusicController.play(false); MusicController.setPosition(0); diff --git a/src/itdelatrisu/opsu/ui/Colors.java b/src/itdelatrisu/opsu/ui/Colors.java index 2f79efb9..7f00603a 100644 --- a/src/itdelatrisu/opsu/ui/Colors.java +++ b/src/itdelatrisu/opsu/ui/Colors.java @@ -42,6 +42,7 @@ public class Colors { DARK_GRAY = new Color(0.3f, 0.3f, 0.3f, 1f), RED_HIGHLIGHT = new Color(246, 154, 161), BLUE_HIGHLIGHT = new Color(173, 216, 230), + BLUE_SCOREBOARD = new Color(133, 208, 212), BLACK_BG_NORMAL = new Color(0, 0, 0, 0.25f), BLACK_BG_HOVER = new Color(0, 0, 0, 0.5f), BLACK_BG_FOCUS = new Color(0, 0, 0, 0.75f); From 79bfa1c255398d3bc1eed4beba566ddf8867b08f Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 13 Oct 2016 17:51:51 -0400 Subject: [PATCH 11/16] Properly fix #177. Previous fix (#186) threw NPE upon failing a song. Thanks to #155. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/Opsu.java | 1 + src/itdelatrisu/opsu/states/Game.java | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index 82e9f553..4a7a3abd 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -230,6 +230,7 @@ public class Opsu extends StateBasedGame { } else { if (id == STATE_GAME) { MusicController.pause(); + MusicController.setPitch(1.0f); MusicController.resume(); } else songMenu.resetTrackOnLoad(); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 7a5b1c27..1eba4156 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -1384,9 +1384,6 @@ public class Game extends BasicGameState { // replays if (isReplay) GameMod.loadModState(previousMods); - - // reset the playback speed - MusicController.setPitch(1f); } /** From 999136407701513b65a20d7d4782b79538c16172 Mon Sep 17 00:00:00 2001 From: Matteo Signer Date: Sat, 3 Dec 2016 14:06:58 +0100 Subject: [PATCH 12/16] Improve slider rendering. Significantly improves looks and performance of sliders, especially on shared memory graphics models like integegrated GPUs. No longer renders using cones and a framebuffer, instead renders quads and triangles for curves, using much less geometry. --- src/itdelatrisu/opsu/GameData.java | 4 +- .../opsu/objects/curves/Curve.java | 8 +- .../opsu/render/CurveRenderState.java | 457 +++++++++++------- 3 files changed, 297 insertions(+), 172 deletions(-) diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index a9c5f54e..98db996d 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -389,7 +389,7 @@ public class GameData { if (hitResultList != null) { for (HitObjectResult hitResult : hitResultList) { if (hitResult.curve != null) - hitResult.curve.discardCache(); + hitResult.curve.discardGeometry(); } } hitResultList = new LinkedBlockingDeque(); @@ -943,7 +943,7 @@ public class GameData { hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME); } else { if (hitResult.curve != null) - hitResult.curve.discardCache(); + hitResult.curve.discardGeometry(); iter.remove(); } } diff --git a/src/itdelatrisu/opsu/objects/curves/Curve.java b/src/itdelatrisu/opsu/objects/curves/Curve.java index e72b137c..7a372cf3 100644 --- a/src/itdelatrisu/opsu/objects/curves/Curve.java +++ b/src/itdelatrisu/opsu/objects/curves/Curve.java @@ -95,12 +95,12 @@ public abstract class Curve { Curve.borderColor = borderColor; ContextCapabilities capabilities = GLContext.getCapabilities(); - mmsliderSupported = capabilities.GL_EXT_framebuffer_object; + mmsliderSupported = capabilities.OpenGL20; if (mmsliderSupported) CurveRenderState.init(width, height, circleDiameter); else { if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER) - Log.warn("New slider style requires FBO support."); + Log.warn("New slider style requires OpenGL 2.0."); } } @@ -172,8 +172,8 @@ public abstract class Curve { /** * Discards the slider cache (only used for mmsliders). */ - public void discardCache() { + public void discardGeometry() { if (renderState != null) - renderState.discardCache(); + renderState.discardGeometry(); } } diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java index 737db42b..aa9d5bc8 100644 --- a/src/itdelatrisu/opsu/render/CurveRenderState.java +++ b/src/itdelatrisu/opsu/render/CurveRenderState.java @@ -28,11 +28,15 @@ import java.nio.IntBuffer; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.EXTFramebufferObject; +import org.lwjgl.opengl.GLContext; +import org.lwjgl.opengl.ContextCapabilities; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL14; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL30; import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.util.Log; @@ -53,17 +57,17 @@ public class CurveRenderState { /** 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 vertex buffer used for the curve's vertices. */ + private int vboID; /** The HitObject associated with the curve to be drawn. */ protected HitObject hitObject; /** The points along the curve to be drawn. */ 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 indices of the points. */ + protected int[] pointIndices; /** * Set the width and height of the container that Curves get drawn into. @@ -79,8 +83,6 @@ public class CurveRenderState { // 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(); } /** @@ -90,7 +92,6 @@ public class CurveRenderState { */ public static void shutdown() { staticState.shutdown(); - FrameBufferCache.shutdown(); } /** @@ -99,9 +100,11 @@ public class CurveRenderState { * @param curve the points along the curve to be drawn */ public CurveRenderState(HitObject hitObject, Vec2f[] curve) { - fbo = null; this.hitObject = hitObject; this.curve = curve; + this.pointIndices = new int[curve.length]; + this.vboID = -1; + } /** @@ -116,70 +119,25 @@ public class CurveRenderState { t = Utils.clamp(t, 0.0f, 1.0f); float alpha = color.a; - // if this curve hasn't been drawn, draw it and cache the result - if (fbo == null) { - 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; + // create curve geometry and store it on the GPU + if (vboID == -1) { + vboID = GL15.glGenBuffers(); + createVertexBuffer(vboID); } - int drawUpTo = (int) (t * curve.length); + int drawUpTo = (int) (t * (curve.length - 1)); - if (lastPointDrawn != drawUpTo) { - if (drawUpTo == lastPointDrawn) - return; - 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); - if (lastPointDrawn <= 0 || lastPointDrawn > drawUpTo) { - lastPointDrawn = 0; - 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, lastPointDrawn, drawUpTo); - lastPointDrawn = drawUpTo; - 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(); + this.renderCurve(color, borderColor, drawUpTo); + //color.a = 1f; } /** - * Discard the cache mapping for this curve object. + * Discard the geometry for this curve object. */ - public void discardCache() { - fbo = null; - FrameBufferCache.getInstance().freeMappingFor(hitObject); + public void discardGeometry() { + GL15.glDeleteBuffers(vboID); + vboID = -1; } /** @@ -193,7 +151,6 @@ public class CurveRenderState { boolean depthEnabled; boolean depthWriteEnabled; boolean texEnabled; - int texUnit; int oldProgram; int oldArrayBuffer; } @@ -209,7 +166,6 @@ public class CurveRenderState { 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); @@ -223,16 +179,11 @@ public class CurveRenderState { 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); + GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE); GL20.glUseProgram(0); - GL11.glMatrixMode(GL11.GL_PROJECTION); - GL11.glPushMatrix(); - GL11.glLoadIdentity(); - GL11.glMatrixMode(GL11.GL_MODELVIEW); - GL11.glPushMatrix(); - GL11.glLoadIdentity(); + GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); return state; } @@ -242,13 +193,9 @@ public class CurveRenderState { * @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); + GL11.glDisable(GL11.GL_TEXTURE_1D); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, state.oldArrayBuffer); if (!state.depthWriteEnabled) GL11.glDepthMask(false); @@ -268,21 +215,246 @@ public class CurveRenderState { * @param bufferID the buffer ID for the OpenGL buffer the vertices should be written into */ private void createVertexBuffer(int bufferID) { + float radius = scale / 2; + float mul_x = 2.f / containerWidth; + float mul_y = 2.f / containerHeight; + + int triangle_count = staticState.DIVIDES; // for curve caps + float last_dx=0, last_dy=0; + float last_alpha = 0; + for (int i = 0; i < curve.length; ++i) { // compute number of triangles + float x = curve[i].x; + float y = curve[i].y; + if (i > 0) { + float last_x = curve[i - 1].x; + float last_y = curve[i - 1].y; + float diff_x = x - last_x; + float diff_y = y - last_y; + + float alpha = (float)Math.atan2(diff_y, diff_x); + + if (i > 1) { + float theta = alpha - last_alpha; + if (theta > Math.PI) theta -= 2*Math.PI; + if (theta < -Math.PI) theta += 2*Math.PI; + + if (Math.abs(theta) < 2*Math.PI / staticState.DIVIDES) { + triangle_count++; + }else{ + int divs = (int)(Math.ceil(staticState.DIVIDES * Math.abs(theta) / (2*Math.PI))); + triangle_count += divs; + } + } + triangle_count += 4; + + last_dx = diff_x; + last_dy = diff_y; + last_alpha = alpha; + } + } + int arrayBufferBinding = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING); - FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * (2 * curve.length - 1) * (NewCurveStyleState.DIVIDES + 2)).asFloatBuffer(); + FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * 3 * (triangle_count)).asFloatBuffer(); + + + last_dx=0; last_dy=0; + float last_length=0; + float last_ox=0, last_oy=0; for (int i = 0; i < curve.length; ++i) { float x = curve[i].x; float y = curve[i].y; - fillCone(buff, x, y); - if (i != 0) { + if (i > 0) { + /* + + Render this shape: + ___ ___ + |A /|C /| + | /B| /D| + |/__|/__| + + + + */ 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; - x = (float) (x - diff_x / 2); - y = (float) (y - diff_y / 2); - fillCone(buff, x, y); + float diff_x = x - last_x; + float diff_y = y - last_y; + float length = (float)Math.hypot(diff_x, diff_y); + float offs_x = radius * diff_y / length; + float offs_y = radius * -diff_x / length; + + float alpha = (float)Math.atan2(diff_y, diff_x); + + + if (i > 1) { + float cross = last_dx * diff_y - last_dy * diff_x; + float theta = alpha - last_alpha; + if (theta > Math.PI) theta -= 2*Math.PI; + if (theta < -Math.PI) theta += 2*Math.PI; + + + if (Math.abs(theta) < 2*Math.PI / staticState.DIVIDES) { // small angle, just render single triangle + if (cross > 0) { // going counterclockwise + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + last_ox);buff.put(last_y + last_oy); + buff.put(1.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + offs_x); buff.put(last_y + offs_y); + buff.put(1.0f); buff.put(1.0f); + } else if (cross < 0) { + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x - offs_x); buff.put(last_y - offs_y); + buff.put(1.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x - last_ox);buff.put(last_y - last_oy); + buff.put(1.0f); buff.put(1.0f); + } else { + //straight line, very unlikely + } + } else { + int divs = (int)(Math.ceil(staticState.DIVIDES * Math.abs(theta) / (2*Math.PI))); + float phi = Math.abs(theta) / divs; + float sinphi = (float)Math.sin(phi); + float cosphi = (float)Math.cos(phi); + + float prev_ox = last_ox; + float prev_oy = last_oy; + if (cross < 0) { + prev_ox = -offs_x; + prev_oy = -offs_y; + } + for (int j = 0; j < divs; j++) { + /* + * Ratation matrix: + * [ cos -sin ] + * [ sin cos ] + */ + float ox = cosphi*prev_ox - sinphi*prev_oy; + float oy = sinphi*prev_ox + cosphi*prev_oy; + + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + prev_ox);buff.put(last_y + prev_oy); + buff.put(1.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + ox); buff.put(last_y + oy); + buff.put(1.0f); buff.put(1.0f); + + prev_ox = ox; prev_oy = oy; + } + } + } else { + int divs = staticState.DIVIDES/2; + + float phi = (float)(Math.PI / divs); + float sinphi = (float)Math.sin(phi); + float cosphi = (float)Math.cos(phi); + float prev_ox = -offs_x; + float prev_oy = -offs_y; + + for (int j = 0; j < divs; j++) { + float ox = cosphi*prev_ox - sinphi*prev_oy; + float oy = sinphi*prev_ox + cosphi*prev_oy; + + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + prev_ox);buff.put(last_y + prev_oy); + buff.put(1.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + ox); buff.put(last_y + oy); + buff.put(1.0f); buff.put(1.0f); + + prev_ox = ox; prev_oy = oy; + } + } + + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x - offs_x); buff.put(last_y - offs_y); + buff.put(1.0f); buff.put(1.0f); + buff.put(1.0f); buff.put(0.5f); + buff.put(x); buff.put(y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(x - offs_x); buff.put(y - offs_y); + buff.put(1.0f); buff.put(1.0f); + + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x - offs_x); buff.put(last_y - offs_y); + buff.put(1.0f); buff.put(1.0f); + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(1.0f); buff.put(0.5f); + buff.put(x); buff.put(y); + buff.put(0.0f); buff.put(1.0f); + + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(x + offs_x); buff.put(y + offs_y); + buff.put(1.0f); buff.put(1.0f); + buff.put(1.0f); buff.put(0.5f); + buff.put(x); buff.put(y); + buff.put(0.0f); buff.put(1.0f); + + buff.put(1.0f); buff.put(0.5f); + buff.put(last_x); buff.put(last_y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(last_x + offs_x); buff.put(last_y + offs_y); + buff.put(1.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(x + offs_x); buff.put(y + offs_y); + buff.put(1.0f); buff.put(1.0f); + + if (i == curve.length-1) { + int divs = staticState.DIVIDES/2; + + float phi = (float)(Math.PI / divs); + float sinphi = (float)Math.sin(phi); + float cosphi = (float)Math.cos(phi); + float prev_ox = offs_x; + float prev_oy = offs_y; + + for (int j = 0; j < divs; j++) { + float ox = cosphi*prev_ox - sinphi*prev_oy; + float oy = sinphi*prev_ox + cosphi*prev_oy; + + buff.put(1.0f); buff.put(0.5f); + buff.put(x); buff.put(y); + buff.put(0.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(x + prev_ox);buff.put(y + prev_oy); + buff.put(1.0f); buff.put(1.0f); + buff.put(0.0f); buff.put(0.5f); + buff.put(x + ox); buff.put(y + oy); + buff.put(1.0f); buff.put(1.0f); + + prev_ox = ox; prev_oy = oy; + } + } + + last_dx = diff_x; + last_dy = diff_y; + last_length = length; + last_ox = offs_x; + last_oy = offs_y; + last_alpha = alpha; } + + pointIndices[i] = buff.position() / 6; // 6 elements per vertex + } buff.flip(); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID); @@ -295,52 +467,38 @@ public class CurveRenderState { * @param color the color of the curve * @param borderColor the curve border color */ - private void renderCurve(Color color, Color borderColor, int from, int to) { + private void renderCurve(Color color, Color borderColor, int to) { staticState.initGradient(); RenderState state = saveRenderState(); staticState.initShaderProgram(); - GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, fbo.getVbo()); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID); GL20.glUseProgram(staticState.program); GL20.glEnableVertexAttribArray(staticState.attribLoc); GL20.glEnableVertexAttribArray(staticState.texCoordLoc); + GL20.glUniform2f(staticState.invSSLoc, 2.f/containerWidth, -2.f/containerHeight); GL20.glUniform1i(staticState.texLoc, 0); - GL20.glUniform3f(staticState.colLoc, color.r, color.g, color.b); + GL20.glUniform4f(staticState.colLoc, color.r, color.g, color.b, color.a); 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); - for (int i = from * 2; i < to * 2 - 1; ++i) - GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, i * (NewCurveStyleState.DIVIDES + 2), NewCurveStyleState.DIVIDES + 2); + + + GL11.glColorMask(false,false,false,false); + GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, pointIndices[to]); + GL11.glColorMask(true,true,true,true); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glDepthFunc(GL11.GL_EQUAL); + GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, pointIndices[to]); + GL11.glDepthFunc(GL11.GL_LESS); + GL11.glFlush(); GL20.glDisableVertexAttribArray(staticState.texCoordLoc); GL20.glDisableVertexAttribArray(staticState.attribLoc); restoreRenderState(state); } - /** - * 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 @@ -350,18 +508,11 @@ public class CurveRenderState { */ 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. + * Used for new style Slider rendering, defines how many vertices there + * are in a circle. */ 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; @@ -371,6 +522,9 @@ public class CurveRenderState { /** OpenGL shader attribute location of the texture coordinate attribute. */ protected int texCoordLoc = 0; + /** OpenGL shader uniform location of the inverse screen size attribute. */ + protected int invSSLoc = 0; + /** OpenGL shader uniform location of the color attribute. */ protected int colLoc = 0; @@ -402,46 +556,14 @@ public class CurveRenderState { 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; + ContextCapabilities capabilities = GLContext.getCapabilities(); + if (capabilities.OpenGL30) { + GL30.glGenerateMipmap(GL11.GL_TEXTURE_1D); + } else if (capabilities.GL_EXT_framebuffer_object) { + EXTFramebufferObject.glGenerateMipmapEXT(GL11.GL_TEXTURE_1D); + } else { + GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL14.GL_GENERATE_MIPMAP, GL11.GL_TRUE); } - //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; } } @@ -455,6 +577,7 @@ public class CurveRenderState { int vtxShdr = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); int frgShdr = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); GL20.glShaderSource(vtxShdr, "#version 110\n" + + "uniform vec2 inv_screensize;\n" + "\n" + "attribute vec4 in_position;\n" + "attribute vec2 in_tex_coord;\n" @@ -462,7 +585,7 @@ public class CurveRenderState { + "varying vec2 tex_coord;\n" + "void main()\n" + "{\n" - + " gl_Position = in_position;\n" + + " gl_Position = vec4(vec2(-1.f,1.f)+inv_screensize*in_position.xy,in_position.zw);\n" + " tex_coord = in_tex_coord;\n" + "}"); GL20.glCompileShader(vtxShdr); @@ -475,7 +598,7 @@ public class CurveRenderState { + "\n" + "uniform sampler1D tex;\n" + "uniform vec2 tex_size;\n" - + "uniform vec3 col_tint;\n" + + "uniform vec4 col_tint;\n" + "uniform vec4 col_border;\n" + "\n" + "varying vec2 tex_coord;\n" @@ -484,7 +607,7 @@ public class CurveRenderState { + "{\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" + + " vec4 new_color = vec4(mix(in_color.xyz*col_border.xyz,col_tint.xyz,blend_factor),in_color.w*col_tint.w);\n" + " gl_FragColor = new_color;\n" + "}"); GL20.glCompileShader(frgShdr); @@ -505,6 +628,7 @@ public class CurveRenderState { GL20.glDeleteShader(frgShdr); attribLoc = GL20.glGetAttribLocation(program, "in_position"); texCoordLoc = GL20.glGetAttribLocation(program, "in_tex_coord"); + invSSLoc = GL20.glGetUniformLocation(program, "inv_screensize"); texLoc = GL20.glGetUniformLocation(program, "tex"); colLoc = GL20.glGetUniformLocation(program, "col_tint"); colBorderLoc = GL20.glGetUniformLocation(program, "col_border"); @@ -525,6 +649,7 @@ public class CurveRenderState { program = 0; attribLoc = 0; texCoordLoc = 0; + invSSLoc = 0; colLoc = 0; colBorderLoc = 0; texLoc = 0; From 041014ab47e8460a2f74eaaa23d132495fbdd44a Mon Sep 17 00:00:00 2001 From: Matteo Signer Date: Sat, 3 Dec 2016 23:53:48 +0100 Subject: [PATCH 13/16] Fix float constants. --- src/itdelatrisu/opsu/render/CurveRenderState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java index aa9d5bc8..96eb71b1 100644 --- a/src/itdelatrisu/opsu/render/CurveRenderState.java +++ b/src/itdelatrisu/opsu/render/CurveRenderState.java @@ -585,7 +585,7 @@ public class CurveRenderState { + "varying vec2 tex_coord;\n" + "void main()\n" + "{\n" - + " gl_Position = vec4(vec2(-1.f,1.f)+inv_screensize*in_position.xy,in_position.zw);\n" + + " gl_Position = vec4(vec2(-1.0,1.0)+inv_screensize*in_position.xy,in_position.zw);\n" + " tex_coord = in_tex_coord;\n" + "}"); GL20.glCompileShader(vtxShdr); From 4b1dc39e4a12d26aab67cfad881ed329f5c879ba Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Tue, 6 Dec 2016 11:53:27 -0500 Subject: [PATCH 14/16] Follow-up to #205: fixed some compile warnings. --- .../opsu/render/CurveRenderState.java | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/src/itdelatrisu/opsu/render/CurveRenderState.java b/src/itdelatrisu/opsu/render/CurveRenderState.java index 96eb71b1..e4f98a16 100644 --- a/src/itdelatrisu/opsu/render/CurveRenderState.java +++ b/src/itdelatrisu/opsu/render/CurveRenderState.java @@ -24,19 +24,17 @@ import itdelatrisu.opsu.objects.curves.Vec2f; import java.nio.ByteBuffer; import java.nio.FloatBuffer; -import java.nio.IntBuffer; import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.EXTFramebufferObject; -import org.lwjgl.opengl.GLContext; import org.lwjgl.opengl.ContextCapabilities; +import org.lwjgl.opengl.EXTFramebufferObject; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; -import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL14; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; +import org.lwjgl.opengl.GLContext; import org.newdawn.slick.Color; import org.newdawn.slick.Image; import org.newdawn.slick.util.Log; @@ -104,7 +102,6 @@ public class CurveRenderState { this.curve = curve; this.pointIndices = new int[curve.length]; this.vboID = -1; - } /** @@ -117,19 +114,15 @@ public class CurveRenderState { */ public void draw(Color color, Color borderColor, float t) { t = Utils.clamp(t, 0.0f, 1.0f); - float alpha = color.a; // create curve geometry and store it on the GPU if (vboID == -1) { vboID = GL15.glGenBuffers(); createVertexBuffer(vboID); } - - int drawUpTo = (int) (t * (curve.length - 1)); - + int drawUpTo = (int) (t * (curve.length - 1)); this.renderCurve(color, borderColor, drawUpTo); - //color.a = 1f; } /** @@ -216,10 +209,7 @@ public class CurveRenderState { */ private void createVertexBuffer(int bufferID) { float radius = scale / 2; - float mul_x = 2.f / containerWidth; - float mul_y = 2.f / containerHeight; - - int triangle_count = staticState.DIVIDES; // for curve caps + int triangle_count = NewCurveStyleState.DIVIDES; // for curve caps float last_dx=0, last_dy=0; float last_alpha = 0; for (int i = 0; i < curve.length; ++i) { // compute number of triangles @@ -238,10 +228,10 @@ public class CurveRenderState { if (theta > Math.PI) theta -= 2*Math.PI; if (theta < -Math.PI) theta += 2*Math.PI; - if (Math.abs(theta) < 2*Math.PI / staticState.DIVIDES) { + if (Math.abs(theta) < 2*Math.PI / NewCurveStyleState.DIVIDES) { triangle_count++; - }else{ - int divs = (int)(Math.ceil(staticState.DIVIDES * Math.abs(theta) / (2*Math.PI))); + } else { + int divs = (int)(Math.ceil(NewCurveStyleState.DIVIDES * Math.abs(theta) / (2*Math.PI))); triangle_count += divs; } } @@ -256,24 +246,19 @@ public class CurveRenderState { int arrayBufferBinding = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING); FloatBuffer buff = BufferUtils.createByteBuffer(4 * (4 + 2) * 3 * (triangle_count)).asFloatBuffer(); - last_dx=0; last_dy=0; - float last_length=0; float last_ox=0, last_oy=0; for (int i = 0; i < curve.length; ++i) { float x = curve[i].x; float y = curve[i].y; if (i > 0) { /* - Render this shape: ___ ___ |A /|C /| | /B| /D| |/__|/__| - - - + */ float last_x = curve[i - 1].x; float last_y = curve[i - 1].y; @@ -285,15 +270,13 @@ public class CurveRenderState { float alpha = (float)Math.atan2(diff_y, diff_x); - if (i > 1) { float cross = last_dx * diff_y - last_dy * diff_x; float theta = alpha - last_alpha; if (theta > Math.PI) theta -= 2*Math.PI; if (theta < -Math.PI) theta += 2*Math.PI; - - if (Math.abs(theta) < 2*Math.PI / staticState.DIVIDES) { // small angle, just render single triangle + if (Math.abs(theta) < 2*Math.PI / NewCurveStyleState.DIVIDES) { // small angle, just render single triangle if (cross > 0) { // going counterclockwise buff.put(1.0f); buff.put(0.5f); buff.put(last_x); buff.put(last_y); @@ -315,10 +298,10 @@ public class CurveRenderState { buff.put(last_x - last_ox);buff.put(last_y - last_oy); buff.put(1.0f); buff.put(1.0f); } else { - //straight line, very unlikely + // straight line, very unlikely } } else { - int divs = (int)(Math.ceil(staticState.DIVIDES * Math.abs(theta) / (2*Math.PI))); + int divs = (int)(Math.ceil(NewCurveStyleState.DIVIDES * Math.abs(theta) / (2*Math.PI))); float phi = Math.abs(theta) / divs; float sinphi = (float)Math.sin(phi); float cosphi = (float)Math.cos(phi); @@ -352,7 +335,7 @@ public class CurveRenderState { } } } else { - int divs = staticState.DIVIDES/2; + int divs = NewCurveStyleState.DIVIDES / 2; float phi = (float)(Math.PI / divs); float sinphi = (float)Math.sin(phi); @@ -419,7 +402,7 @@ public class CurveRenderState { buff.put(1.0f); buff.put(1.0f); if (i == curve.length-1) { - int divs = staticState.DIVIDES/2; + int divs = NewCurveStyleState.DIVIDES / 2; float phi = (float)(Math.PI / divs); float sinphi = (float)Math.sin(phi); @@ -447,21 +430,19 @@ public class CurveRenderState { last_dx = diff_x; last_dy = diff_y; - last_length = length; last_ox = offs_x; last_oy = offs_y; last_alpha = alpha; } pointIndices[i] = buff.position() / 6; // 6 elements per vertex - } 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 @@ -484,7 +465,6 @@ public class CurveRenderState { 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); - GL11.glColorMask(false,false,false,false); GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, pointIndices[to]); GL11.glColorMask(true,true,true,true); @@ -499,7 +479,6 @@ public class CurveRenderState { restoreRenderState(state); } - /** * 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. From af667a48d5178948207a9dc01d2d1c3974106c43 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 8 Dec 2016 20:00:14 -0500 Subject: [PATCH 15/16] Added GL version/vendor to error report. (#207) Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/Container.java | 1 + src/itdelatrisu/opsu/ErrorHandler.java | 20 ++++++++++++++++++++ src/itdelatrisu/opsu/Opsu.java | 13 ++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/itdelatrisu/opsu/Container.java b/src/itdelatrisu/opsu/Container.java index fd0300ee..1a2b96d4 100644 --- a/src/itdelatrisu/opsu/Container.java +++ b/src/itdelatrisu/opsu/Container.java @@ -68,6 +68,7 @@ public class Container extends AppGameContainer { public void start() throws SlickException { try { setup(); + ErrorHandler.setGlString(); getDelta(); while (running()) gameLoop(); diff --git a/src/itdelatrisu/opsu/ErrorHandler.java b/src/itdelatrisu/opsu/ErrorHandler.java index 1dc4d227..3fb23259 100644 --- a/src/itdelatrisu/opsu/ErrorHandler.java +++ b/src/itdelatrisu/opsu/ErrorHandler.java @@ -33,6 +33,7 @@ import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.UIManager; +import org.lwjgl.opengl.GL11; import org.newdawn.slick.util.Log; import org.newdawn.slick.util.ResourceLoader; @@ -73,9 +74,23 @@ public class ErrorHandler { message = { desc, scroll }, messageReport = { descReport, scroll }; + /** OpenGL string (if any). */ + private static String glString = null; + // This class should not be instantiated. private ErrorHandler() {} + /** + * Sets the OpenGL version string. + */ + public static void setGlString() { + try { + String glVersion = GL11.glGetString(GL11.GL_VERSION); + String glVendor = GL11.glGetString(GL11.GL_VENDOR); + glString = String.format("%s (%s)", glVersion, glVendor); + } catch (Exception e) {} + } + /** * Displays an error popup and logs the given error. * @param error a description of the error @@ -197,6 +212,11 @@ public class ErrorHandler { sb.append("**JRE:** "); sb.append(System.getProperty("java.version")); sb.append('\n'); + if (glString != null) { + sb.append("**OpenGL Version:** "); + sb.append(glString); + sb.append('\n'); + } if (error != null) { sb.append("**Error:** `"); sb.append(error); diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index 4a7a3abd..f3b9c0df 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -107,10 +107,13 @@ public class Opsu extends StateBasedGame { } catch (FileNotFoundException e) { Log.error(e); } + + // set default exception handler Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { ErrorHandler.error("** Uncaught Exception! **", e, true); + System.exit(1); } }); @@ -123,15 +126,19 @@ public class Opsu extends StateBasedGame { } catch (UnknownHostException e) { // shouldn't happen } catch (IOException e) { - ErrorHandler.error(String.format( + errorAndExit( + null, + String.format( "opsu! could not be launched for one of these reasons:\n" + "- An instance of opsu! is already running.\n" + "- Another program is bound to port %d. " + "You can change the port opsu! uses by editing the \"Port\" field in the configuration file.", - Options.getPort()), null, false); - System.exit(1); + Options.getPort() + ) + ); } + // load natives File nativeDir; if (!Utils.isJarRunning() && ( (nativeDir = new File("./target/natives/")).isDirectory() || From 38daee002fa72a467a8c3aa7fd8a2da96a649d2b Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Thu, 8 Dec 2016 20:05:08 -0500 Subject: [PATCH 16/16] Follow-up to af667a4. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/Opsu.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/itdelatrisu/opsu/Opsu.java b/src/itdelatrisu/opsu/Opsu.java index f3b9c0df..9641ab3c 100644 --- a/src/itdelatrisu/opsu/Opsu.java +++ b/src/itdelatrisu/opsu/Opsu.java @@ -134,7 +134,8 @@ public class Opsu extends StateBasedGame { "- Another program is bound to port %d. " + "You can change the port opsu! uses by editing the \"Port\" field in the configuration file.", Options.getPort() - ) + ), + false ); } @@ -171,7 +172,7 @@ public class Opsu extends StateBasedGame { try { DBController.init(); } catch (UnsatisfiedLinkError e) { - errorAndExit(e, "The databases could not be initialized."); + errorAndExit(e, "The databases could not be initialized.", true); } // check if just updated @@ -218,7 +219,7 @@ public class Opsu extends StateBasedGame { } } } catch (SlickException e) { - errorAndExit(e, "An error occurred while creating the game container."); + errorAndExit(e, "An error occurred while creating the game container.", true); } } @@ -284,15 +285,16 @@ public class Opsu extends StateBasedGame { * Throws an error and exits the application with the given message. * @param e the exception that caused the crash * @param message the message to display + * @param report whether to ask to report the error */ - private static void errorAndExit(Throwable e, String message) { + private static void errorAndExit(Throwable e, String message, boolean report) { // JARs will not run properly inside directories containing '!' // http://bugs.java.com/view_bug.do?bug_id=4523159 if (Utils.isJarRunning() && Utils.getRunningDirectory() != null && Utils.getRunningDirectory().getAbsolutePath().indexOf('!') != -1) ErrorHandler.error("JARs cannot be run from some paths containing '!'. Please move or rename the file and try again.", null, false); else - ErrorHandler.error(message, e, true); + ErrorHandler.error(message, e, report); System.exit(1); } }