Initial commit
This commit is contained in:
0
pp/catch_the_pp/osu_parser/__init__.py
Normal file
0
pp/catch_the_pp/osu_parser/__init__.py
Normal file
222
pp/catch_the_pp/osu_parser/beatmap.pyx
Normal file
222
pp/catch_the_pp/osu_parser/beatmap.pyx
Normal file
@@ -0,0 +1,222 @@
|
||||
from . import mathhelper
|
||||
from .hitobject import HitObject
|
||||
|
||||
cdef class Beatmap(object):
|
||||
"""
|
||||
Beatmap object for beatmap parsing and handling
|
||||
"""
|
||||
|
||||
cdef public str file_name
|
||||
cdef public int version
|
||||
cdef public int header
|
||||
cdef public dict difficulty
|
||||
cdef public dict timing_points
|
||||
cdef public float slider_point_distance
|
||||
cdef public list hitobjects
|
||||
cdef public int max_combo
|
||||
|
||||
def __init__(self, file_name):
|
||||
"""
|
||||
file_name -- Directory for beatmap file (.osu)
|
||||
"""
|
||||
self.file_name = file_name
|
||||
self.version = -1 #Unknown by default
|
||||
self.header = -1
|
||||
self.difficulty = {}
|
||||
self.timing_points = {
|
||||
"raw_bpm": {}, #Raw bpm modifier code
|
||||
"raw_spm": {}, #Raw speed modifier code
|
||||
"bpm": {}, #Beats pr minute
|
||||
"spm": {} #Speed modifier
|
||||
}
|
||||
self.slider_point_distance = 1 #Changes after [Difficulty] is fully parsed
|
||||
self.hitobjects = []
|
||||
self.max_combo = 0
|
||||
self.parse_beatmap()
|
||||
|
||||
if "ApproachRate" not in self.difficulty.keys(): #Fix old osu version
|
||||
self.difficulty["ApproachRate"] = self.difficulty["OverallDifficulty"]
|
||||
|
||||
cpdef parse_beatmap(self):
|
||||
"""
|
||||
Parses beatmap file line by line by passing each line into parse_line.
|
||||
"""
|
||||
cdef str line
|
||||
with open(self.file_name, encoding="utf8") as file_stream:
|
||||
ver_line = ""
|
||||
while len(ver_line) < 2: #Find the line where beatmap version is spesified (normaly first line)
|
||||
ver_line = file_stream.readline()
|
||||
self.version = int(''.join(list(filter(str.isdigit, ver_line)))) #Set version
|
||||
for line in file_stream:
|
||||
self.parse_line(line.replace("\n", ""))
|
||||
|
||||
cpdef parse_line(self, str line):
|
||||
"""
|
||||
Parse a beatmapfile line.
|
||||
|
||||
Handles lines that are required for our use case (Difficulty, TimingPoints & hitobjects),
|
||||
everything else is skipped.
|
||||
"""
|
||||
if len(line) < 1:
|
||||
return
|
||||
|
||||
if line.startswith("["):
|
||||
if line == "[Difficulty]":
|
||||
self.header = 0
|
||||
elif line == "[TimingPoints]":
|
||||
self.header = 1
|
||||
elif line == "[HitObjects]":
|
||||
self.header = 2
|
||||
self.slider_point_distance = (100 * self.difficulty["SliderMultiplier"]) / self.difficulty["SliderTickRate"]
|
||||
else:
|
||||
self.header = -1
|
||||
return
|
||||
|
||||
if self.header == -1: #We return if we are reading under a header we dont care about
|
||||
return
|
||||
|
||||
if self.header == 0:
|
||||
self.handle_difficulty_propperty(line)
|
||||
elif self.header == 1:
|
||||
self.handle_timing_point(line)
|
||||
elif self.header == 2:
|
||||
self.handle_hitobject(line)
|
||||
|
||||
cpdef handle_difficulty_propperty(self, str propperty):
|
||||
"""
|
||||
Puts the [Difficulty] propperty into the difficulty dict.
|
||||
"""
|
||||
prop = propperty.split(":")
|
||||
self.difficulty[prop[0]] = float(prop[1])
|
||||
|
||||
cpdef handle_timing_point(self, str timing_point):
|
||||
"""
|
||||
Formats timing points used for slider velocity changes,
|
||||
and store them into self.timing_points dict.
|
||||
"""
|
||||
timing_point_split = timing_point.split(",")
|
||||
timing_point_time = int(float(timing_point_split[0])) #Fixes some special mappers special needs
|
||||
timing_point_focus = timing_point_split[1]
|
||||
|
||||
timing_point_type = 1
|
||||
if len(timing_point_split) >= 7: #Fix for old beatmaps that only stores bpm change and timestamp (only BPM change) [v3?]
|
||||
timing_point_type = int(timing_point_split[6])
|
||||
|
||||
if timing_point_type == 0 and not timing_point_focus.startswith("-"):
|
||||
timing_point_focus = "-100"
|
||||
|
||||
if timing_point_focus.startswith("-"): #If not then its not a slider velocity modifier
|
||||
self.timing_points["spm"][timing_point_time] = -100 / float(timing_point_focus) #Convert to normalized value and store
|
||||
self.timing_points["raw_spm"][timing_point_time] = float(timing_point_focus)
|
||||
else:
|
||||
if len(self.timing_points["bpm"]) == 0: #Fixes if hitobjects shows up before bpm is set
|
||||
timing_point_time = 0
|
||||
|
||||
self.timing_points["bpm"][timing_point_time] = 60000 / float(timing_point_focus)#^
|
||||
self.timing_points["raw_bpm"][timing_point_time] = float(timing_point_focus)
|
||||
#This trash of a game resets the spm when bpm change >.>
|
||||
self.timing_points["spm"][timing_point_time] = 1
|
||||
self.timing_points["raw_spm"][timing_point_time] = -100
|
||||
|
||||
cpdef handle_hitobject(self, str line):
|
||||
"""
|
||||
Puts every hitobject into the hitobjects array.
|
||||
|
||||
Creates hitobjects, hitobject_sliders or skip depending on the given data.
|
||||
We skip everything that is not important for us for our use case (Spinners)
|
||||
"""
|
||||
split_object = line.split(",")
|
||||
time = int(split_object[2])
|
||||
object_type = int(split_object[3])
|
||||
|
||||
if not (1 & object_type > 0 or 2 & object_type > 0): #We only want sliders and circles as spinners are random bannanas etc.
|
||||
return
|
||||
|
||||
if 2 & object_type: #Slider
|
||||
repeat = int(split_object[6])
|
||||
pixel_length = float(split_object[7])
|
||||
|
||||
time_point = self.get_timing_point_all(time)
|
||||
|
||||
tick_distance = (100 * self.difficulty["SliderMultiplier"]) / self.difficulty["SliderTickRate"]
|
||||
if self.version >= 8:
|
||||
tick_distance /= (mathhelper.clamp(-time_point["raw_spm"], 10, 1000) / 100)
|
||||
|
||||
curve_split = split_object[5].split("|")
|
||||
curve_points = []
|
||||
for i in range(1, len(curve_split)):
|
||||
vector_split = curve_split[i].split(":")
|
||||
vector = mathhelper.Vec2(int(vector_split[0]), int(vector_split[1]))
|
||||
curve_points.append(vector)
|
||||
|
||||
slider_type = curve_split[0]
|
||||
if self.version <= 6 and len(curve_points) >= 2:
|
||||
if slider_type == "L":
|
||||
slider_type = "B"
|
||||
|
||||
if len(curve_points) == 2:
|
||||
if (int(split_object[0]) == curve_points[0].x and int(split_object[1]) == curve_points[0].y) or (curve_points[0].x == curve_points[1].x and curve_points[0].y == curve_points[1].y):
|
||||
del curve_points[0]
|
||||
slider_type = "L"
|
||||
|
||||
if len(curve_points) == 0: #Incase of ExGon meme (Sliders that acts like hitcircles)
|
||||
hitobject = HitObject(int(split_object[0]), int(split_object[1]), time, 1)
|
||||
else:
|
||||
hitobject = HitObject(int(split_object[0]), int(split_object[1]), time, object_type, slider_type, curve_points, repeat, pixel_length, time_point, self.difficulty, tick_distance)
|
||||
else:
|
||||
hitobject = HitObject(int(split_object[0]), int(split_object[1]), time, object_type)
|
||||
|
||||
self.hitobjects.append(hitobject)
|
||||
self.max_combo += hitobject.get_combo()
|
||||
|
||||
def get_timing_point_all(self, time):
|
||||
"""
|
||||
Returns a object of all current timing types
|
||||
|
||||
time -- timestamp
|
||||
return -- {"raw_bpm": Float, "raw_spm": Float, "bpm": Float, "spm": Float}
|
||||
"""
|
||||
types = {
|
||||
"raw_bpm": 600,
|
||||
"raw_spm": -100,
|
||||
"bpm": 100,
|
||||
"spm": 1
|
||||
} #Will return the default value if timing point were not found
|
||||
for t in types.keys():
|
||||
r = self.get_timing_point(time, t)
|
||||
if r is not None:
|
||||
types[t] = r
|
||||
#else:
|
||||
#print("{} were not found for timestamp {}, using {} instead.".format(t, time, types[t]))
|
||||
|
||||
return types
|
||||
|
||||
def get_timing_point(self, time, timing_type):
|
||||
"""
|
||||
Returns latest timing point by timestamp (Current)
|
||||
|
||||
time -- timestamp
|
||||
timing_type -- mpb, bmp or spm
|
||||
return -- self.timing_points object
|
||||
"""
|
||||
r = None
|
||||
try:
|
||||
for key in sorted(self.timing_points[timing_type].keys(), key=lambda k: k):
|
||||
if key <= time:
|
||||
r = self.timing_points[timing_type][key]
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return r
|
||||
|
||||
def get_object_count(self):
|
||||
"""
|
||||
Get the total hitobject count for the parsed beatmap (Normal hitobjects, sliders & sliderticks)
|
||||
|
||||
return -- total hitobjects for parsed beatmap
|
||||
"""
|
||||
cdef int count = 0
|
||||
for hitobject in self.hitobjects:
|
||||
count += hitobject.get_points()
|
||||
return count
|
166
pp/catch_the_pp/osu_parser/curves.pyx
Normal file
166
pp/catch_the_pp/osu_parser/curves.pyx
Normal file
@@ -0,0 +1,166 @@
|
||||
import math
|
||||
from .. import constants
|
||||
from . import mathhelper
|
||||
|
||||
class Linear(object): #Because it made sense at the time...
|
||||
def __init__(self, points):
|
||||
self.pos = points
|
||||
|
||||
cdef class Bezier(object):
|
||||
cdef public list points, pos
|
||||
cdef public int order
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.order = len(self.points)
|
||||
self.pos = []
|
||||
self.calc_points()
|
||||
|
||||
cpdef calc_points(self):
|
||||
if len(self.pos) != 0: #This should never happen but since im working on this I want to warn myself if I fuck up
|
||||
raise Exception("Bezier was calculated twice!")
|
||||
|
||||
cdef list sub_points = []
|
||||
for i in range(len(self.points)):
|
||||
if i == len(self.points) - 1:
|
||||
sub_points.append(self.points[i])
|
||||
self.bezier(sub_points)
|
||||
sub_points.clear()
|
||||
elif len(sub_points) > 1 and self.points[i] == sub_points[-1]:
|
||||
self.bezier(sub_points)
|
||||
sub_points.clear()
|
||||
|
||||
sub_points.append(self.points[i])
|
||||
|
||||
cpdef bezier(self, list points):
|
||||
cdef int order = len(points)
|
||||
cdef float step = 0.25 / constants.SLIDER_QUALITY / order #Normaly 0.0025
|
||||
cdef float i = 0
|
||||
cdef int n = order - 1
|
||||
|
||||
cdef float x, y
|
||||
cdef int p
|
||||
|
||||
while i < 1 + step:
|
||||
x = 0
|
||||
y = 0
|
||||
|
||||
for p in range(n + 1):
|
||||
a = mathhelper.cpn(p, n) * ((1 - i) ** (n - p)) * (i ** p)
|
||||
x += a * points[p].x
|
||||
y += a * points[p].y
|
||||
|
||||
point = mathhelper.Vec2(x, y)
|
||||
self.pos.append(point)
|
||||
i += step
|
||||
|
||||
def point_at_distance(self, length):
|
||||
return {
|
||||
0: False,
|
||||
1: self.points[0],
|
||||
}.get(self.order, self.rec(length))
|
||||
|
||||
def rec(self, length):
|
||||
return mathhelper.point_at_distance(self.pos, length)
|
||||
|
||||
cdef class Catmull(object): #Yes... I cry deep down on the inside aswell
|
||||
cdef public list points, pos
|
||||
cdef public int order
|
||||
cdef public float step
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.order = len(points)
|
||||
self.step = 2.5 / constants.SLIDER_QUALITY #Normaly 0.025
|
||||
self.pos = []
|
||||
self.calc_points()
|
||||
|
||||
cpdef calc_points(self):
|
||||
if len(self.pos) != 0: #This should never happen but since im working on this I want to warn myself if I fuck up
|
||||
raise Exception("Catmull was calculated twice!")
|
||||
|
||||
cdef int x
|
||||
cdef float t
|
||||
cdef object v1, v2, v3
|
||||
for x in range(self.order - 1):
|
||||
t = 0
|
||||
while t < self.step + 1:
|
||||
if x >= 1:
|
||||
v1 = self.points[x - 1]
|
||||
else:
|
||||
v1 = self.points[x]
|
||||
|
||||
v2 = self.points[x]
|
||||
|
||||
if x + 1 < self.order:
|
||||
v3 = self.points[x + 1]
|
||||
else:
|
||||
v3 = v2.calc(1, v2.calc(-1, v1))
|
||||
|
||||
if x + 2 < self.order:
|
||||
v4 = self.points[x + 2]
|
||||
else:
|
||||
v4 = v3.calc(1, v3.calc(-1, v2))
|
||||
|
||||
point = get_point([v1, v2, v3, v4], t)
|
||||
self.pos.append(point)
|
||||
t += self.step
|
||||
|
||||
def point_at_distance(self, length):
|
||||
return {
|
||||
0: False,
|
||||
1: self.points[0],
|
||||
}.get(self.order, self.rec(length))
|
||||
|
||||
def rec(self, length):
|
||||
return mathhelper.point_at_distance(self.pos, length)
|
||||
|
||||
cdef class Perfect(object):
|
||||
cdef public list points
|
||||
cdef float cx, cy
|
||||
cdef float radius
|
||||
|
||||
def __init__(self, points):
|
||||
self.points = points
|
||||
self.cx = 0
|
||||
self.cy = 0
|
||||
self.radius = 0
|
||||
self.setup_path()
|
||||
|
||||
def setup_path(self):
|
||||
self.cx, self.cy, self.radius = get_circum_circle(self.points)
|
||||
if is_left(self.points):
|
||||
self.radius *= -1
|
||||
|
||||
cpdef point_at_distance(self, float length):
|
||||
cdef float radians = length / self.radius
|
||||
return rotate(self.cx, self.cy, self.points[0], radians)
|
||||
|
||||
cpdef object get_point(object p, float length):
|
||||
cdef float x = mathhelper.catmull([o.x for o in p], length)
|
||||
cdef float y = mathhelper.catmull([o.y for o in p], length)
|
||||
return mathhelper.Vec2(x, y)
|
||||
|
||||
cpdef tuple get_circum_circle(list p):
|
||||
cdef float d = 2 * (p[0].x * (p[1].y - p[2].y) + p[1].x * (p[2].y - p[0].y) + p[2].x * (p[0].y - p[1].y))
|
||||
|
||||
if d == 0:
|
||||
raise Exception("Invalid circle! Unable to chose angle.")
|
||||
|
||||
cdef float ux = ((pow(p[0].x, 2) + pow(p[0].y, 2)) * (p[1].y - p[2].y) + (pow(p[1].x, 2) + pow(p[1].y, 2)) * (p[2].y - p[0].y) + (pow(p[2].x, 2) + pow(p[2].y, 2)) * (p[0].y - p[1].y)) / d
|
||||
cdef float uy = ((pow(p[0].x, 2) + pow(p[0].y, 2)) * (p[2].x - p[1].x) + (pow(p[1].x, 2) + pow(p[1].y, 2)) * (p[0].x - p[2].x) + (pow(p[2].x, 2) + pow(p[2].y, 2)) * (p[1].x - p[0].x)) / d
|
||||
|
||||
cdef float px = ux - p[0].x
|
||||
cdef float py = uy - p[0].y
|
||||
cdef float r = pow(pow(px, 2) + pow(py, 2), 0.5)
|
||||
|
||||
return ux, uy, r
|
||||
|
||||
cpdef float is_left(object p):
|
||||
return ((p[1].x - p[0].x) * (p[2].y - p[0].y) - (p[1].y - p[0].y) * (p[2].x - p[0].x)) < 0
|
||||
|
||||
cpdef object rotate(float cx, float cy, object p, float radians):
|
||||
cdef float cos = math.cos(radians)
|
||||
cdef float sin = math.sin(radians)
|
||||
|
||||
return mathhelper.Vec2((cos * (p.x - cx)) - (sin * (p.y - cy)) + cx, (sin * (p.x - cx)) + (cos * (p.y - cy)) + cy)
|
169
pp/catch_the_pp/osu_parser/hitobject.pyx
Normal file
169
pp/catch_the_pp/osu_parser/hitobject.pyx
Normal file
@@ -0,0 +1,169 @@
|
||||
import copy
|
||||
from . import mathhelper
|
||||
from . import curves
|
||||
|
||||
cdef class SliderTick:
|
||||
cdef public float x, y, time
|
||||
|
||||
def __init__(self, x, y, time):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.time = time
|
||||
|
||||
cdef class HitObject(object):
|
||||
cdef public float x, y, time, end_time, pixel_length, tick_distance, duration
|
||||
cdef public int type, repeat
|
||||
cdef public str slider_type
|
||||
cdef public list curve_points, ticks, end_ticks, path
|
||||
cdef public dict timing_point
|
||||
cdef public object difficulty, end
|
||||
|
||||
def __init__(self, x, y, time, object_type, slider_type = None, curve_points = None, repeat = 1, pixel_length = 0, timing_point = None, difficulty = None, tick_distance = 1):
|
||||
"""
|
||||
HitObject params for normal hitobject and sliders
|
||||
|
||||
x -- x position
|
||||
y -- y position
|
||||
time -- timestamp
|
||||
object_type -- type of object (bitmask)
|
||||
|
||||
[+] IF SLIDER
|
||||
slider_type -- type of slider (L, P, B, C)
|
||||
curve_points -- points in the curve path
|
||||
repeat -- amount of repeats for the slider (+1)
|
||||
pixel_length -- length of the slider
|
||||
timing_point -- ref of current timing point for the timestamp
|
||||
difficulty -- ref of beatmap difficulty
|
||||
tick_distance -- distance betwin each slidertick
|
||||
"""
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.time = time
|
||||
self.end_time = 0
|
||||
self.type = object_type
|
||||
|
||||
#isSlider?
|
||||
if 2 & self.type:
|
||||
self.slider_type = slider_type
|
||||
self.curve_points = [mathhelper.Vec2(self.x, self.y)] + curve_points
|
||||
self.repeat = repeat
|
||||
self.pixel_length = pixel_length
|
||||
|
||||
#For slider tick calculations
|
||||
self.timing_point = timing_point
|
||||
self.difficulty = difficulty
|
||||
self.tick_distance = tick_distance
|
||||
self.duration = (int(self.timing_point["raw_bpm"]) * (pixel_length / (self.difficulty["SliderMultiplier"] * self.timing_point["spm"])) / 100) * self.repeat
|
||||
|
||||
self.ticks = []
|
||||
self.end_ticks = []
|
||||
self.path = []
|
||||
self.end = None
|
||||
|
||||
self.calc_slider()
|
||||
|
||||
def calc_slider(self, calc_path = False):
|
||||
#Fix broken objects
|
||||
if self.slider_type == "P" and len(self.curve_points) > 3:
|
||||
self.slider_type = "B"
|
||||
elif len(self.curve_points) == 2:
|
||||
self.slider_type = "L"
|
||||
|
||||
#Make curve
|
||||
if self.slider_type == "P": #Perfect
|
||||
try:
|
||||
curve = curves.Perfect(self.curve_points)
|
||||
except:
|
||||
curve = curves.Bezier(self.curve_points)
|
||||
self.slider_type = "B"
|
||||
elif self.slider_type == "B": #Bezier
|
||||
curve = curves.Bezier(self.curve_points)
|
||||
elif self.slider_type == "C": #Catmull
|
||||
curve = curves.Catmull(self.curve_points)
|
||||
|
||||
#Quickest to skip this
|
||||
if calc_path: #Make path if requested (For drawing visual for testing)
|
||||
if self.slider_type == "L": #Linear
|
||||
self.path = curves.Linear(self.curve_points).pos
|
||||
elif self.slider_type == "P": #Perfect
|
||||
self.path = []
|
||||
l = 0
|
||||
step = 5
|
||||
while l <= self.pixel_length:
|
||||
self.path.append(curve.point_at_distance(l))
|
||||
l += step
|
||||
elif self.slider_type == "B": #Bezier
|
||||
self.path = curve.pos
|
||||
elif self.slider_type == "C": #Catmull
|
||||
self.path = curve.pos
|
||||
else:
|
||||
raise Exception("Slidertype not supported! ({})".format(self.slider_type))
|
||||
|
||||
#Set slider ticks
|
||||
current_distance = self.tick_distance
|
||||
time_add = self.duration * (self.tick_distance / (self.pixel_length * self.repeat))
|
||||
|
||||
while current_distance < self.pixel_length - self.tick_distance / 8:
|
||||
if self.slider_type == "L": #Linear
|
||||
point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], current_distance)
|
||||
else: #Perfect, Bezier & Catmull uses the same function
|
||||
point = curve.point_at_distance(current_distance)
|
||||
|
||||
self.ticks.append(SliderTick(point.x, point.y, self.time + time_add * (len(self.ticks) + 1)))
|
||||
current_distance += self.tick_distance
|
||||
|
||||
#Adds slider_ends / repeat_points
|
||||
repeat_id = 1
|
||||
repeat_bonus_ticks = []
|
||||
while repeat_id < self.repeat:
|
||||
dist = (1 & repeat_id) * self.pixel_length
|
||||
time_offset = (self.duration / self.repeat) * repeat_id
|
||||
|
||||
if self.slider_type == "L": #Linear
|
||||
point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], dist)
|
||||
else: #Perfect, Bezier & Catmull uses the same function
|
||||
point = curve.point_at_distance(dist)
|
||||
|
||||
self.end_ticks.append(SliderTick(point.x, point.y, self.time + time_offset))
|
||||
|
||||
#Adds the ticks that already exists on the slider back (but reversed)
|
||||
repeat_ticks = copy.deepcopy(self.ticks)
|
||||
|
||||
if 1 & repeat_id: #We have to reverse the timing normalizer
|
||||
repeat_ticks = list(reversed(repeat_ticks))
|
||||
normalize_time_value = self.time + (self.duration / self.repeat)
|
||||
else:
|
||||
normalize_time_value = self.time
|
||||
|
||||
#Correct timing
|
||||
for tick in repeat_ticks:
|
||||
tick.time = self.time + time_offset + abs(tick.time - normalize_time_value)
|
||||
|
||||
repeat_bonus_ticks += repeat_ticks
|
||||
|
||||
repeat_id += 1
|
||||
|
||||
self.ticks += repeat_bonus_ticks
|
||||
|
||||
#Add endpoint for slider
|
||||
dist_end = (1 & self.repeat) * self.pixel_length
|
||||
if self.slider_type == "L": #Linear
|
||||
point = mathhelper.point_on_line(self.curve_points[0], self.curve_points[1], dist_end)
|
||||
else: #Perfect, Bezier & Catmull uses the same function
|
||||
point = curve.point_at_distance(dist_end)
|
||||
|
||||
self.end_ticks.append(SliderTick(point.x, point.y, self.time + self.duration))
|
||||
|
||||
def get_combo(self):
|
||||
"""
|
||||
Returns the combo given by this object
|
||||
1 if normal hitobject, 2+ if slider (adds sliderticks)
|
||||
"""
|
||||
if 2 & self.type: #Slider
|
||||
val = 1 #Start of the slider
|
||||
val += len(self.ticks) #The amount of sliderticks
|
||||
val += self.repeat #Reverse slider
|
||||
else: #Normal
|
||||
val = 1 #Itself...
|
||||
|
||||
return val
|
124
pp/catch_the_pp/osu_parser/mathhelper.pyx
Normal file
124
pp/catch_the_pp/osu_parser/mathhelper.pyx
Normal file
@@ -0,0 +1,124 @@
|
||||
import math
|
||||
|
||||
cpdef float clamp(float value, float mn, float mx):
|
||||
return min(max(mn, value), mx)
|
||||
|
||||
cpdef sign(float value):
|
||||
if value == 0:
|
||||
return 0
|
||||
elif value > 0:
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
cpdef cpn(int p, int n):
|
||||
if p < 0 or p > n:
|
||||
return 0
|
||||
p = min(p, n - p)
|
||||
out = 1
|
||||
for i in range(1, p + 1):
|
||||
out = out * (n - p + i) / i
|
||||
|
||||
return out
|
||||
|
||||
cpdef float catmull(p, t): # WARNING: Worst math formula incomming
|
||||
return 0.5 * (
|
||||
(2 * p[1]) +
|
||||
(-p[0] + p[2]) * t +
|
||||
(2 * p[0] - 5 * p[1] + 4 * p[2] - p[3]) * (t ** 2) +
|
||||
(-p[0] + 3 * p[1] - 3 * p[2] + p[3]) * (t ** 3))
|
||||
|
||||
cpdef Vec2 point_on_line(Vec2 p0, Vec2 p1, float length):
|
||||
cdef float full_length = (((p1.x - p0.x) ** 2) + ((p1.y - p0.y) ** 2)) ** 0.5
|
||||
cdef float n = full_length - length
|
||||
|
||||
if full_length == 0: #Fix for something that seems unknown... (We warn if this happens)
|
||||
full_length = 1
|
||||
|
||||
cdef float x = (n * p0.x + length * p1.x) / full_length
|
||||
cdef float y = (n * p0.y + length * p1.y) / full_length
|
||||
|
||||
return Vec2(x, y)
|
||||
|
||||
cpdef float angle_from_points(Vec2 p0, Vec2 p1):
|
||||
return math.atan2(p1.y - p0.y, p1.x - p0.x)
|
||||
|
||||
cpdef float distance_from_points(array):
|
||||
cdef float distance = 0
|
||||
cdef int i
|
||||
|
||||
for i in range(1, len(array)):
|
||||
distance += array[i].distance(array[i - 1])
|
||||
|
||||
return distance
|
||||
|
||||
cpdef Vec2 cart_from_pol(r, t):
|
||||
cdef float x = (r * math.cos(t))
|
||||
cdef float y = (r * math.sin(t))
|
||||
|
||||
return Vec2(x, y)
|
||||
|
||||
cpdef point_at_distance(array, float distance): #TODO: Optimize...
|
||||
cdef int i = 0
|
||||
cdef float x, y, current_distance = 0, new_distance = 0, angle
|
||||
cdef Vec2 coord, cart
|
||||
|
||||
if len(array) < 2:
|
||||
return Vec2(0, 0)
|
||||
|
||||
if distance == 0:
|
||||
return array[0]
|
||||
|
||||
if distance_from_points(array) <= distance:
|
||||
return array[len(array) - 1]
|
||||
|
||||
for i in range(len(array) - 2):
|
||||
x = (array[i].x - array[i + 1].x)
|
||||
y = (array[i].y - array[i + 1].y)
|
||||
|
||||
new_distance = math.sqrt(x * x + y * y)
|
||||
current_distance += new_distance
|
||||
|
||||
if distance <= current_distance:
|
||||
break
|
||||
|
||||
current_distance -= new_distance
|
||||
|
||||
if distance == current_distance:
|
||||
return array[i]
|
||||
else:
|
||||
angle = angle_from_points(array[i], array[i + 1])
|
||||
cart = cart_from_pol((distance - current_distance), angle)
|
||||
|
||||
if array[i].x > array[i + 1].x:
|
||||
coord = Vec2((array[i].x - cart.x), (array[i].y - cart.y))
|
||||
else:
|
||||
coord = Vec2((array[i].x + cart.y), (array[i].y + cart.y))
|
||||
return coord
|
||||
|
||||
cdef class Vec2(object):
|
||||
cdef public float x
|
||||
cdef public float y
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __richcmp__(x, y, op):
|
||||
if op == 2:#Py_EQ
|
||||
return x.__is_equal(y)
|
||||
else:#Py_NE
|
||||
return not x.__is_equal(y)
|
||||
|
||||
def __is_equal(self, other):
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
cpdef float distance(Vec2 self, Vec2 other):
|
||||
cdef float x = self.x - other.x
|
||||
cdef float y = self.y - other.y
|
||||
return (x*x + y*y) ** 0.5 #sqrt, lol
|
||||
|
||||
cpdef Vec2 calc(Vec2 self, float value, Vec2 other): #I dont know what to call this function yet
|
||||
cdef float x = self.x + value * other.x
|
||||
cdef float y = self.y + value * other.y
|
||||
return Vec2(x, y)
|
Reference in New Issue
Block a user