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:
fd
2015-06-13 20:28:30 -04:00
88 changed files with 9798 additions and 1575 deletions

View File

@@ -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;
}
}

View File

@@ -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();
}

View 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();
}

View File

@@ -1,3 +1,5 @@
//TODO rename
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014, 2015 Jeffrey Han

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)

View 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);
}
}

View File

@@ -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 CatmullRom spline.
* (Currently not technically Centripetal CatmullRom.)
* 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;
}
}

View File

@@ -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; }

View File

@@ -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();
}
}

View 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; }
}

View 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; }
}

View File

@@ -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; }
}

View File

@@ -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); }
}