Code style cleanup from #12.
- Moved all curve-related classes into a new package. - Added some fields and methods to Curve abstract class. - Removed the old (no longer used) Bezier subclass. - Changed Error throwing to ErrorHandler.error() calls. - Formatted code. Also fixed a crash when reaching the ranking screen with the "Auto" mod active. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
94d7ff37eb
commit
e93fe25834
|
@ -297,11 +297,11 @@ public class GameData {
|
||||||
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
comboBurstImages = GameImage.COMBO_BURST.getImages();
|
||||||
else
|
else
|
||||||
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
comboBurstImages = new Image[]{ GameImage.COMBO_BURST.getImage() };
|
||||||
|
|
||||||
// scorebar-colour animation
|
// scorebar-colour animation
|
||||||
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
Image[] scorebar = GameImage.SCOREBAR_COLOUR.getImages();
|
||||||
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
scorebarColour = (scorebar != null) ? new Animation(scorebar, 60) : null;
|
||||||
|
|
||||||
// default symbol images
|
// default symbol images
|
||||||
defaultSymbols = new Image[10];
|
defaultSymbols = new Image[10];
|
||||||
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
|
defaultSymbols[0] = GameImage.DEFAULT_0.getImage();
|
||||||
|
|
|
@ -104,17 +104,26 @@ public class OsuHitObject {
|
||||||
public static void init(int width, int height) {
|
public static void init(int width, int height) {
|
||||||
int swidth = width;
|
int swidth = width;
|
||||||
int sheight = height;
|
int sheight = height;
|
||||||
if(swidth*3>sheight*4){
|
if (swidth * 3 > sheight * 4)
|
||||||
swidth = sheight*4/3;
|
swidth = sheight * 4 / 3;
|
||||||
}else{
|
else
|
||||||
sheight = swidth*3/4;
|
sheight = swidth * 3 / 4;
|
||||||
}
|
xMultiplier = swidth / 640f;
|
||||||
xMultiplier = swidth / 640f; //(width * 1f) / MAX_X; // width * MAX_X/800f / MAX_X
|
yMultiplier = sheight / 480f;
|
||||||
yMultiplier = sheight / 480f;//(height * 1f) / MAX_Y;
|
xOffset = (int) (width - MAX_X * xMultiplier) / 2;
|
||||||
xOffset = (int)(width - MAX_X * xMultiplier)/2 ;//width / 5; 800-512/2
|
yOffset = (int) (height - MAX_Y * yMultiplier) / 2;
|
||||||
yOffset = (int)(height - MAX_Y * yMultiplier)/2 ;//height / 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the X multiplier for coordinates.
|
||||||
|
*/
|
||||||
|
public static float getXMultiplier() { return xMultiplier; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Y multiplier for coordinates.
|
||||||
|
*/
|
||||||
|
public static float getYMultiplier() { return yMultiplier; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param line the line to be parsed
|
* @param line the line to be parsed
|
||||||
|
@ -287,11 +296,4 @@ public class OsuHitObject {
|
||||||
* @return true if new combo
|
* @return true if new combo
|
||||||
*/
|
*/
|
||||||
public boolean isNewCombo() { return (type & TYPE_NEWCOMBO) > 0; }
|
public boolean isNewCombo() { return (type & TYPE_NEWCOMBO) > 0; }
|
||||||
/**
|
|
||||||
* Returns the multiplier for coordinates
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static float getMultiplier() {
|
|
||||||
return xMultiplier;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -535,7 +535,7 @@ public class OsuParser {
|
||||||
// set combo info
|
// set combo info
|
||||||
// - new combo: get next combo index, reset combo number
|
// - new combo: get next combo index, reset combo number
|
||||||
// - else: maintain combo index, increase combo number
|
// - else: maintain combo index, increase combo number
|
||||||
if (hitObject.isNewCombo() && !hitObject.isSpinner() || objectIndex==0) {
|
if ((hitObject.isNewCombo() && !hitObject.isSpinner()) || objectIndex == 0) {
|
||||||
comboIndex = (comboIndex + 1) % osu.combo.length;
|
comboIndex = (comboIndex + 1) % osu.combo.length;
|
||||||
comboNumber = 1;
|
comboNumber = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.objects;
|
package itdelatrisu.opsu.objects;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameData;
|
||||||
import itdelatrisu.opsu.GameImage;
|
import itdelatrisu.opsu.GameImage;
|
||||||
import itdelatrisu.opsu.GameMod;
|
import itdelatrisu.opsu.GameMod;
|
||||||
import itdelatrisu.opsu.GameData;
|
|
||||||
import itdelatrisu.opsu.OsuHitObject;
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
|
|
|
@ -18,16 +18,16 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.objects;
|
package itdelatrisu.opsu.objects;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import itdelatrisu.opsu.GameData;
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
import itdelatrisu.opsu.GameImage;
|
import itdelatrisu.opsu.GameImage;
|
||||||
import itdelatrisu.opsu.GameMod;
|
import itdelatrisu.opsu.GameMod;
|
||||||
import itdelatrisu.opsu.GameData;
|
|
||||||
import itdelatrisu.opsu.OsuFile;
|
import itdelatrisu.opsu.OsuFile;
|
||||||
import itdelatrisu.opsu.OsuHitObject;
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
|
import itdelatrisu.opsu.objects.curves.CircumscribedCircle;
|
||||||
|
import itdelatrisu.opsu.objects.curves.Curve;
|
||||||
|
import itdelatrisu.opsu.objects.curves.LinearBezier;
|
||||||
import itdelatrisu.opsu.states.Game;
|
import itdelatrisu.opsu.states.Game;
|
||||||
|
|
||||||
import org.newdawn.slick.Animation;
|
import org.newdawn.slick.Animation;
|
||||||
|
@ -61,8 +61,8 @@ public class Slider implements HitObject {
|
||||||
/** The color of this slider. */
|
/** The color of this slider. */
|
||||||
private Color color;
|
private Color color;
|
||||||
|
|
||||||
/** The underlying Bezier object. */
|
/** The underlying Curve. */
|
||||||
private Curve bezier;
|
private Curve curve;
|
||||||
|
|
||||||
/** The time duration of the slider, in milliseconds. */
|
/** The time duration of the slider, in milliseconds. */
|
||||||
private float sliderTime = 0f;
|
private float sliderTime = 0f;
|
||||||
|
@ -91,679 +91,6 @@ public class Slider implements HitObject {
|
||||||
/** Number of ticks hit and tick intervals so far. */
|
/** Number of ticks hit and tick intervals so far. */
|
||||||
private int ticksHit = 0, tickIntervals = 1;
|
private int ticksHit = 0, tickIntervals = 1;
|
||||||
|
|
||||||
private abstract class Curve{
|
|
||||||
/**
|
|
||||||
* Returns the point on the curve at a value t.
|
|
||||||
* @param t the t value [0, 1]
|
|
||||||
* @return the point [x, y]
|
|
||||||
*/
|
|
||||||
public abstract float[] pointAt(float t);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the full Bezier curve to the graphics context.
|
|
||||||
*/
|
|
||||||
public abstract void draw();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the angle of the first control point.
|
|
||||||
*/
|
|
||||||
public abstract float getEndAngle();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the angle of the last control point.
|
|
||||||
*/
|
|
||||||
public abstract float getStartAngle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A two dimensional vector
|
|
||||||
*/
|
|
||||||
private class Vec2f{
|
|
||||||
float x, y;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor of the (nx, ny) Vector
|
|
||||||
* @param nx
|
|
||||||
* @param ny
|
|
||||||
*/
|
|
||||||
public Vec2f(float nx, float ny) {
|
|
||||||
x=nx;
|
|
||||||
y=ny;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Constructor of the (0,0) Vector
|
|
||||||
*/
|
|
||||||
public Vec2f() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the midpoint between this Vector and "o" Vector
|
|
||||||
* @param o the other Vector
|
|
||||||
* @return midpoint vector
|
|
||||||
*/
|
|
||||||
public Vec2f midPoint(Vec2f o){
|
|
||||||
return new Vec2f((x+o.x)/2, (y+o.y)/2);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Subtracts the "o" vector from this vector
|
|
||||||
* @param o the other Vector
|
|
||||||
* @return itself for chaining
|
|
||||||
*/
|
|
||||||
public Vec2f sub(Vec2f o){
|
|
||||||
x-=o.x;
|
|
||||||
y-=o.y;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets this Vector to the normal of this Vector
|
|
||||||
* @return itself for chaining
|
|
||||||
*/
|
|
||||||
public Vec2f nor(){
|
|
||||||
float nx = -y, ny =x;
|
|
||||||
x=nx;
|
|
||||||
y=ny;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Makes a new Vector that is a copy of this Vector
|
|
||||||
* @return a copy of this Vector
|
|
||||||
*/
|
|
||||||
public Vec2f cpy(){
|
|
||||||
return new Vec2f(x, y);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Adds nx to the x component and ny to the y component of this Vector
|
|
||||||
* @param nx
|
|
||||||
* @param ny
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Vec2f add(float nx, float ny) {
|
|
||||||
x+=nx;
|
|
||||||
y+=ny;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the length of this Vector
|
|
||||||
* @return the length of this Vector
|
|
||||||
*/
|
|
||||||
public float len() {
|
|
||||||
return (float) Math.sqrt(x*x + y*y);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Compares this vector to another Vector
|
|
||||||
* @param o the Other Vector
|
|
||||||
* @return true if the two Vector are numerically equal
|
|
||||||
*/
|
|
||||||
public boolean equals(Vec2f o){
|
|
||||||
return x==o.x && y==o.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Representation of a curve along a Circumscribed Circle of three points.
|
|
||||||
* http://en.wikipedia.org/wiki/Circumscribed_circle
|
|
||||||
*/
|
|
||||||
private class CircumscribedCircle extends Curve{
|
|
||||||
/** The center of the Circumscribed Circle */
|
|
||||||
Vec2f circleCenter;
|
|
||||||
|
|
||||||
/** The radius of the Circumscribed Circle */
|
|
||||||
float radius;
|
|
||||||
|
|
||||||
/** * The three points to create the Circumscribed Circle from */
|
|
||||||
Vec2f start ,mid ,end;
|
|
||||||
|
|
||||||
/** The three angles relative to the circle center */
|
|
||||||
float startAng,endAng,midAng;
|
|
||||||
|
|
||||||
/** The start and end angles for drawing */
|
|
||||||
float drawStartAngle,drawEndAngle;
|
|
||||||
|
|
||||||
/** Two times Pi or one full circle in radians */
|
|
||||||
final float TWO_PI = (float) (2*Math.PI);
|
|
||||||
/** Pi divided by two or a quarter of a circle in radians */
|
|
||||||
final float HALF_PI = (float) (Math.PI/2);
|
|
||||||
|
|
||||||
/** The number of steps in the curve to draw */
|
|
||||||
private float step;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public CircumscribedCircle(){
|
|
||||||
this.step = hitObject.getPixelLength() / 5;
|
|
||||||
|
|
||||||
//construct the three points
|
|
||||||
start = new Vec2f(getX(0), getY(0));
|
|
||||||
mid = new Vec2f(getX(1), getY(1));
|
|
||||||
end = new Vec2f(getX(2), getY(2));
|
|
||||||
|
|
||||||
//find the circle center
|
|
||||||
Vec2f mida = start.midPoint(mid);
|
|
||||||
Vec2f midb = end.midPoint(mid);
|
|
||||||
Vec2f nora = mid.cpy().sub(start).nor();
|
|
||||||
Vec2f norb = mid.cpy().sub(end).nor();
|
|
||||||
|
|
||||||
circleCenter = intersect(mida, nora, midb, norb);
|
|
||||||
|
|
||||||
//find the angles relative to the circle center
|
|
||||||
Vec2f startAngPoint = start.cpy().sub(circleCenter);
|
|
||||||
Vec2f midAngPoint = mid.cpy().sub(circleCenter);
|
|
||||||
Vec2f endAngPoint = end.cpy().sub(circleCenter);
|
|
||||||
|
|
||||||
startAng = (float) Math.atan2(startAngPoint.y, startAngPoint.x);
|
|
||||||
midAng = (float) Math.atan2(midAngPoint.y, midAngPoint.x);
|
|
||||||
endAng = (float) Math.atan2(endAngPoint.y, endAngPoint.x);
|
|
||||||
|
|
||||||
|
|
||||||
//find angles that passes thru midAng
|
|
||||||
if(!isIn(startAng,midAng,endAng)){
|
|
||||||
if(Math.abs(startAng+TWO_PI-endAng)<TWO_PI && isIn(startAng+(TWO_PI),midAng,endAng)){
|
|
||||||
startAng+=TWO_PI;
|
|
||||||
}else if(Math.abs(startAng-(endAng+TWO_PI))<TWO_PI && isIn(startAng,midAng,endAng+(TWO_PI))){
|
|
||||||
endAng+=TWO_PI;
|
|
||||||
}else if(Math.abs(startAng-TWO_PI-endAng)<TWO_PI && isIn(startAng-(TWO_PI),midAng,endAng)){
|
|
||||||
startAng-=TWO_PI;
|
|
||||||
}else if(Math.abs(startAng-(endAng-TWO_PI))<TWO_PI && isIn(startAng,midAng,endAng-(TWO_PI))){
|
|
||||||
endAng-=TWO_PI;
|
|
||||||
}else{
|
|
||||||
throw new Error("Cannot find Angles between midAng "+startAng+" "+midAng+" "+endAng);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//Find an angle with an arc length of pixellength along this cirlce
|
|
||||||
radius = startAngPoint.len();
|
|
||||||
float pixelLength = hitObject.getPixelLength() * OsuHitObject.getMultiplier();
|
|
||||||
float arcAng = pixelLength / radius; //len = theta * r / theta = len/r
|
|
||||||
|
|
||||||
//now use it for our new end angle
|
|
||||||
if(endAng>startAng){
|
|
||||||
endAng=startAng+arcAng;
|
|
||||||
}else{
|
|
||||||
endAng=startAng-arcAng;
|
|
||||||
}
|
|
||||||
|
|
||||||
//finds the angles to draw for repeats
|
|
||||||
drawEndAngle = (float) ((endAng+(startAng>endAng?HALF_PI:-HALF_PI)) * 180 / Math.PI);
|
|
||||||
drawStartAngle = (float) ((startAng+(startAng>endAng?-HALF_PI:HALF_PI)) * 180 / Math.PI);
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks to see if "b" is between "a" and "c"
|
|
||||||
* @param a
|
|
||||||
* @param b
|
|
||||||
* @param c
|
|
||||||
* @return true if b is between a and c
|
|
||||||
*/
|
|
||||||
private boolean isIn(float a,float b,float c){
|
|
||||||
return (b>a && b<c) || (b<a && b>c);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Finds the point of intersection between two parametric lines of A = a + ta*t and B = b + tb*u
|
|
||||||
* http://gamedev.stackexchange.com/questions/44720/line-intersection-from-parametric-equation
|
|
||||||
* @param a the initial position of the line A
|
|
||||||
* @param ta the direction of the line A
|
|
||||||
* @param b the initial position of the line B
|
|
||||||
* @param tb the direction of the line B
|
|
||||||
* @return the point at which the two lines interssect
|
|
||||||
*/
|
|
||||||
private Vec2f intersect(Vec2f a, Vec2f ta, Vec2f b, Vec2f tb) {
|
|
||||||
// xy = a + ta * t = b + tb * u
|
|
||||||
// t =(b + tb*u -a)/ta
|
|
||||||
//t(x) == t(y)
|
|
||||||
//(b.x + tb.x*u -a.x)/ta.x = (b.y + tb.y*u -a.y)/ta.y
|
|
||||||
// b.x*ta.y + tb.x*u*ta.y -a.x*ta.y = b.y*ta.x + tb.y*u*ta.x -a.y*ta.x
|
|
||||||
// tb.x*u*ta.y - tb.y*u*ta.x= b.y*ta.x -a.y*ta.x -b.x*ta.y +a.x*ta.y
|
|
||||||
//u *(tb.x*ta.y - tb.y*ta.x) = (b.y-a.y)ta.x +(a.x-b.x)ta.y
|
|
||||||
//u = ((b.y-a.y)ta.x +(a.x-b.x)ta.y) / (tb.x*ta.y - tb.y*ta.x);
|
|
||||||
|
|
||||||
float des = tb.x*ta.y - tb.y*ta.x;
|
|
||||||
if(Math.abs(des)<0.00001f){
|
|
||||||
throw new Error("parallel ");
|
|
||||||
}
|
|
||||||
float u = ((b.y-a.y)*ta.x + (a.x-b.x)*ta.y) / des;
|
|
||||||
return b.cpy().add(tb.x*u,tb.y*u);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float[] pointAt(float t) {
|
|
||||||
float ang = lerp(startAng, endAng, t);
|
|
||||||
return new float[]{(float) (Math.cos(ang)*radius+circleCenter.x),(float) (Math.sin(ang)*radius+circleCenter.y)};
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void draw() {
|
|
||||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
|
||||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
|
||||||
// draw overlay and hit circle
|
|
||||||
for(int i=0; i<step; i++){
|
|
||||||
float[] xy = pointAt(i/step);
|
|
||||||
Utils.drawCentered(hitCircleOverlay, xy[0], xy[1], Utils.COLOR_WHITE_FADE);
|
|
||||||
}
|
|
||||||
for(int i=0; i<step; i++){
|
|
||||||
float[] xy = pointAt(i/step);
|
|
||||||
Utils.drawCentered(hitCircle, xy[0], xy[1], color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public float getEndAngle() {
|
|
||||||
return drawEndAngle;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public float getStartAngle() {
|
|
||||||
return drawStartAngle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Representation of a Bezier curve, the main component of a slider.
|
|
||||||
*
|
|
||||||
* @author Alex Gheorghiu (http://html5tutorial.com/how-to-draw-n-grade-bezier-curve-with-canvas-api/)
|
|
||||||
* @author pictuga (https://github.com/pictuga/osu-web)
|
|
||||||
*/
|
|
||||||
private class Bezier {
|
|
||||||
/** The order of the Bezier curve. */
|
|
||||||
private int order;
|
|
||||||
|
|
||||||
/** The step size (used for drawing). */
|
|
||||||
private float step;
|
|
||||||
|
|
||||||
/** The curve points for drawing with step size given by 'step'. */
|
|
||||||
private float[] curveX, curveY;
|
|
||||||
|
|
||||||
/** The angles of the first and last control points. */
|
|
||||||
private float startAngle, endAngle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*/
|
|
||||||
public Bezier() {
|
|
||||||
this.order = hitObject.getSliderX().length + 1;
|
|
||||||
this.step = 5 / hitObject.getPixelLength();
|
|
||||||
|
|
||||||
// calculate curve points for drawing
|
|
||||||
int N = (int) (1 / step);
|
|
||||||
this.curveX = new float[N + 1];
|
|
||||||
this.curveY = new float[N + 1];
|
|
||||||
float t = 0f;
|
|
||||||
for (int i = 0; i < N; i++, t += step) {
|
|
||||||
float[] c = pointAt(t);
|
|
||||||
curveX[i] = c[0];
|
|
||||||
curveY[i] = c[1];
|
|
||||||
}
|
|
||||||
curveX[N] = getX(order - 1);
|
|
||||||
curveY[N] = getY(order - 1);
|
|
||||||
|
|
||||||
// calculate angles (if needed)
|
|
||||||
if (hitObject.getRepeatCount() > 1) {
|
|
||||||
float[] c1 = pointAt(0f);
|
|
||||||
float[] c2 = pointAt(step);
|
|
||||||
startAngle = (float) (Math.atan2(c2[1] - c1[1], c2[0] - c1[0]) * 180 / Math.PI);
|
|
||||||
c1 = pointAt(1f);
|
|
||||||
c2 = pointAt(1f - step);
|
|
||||||
endAngle = (float) (Math.atan2(c2[1] - c1[1], c2[0] - c1[0]) * 180 / Math.PI);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the x coordinate of the control point at index i.
|
|
||||||
*/
|
|
||||||
private float getX(int i) {
|
|
||||||
return (i == 0) ? hitObject.getX() : hitObject.getSliderX()[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the y coordinate of the control point at index i.
|
|
||||||
*/
|
|
||||||
private float getY(int i) {
|
|
||||||
return (i == 0) ? hitObject.getY() : hitObject.getSliderY()[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the angle of the first control point.
|
|
||||||
*/
|
|
||||||
private float getStartAngle() { return startAngle; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the angle of the last control point.
|
|
||||||
*/
|
|
||||||
private float getEndAngle() { return endAngle; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the factorial of a number.
|
|
||||||
*/
|
|
||||||
private long factorial(int n) {
|
|
||||||
return (n <= 1 || n > 20) ? 1 : n * factorial(n - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the Bernstein polynomial.
|
|
||||||
* @param i the index
|
|
||||||
* @param n the degree of the polynomial (i.e. number of points)
|
|
||||||
* @param t the t value [0, 1]
|
|
||||||
*/
|
|
||||||
private double bernstein(int i, int n, float t) {
|
|
||||||
return factorial(n) / (factorial(i) * factorial(n-i)) *
|
|
||||||
Math.pow(t, i) * Math.pow(1-t, n-i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the point on the Bezier curve at a value t.
|
|
||||||
* For curves of order greater than 4, points will be generated along
|
|
||||||
* a path of overlapping cubic (at most) Beziers.
|
|
||||||
* @param t the t value [0, 1]
|
|
||||||
* @return the point [x, y]
|
|
||||||
*/
|
|
||||||
public float[] pointAt(float t) {
|
|
||||||
float[] c = { 0f, 0f };
|
|
||||||
int n = order - 1;
|
|
||||||
if (n < 4) { // normal curve
|
|
||||||
for (int i = 0; i <= n; i++) {
|
|
||||||
c[0] += getX(i) * bernstein(i, n, t);
|
|
||||||
c[1] += getY(i) * bernstein(i, n, t);
|
|
||||||
}
|
|
||||||
} else { // split curve into path
|
|
||||||
// TODO: this is probably wrong...
|
|
||||||
int segmentCount = (n / 3) + 1;
|
|
||||||
int segment = (int) Math.floor(t * segmentCount);
|
|
||||||
int startIndex = 3 * segment;
|
|
||||||
int segmentOrder = Math.min(startIndex + 3, n) - startIndex;
|
|
||||||
float segmentT = (t * segmentCount) - segment;
|
|
||||||
for (int i = 0; i <= segmentOrder; i++) {
|
|
||||||
c[0] += getX(i + startIndex) * bernstein(i, segmentOrder, segmentT);
|
|
||||||
c[1] += getY(i + startIndex) * bernstein(i, segmentOrder, segmentT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the full Bezier curve to the graphics context.
|
|
||||||
*/
|
|
||||||
public void draw() {
|
|
||||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
|
||||||
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
|
||||||
|
|
||||||
// draw overlay and hit circle
|
|
||||||
for (int i = curveX.length - 1; i >= 0; i--)
|
|
||||||
Utils.drawCentered(hitCircleOverlay, curveX[i], curveY[i], Utils.COLOR_WHITE_FADE);
|
|
||||||
for (int i = curveX.length - 1; i >= 0; i--)
|
|
||||||
Utils.drawCentered(hitCircle, curveX[i], curveY[i], color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representation of a Bezier curve with equal distant points.
|
|
||||||
* http://pomax.github.io/bezierinfo/#tracing
|
|
||||||
*/
|
|
||||||
private 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 */
|
|
||||||
LinkedList<Bezier2> beziers = new LinkedList<Bezier2>();
|
|
||||||
|
|
||||||
/** Points along the curve at equal distance. */
|
|
||||||
Vec2f[] curve;
|
|
||||||
|
|
||||||
/** The number of points along the curve */
|
|
||||||
int ncurve;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public LinearBezier(){
|
|
||||||
//splits points into different beziers if has the same points(Red points)
|
|
||||||
|
|
||||||
int npoints = hitObject.getSliderX().length + 1; //The number of control points
|
|
||||||
LinkedList<Vec2f> points = new LinkedList<Vec2f>(); // a temporary list of points to separete different bezier curves
|
|
||||||
Vec2f lastPoi = null;
|
|
||||||
for(int i=0; i<npoints; i++){
|
|
||||||
Vec2f tpoi = new Vec2f(getX(i), getY(i));
|
|
||||||
if(lastPoi!=null && tpoi.equals(lastPoi)){
|
|
||||||
if(points.size()>=2){
|
|
||||||
beziers.add(new Bezier2(points.toArray(new Vec2f[0])));
|
|
||||||
}
|
|
||||||
points.clear();
|
|
||||||
}
|
|
||||||
points.add(tpoi);
|
|
||||||
lastPoi = tpoi;
|
|
||||||
|
|
||||||
}
|
|
||||||
if(points.size()<2){
|
|
||||||
//Ending on a red point (probably) just ignore
|
|
||||||
//throw new Error("trying to continue Beziers with less than 2 points");
|
|
||||||
}else{
|
|
||||||
beziers.add(new Bezier2(points.toArray(new Vec2f[0])));
|
|
||||||
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 equal distance to eachother
|
|
||||||
ncurve = (int) (hitObject.getPixelLength()/5f);
|
|
||||||
curve = new Vec2f[ncurve+1];
|
|
||||||
|
|
||||||
float distanceAt = 0;
|
|
||||||
Iterator<Bezier2> ita = beziers.iterator();
|
|
||||||
|
|
||||||
int curPoint=0;
|
|
||||||
Bezier2 curBezier=ita.next();
|
|
||||||
|
|
||||||
Vec2f lastCurve = curBezier.curve[0];
|
|
||||||
float lastDistanceAt = 0;
|
|
||||||
//length of Bezier should equal pixel length (in 640x480)
|
|
||||||
float pixelLength = hitObject.getPixelLength()*OsuHitObject.getMultiplier();
|
|
||||||
|
|
||||||
//For each distance, try to get in between the two points that is between it.
|
|
||||||
for(int i=0;i<ncurve+1;i++){
|
|
||||||
int prefDistance = (int) (i*pixelLength/ncurve);
|
|
||||||
while(distanceAt<prefDistance){
|
|
||||||
lastDistanceAt = distanceAt;
|
|
||||||
lastCurve = curBezier.curve[curPoint];
|
|
||||||
distanceAt+=curBezier.curveDis[curPoint++];
|
|
||||||
|
|
||||||
if(curPoint >= curBezier.ncurve){
|
|
||||||
if(ita.hasNext()){
|
|
||||||
curBezier = ita.next();
|
|
||||||
curPoint = 0;
|
|
||||||
}else{
|
|
||||||
curPoint = curBezier.ncurve -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vec2f thisCurve = curBezier.curve[curPoint];
|
|
||||||
//interpolate the point between the two closest distances
|
|
||||||
if(distanceAt-lastDistanceAt > 1){
|
|
||||||
float t = (prefDistance-lastDistanceAt)/(float)(distanceAt-lastDistanceAt);
|
|
||||||
curve[i] = new Vec2f( lerp(lastCurve.x,thisCurve.x,t), lerp(lastCurve.y,thisCurve.y,t));
|
|
||||||
//System.out.println("Dis "+i+" "+prefDistance+" "+lastDistanceAt+" "+distanceAt+" "+curPoint+" "+t);
|
|
||||||
|
|
||||||
}else{
|
|
||||||
curve[i] = thisCurve;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//if (hitObject.getRepeatCount() > 1) {
|
|
||||||
Vec2f c1 = curve[0];
|
|
||||||
int cnt = 1;
|
|
||||||
Vec2f c2 = curve[cnt++];
|
|
||||||
while(c2.cpy().sub(c1).len()<1){
|
|
||||||
c2 = curve[cnt++];
|
|
||||||
}
|
|
||||||
startAngle = (float) (Math.atan2(c2.y - c1.y, c2.x - c1.x) * 180 / Math.PI);
|
|
||||||
c1 = curve[ncurve-1];
|
|
||||||
cnt= ncurve-2;
|
|
||||||
c2 = curve[cnt];
|
|
||||||
while(c2.cpy().sub(c1).len()<1){
|
|
||||||
c2 = curve[cnt--];
|
|
||||||
}
|
|
||||||
endAngle = (float) (Math.atan2(c2.y - c1.y, c2.x - c1.x) * 180 / Math.PI);
|
|
||||||
//}
|
|
||||||
//System.out.println("Total Distance: "+totalDistance+" "+distanceAt+" "+beziers.size()+" "+hitObject.getPixelLength()+" "+hitObject.xMultiplier);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public float[] pointAt(float t) {
|
|
||||||
|
|
||||||
float index = t * ncurve;
|
|
||||||
|
|
||||||
if((int)index>=ncurve){
|
|
||||||
Vec2f poi = curve[ncurve-1];
|
|
||||||
return new float[]{poi.x, poi.y};
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2f poi = curve[(int)index];
|
|
||||||
float t2 = index - (int)index;
|
|
||||||
Vec2f poi2 = curve[(int)index+1];
|
|
||||||
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();
|
|
||||||
|
|
||||||
// draw overlay and hit circle
|
|
||||||
for (int i = curve.length - 2; i >= 0; i--)
|
|
||||||
Utils.drawCentered(hitCircleOverlay, curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
|
||||||
for (int i = curve.length - 2; i >= 0; i--)
|
|
||||||
Utils.drawCentered(hitCircle, curve[i].x, curve[i].y, color);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public float getEndAngle() {
|
|
||||||
return endAngle;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public float getStartAngle() {
|
|
||||||
return startAngle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Representation of a Bezier curve with the distance between each point calculated.
|
|
||||||
*/
|
|
||||||
private class Bezier2{
|
|
||||||
/** The control points of the Bezier curve */
|
|
||||||
Vec2f[] points;
|
|
||||||
|
|
||||||
/** Points along the curve of the Bezier curve */
|
|
||||||
Vec2f[] curve;
|
|
||||||
|
|
||||||
/** distance between this point of the curve and the last point */
|
|
||||||
float[] curveDis;
|
|
||||||
|
|
||||||
/** The number of points along the curve */
|
|
||||||
int ncurve;
|
|
||||||
|
|
||||||
/** The total distances of this Bezier */
|
|
||||||
float totalDistance;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public Bezier2(Vec2f[] points) {
|
|
||||||
|
|
||||||
this.points = points;
|
|
||||||
//approximate by finding the length of all points(which should be the max possible length of the curve)
|
|
||||||
float approxlength = 0;
|
|
||||||
for(int i=0;i<points.length-1;i++){
|
|
||||||
approxlength+= points[i].cpy().sub(points[i+1]).len();
|
|
||||||
}
|
|
||||||
|
|
||||||
//subdivide the curve
|
|
||||||
ncurve= (int)(approxlength/4);
|
|
||||||
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
|
|
||||||
curveDis= new float[ncurve];
|
|
||||||
for(int i=0; i<ncurve; i++){
|
|
||||||
if(i==0)
|
|
||||||
curveDis[i] = 0;
|
|
||||||
else
|
|
||||||
curveDis[i] = curve[i].cpy().sub(curve[i-1]).len();
|
|
||||||
totalDistance+=curveDis[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
//System.out.println("New Bezier2 "+points.length+" "+approxlength+" "+totalDistance());
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the total Distances of this Bezier Curve
|
|
||||||
*/
|
|
||||||
public float totalDistance(){
|
|
||||||
return totalDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the point on the Bezier curve at a value t.
|
|
||||||
* @param t the t value [0, 1]
|
|
||||||
* @return the point [x, y]
|
|
||||||
*/
|
|
||||||
public Vec2f pointAt(float t) {
|
|
||||||
Vec2f c = new Vec2f();
|
|
||||||
int n = points.length-1;
|
|
||||||
for (int i = 0; i <= n; i++) {
|
|
||||||
c.x += points[i].x * bernstein(i, n, t);
|
|
||||||
c.y += points[i].y * bernstein(i, n, t);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Calculates the factorial of a number.
|
|
||||||
*/
|
|
||||||
private long factorial(int n) {
|
|
||||||
return (n <= 1 || n > 20) ? 1 : n * factorial(n - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the Bernstein polynomial.
|
|
||||||
* @param i the index
|
|
||||||
* @param n the degree of the polynomial (i.e. number of points)
|
|
||||||
* @param t the t value [0, 1]
|
|
||||||
*/
|
|
||||||
private double bernstein(int i, int n, float t) {
|
|
||||||
return factorial(n) / (factorial(i) * factorial(n-i)) *
|
|
||||||
Math.pow(t, i) * Math.pow(1-t, n-i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Linear interpolation of a and b at t
|
|
||||||
* @param a
|
|
||||||
* @param b
|
|
||||||
* @param t
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private float lerp(float a, float b, float t){
|
|
||||||
return a*(1-t) + b*t;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* "a recursive method to evaluate polynomials in Bernstein form or Bezier curves"
|
|
||||||
* http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
|
|
||||||
*/
|
|
||||||
private float deCasteljau (float[] a, int i, int order, float t){
|
|
||||||
if(order==0)
|
|
||||||
return a[i];
|
|
||||||
return lerp( deCasteljau(a,i,order-1,t), deCasteljau(a,i+1,order-1,t), t);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the x coordinate of the control point at index i.
|
|
||||||
*/
|
|
||||||
private float getX(int i) {
|
|
||||||
return (i == 0) ? hitObject.getX() : hitObject.getSliderX()[i - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the y coordinate of the control point at index i.
|
|
||||||
*/
|
|
||||||
private float getY(int i) {
|
|
||||||
return (i == 0) ? hitObject.getY() : hitObject.getSliderY()[i - 1];
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Initializes the Slider data type with images and dimensions.
|
* Initializes the Slider data type with images and dimensions.
|
||||||
* @param container the game container
|
* @param container the game container
|
||||||
|
@ -798,7 +125,7 @@ public class Slider implements HitObject {
|
||||||
* @param hitObject the associated OsuHitObject
|
* @param hitObject the associated OsuHitObject
|
||||||
* @param game the associated Game object
|
* @param game the associated Game object
|
||||||
* @param data the associated GameData object
|
* @param data the associated GameData object
|
||||||
* @param color the color of this circle
|
* @param color the color of this slider
|
||||||
* @param comboEnd true if this is the last hit object in the combo
|
* @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(OsuHitObject hitObject, Game game, GameData data, Color color, boolean comboEnd) {
|
||||||
|
@ -807,18 +134,16 @@ public class Slider implements HitObject {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.comboEnd = comboEnd;
|
this.comboEnd = comboEnd;
|
||||||
if(hitObject.getSliderType() == 'P' && hitObject.getSliderX().length==2){
|
|
||||||
this.bezier = new CircumscribedCircle();
|
|
||||||
}else {
|
|
||||||
this.bezier = new LinearBezier();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (hitObject.getSliderType() == 'P' && hitObject.getSliderX().length == 2)
|
||||||
|
this.curve = new CircumscribedCircle(hitObject, color);
|
||||||
|
else
|
||||||
|
this.curve = new LinearBezier(hitObject, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(int trackPosition, boolean currentObject, Graphics g) {
|
public void draw(int trackPosition, boolean currentObject, Graphics g) {
|
||||||
float x = hitObject.getX(), y = hitObject.getY();
|
float x = hitObject.getX(), y = hitObject.getY();
|
||||||
float[] sliderX = hitObject.getSliderX(), sliderY = hitObject.getSliderY();
|
|
||||||
int timeDiff = hitObject.getTime() - trackPosition;
|
int timeDiff = hitObject.getTime() - trackPosition;
|
||||||
|
|
||||||
float approachScale = (timeDiff >= 0) ? 1 + (timeDiff * 2f / game.getApproachTime()) : 1f;
|
float approachScale = (timeDiff >= 0) ? 1 + (timeDiff * 2f / game.getApproachTime()) : 1f;
|
||||||
|
@ -828,14 +153,14 @@ public class Slider implements HitObject {
|
||||||
color.a = alpha;
|
color.a = alpha;
|
||||||
Utils.COLOR_WHITE_FADE.a = alpha;
|
Utils.COLOR_WHITE_FADE.a = alpha;
|
||||||
|
|
||||||
// bezier
|
// curve
|
||||||
bezier.draw();
|
curve.draw();
|
||||||
|
|
||||||
// ticks
|
// ticks
|
||||||
if (currentObject && ticksT != null) {
|
if (currentObject && ticksT != null) {
|
||||||
Image tick = GameImage.SLIDER_TICK.getImage();
|
Image tick = GameImage.SLIDER_TICK.getImage();
|
||||||
for (int i = 0; i < ticksT.length; i++) {
|
for (int i = 0; i < ticksT.length; i++) {
|
||||||
float[] c = bezier.pointAt(ticksT[i]);
|
float[] c = curve.pointAt(ticksT[i]);
|
||||||
tick.drawCentered(c[0], c[1]);
|
tick.drawCentered(c[0], c[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -844,11 +169,10 @@ public class Slider implements HitObject {
|
||||||
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||||
|
|
||||||
// end circle
|
// end circle
|
||||||
//int lastIndex = sliderX.length - 1;
|
float[] endPos = curve.pointAt(1);
|
||||||
float[] endPos = bezier.pointAt(1);
|
|
||||||
Utils.drawCentered(hitCircle, endPos[0], endPos[1], color);
|
Utils.drawCentered(hitCircle, endPos[0], endPos[1], color);
|
||||||
Utils.drawCentered(hitCircleOverlay, endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
|
Utils.drawCentered(hitCircleOverlay, endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
|
||||||
|
|
||||||
// start circle
|
// start circle
|
||||||
Utils.drawCentered(hitCircleOverlay, x, y, Utils.COLOR_WHITE_FADE);
|
Utils.drawCentered(hitCircleOverlay, x, y, Utils.COLOR_WHITE_FADE);
|
||||||
Utils.drawCentered(hitCircle, x, y, color);
|
Utils.drawCentered(hitCircle, x, y, color);
|
||||||
|
@ -865,17 +189,18 @@ public class Slider implements HitObject {
|
||||||
for(int tcurRepeat = currentRepeats; tcurRepeat<=currentRepeats+1; tcurRepeat++){
|
for(int tcurRepeat = currentRepeats; tcurRepeat<=currentRepeats+1; tcurRepeat++){
|
||||||
if (hitObject.getRepeatCount() - 1 > tcurRepeat) {
|
if (hitObject.getRepeatCount() - 1 > tcurRepeat) {
|
||||||
Image arrow = GameImage.REVERSEARROW.getImage();
|
Image arrow = GameImage.REVERSEARROW.getImage();
|
||||||
if(tcurRepeat != currentRepeats){
|
if (tcurRepeat != currentRepeats) {
|
||||||
float t = getT(trackPosition, true);
|
float t = getT(trackPosition, true);
|
||||||
arrow.setAlpha((float) (t-Math.floor(t)));
|
arrow.setAlpha((float) (t - Math.floor(t)));
|
||||||
}else{
|
} else
|
||||||
arrow.setAlpha(1f);
|
arrow.setAlpha(1f);
|
||||||
}
|
if (tcurRepeat % 2 == 0) {
|
||||||
if (tcurRepeat % 2 == 0) { // last circle
|
// last circle
|
||||||
arrow.setRotation(bezier.getEndAngle());
|
arrow.setRotation(curve.getEndAngle());
|
||||||
arrow.drawCentered(endPos[0], endPos[1]);
|
arrow.drawCentered(endPos[0], endPos[1]);
|
||||||
} else { // first circle
|
} else {
|
||||||
arrow.setRotation(bezier.getStartAngle());
|
// first circle
|
||||||
|
arrow.setRotation(curve.getStartAngle());
|
||||||
arrow.drawCentered(x, y);
|
arrow.drawCentered(x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,7 +210,7 @@ public class Slider implements HitObject {
|
||||||
// approach circle
|
// approach circle
|
||||||
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
|
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
|
||||||
} else {
|
} else {
|
||||||
float[] c = bezier.pointAt(getT(trackPosition, false));
|
float[] c = curve.pointAt(getT(trackPosition, false));
|
||||||
|
|
||||||
// slider ball
|
// slider ball
|
||||||
Utils.drawCentered(sliderBall, c[0], c[1]);
|
Utils.drawCentered(sliderBall, c[0], c[1]);
|
||||||
|
@ -901,7 +226,6 @@ public class Slider implements HitObject {
|
||||||
* @return the hit result (GameData.HIT_* constants)
|
* @return the hit result (GameData.HIT_* constants)
|
||||||
*/
|
*/
|
||||||
private int hitResult() {
|
private int hitResult() {
|
||||||
int lastIndex = hitObject.getSliderX().length - 1;
|
|
||||||
float tickRatio = (float) ticksHit / tickIntervals;
|
float tickRatio = (float) ticksHit / tickIntervals;
|
||||||
|
|
||||||
int result;
|
int result;
|
||||||
|
@ -914,14 +238,14 @@ public class Slider implements HitObject {
|
||||||
else
|
else
|
||||||
result = GameData.HIT_MISS;
|
result = GameData.HIT_MISS;
|
||||||
|
|
||||||
if (currentRepeats % 2 == 0) {// last circle
|
if (currentRepeats % 2 == 0) { // last circle
|
||||||
float[] lastPos = bezier.pointAt(1);
|
float[] lastPos = curve.pointAt(1);
|
||||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||||
lastPos[0],lastPos[1],
|
lastPos[0],lastPos[1], color, comboEnd, hitObject.getHitSoundType());
|
||||||
color, comboEnd, hitObject.getHitSoundType());
|
} else { // first circle
|
||||||
}else // first circle
|
|
||||||
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
|
||||||
hitObject.getX(), hitObject.getY(), color, comboEnd, hitObject.getHitSoundType());
|
hitObject.getX(), hitObject.getY(), color, comboEnd, hitObject.getHitSoundType());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1020,7 +344,7 @@ public class Slider implements HitObject {
|
||||||
|
|
||||||
// check if cursor pressed and within end circle
|
// check if cursor pressed and within end circle
|
||||||
else if (Utils.isGameKeyPressed()) {
|
else if (Utils.isGameKeyPressed()) {
|
||||||
float[] c = bezier.pointAt(getT(trackPosition, false));
|
float[] c = curve.pointAt(getT(trackPosition, false));
|
||||||
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
|
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
|
||||||
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
|
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
|
||||||
if (distance < followCircleRadius)
|
if (distance < followCircleRadius)
|
||||||
|
@ -1057,7 +381,7 @@ public class Slider implements HitObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// holding slider...
|
// holding slider...
|
||||||
float[] c = bezier.pointAt(getT(trackPosition, false));
|
float[] c = curve.pointAt(getT(trackPosition, false));
|
||||||
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
|
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
|
||||||
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
|
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
|
||||||
if ((Utils.isGameKeyPressed() && distance < followCircleRadius) || isAutoMod) {
|
if ((Utils.isGameKeyPressed() && distance < followCircleRadius) || isAutoMod) {
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.objects;
|
package itdelatrisu.opsu.objects;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameData;
|
||||||
import itdelatrisu.opsu.GameImage;
|
import itdelatrisu.opsu.GameImage;
|
||||||
import itdelatrisu.opsu.GameMod;
|
import itdelatrisu.opsu.GameMod;
|
||||||
import itdelatrisu.opsu.GameData;
|
|
||||||
import itdelatrisu.opsu.OsuHitObject;
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
|
|
121
src/itdelatrisu/opsu/objects/curves/Bezier2.java
Normal file
121
src/itdelatrisu/opsu/objects/curves/Bezier2.java
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* 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 Bezier curve with the distance between each point calculated.
|
||||||
|
*/
|
||||||
|
public class Bezier2 {
|
||||||
|
/** 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
|
||||||
|
*/
|
||||||
|
public Bezier2(Vec2f[] points) {
|
||||||
|
this.points = points;
|
||||||
|
|
||||||
|
// approximate by finding the length of all points
|
||||||
|
// (which should be the max possible length of the curve)
|
||||||
|
float approxlength = 0;
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the point on the Bezier curve at a value t.
|
||||||
|
* @param t the t value [0, 1]
|
||||||
|
* @return the point [x, y]
|
||||||
|
*/
|
||||||
|
public Vec2f pointAt(float t) {
|
||||||
|
Vec2f c = new Vec2f();
|
||||||
|
int n = points.length - 1;
|
||||||
|
for (int i = 0; i <= n; i++) {
|
||||||
|
c.x += points[i].x * bernstein(i, n, t);
|
||||||
|
c.y += points[i].y * bernstein(i, n, t);
|
||||||
|
}
|
||||||
|
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 factorial of a number.
|
||||||
|
*/
|
||||||
|
private static long factorial(int n) {
|
||||||
|
return (n <= 1 || n > 20) ? 1 : n * factorial(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the Bernstein polynomial.
|
||||||
|
* @param i the index
|
||||||
|
* @param n the degree of the polynomial (i.e. number of points)
|
||||||
|
* @param t the t value [0, 1]
|
||||||
|
*/
|
||||||
|
private static double bernstein(int i, int n, float t) {
|
||||||
|
return factorial(n) / (factorial(i) * factorial(n - i)) *
|
||||||
|
Math.pow(t, i) * Math.pow(1 - t, n - i);
|
||||||
|
}
|
||||||
|
}
|
188
src/itdelatrisu/opsu/objects/curves/CircumscribedCircle.java
Normal file
188
src/itdelatrisu/opsu/objects/curves/CircumscribedCircle.java
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
/*
|
||||||
|
* 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.GameImage;
|
||||||
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
|
import itdelatrisu.opsu.Utils;
|
||||||
|
|
||||||
|
import org.newdawn.slick.Color;
|
||||||
|
import org.newdawn.slick.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a curve along a Circumscribed Circle of three points.
|
||||||
|
* http://en.wikipedia.org/wiki/Circumscribed_circle
|
||||||
|
*/
|
||||||
|
public class CircumscribedCircle extends Curve {
|
||||||
|
/** PI constants. */
|
||||||
|
private static final float
|
||||||
|
TWO_PI = (float) (Math.PI * 2),
|
||||||
|
HALF_PI = (float) (Math.PI / 2);
|
||||||
|
|
||||||
|
/** The center of the Circumscribed Circle. */
|
||||||
|
private Vec2f circleCenter;
|
||||||
|
|
||||||
|
/** The radius of the Circumscribed Circle. */
|
||||||
|
private float radius;
|
||||||
|
|
||||||
|
/** The three points to create the Circumscribed Circle from. */
|
||||||
|
private Vec2f start, mid, end;
|
||||||
|
|
||||||
|
/** The three angles relative to the circle center. */
|
||||||
|
private float startAng, endAng, midAng;
|
||||||
|
|
||||||
|
/** The start and end angles for drawing. */
|
||||||
|
private float drawStartAngle, drawEndAngle;
|
||||||
|
|
||||||
|
/** The number of steps in the curve to draw. */
|
||||||
|
private float step;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param hitObject the associated OsuHitObject
|
||||||
|
* @param color the color of this curve
|
||||||
|
*/
|
||||||
|
public CircumscribedCircle(OsuHitObject 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));
|
||||||
|
this.end = new Vec2f(getX(2), getY(2));
|
||||||
|
|
||||||
|
// find the circle center
|
||||||
|
Vec2f mida = start.midPoint(mid);
|
||||||
|
Vec2f midb = end.midPoint(mid);
|
||||||
|
Vec2f nora = mid.cpy().sub(start).nor();
|
||||||
|
Vec2f norb = mid.cpy().sub(end).nor();
|
||||||
|
|
||||||
|
this.circleCenter = intersect(mida, nora, midb, norb);
|
||||||
|
if (circleCenter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// find the angles relative to the circle center
|
||||||
|
Vec2f startAngPoint = start.cpy().sub(circleCenter);
|
||||||
|
Vec2f midAngPoint = mid.cpy().sub(circleCenter);
|
||||||
|
Vec2f endAngPoint = end.cpy().sub(circleCenter);
|
||||||
|
|
||||||
|
this.startAng = (float) Math.atan2(startAngPoint.y, startAngPoint.x);
|
||||||
|
this.midAng = (float) Math.atan2(midAngPoint.y, midAngPoint.x);
|
||||||
|
this.endAng = (float) Math.atan2(endAngPoint.y, endAngPoint.x);
|
||||||
|
|
||||||
|
// find the angles that pass through midAng
|
||||||
|
if (!isIn(startAng, midAng, endAng)) {
|
||||||
|
if (Math.abs(startAng + TWO_PI - endAng) < TWO_PI && isIn(startAng + (TWO_PI), midAng, endAng))
|
||||||
|
startAng += TWO_PI;
|
||||||
|
else if (Math.abs(startAng - (endAng + TWO_PI)) < TWO_PI && isIn(startAng, midAng, endAng + (TWO_PI)))
|
||||||
|
endAng += TWO_PI;
|
||||||
|
else if (Math.abs(startAng - TWO_PI - endAng) < TWO_PI && isIn(startAng - (TWO_PI), midAng, endAng))
|
||||||
|
startAng -= TWO_PI;
|
||||||
|
else if (Math.abs(startAng - (endAng - TWO_PI)) < TWO_PI && isIn(startAng, midAng, endAng - (TWO_PI)))
|
||||||
|
endAng -= TWO_PI;
|
||||||
|
else {
|
||||||
|
ErrorHandler.error(
|
||||||
|
String.format("Cannot find angles between midAng (%.3f %.3f %.3f).",
|
||||||
|
startAng, midAng, endAng), null, true
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find an angle with an arc length of pixelLength along this circle
|
||||||
|
this.radius = startAngPoint.len();
|
||||||
|
float pixelLength = hitObject.getPixelLength() * OsuHitObject.getXMultiplier();
|
||||||
|
float arcAng = pixelLength / radius; // len = theta * r / theta = len / r
|
||||||
|
|
||||||
|
// now use it for our new end angle
|
||||||
|
this.endAng = (endAng > startAng) ? startAng + arcAng : startAng - arcAng;
|
||||||
|
|
||||||
|
// finds the angles to draw for repeats
|
||||||
|
this.drawEndAngle = (float) ((endAng + (startAng > endAng ? HALF_PI : -HALF_PI)) * 180 / Math.PI);
|
||||||
|
this.drawStartAngle = (float) ((startAng + (startAng > endAng ? -HALF_PI : HALF_PI)) * 180 / Math.PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if "b" is between "a" and "c"
|
||||||
|
* @return true if b is between a and c
|
||||||
|
*/
|
||||||
|
private boolean isIn(float a, float b, float c) {
|
||||||
|
return (b > a && b < c) || (b < a && b > c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the point of intersection between the two parametric lines
|
||||||
|
* {@code A = a + ta*t} and {@code B = b + tb*u}.
|
||||||
|
* http://gamedev.stackexchange.com/questions/44720/
|
||||||
|
* @param a the initial position of the line A
|
||||||
|
* @param ta the direction of the line A
|
||||||
|
* @param b the initial position of the line B
|
||||||
|
* @param tb the direction of the line B
|
||||||
|
* @return the point at which the two lines intersect
|
||||||
|
*/
|
||||||
|
private Vec2f intersect(Vec2f a, Vec2f ta, Vec2f b, Vec2f tb) {
|
||||||
|
// xy = a + ta * t = b + tb * u
|
||||||
|
// t =(b + tb*u -a)/ta
|
||||||
|
//t(x) == t(y)
|
||||||
|
//(b.x + tb.x*u -a.x)/ta.x = (b.y + tb.y*u -a.y)/ta.y
|
||||||
|
// b.x*ta.y + tb.x*u*ta.y -a.x*ta.y = b.y*ta.x + tb.y*u*ta.x -a.y*ta.x
|
||||||
|
// tb.x*u*ta.y - tb.y*u*ta.x= b.y*ta.x -a.y*ta.x -b.x*ta.y +a.x*ta.y
|
||||||
|
//u *(tb.x*ta.y - tb.y*ta.x) = (b.y-a.y)ta.x +(a.x-b.x)ta.y
|
||||||
|
//u = ((b.y-a.y)ta.x +(a.x-b.x)ta.y) / (tb.x*ta.y - tb.y*ta.x);
|
||||||
|
|
||||||
|
float des = tb.x * ta.y - tb.y * ta.x;
|
||||||
|
if (Math.abs(des) < 0.00001f) {
|
||||||
|
ErrorHandler.error("Vectors are parallel.", null, true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
float u = ((b.y - a.y) * ta.x + (a.x - b.x) * ta.y) / des;
|
||||||
|
return b.cpy().add(tb.x * u, tb.y * u);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] pointAt(float t) {
|
||||||
|
float ang = lerp(startAng, endAng, t);
|
||||||
|
return new float[] {
|
||||||
|
(float) (Math.cos(ang) * radius + circleCenter.x),
|
||||||
|
(float) (Math.sin(ang) * radius + circleCenter.y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw() {
|
||||||
|
Image hitCircle = GameImage.HITCIRCLE.getImage();
|
||||||
|
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
|
||||||
|
for (int i = 0; i < step; i++) {
|
||||||
|
float[] xy = pointAt(i / step);
|
||||||
|
Utils.drawCentered(hitCircleOverlay, xy[0], xy[1], Utils.COLOR_WHITE_FADE);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < step; i++) {
|
||||||
|
float[] xy = pointAt(i / step);
|
||||||
|
Utils.drawCentered(hitCircle, xy[0], xy[1], color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEndAngle() { return drawEndAngle; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getStartAngle() { return drawStartAngle; }
|
||||||
|
}
|
97
src/itdelatrisu/opsu/objects/curves/Curve.java
Normal file
97
src/itdelatrisu/opsu/objects/curves/Curve.java
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* 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.OsuHitObject;
|
||||||
|
|
||||||
|
import org.newdawn.slick.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a curve.
|
||||||
|
*/
|
||||||
|
public abstract class Curve {
|
||||||
|
/** The associated OsuHitObject. */
|
||||||
|
protected OsuHitObject hitObject;
|
||||||
|
|
||||||
|
/** The color of this curve. */
|
||||||
|
protected Color color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param hitObject the associated OsuHitObject
|
||||||
|
* @param color the color of this curve
|
||||||
|
*/
|
||||||
|
protected Curve(OsuHitObject hitObject, Color color) {
|
||||||
|
this.hitObject = hitObject;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the point on the curve at a value t.
|
||||||
|
* @param t the t value [0, 1]
|
||||||
|
* @return the point [x, y]
|
||||||
|
*/
|
||||||
|
public abstract float[] pointAt(float t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the full curve to the graphics context.
|
||||||
|
*/
|
||||||
|
public abstract void draw();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the angle of the first control point.
|
||||||
|
*/
|
||||||
|
public abstract float getEndAngle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the angle of the last control point.
|
||||||
|
*/
|
||||||
|
public abstract float getStartAngle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the x coordinate of the control point at index i.
|
||||||
|
*/
|
||||||
|
protected float getX(int i) {
|
||||||
|
return (i == 0) ? hitObject.getX() : hitObject.getSliderX()[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the y coordinate of the control point at index i.
|
||||||
|
*/
|
||||||
|
protected float getY(int i) {
|
||||||
|
return (i == 0) ? hitObject.getY() : hitObject.getSliderY()[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear interpolation of a and b at t.
|
||||||
|
*/
|
||||||
|
protected float lerp(float a, float b, float t) {
|
||||||
|
return a * (1 - t) + b * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A recursive method to evaluate polynomials in Bernstein form or Bezier curves.
|
||||||
|
* http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
|
||||||
|
*/
|
||||||
|
protected float deCasteljau(float[] a, int i, int order, float t) {
|
||||||
|
if (order == 0)
|
||||||
|
return a[i];
|
||||||
|
return lerp(deCasteljau(a, i, order - 1, t), deCasteljau(a, i + 1, order - 1, t), t);
|
||||||
|
}
|
||||||
|
}
|
175
src/itdelatrisu/opsu/objects/curves/LinearBezier.java
Normal file
175
src/itdelatrisu/opsu/objects/curves/LinearBezier.java
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* 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.GameImage;
|
||||||
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
|
import itdelatrisu.opsu.Utils;
|
||||||
|
|
||||||
|
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.
|
||||||
|
* http://pomax.github.io/bezierinfo/#tracing
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param hitObject the associated OsuHitObject
|
||||||
|
* @param color the color of this curve
|
||||||
|
*/
|
||||||
|
public LinearBezier(OsuHitObject hitObject, Color color) {
|
||||||
|
super(hitObject, color);
|
||||||
|
|
||||||
|
// splits points into different Beziers if has the same points (red points)
|
||||||
|
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 (points.size() >= 2)
|
||||||
|
beziers.add(new Bezier2(points.toArray(new Vec2f[0])));
|
||||||
|
points.clear();
|
||||||
|
}
|
||||||
|
points.add(tpoi);
|
||||||
|
lastPoi = tpoi;
|
||||||
|
}
|
||||||
|
if (points.size() < 2) {
|
||||||
|
// trying to continue Bezier with less than 2 points
|
||||||
|
// probably ending on a red point, just ignore it
|
||||||
|
} else {
|
||||||
|
beziers.add(new Bezier2(points.toArray(new Vec2f[0])));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
// System.out.println("Dis "+i+" "+prefDistance+" "+lastDistanceAt+" "+distanceAt+" "+curPoint+" "+t);
|
||||||
|
} else
|
||||||
|
curve[i] = thisCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (hitObject.getRepeatCount() > 1) {
|
||||||
|
Vec2f c1 = curve[0];
|
||||||
|
int cnt = 1;
|
||||||
|
Vec2f c2 = curve[cnt++];
|
||||||
|
while (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 - 1];
|
||||||
|
cnt = ncurve - 2;
|
||||||
|
c2 = curve[cnt];
|
||||||
|
while (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);
|
||||||
|
// }
|
||||||
|
// System.out.println("Total Distance: "+totalDistance+" "+distanceAt+" "+beziers.size()+" "+hitObject.getPixelLength()+" "+OsuHitObject.getXMultiplier());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] pointAt(float t) {
|
||||||
|
float indexF = t * ncurve;
|
||||||
|
int index = (int) indexF;
|
||||||
|
if (index >= ncurve) {
|
||||||
|
Vec2f poi = curve[ncurve - 1];
|
||||||
|
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--)
|
||||||
|
Utils.drawCentered(hitCircleOverlay, curve[i].x, curve[i].y, Utils.COLOR_WHITE_FADE);
|
||||||
|
for (int i = curve.length - 2; i >= 0; i--)
|
||||||
|
Utils.drawCentered(hitCircle, curve[i].x, curve[i].y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getEndAngle() { return endAngle; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getStartAngle() { return startAngle; }
|
||||||
|
}
|
98
src/itdelatrisu/opsu/objects/curves/Vec2f.java
Normal file
98
src/itdelatrisu/opsu/objects/curves/Vec2f.java
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A two-dimensional floating-point vector.
|
||||||
|
*/
|
||||||
|
public class Vec2f {
|
||||||
|
/** Vector coordinates. */
|
||||||
|
public float x, y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of the (nx, ny) vector.
|
||||||
|
*/
|
||||||
|
public Vec2f(float nx, float ny) {
|
||||||
|
x = nx;
|
||||||
|
y = ny;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of the (0,0) vector.
|
||||||
|
*/
|
||||||
|
public Vec2f() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the midpoint between this vector and another vector.
|
||||||
|
* @param o the other vector
|
||||||
|
* @return a midpoint vector
|
||||||
|
*/
|
||||||
|
public Vec2f midPoint(Vec2f o) {
|
||||||
|
return new Vec2f((x + o.x) / 2, (y + o.y) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts a vector from this vector.
|
||||||
|
* @param o the other vector
|
||||||
|
* @return itself (for chaining)
|
||||||
|
*/
|
||||||
|
public Vec2f sub(Vec2f o) {
|
||||||
|
x -= o.x;
|
||||||
|
y -= o.y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets this vector to the normal of this vector.
|
||||||
|
* @return itself (for chaining)
|
||||||
|
*/
|
||||||
|
public Vec2f nor() {
|
||||||
|
float nx = -y, ny = x;
|
||||||
|
x = nx;
|
||||||
|
y = ny;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this vector.
|
||||||
|
*/
|
||||||
|
public Vec2f cpy() { return new Vec2f(x, y); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds nx to the x component and ny to the y component of this vector.
|
||||||
|
* @return itself (for chaining)
|
||||||
|
*/
|
||||||
|
public Vec2f add(float nx, float ny) {
|
||||||
|
x += nx;
|
||||||
|
y += ny;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of this vector.
|
||||||
|
*/
|
||||||
|
public float len() { return (float) Math.sqrt(x * x + y * y); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this vector to another vector.
|
||||||
|
* @param o the other vector
|
||||||
|
* @return true if the two vectors are numerically equal
|
||||||
|
*/
|
||||||
|
public boolean equals(Vec2f o) { return (x == o.x && y == o.y); }
|
||||||
|
}
|
|
@ -19,9 +19,9 @@
|
||||||
package itdelatrisu.opsu.states;
|
package itdelatrisu.opsu.states;
|
||||||
|
|
||||||
import itdelatrisu.opsu.ErrorHandler;
|
import itdelatrisu.opsu.ErrorHandler;
|
||||||
|
import itdelatrisu.opsu.GameData;
|
||||||
import itdelatrisu.opsu.GameImage;
|
import itdelatrisu.opsu.GameImage;
|
||||||
import itdelatrisu.opsu.GameMod;
|
import itdelatrisu.opsu.GameMod;
|
||||||
import itdelatrisu.opsu.GameData;
|
|
||||||
import itdelatrisu.opsu.MenuButton;
|
import itdelatrisu.opsu.MenuButton;
|
||||||
import itdelatrisu.opsu.Opsu;
|
import itdelatrisu.opsu.Opsu;
|
||||||
import itdelatrisu.opsu.Options;
|
import itdelatrisu.opsu.Options;
|
||||||
|
@ -29,6 +29,7 @@ import itdelatrisu.opsu.OsuFile;
|
||||||
import itdelatrisu.opsu.OsuHitObject;
|
import itdelatrisu.opsu.OsuHitObject;
|
||||||
import itdelatrisu.opsu.OsuTimingPoint;
|
import itdelatrisu.opsu.OsuTimingPoint;
|
||||||
import itdelatrisu.opsu.ScoreDB;
|
import itdelatrisu.opsu.ScoreDB;
|
||||||
|
import itdelatrisu.opsu.ScoreData;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.HitSound;
|
import itdelatrisu.opsu.audio.HitSound;
|
||||||
import itdelatrisu.opsu.audio.MusicController;
|
import itdelatrisu.opsu.audio.MusicController;
|
||||||
|
@ -421,8 +422,9 @@ public class Game extends BasicGameState {
|
||||||
game.closeRequested();
|
game.closeRequested();
|
||||||
else { // go to ranking screen
|
else { // go to ranking screen
|
||||||
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
((GameRanking) game.getState(Opsu.STATE_GAMERANKING)).setGameData(data);
|
||||||
|
ScoreData score = data.getScoreData(osu);
|
||||||
if (!GameMod.AUTO.isActive())
|
if (!GameMod.AUTO.isActive())
|
||||||
ScoreDB.addScore(data.getScoreData(osu));
|
ScoreDB.addScore(score);
|
||||||
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
game.enterState(Opsu.STATE_GAMERANKING, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -30,8 +30,8 @@ import itdelatrisu.opsu.OsuGroupList;
|
||||||
import itdelatrisu.opsu.OsuGroupNode;
|
import itdelatrisu.opsu.OsuGroupNode;
|
||||||
import itdelatrisu.opsu.OsuParser;
|
import itdelatrisu.opsu.OsuParser;
|
||||||
import itdelatrisu.opsu.OszUnpacker;
|
import itdelatrisu.opsu.OszUnpacker;
|
||||||
import itdelatrisu.opsu.ScoreData;
|
|
||||||
import itdelatrisu.opsu.ScoreDB;
|
import itdelatrisu.opsu.ScoreDB;
|
||||||
|
import itdelatrisu.opsu.ScoreData;
|
||||||
import itdelatrisu.opsu.SongSort;
|
import itdelatrisu.opsu.SongSort;
|
||||||
import itdelatrisu.opsu.Utils;
|
import itdelatrisu.opsu.Utils;
|
||||||
import itdelatrisu.opsu.audio.HitSound;
|
import itdelatrisu.opsu.audio.HitSound;
|
||||||
|
|
|
@ -148,7 +148,7 @@ public class Splash extends BasicGameState {
|
||||||
// close program
|
// close program
|
||||||
if (++escapeCount >= 3)
|
if (++escapeCount >= 3)
|
||||||
container.exit();
|
container.exit();
|
||||||
|
|
||||||
// stop parsing OsuFiles by sending interrupt to OsuParser
|
// stop parsing OsuFiles by sending interrupt to OsuParser
|
||||||
else if (thread != null)
|
else if (thread != null)
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user