Merge remote-tracking branch 'org/master' into ReplayTest
Conflicts: src/itdelatrisu/opsu/GameData.java src/itdelatrisu/opsu/Options.java src/itdelatrisu/opsu/OsuFile.java src/itdelatrisu/opsu/OsuGroupList.java src/itdelatrisu/opsu/OsuHitObject.java src/itdelatrisu/opsu/OsuParser.java src/itdelatrisu/opsu/UI.java src/itdelatrisu/opsu/db/OsuDB.java src/itdelatrisu/opsu/objects/Circle.java src/itdelatrisu/opsu/objects/HitObject.java src/itdelatrisu/opsu/objects/Slider.java src/itdelatrisu/opsu/objects/Spinner.java src/itdelatrisu/opsu/states/Game.java src/itdelatrisu/opsu/states/Splash.java
This commit is contained in:
@@ -19,10 +19,12 @@
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.GameData.HitObjectType;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.GameMod;
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.states.Game;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
@@ -32,14 +34,14 @@ import org.newdawn.slick.Graphics;
|
||||
/**
|
||||
* Data type representing a circle object.
|
||||
*/
|
||||
public class Circle implements HitObject {
|
||||
public class Circle implements GameObject {
|
||||
/** The amount of time, in milliseconds, to fade in the circle. */
|
||||
private static final int FADE_IN_TIME = 375;
|
||||
|
||||
private static float diameter;
|
||||
|
||||
/** The associated OsuHitObject. */
|
||||
private OsuHitObject hitObject;
|
||||
/** The associated HitObject. */
|
||||
private HitObject hitObject;
|
||||
|
||||
/** The scaled starting x, y coordinates. */
|
||||
private float x, y;
|
||||
@@ -69,17 +71,22 @@ public class Circle implements HitObject {
|
||||
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt));
|
||||
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameterInt, diameterInt));
|
||||
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameterInt, diameterInt));
|
||||
/*int diameter = (int) (104 - (circleSize * 8));
|
||||
diameter = (int) (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
||||
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
|
||||
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
|
||||
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter));*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
* @param game the associated Game object
|
||||
* @param data the associated GameData object
|
||||
* @param color the color of this circle
|
||||
* @param comboEnd true if this is the last hit object in the combo
|
||||
*/
|
||||
public Circle(OsuHitObject hitObject, Game game, GameData data, Color color, boolean comboEnd) {
|
||||
public Circle(HitObject hitObject, Game game, GameData data, Color color, boolean comboEnd) {
|
||||
this.hitObject = hitObject;
|
||||
this.game = game;
|
||||
this.data = data;
|
||||
@@ -102,9 +109,13 @@ public class Circle implements HitObject {
|
||||
if (timeDiff >= 0)
|
||||
GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale).drawCentered(x, y, color);
|
||||
GameImage.HITCIRCLE.getImage().drawCentered(x, y, color);
|
||||
GameImage.HITCIRCLE_OVERLAY.getImage().drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber();
|
||||
if (!overlayAboveNumber)
|
||||
GameImage.HITCIRCLE_OVERLAY.getImage().drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
|
||||
GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
||||
if (overlayAboveNumber)
|
||||
GameImage.HITCIRCLE_OVERLAY.getImage().drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
|
||||
Utils.COLOR_WHITE_FADE.a = oldAlpha;
|
||||
}
|
||||
@@ -144,7 +155,7 @@ public class Circle implements HitObject {
|
||||
|
||||
if (result > -1) {
|
||||
data.addHitError(hitObject.getTime(), x, y, timeDiff);
|
||||
data.hitResult(hitObject.getTime(), result, this.x, this.y, color, comboEnd, hitObject, 0);
|
||||
data.hitResult(trackPosition, result, this.x, this.y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -160,17 +171,17 @@ public class Circle implements HitObject {
|
||||
|
||||
if (trackPosition > time + hitResultOffset[GameData.HIT_50]) {
|
||||
if (isAutoMod) // "auto" mod: catch any missed notes due to lag
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0);
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
|
||||
else // no more points can be scored, so send a miss
|
||||
data.hitResult(time, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, 0);
|
||||
data.hitResult(trackPosition, GameData.HIT_MISS, x, y, null, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// "auto" mod: send a perfect hit result
|
||||
else if (isAutoMod) {
|
||||
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0);
|
||||
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitObject, 0, HitObjectType.CIRCLE, null, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,25 @@
|
||||
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
|
||||
import org.newdawn.slick.Graphics;
|
||||
|
||||
/**
|
||||
* Dummy hit object, used when another HitObject class cannot be created.
|
||||
* Dummy hit object, used when another GameObject class cannot be created.
|
||||
*/
|
||||
public class DummyObject implements HitObject {
|
||||
/** The associated OsuHitObject. */
|
||||
private OsuHitObject hitObject;
|
||||
public class DummyObject implements GameObject {
|
||||
/** The associated HitObject. */
|
||||
private HitObject hitObject;
|
||||
|
||||
/** The scaled starting x, y coordinates. */
|
||||
private float x, y;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
*/
|
||||
public DummyObject(OsuHitObject hitObject) {
|
||||
public DummyObject(HitObject hitObject) {
|
||||
this.hitObject = hitObject;
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
72
src/itdelatrisu/opsu/objects/GameObject.java
Normal file
72
src/itdelatrisu/opsu/objects/GameObject.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import org.newdawn.slick.Graphics;
|
||||
|
||||
/**
|
||||
* Interface for hit object types used during gameplay.
|
||||
*/
|
||||
public interface GameObject {
|
||||
/**
|
||||
* Draws the hit object to the graphics context.
|
||||
* @param g the graphics context
|
||||
* @param trackPosition the current track position
|
||||
*/
|
||||
public void draw(Graphics g, int trackPosition);
|
||||
|
||||
/**
|
||||
* Updates the hit object.
|
||||
* @param overlap true if the next object's start time has already passed
|
||||
* @param delta the delta interval since the last call
|
||||
* @param mouseX the x coordinate of the mouse
|
||||
* @param mouseY the y coordinate of the mouse
|
||||
* @param keyPressed whether or not a game key is currently pressed
|
||||
* @param trackPosition the track position
|
||||
* @return true if object ended
|
||||
*/
|
||||
public boolean update(boolean overlap, int delta, int mouseX, int mouseY, boolean keyPressed, int trackPosition);
|
||||
|
||||
/**
|
||||
* Processes a mouse click.
|
||||
* @param x the x coordinate of the mouse
|
||||
* @param y the y coordinate of the mouse
|
||||
* @param trackPosition the track position
|
||||
* @return true if a hit result was processed
|
||||
*/
|
||||
public boolean mousePressed(int x, int y, int trackPosition);
|
||||
|
||||
/**
|
||||
* Returns the coordinates of the hit object at a given track position.
|
||||
* @param trackPosition the track position
|
||||
* @return the [x,y] coordinates
|
||||
*/
|
||||
public float[] getPointAt(int trackPosition);
|
||||
|
||||
/**
|
||||
* Returns the end time of the hit object.
|
||||
* @return the end time, in milliseconds
|
||||
*/
|
||||
public int getEndTime();
|
||||
|
||||
/**
|
||||
* Updates the position of the hit object.
|
||||
*/
|
||||
public void updatePosition();
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//TODO rename
|
||||
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
|
||||
@@ -19,11 +19,14 @@
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.GameData.HitObjectType;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.GameMod;
|
||||
import itdelatrisu.opsu.OsuFile;
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.Beatmap;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.objects.curves.CatmullCurve;
|
||||
import itdelatrisu.opsu.objects.curves.CircumscribedCircle;
|
||||
import itdelatrisu.opsu.objects.curves.Curve;
|
||||
import itdelatrisu.opsu.objects.curves.LinearBezier;
|
||||
@@ -37,7 +40,7 @@ import org.newdawn.slick.Image;
|
||||
/**
|
||||
* Data type representing a slider object.
|
||||
*/
|
||||
public class Slider implements HitObject {
|
||||
public class Slider implements GameObject {
|
||||
/** Slider ball frames. */
|
||||
private static Image[] sliderBallImages;
|
||||
|
||||
@@ -54,8 +57,8 @@ public class Slider implements HitObject {
|
||||
/** The amount of time, in milliseconds, to fade in the slider. */
|
||||
private static final int FADE_IN_TIME = 375;
|
||||
|
||||
/** The associated OsuHitObject. */
|
||||
private OsuHitObject hitObject;
|
||||
/** The associated HitObject. */
|
||||
private HitObject hitObject;
|
||||
|
||||
/** The scaled starting x, y coordinates. */
|
||||
protected float x, y;
|
||||
@@ -108,9 +111,9 @@ public class Slider implements HitObject {
|
||||
* Initializes the Slider data type with images and dimensions.
|
||||
* @param container the game container
|
||||
* @param circleSize the map's circleSize value
|
||||
* @param osu the associated OsuFile object
|
||||
* @param beatmap the associated beatmap
|
||||
*/
|
||||
public static void init(GameContainer container, float circleSize, OsuFile osu) {
|
||||
public static void init(GameContainer container, float circleSize, Beatmap beatmap) {
|
||||
containerWidth = container.getWidth();
|
||||
containerHeight = container.getHeight();
|
||||
|
||||
@@ -119,7 +122,11 @@ public class Slider implements HitObject {
|
||||
int diameterInt = (int)diameter;
|
||||
|
||||
followRadius = diameter / 2 * 3f;
|
||||
|
||||
//TODO conflict
|
||||
/*
|
||||
int diameter = (int) (104 - (circleSize * 8));
|
||||
diameter = (int) (diameter * HitObject.getXMultiplier()); // convert from Osupixels (640x480)
|
||||
*/
|
||||
// slider ball
|
||||
if (GameImage.SLIDER_BALL.hasSkinImages() ||
|
||||
(!GameImage.SLIDER_BALL.hasSkinImage() && GameImage.SLIDER_BALL.getImages() != null))
|
||||
@@ -133,19 +140,19 @@ public class Slider implements HitObject {
|
||||
GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameterInt, diameterInt));
|
||||
GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameterInt / 4, diameterInt / 4));
|
||||
|
||||
sliderMultiplier = osu.sliderMultiplier;
|
||||
sliderTickRate = osu.sliderTickRate;
|
||||
sliderMultiplier = beatmap.sliderMultiplier;
|
||||
sliderTickRate = beatmap.sliderTickRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
* @param game the associated Game object
|
||||
* @param data the associated GameData object
|
||||
* @param color the color of this slider
|
||||
* @param comboEnd true if this is the last hit object in the combo
|
||||
*/
|
||||
public Slider(OsuHitObject hitObject, Game game, GameData data, Color color, boolean comboEnd) {
|
||||
public Slider(HitObject hitObject, Game game, GameData data, Color color, boolean comboEnd) {
|
||||
this.hitObject = hitObject;
|
||||
this.game = game;
|
||||
this.data = data;
|
||||
@@ -176,12 +183,25 @@ public class Slider implements HitObject {
|
||||
float fadeinScale = (timeDiff - game.getApproachTime() + FADE_IN_TIME) / (float) FADE_IN_TIME;
|
||||
float approachScale = 1 + scale * 3;
|
||||
float alpha = Utils.clamp(1 - fadeinScale, 0, 1);
|
||||
boolean overlayAboveNumber = Options.getSkin().isHitCircleOverlayAboveNumber();
|
||||
|
||||
float oldAlpha = Utils.COLOR_WHITE_FADE.a;
|
||||
Utils.COLOR_WHITE_FADE.a = color.a = alpha;
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
float[] endPos = curve.pointAt(1);
|
||||
|
||||
// curve
|
||||
curve.draw();
|
||||
curve.draw(color);
|
||||
color.a = alpha;
|
||||
|
||||
// end circle
|
||||
hitCircle.drawCentered(endPos[0], endPos[1], color);
|
||||
hitCircleOverlay.drawCentered(endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
|
||||
|
||||
// start circle
|
||||
hitCircle.drawCentered(x, y, color);
|
||||
if (!overlayAboveNumber)
|
||||
hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
|
||||
// ticks
|
||||
if (ticksT != null) {
|
||||
@@ -191,23 +211,13 @@ public class Slider implements HitObject {
|
||||
tick.drawCentered(c[0], c[1], Utils.COLOR_WHITE_FADE);
|
||||
}
|
||||
}
|
||||
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
|
||||
// end circle
|
||||
float[] endPos = curve.pointAt(1);
|
||||
hitCircle.drawCentered(endPos[0], endPos[1], color);
|
||||
hitCircleOverlay.drawCentered(endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
|
||||
|
||||
// start circle
|
||||
hitCircle.drawCentered(x, y, color);
|
||||
hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
if (sliderClickedInitial)
|
||||
; // don't draw current combo number if already clicked
|
||||
else
|
||||
data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
|
||||
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
||||
hitCircle.getWidth() * 0.40f / data.getDefaultSymbolImage(0).getHeight(), alpha);
|
||||
if (overlayAboveNumber)
|
||||
hitCircleOverlay.drawCentered(x, y, Utils.COLOR_WHITE_FADE);
|
||||
|
||||
// repeats
|
||||
for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) {
|
||||
@@ -245,7 +255,7 @@ public class Slider implements HitObject {
|
||||
float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f);
|
||||
|
||||
float t = getT(trackPosition, false);
|
||||
// float dis = hitObject.getPixelLength() * OsuHitObject.getXMultiplier() * (t - (int) t);
|
||||
// float dis = hitObject.getPixelLength() * HitObject.getXMultiplier() * (t - (int) t);
|
||||
// Image sliderBallFrame = sliderBallImages[(int) (dis / (diameter * Math.PI) * 30) % sliderBallImages.length];
|
||||
Image sliderBallFrame = sliderBallImages[(int) (t * sliderTime * 60 / 1000) % sliderBallImages.length];
|
||||
float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI);
|
||||
@@ -336,14 +346,20 @@ public class Slider implements HitObject {
|
||||
else
|
||||
result = GameData.HIT_MISS;
|
||||
|
||||
float cx, cy;
|
||||
HitObjectType type;
|
||||
if (currentRepeats % 2 == 0) { // last circle
|
||||
float[] lastPos = curve.pointAt(1);
|
||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||
lastPos[0], lastPos[1], color, comboEnd, hitObject, currentRepeats + 1);
|
||||
cx = lastPos[0];
|
||||
cy = lastPos[1];
|
||||
type = HitObjectType.SLIDER_LAST;
|
||||
} else { // first circle
|
||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||
x, y, color, comboEnd, hitObject, currentRepeats + 1);
|
||||
cx = x;
|
||||
cy = y;
|
||||
type = HitObjectType.SLIDER_FIRST;
|
||||
}
|
||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||
cx, cy, color, comboEnd, hitObject, currentRepeats + 1, type, curve, sliderClickedFinal);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -509,10 +525,12 @@ public class Slider implements HitObject {
|
||||
this.x = hitObject.getScaledX();
|
||||
this.y = hitObject.getScaledY();
|
||||
|
||||
if (hitObject.getSliderType() == OsuHitObject.SLIDER_PASSTHROUGH && hitObject.getSliderX().length == 2)
|
||||
if (hitObject.getSliderType() == HitObject.SLIDER_PASSTHROUGH && hitObject.getSliderX().length == 2)
|
||||
this.curve = new CircumscribedCircle(hitObject, color);
|
||||
else if (hitObject.getSliderType() == HitObject.SLIDER_CATMULL)
|
||||
this.curve = new CatmullCurve(hitObject, color);
|
||||
else
|
||||
this.curve = new LinearBezier(hitObject, color);
|
||||
this.curve = new LinearBezier(hitObject, color, hitObject.getSliderType() == HitObject.SLIDER_LINEAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
package itdelatrisu.opsu.objects;
|
||||
|
||||
import itdelatrisu.opsu.GameData;
|
||||
import itdelatrisu.opsu.GameData.HitObjectType;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.GameMod;
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.audio.SoundController;
|
||||
import itdelatrisu.opsu.audio.SoundEffect;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.states.Game;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
@@ -35,7 +37,7 @@ import org.newdawn.slick.Image;
|
||||
/**
|
||||
* Data type representing a spinner object.
|
||||
*/
|
||||
public class Spinner implements HitObject {
|
||||
public class Spinner implements GameObject {
|
||||
/** Container dimensions. */
|
||||
private static int width, height;
|
||||
|
||||
@@ -62,8 +64,8 @@ public class Spinner implements HitObject {
|
||||
|
||||
private static final float MAX_ANG_DIFF = DELTA_UPDATE_TIME * 477 / 60 / 1000 * TWO_PI; // ~95.3
|
||||
|
||||
/** The associated OsuHitObject. */
|
||||
private OsuHitObject hitObject;
|
||||
/** The associated HitObject. */
|
||||
private HitObject hitObject;
|
||||
|
||||
/** The associated GameData object. */
|
||||
private GameData data;
|
||||
@@ -110,11 +112,11 @@ public class Spinner implements HitObject {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
* @param game the associated Game object
|
||||
* @param data the associated GameData object
|
||||
*/
|
||||
public Spinner(OsuHitObject hitObject, Game game, GameData data) {
|
||||
public Spinner(HitObject hitObject, Game game, GameData data) {
|
||||
this.hitObject = hitObject;
|
||||
this.data = data;
|
||||
|
||||
@@ -132,15 +134,17 @@ public class Spinner implements HitObject {
|
||||
return;
|
||||
|
||||
boolean spinnerComplete = (rotations >= rotationsNeeded);
|
||||
float alpha = Utils.clamp(1 - (float) timeDiff / FADE_IN_TIME, 0f, 1f);
|
||||
|
||||
// darken screen
|
||||
float alpha = Utils.clamp(1 - (float) timeDiff / FADE_IN_TIME, 0f, 1f);
|
||||
float oldAlpha = Utils.COLOR_BLACK_ALPHA.a;
|
||||
if (timeDiff > 0)
|
||||
Utils.COLOR_BLACK_ALPHA.a *= alpha;
|
||||
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
||||
g.fillRect(0, 0, width, height);
|
||||
Utils.COLOR_BLACK_ALPHA.a = oldAlpha;
|
||||
if (Options.getSkin().isSpinnerFadePlayfield()) {
|
||||
float oldAlpha = Utils.COLOR_BLACK_ALPHA.a;
|
||||
if (timeDiff > 0)
|
||||
Utils.COLOR_BLACK_ALPHA.a *= alpha;
|
||||
g.setColor(Utils.COLOR_BLACK_ALPHA);
|
||||
g.fillRect(0, 0, width, height);
|
||||
Utils.COLOR_BLACK_ALPHA.a = oldAlpha;
|
||||
}
|
||||
|
||||
// rpm
|
||||
Image rpmImg = GameImage.SPINNER_RPM.getImage();
|
||||
@@ -198,7 +202,7 @@ public class Spinner implements HitObject {
|
||||
result = GameData.HIT_MISS;
|
||||
|
||||
data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
|
||||
Color.transparent, true, hitObject, 0);
|
||||
Color.transparent, true, hitObject, 0, HitObjectType.SPINNER, null, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,22 +23,10 @@ package itdelatrisu.opsu.objects.curves;
|
||||
*
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public class Bezier2 {
|
||||
public class Bezier2 extends CurveType {
|
||||
/** The control points of the Bezier curve. */
|
||||
private Vec2f[] points;
|
||||
|
||||
/** Points along the curve of the Bezier curve. */
|
||||
private Vec2f[] curve;
|
||||
|
||||
/** Distances between a point of the curve and the last point. */
|
||||
private float[] curveDis;
|
||||
|
||||
/** The number of points along the curve. */
|
||||
private int ncurve;
|
||||
|
||||
/** The total distances of this Bezier. */
|
||||
private float totalDistance;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param points the control points
|
||||
@@ -52,27 +40,10 @@ public class Bezier2 {
|
||||
for (int i = 0; i < points.length - 1; i++)
|
||||
approxlength += points[i].cpy().sub(points[i + 1]).len();
|
||||
|
||||
// subdivide the curve
|
||||
this.ncurve = (int) (approxlength / 4);
|
||||
this.curve = new Vec2f[ncurve];
|
||||
for (int i = 0; i < ncurve; i++)
|
||||
curve[i] = pointAt(i / (float) ncurve);
|
||||
|
||||
// find the distance of each point from the previous point
|
||||
this.curveDis = new float[ncurve];
|
||||
this.totalDistance = 0;
|
||||
for (int i = 0; i < ncurve; i++) {
|
||||
curveDis[i] = (i == 0) ? 0 : curve[i].cpy().sub(curve[i - 1]).len();
|
||||
totalDistance += curveDis[i];
|
||||
}
|
||||
// System.out.println("New Bezier2 "+points.length+" "+approxlength+" "+totalDistance());
|
||||
init(approxlength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the point on the Bezier curve at a value t.
|
||||
* @param t the t value [0, 1]
|
||||
* @return the point [x, y]
|
||||
*/
|
||||
@Override
|
||||
public Vec2f pointAt(float t) {
|
||||
Vec2f c = new Vec2f();
|
||||
int n = points.length - 1;
|
||||
@@ -84,31 +55,11 @@ public class Bezier2 {
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the points along the curve of the Bezier curve.
|
||||
*/
|
||||
public Vec2f[] getCurve() { return curve; }
|
||||
|
||||
/**
|
||||
* Returns the distances between a point of the curve and the last point.
|
||||
*/
|
||||
public float[] getCurveDistances() { return curveDis; }
|
||||
|
||||
/**
|
||||
* Returns the number of points along the curve.
|
||||
*/
|
||||
public int points() { return ncurve; }
|
||||
|
||||
/**
|
||||
* Returns the total distances of this Bezier curve.
|
||||
*/
|
||||
public float totalDistance() { return totalDistance; }
|
||||
|
||||
/**
|
||||
* Calculates the binomial coefficient.
|
||||
* http://en.wikipedia.org/wiki/Binomial_coefficient#Binomial_coefficient_in_programming_languages
|
||||
*/
|
||||
public static long binomialCoefficient(int n, int k) {
|
||||
private static long binomialCoefficient(int n, int k) {
|
||||
if (k < 0 || k > n)
|
||||
return 0;
|
||||
if (k == 0 || k == n)
|
||||
|
||||
77
src/itdelatrisu/opsu/objects/curves/CatmullCurve.java
Normal file
77
src/itdelatrisu/opsu/objects/curves/CatmullCurve.java
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.SlickException;
|
||||
|
||||
/**
|
||||
* Representation of Catmull Curve with equidistant points.
|
||||
*
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public class CatmullCurve extends EqualDistanceMultiCurve {
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated HitObject
|
||||
* @param color the color of this curve
|
||||
*/
|
||||
public CatmullCurve(HitObject hitObject, Color color) {
|
||||
super(hitObject, color);
|
||||
LinkedList<CurveType> catmulls = new LinkedList<CurveType>();
|
||||
int ncontrolPoints = hitObject.getSliderX().length + 1;
|
||||
LinkedList<Vec2f> points = new LinkedList<Vec2f>(); // temporary list of points to separate different curves
|
||||
|
||||
// repeat the first and last points as controls points
|
||||
// only if the first/last two points are different
|
||||
// aabb
|
||||
// aabc abcc
|
||||
// aabc abcd bcdd
|
||||
if (getX(0) != getX(1) || getY(0) != getY(1))
|
||||
points.addLast(new Vec2f(getX(0), getY(0)));
|
||||
for (int i = 0; i < ncontrolPoints; i++) {
|
||||
points.addLast(new Vec2f(getX(i), getY(i)));
|
||||
if (points.size() >= 4) {
|
||||
try {
|
||||
catmulls.add(new CentripetalCatmullRom(points.toArray(new Vec2f[0])));
|
||||
} catch (SlickException e) {
|
||||
ErrorHandler.error(null, e, true);
|
||||
}
|
||||
points.removeFirst();
|
||||
}
|
||||
}
|
||||
if (getX(ncontrolPoints - 1) != getX(ncontrolPoints - 2)
|
||||
||getY(ncontrolPoints - 1) != getY(ncontrolPoints - 2))
|
||||
points.addLast(new Vec2f(getX(ncontrolPoints - 1), getY(ncontrolPoints - 1)));
|
||||
if (points.size() >= 4) {
|
||||
try {
|
||||
catmulls.add(new CentripetalCatmullRom(points.toArray(new Vec2f[0])));
|
||||
} catch (SlickException e) {
|
||||
ErrorHandler.error(null, e, true);
|
||||
}
|
||||
}
|
||||
|
||||
init(catmulls);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import org.newdawn.slick.SlickException;
|
||||
|
||||
/**
|
||||
* Representation of a Centripetal Catmull–Rom spline.
|
||||
* (Currently not technically Centripetal Catmull–Rom.)
|
||||
* http://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
|
||||
*
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public class CentripetalCatmullRom extends CurveType {
|
||||
/** The time values of the Catmull curve. */
|
||||
private float [] time;
|
||||
|
||||
/** The control points of the Catmull curve. */
|
||||
private Vec2f[] points;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param points the control points of the curve
|
||||
* @throws SlickException
|
||||
*/
|
||||
protected CentripetalCatmullRom(Vec2f[] points) throws SlickException {
|
||||
if (points.length != 4)
|
||||
throw new SlickException(String.format("Need exactly 4 points to initialize CentripetalCatmullRom, %d provided.", points.length));
|
||||
|
||||
this.points = points;
|
||||
time = new float[4];
|
||||
time[0] = 0;
|
||||
float approxLength = 0;
|
||||
for (int i = 1; i < 4; i++) {
|
||||
float len = 0;
|
||||
if (i > 0)
|
||||
len = points[i].cpy().sub(points[i - 1]).len();
|
||||
if (len <= 0)
|
||||
len += 0.0001f;
|
||||
approxLength += len;
|
||||
// time[i] = (float) Math.sqrt(len) + time[i-1];// ^(0.5)
|
||||
time[i] = i;
|
||||
}
|
||||
|
||||
init(approxLength / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2f pointAt(float t) {
|
||||
t = t * (time[2] - time[1]) + time[1];
|
||||
|
||||
Vec2f A1 = points[0].cpy().scale((time[1] - t) / (time[1] - time[0]))
|
||||
.add(points[1].cpy().scale((t - time[0]) / (time[1] - time[0])));
|
||||
Vec2f A2 = points[1].cpy().scale((time[2] - t) / (time[2] - time[1]))
|
||||
.add(points[2].cpy().scale((t - time[1]) / (time[2] - time[1])));
|
||||
Vec2f A3 = points[2].cpy().scale((time[3] - t) / (time[3] - time[2]))
|
||||
.add(points[3].cpy().scale((t - time[2]) / (time[3] - time[2])));
|
||||
|
||||
Vec2f B1 = A1.cpy().scale((time[2] - t) / (time[2] - time[0]))
|
||||
.add(A2.cpy().scale((t - time[0]) / (time[2] - time[0])));
|
||||
Vec2f B2 = A2.cpy().scale((time[3] - t) / (time[3] - time[1]))
|
||||
.add(A3.cpy().scale((t - time[1]) / (time[3] - time[1])));
|
||||
|
||||
Vec2f C = B1.cpy().scale((time[2] - t) / (time[2] - time[1]))
|
||||
.add(B2.cpy().scale((t - time[1]) / (time[2] - time[1])));
|
||||
|
||||
return C;
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,9 @@
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import itdelatrisu.opsu.ErrorHandler;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
|
||||
/**
|
||||
* Representation of a curve along a Circumscribed Circle of three points.
|
||||
@@ -53,22 +50,14 @@ public class CircumscribedCircle extends Curve {
|
||||
/** The start and end angles for drawing. */
|
||||
private float drawStartAngle, drawEndAngle;
|
||||
|
||||
/** The number of steps in the curve to draw. */
|
||||
private float step;
|
||||
|
||||
/** Points along the curve. */
|
||||
private Vec2f[] curve;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
* @param color the color of this curve
|
||||
*/
|
||||
public CircumscribedCircle(OsuHitObject hitObject, Color color) {
|
||||
public CircumscribedCircle(HitObject hitObject, Color color) {
|
||||
super(hitObject, color);
|
||||
|
||||
this.step = hitObject.getPixelLength() / 5f;
|
||||
|
||||
// construct the three points
|
||||
this.start = new Vec2f(getX(0), getY(0));
|
||||
this.mid = new Vec2f(getX(1), getY(1));
|
||||
@@ -114,7 +103,7 @@ public class CircumscribedCircle extends Curve {
|
||||
|
||||
// find an angle with an arc length of pixelLength along this circle
|
||||
this.radius = startAngPoint.len();
|
||||
float pixelLength = hitObject.getPixelLength() * OsuHitObject.getXMultiplier();
|
||||
float pixelLength = hitObject.getPixelLength() * HitObject.getXMultiplier();
|
||||
float arcAng = pixelLength / radius; // len = theta * r / theta = len / r
|
||||
|
||||
// now use it for our new end angle
|
||||
@@ -125,6 +114,7 @@ public class CircumscribedCircle extends Curve {
|
||||
this.drawStartAngle = (float) ((startAng + (startAng > endAng ? -HALF_PI : HALF_PI)) * 180 / Math.PI);
|
||||
|
||||
// calculate points
|
||||
float step = hitObject.getPixelLength() / CURVE_POINTS_SEPERATION;
|
||||
curve = new Vec2f[(int) step + 1];
|
||||
for (int i = 0; i < curve.length; i++) {
|
||||
float[] xy = pointAt(i / step);
|
||||
@@ -178,16 +168,6 @@ public class CircumscribedCircle extends Curve {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw() {
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
for (int i = 0; i < step; i++)
|
||||
hitCircleOverlay.drawCentered(curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
||||
for (int i = 0; i < step; i++)
|
||||
hitCircle.drawCentered(curve[i].x, curve[i].y, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEndAngle() { return drawEndAngle; }
|
||||
|
||||
|
||||
@@ -18,9 +18,17 @@
|
||||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.Options;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
import itdelatrisu.opsu.render.CurveRenderState;
|
||||
import itdelatrisu.opsu.skins.Skin;
|
||||
|
||||
import org.lwjgl.opengl.GLContext;
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* Representation of a curve.
|
||||
@@ -28,11 +36,17 @@ import org.newdawn.slick.Color;
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public abstract class Curve {
|
||||
/** The associated OsuHitObject. */
|
||||
protected OsuHitObject hitObject;
|
||||
/** Points generated along the curve should be spaced this far apart. */
|
||||
protected static float CURVE_POINTS_SEPERATION = 5;
|
||||
|
||||
/** The color of this curve. */
|
||||
protected Color color;
|
||||
/** The curve border color. */
|
||||
private static Color borderColor;
|
||||
|
||||
/** Whether OpenGL 3.0 is supported. */
|
||||
private static boolean openGL30 = false;
|
||||
|
||||
/** The associated HitObject. */
|
||||
protected HitObject hitObject;
|
||||
|
||||
/** The scaled starting x, y coordinates. */
|
||||
protected float x, y;
|
||||
@@ -40,18 +54,43 @@ public abstract class Curve {
|
||||
/** The scaled slider x, y coordinate lists. */
|
||||
protected float[] sliderX, sliderY;
|
||||
|
||||
/** Per-curve render-state used for the new style curve renders. */
|
||||
private CurveRenderState renderState;
|
||||
|
||||
/** Points along the curve (set by inherited classes). */
|
||||
protected Vec2f[] curve;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
* @param color the color of this curve
|
||||
*/
|
||||
protected Curve(OsuHitObject hitObject, Color color) {
|
||||
protected Curve(HitObject hitObject, Color color) {
|
||||
this.hitObject = hitObject;
|
||||
this.x = hitObject.getScaledX();
|
||||
this.y = hitObject.getScaledY();
|
||||
this.sliderX = hitObject.getScaledSliderX();
|
||||
this.sliderY = hitObject.getScaledSliderY();
|
||||
this.color = color;
|
||||
this.renderState = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width and height of the container that Curves get drawn into.
|
||||
* Should be called before any curves are drawn.
|
||||
* @param width the container width
|
||||
* @param height the container height
|
||||
* @param circleSize the circle size
|
||||
* @param borderColor the curve border color
|
||||
*/
|
||||
public static void init(int width, int height, float circleSize, Color borderColor) {
|
||||
Curve.borderColor = borderColor;
|
||||
openGL30 = GLContext.getCapabilities().OpenGL30;
|
||||
if (openGL30)
|
||||
CurveRenderState.init(width, height, circleSize);
|
||||
else {
|
||||
if (Options.getSkin().getSliderStyle() != Skin.STYLE_PEPPYSLIDER)
|
||||
Log.warn("New slider style requires OpenGL 3.0, which is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +102,29 @@ public abstract class Curve {
|
||||
|
||||
/**
|
||||
* Draws the full curve to the graphics context.
|
||||
* @param color the color filter
|
||||
*/
|
||||
public abstract void draw();
|
||||
public void draw(Color color) {
|
||||
if (curve == null)
|
||||
return;
|
||||
|
||||
// peppysliders
|
||||
if (Options.getSkin().getSliderStyle() == Skin.STYLE_PEPPYSLIDER || !openGL30) {
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
hitCircleOverlay.drawCentered(curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
hitCircle.drawCentered(curve[i].x, curve[i].y, color);
|
||||
}
|
||||
|
||||
// mmsliders
|
||||
else {
|
||||
if (renderState == null)
|
||||
renderState = new CurveRenderState(hitObject);
|
||||
renderState.draw(color, borderColor, curve);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the angle of the first control point.
|
||||
@@ -94,4 +154,12 @@ public abstract class Curve {
|
||||
protected float lerp(float a, float b, float t) {
|
||||
return a * (1 - t) + b * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the slider cache (only used for mmsliders).
|
||||
*/
|
||||
public void discardCache() {
|
||||
if (renderState != null)
|
||||
renderState.discardCache();
|
||||
}
|
||||
}
|
||||
|
||||
86
src/itdelatrisu/opsu/objects/curves/CurveType.java
Normal file
86
src/itdelatrisu/opsu/objects/curves/CurveType.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
/**
|
||||
* Representation of a curve with the distance between each point calculated.
|
||||
*
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public abstract class CurveType {
|
||||
/** Points along the curve of the Bezier curve. */
|
||||
private Vec2f[] curve;
|
||||
|
||||
/** Distances between a point of the curve and the last point. */
|
||||
private float[] curveDis;
|
||||
|
||||
/** The number of points along the curve. */
|
||||
private int ncurve;
|
||||
|
||||
/** The total distances of this Bezier. */
|
||||
private float totalDistance;
|
||||
|
||||
/**
|
||||
* Returns the point on the curve at a value t.
|
||||
* @param t the t value [0, 1]
|
||||
* @return the point [x, y]
|
||||
*/
|
||||
public abstract Vec2f pointAt(float t);
|
||||
|
||||
/**
|
||||
* Initialize the curve points and distance.
|
||||
* Must be called by inherited classes.
|
||||
* @param approxlength an approximate length of the curve
|
||||
*/
|
||||
public void init(float approxlength) {
|
||||
// subdivide the curve
|
||||
this.ncurve = (int) (approxlength / 4) + 2;
|
||||
this.curve = new Vec2f[ncurve];
|
||||
for (int i = 0; i < ncurve; i++)
|
||||
curve[i] = pointAt(i / (float) (ncurve - 1));
|
||||
|
||||
// find the distance of each point from the previous point
|
||||
this.curveDis = new float[ncurve];
|
||||
this.totalDistance = 0;
|
||||
for (int i = 0; i < ncurve; i++) {
|
||||
curveDis[i] = (i == 0) ? 0 : curve[i].cpy().sub(curve[i - 1]).len();
|
||||
totalDistance += curveDis[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the points along the curve of the Bezier curve.
|
||||
*/
|
||||
public Vec2f[] getCurvePoint() { return curve; }
|
||||
|
||||
/**
|
||||
* Returns the distances between a point of the curve and the last point.
|
||||
*/
|
||||
public float[] getCurveDistances() { return curveDis; }
|
||||
|
||||
/**
|
||||
* Returns the number of points along the curve.
|
||||
*/
|
||||
public int getCurvesCount() { return ncurve; }
|
||||
|
||||
/**
|
||||
* Returns the total distances of this Bezier curve.
|
||||
*/
|
||||
public float totalDistance() { return totalDistance; }
|
||||
}
|
||||
142
src/itdelatrisu/opsu/objects/curves/EqualDistanceMultiCurve.java
Normal file
142
src/itdelatrisu/opsu/objects/curves/EqualDistanceMultiCurve.java
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* opsu! - an open-source osu! client
|
||||
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||
*
|
||||
* opsu! is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* opsu! is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
|
||||
/**
|
||||
* Representation of multiple curve with equidistant points.
|
||||
* http://pomax.github.io/bezierinfo/#tracing
|
||||
*
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public abstract class EqualDistanceMultiCurve extends Curve {
|
||||
/** The angles of the first and last control points for drawing. */
|
||||
private float startAngle, endAngle;
|
||||
|
||||
/** The number of points along the curve. */
|
||||
private int ncurve;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated HitObject
|
||||
* @param color the color of this curve
|
||||
*/
|
||||
public EqualDistanceMultiCurve(HitObject hitObject, Color color) {
|
||||
super(hitObject, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the curve points with equal distance.
|
||||
* Must be called by inherited classes.
|
||||
* @param curvesList a list of curves to join
|
||||
*/
|
||||
public void init(LinkedList<CurveType> curvesList){
|
||||
// now try to creates points the are equidistant to each other
|
||||
this.ncurve = (int) (hitObject.getPixelLength() / CURVE_POINTS_SEPERATION);
|
||||
this.curve = new Vec2f[ncurve + 1];
|
||||
|
||||
float distanceAt = 0;
|
||||
Iterator<CurveType> iter = curvesList.iterator();
|
||||
int curPoint = 0;
|
||||
CurveType curCurve = iter.next();
|
||||
Vec2f lastCurve = curCurve.getCurvePoint()[0];
|
||||
float lastDistanceAt = 0;
|
||||
|
||||
// length of Curve should equal pixel length (in 640x480)
|
||||
float pixelLength = hitObject.getPixelLength() * HitObject.getXMultiplier();
|
||||
|
||||
// for each distance, try to get in between the two points that are between it
|
||||
for (int i = 0; i < ncurve + 1; i++) {
|
||||
int prefDistance = (int) (i * pixelLength / ncurve);
|
||||
while (distanceAt < prefDistance) {
|
||||
lastDistanceAt = distanceAt;
|
||||
lastCurve = curCurve.getCurvePoint()[curPoint];
|
||||
curPoint++;
|
||||
|
||||
if (curPoint >= curCurve.getCurvesCount()) {
|
||||
if (iter.hasNext()) {
|
||||
curCurve = iter.next();
|
||||
curPoint = 0;
|
||||
} else {
|
||||
curPoint = curCurve.getCurvesCount() - 1;
|
||||
if (lastDistanceAt == distanceAt) {
|
||||
// out of points even though the preferred distance hasn't been reached
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
distanceAt += curCurve.getCurveDistances()[curPoint];
|
||||
}
|
||||
Vec2f thisCurve = curCurve.getCurvePoint()[curPoint];
|
||||
|
||||
// interpolate the point between the two closest distances
|
||||
if (distanceAt - lastDistanceAt > 1) {
|
||||
float t = (prefDistance - lastDistanceAt) / (distanceAt - lastDistanceAt);
|
||||
curve[i] = new Vec2f(lerp(lastCurve.x, thisCurve.x, t), lerp(lastCurve.y, thisCurve.y, t));
|
||||
} else
|
||||
curve[i] = thisCurve;
|
||||
}
|
||||
|
||||
// if (hitObject.getRepeatCount() > 1) {
|
||||
Vec2f c1 = curve[0];
|
||||
int cnt = 1;
|
||||
Vec2f c2 = curve[cnt++];
|
||||
while (cnt <= ncurve && c2.cpy().sub(c1).len() < 1)
|
||||
c2 = curve[cnt++];
|
||||
this.startAngle = (float) (Math.atan2(c2.y - c1.y, c2.x - c1.x) * 180 / Math.PI);
|
||||
|
||||
c1 = curve[ncurve];
|
||||
cnt = ncurve - 1;
|
||||
c2 = curve[cnt--];
|
||||
while (cnt >= 0 && c2.cpy().sub(c1).len() < 1)
|
||||
c2 = curve[cnt--];
|
||||
this.endAngle = (float) (Math.atan2(c2.y - c1.y, c2.x - c1.x) * 180 / Math.PI);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] pointAt(float t) {
|
||||
float indexF = t * ncurve;
|
||||
int index = (int) indexF;
|
||||
if (index >= ncurve) {
|
||||
Vec2f poi = curve[ncurve];
|
||||
return new float[] { poi.x, poi.y };
|
||||
} else {
|
||||
Vec2f poi = curve[index];
|
||||
Vec2f poi2 = curve[index + 1];
|
||||
float t2 = indexF - index;
|
||||
return new float[] {
|
||||
lerp(poi.x, poi2.x, t2),
|
||||
lerp(poi.y, poi2.y, t2)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEndAngle() { return endAngle; }
|
||||
|
||||
@Override
|
||||
public float getStartAngle() { return startAngle; }
|
||||
}
|
||||
@@ -18,50 +18,46 @@
|
||||
|
||||
package itdelatrisu.opsu.objects.curves;
|
||||
|
||||
import itdelatrisu.opsu.GameImage;
|
||||
import itdelatrisu.opsu.OsuHitObject;
|
||||
import itdelatrisu.opsu.Utils;
|
||||
import itdelatrisu.opsu.beatmap.HitObject;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
|
||||
/**
|
||||
* Representation of a Bezier curve with equidistant points.
|
||||
* Representation of Bezier curve with equidistant points.
|
||||
* http://pomax.github.io/bezierinfo/#tracing
|
||||
*
|
||||
* @author fluddokt (https://github.com/fluddokt)
|
||||
*/
|
||||
public class LinearBezier extends Curve {
|
||||
/** The angles of the first and last control points for drawing. */
|
||||
private float startAngle, endAngle;
|
||||
|
||||
/** List of Bezier curves in the set of points. */
|
||||
private LinkedList<Bezier2> beziers = new LinkedList<Bezier2>();
|
||||
|
||||
/** Points along the curve at equal distance. */
|
||||
private Vec2f[] curve;
|
||||
|
||||
/** The number of points along the curve. */
|
||||
private int ncurve;
|
||||
|
||||
public class LinearBezier extends EqualDistanceMultiCurve {
|
||||
/**
|
||||
* Constructor.
|
||||
* @param hitObject the associated OsuHitObject
|
||||
* @param hitObject the associated HitObject
|
||||
* @param color the color of this curve
|
||||
* @param line whether a new curve should be generated for each sequential pair
|
||||
*/
|
||||
public LinearBezier(OsuHitObject hitObject, Color color) {
|
||||
public LinearBezier(HitObject hitObject, Color color, boolean line) {
|
||||
super(hitObject, color);
|
||||
|
||||
// splits points into different Beziers if has the same points (red points)
|
||||
LinkedList<CurveType> beziers = new LinkedList<CurveType>();
|
||||
|
||||
// Beziers: splits points into different Beziers if has the same points (red points)
|
||||
// a b c - c d - d e f g
|
||||
// Lines: generate a new curve for each sequential pair
|
||||
// ab bc cd de ef fg
|
||||
int controlPoints = hitObject.getSliderX().length + 1;
|
||||
LinkedList<Vec2f> points = new LinkedList<Vec2f>(); // temporary list of points to separate different Bezier curves
|
||||
Vec2f lastPoi = null;
|
||||
for (int i = 0; i < controlPoints; i++) {
|
||||
Vec2f tpoi = new Vec2f(getX(i), getY(i));
|
||||
if (lastPoi != null && tpoi.equals(lastPoi)) {
|
||||
if (line) {
|
||||
if (lastPoi != null) {
|
||||
points.add(tpoi);
|
||||
beziers.add(new Bezier2(points.toArray(new Vec2f[0])));
|
||||
points.clear();
|
||||
}
|
||||
} else if (lastPoi != null && tpoi.equals(lastPoi)) {
|
||||
if (points.size() >= 2)
|
||||
beziers.add(new Bezier2(points.toArray(new Vec2f[0])));
|
||||
points.clear();
|
||||
@@ -69,7 +65,7 @@ public class LinearBezier extends Curve {
|
||||
points.add(tpoi);
|
||||
lastPoi = tpoi;
|
||||
}
|
||||
if (points.size() < 2) {
|
||||
if (line || points.size() < 2) {
|
||||
// trying to continue Bezier with less than 2 points
|
||||
// probably ending on a red point, just ignore it
|
||||
} else {
|
||||
@@ -77,105 +73,6 @@ public class LinearBezier extends Curve {
|
||||
points.clear();
|
||||
}
|
||||
|
||||
// find the length of all beziers
|
||||
// int totalDistance = 0;
|
||||
// for (Bezier2 bez : beziers) {
|
||||
// totalDistance += bez.totalDistance();
|
||||
// }
|
||||
|
||||
// now try to creates points the are equidistant to each other
|
||||
this.ncurve = (int) (hitObject.getPixelLength() / 5f);
|
||||
this.curve = new Vec2f[ncurve + 1];
|
||||
|
||||
float distanceAt = 0;
|
||||
Iterator<Bezier2> iter = beziers.iterator();
|
||||
int curPoint = 0;
|
||||
Bezier2 curBezier = iter.next();
|
||||
Vec2f lastCurve = curBezier.getCurve()[0];
|
||||
float lastDistanceAt = 0;
|
||||
|
||||
// length of Bezier should equal pixel length (in 640x480)
|
||||
float pixelLength = hitObject.getPixelLength() * OsuHitObject.getXMultiplier();
|
||||
|
||||
// for each distance, try to get in between the two points that are between it
|
||||
for (int i = 0; i < ncurve + 1; i++) {
|
||||
int prefDistance = (int) (i * pixelLength / ncurve);
|
||||
while (distanceAt < prefDistance) {
|
||||
lastDistanceAt = distanceAt;
|
||||
lastCurve = curBezier.getCurve()[curPoint];
|
||||
distanceAt += curBezier.getCurveDistances()[curPoint++];
|
||||
|
||||
if (curPoint >= curBezier.points()) {
|
||||
if (iter.hasNext()) {
|
||||
curBezier = iter.next();
|
||||
curPoint = 0;
|
||||
} else {
|
||||
curPoint = curBezier.points() - 1;
|
||||
if (lastDistanceAt == distanceAt) {
|
||||
// out of points even though the preferred distance hasn't been reached
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vec2f thisCurve = curBezier.getCurve()[curPoint];
|
||||
|
||||
// interpolate the point between the two closest distances
|
||||
if (distanceAt - lastDistanceAt > 1) {
|
||||
float t = (prefDistance - lastDistanceAt) / (distanceAt - lastDistanceAt);
|
||||
curve[i] = new Vec2f(lerp(lastCurve.x, thisCurve.x, t), lerp(lastCurve.y, thisCurve.y, t));
|
||||
} else
|
||||
curve[i] = thisCurve;
|
||||
}
|
||||
|
||||
// if (hitObject.getRepeatCount() > 1) {
|
||||
Vec2f c1 = curve[0];
|
||||
int cnt = 1;
|
||||
Vec2f c2 = curve[cnt++];
|
||||
while (cnt <= ncurve && c2.cpy().sub(c1).len() < 1)
|
||||
c2 = curve[cnt++];
|
||||
this.startAngle = (float) (Math.atan2(c2.y - c1.y, c2.x - c1.x) * 180 / Math.PI);
|
||||
|
||||
c1 = curve[ncurve];
|
||||
cnt = ncurve - 1;
|
||||
c2 = curve[cnt--];
|
||||
while (cnt >= 0 && c2.cpy().sub(c1).len() < 1)
|
||||
c2 = curve[cnt--];
|
||||
this.endAngle = (float) (Math.atan2(c2.y - c1.y, c2.x - c1.x) * 180 / Math.PI);
|
||||
// }
|
||||
init(beziers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] pointAt(float t) {
|
||||
float indexF = t * ncurve;
|
||||
int index = (int) indexF;
|
||||
if (index >= ncurve) {
|
||||
Vec2f poi = curve[ncurve];
|
||||
return new float[] { poi.x, poi.y };
|
||||
} else {
|
||||
Vec2f poi = curve[index];
|
||||
Vec2f poi2 = curve[index + 1];
|
||||
float t2 = indexF - index;
|
||||
return new float[] {
|
||||
lerp(poi.x, poi2.x, t2),
|
||||
lerp(poi.y, poi2.y, t2)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw() {
|
||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||
for (int i = curve.length - 2; i >= 0; i--)
|
||||
hitCircleOverlay.drawCentered(curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
||||
for (int i = curve.length - 2; i >= 0; i--)
|
||||
hitCircle.drawCentered(curve[i].x, curve[i].y, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEndAngle() { return endAngle; }
|
||||
|
||||
@Override
|
||||
public float getStartAngle() { return startAngle; }
|
||||
}
|
||||
|
||||
@@ -49,6 +49,28 @@ public class Vec2f {
|
||||
return new Vec2f((x + o.x) / 2, (y + o.y) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the vector.
|
||||
* @param s scaler to scale by
|
||||
* @return itself (for chaining)
|
||||
*/
|
||||
public Vec2f scale(float s) {
|
||||
x *= s;
|
||||
y *= s;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a vector to this vector.
|
||||
* @param o the other vector
|
||||
* @return itself (for chaining)
|
||||
*/
|
||||
public Vec2f add(Vec2f o) {
|
||||
x += o.x;
|
||||
y += o.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a vector from this vector.
|
||||
* @param o the other vector
|
||||
@@ -97,4 +119,7 @@ public class Vec2f {
|
||||
* @return true if the two vectors are numerically equal
|
||||
*/
|
||||
public boolean equals(Vec2f o) { return (x == o.x && y == o.y); }
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("(%.3f, %.3f)", x, y); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user