pythonmathrandom2dacceleration

How to properly add gradually increasing/decreasing space between objects?


I've trying to implement transition from an amount of space to another which is similar to acceleration and deceleration, except i failed and the only thing that i got from this was this infinite stack of mess, here is a screenshot showing this in action:

enter image description here

you can see a very black circle here, which are in reality something like 100 or 200 circles stacked on top of each other

and i reached this result using this piece of code:

def Place_circles(curve, circle_space, cs, draw=True, screen=None):
    curve_acceleration = []
    if type(curve) == tuple:
        curve_acceleration = curve[1][0]
        curve_intensity = curve[1][1]
        curve = curve[0]
        #print(curve_intensity)
        #print(curve_acceleration)
    Circle_list = []
    idx = [0,0]
    for c in reversed(range(0,len(curve))):
        for p in reversed(range(0,len(curve[c]))):
            user_dist = circle_space[curve_intensity[c]] + curve_acceleration[c] * p
            dist = math.sqrt(math.pow(curve[c][p][0] - curve[idx[0]][idx[1]][0],2)+math.pow(curve [c][p][1] - curve[idx[0]][idx[1]][1],2))
            if dist > user_dist:
                idx = [c,p]
                Circle_list.append(circles.circles(round(curve[c][p][0]), round(curve[c][p][1]), cs, draw, screen))

This place circles depending on the intensity (a number between 0 and 2, random) of the current curve, which equal to an amount of space (let's say between 20 and 30 here, 20 being index 0, 30 being index 2 and a number between these 2 being index 1).

This create the stack you see above and isn't what i want, i also came to the conclusion that i cannot use acceleration since the amount of time to move between 2 points depend on the amount of circles i need to click on, knowing that there are multiple circles between each points, but not being able to determine how many lead to me being unable to the the classic acceleration formula.

So I'm running out of options here and ideas on how to transition from an amount of space to another. any idea?

PS: i scrapped the idea above and switched back to my master branch but the code for this is still available in the branch i created here https://github.com/Mrcubix/Osu-StreamGenerator/tree/acceleration . So now I'm back with my normal code that don't possess acceleration or deceleration.

TL:DR i can't use acceleration since i don't know the amount of circles that are going to be placed between the 2 points and make the time of travel vary (i need for exemple to click circles at 180 bpm of one circle every 0.333s) so I'm looking for another way to generate gradually changing space.


Solution

  • First, i took my function that was generating the intensity for each curves in [0 ; 2] Then i scrapped the acceleration formula as it's unusable. Now i'm using a basic algorithm to determine the maximum amount of circles i can place on a curve.

    Now the way my script work is the following: i first generate a stream (multiple circles that need to be clicked at high bpm) this way i obtain the length of each curves (or segments) of the polyline. i generate an intensity for each curve using the following function:

    def generate_intensity(Circle_list: list = None, circle_space: int = None, Args: list = None):
        curve_intensity = []
        if not Args or Args[0] == "NewProfile":
            prompt = True
            while prompt:
                max_duration_intensity = input("Choose the maximum amount of curve the change in intensity will occur for: ")
                if max_duration_intensity.isdigit():
                    max_duration_intensity = int(max_duration_intensity)
                    prompt = False
            prompt = True
            while prompt:
                intensity_change_odds = input("Choose the odds of occurence for changes in intensity (1-100): ")
                if intensity_change_odds.isdigit():
                    intensity_change_odds = int(intensity_change_odds)
                    if 0 < intensity_change_odds <= 100:
                        prompt = False      
            prompt = True
            while prompt:
                min_intensity = input("Choose the lowest amount of spacing a circle will have: ")
                if min_intensity.isdigit():
                    min_intensity = float(min_intensity)
                    if min_intensity < circle_space:
                        prompt = False 
            prompt = True
            while prompt:
                max_intensity = input("Choose the highest amount of spacing a circle will have: ")
                if max_intensity.isdigit():
                    max_intensity = float(max_intensity)
                    if max_intensity > circle_space:
                        prompt = False
            prompt = True
            if Args:
                if Args[0] == "NewProfile":
                    return [max_duration_intensity, intensity_change_odds,  min_intensity, max_intensity]
        elif Args[0] == "GenMap":
            max_duration_intensity = Args[1]  
            intensity_change_odds = Args[2]
            min_intensity = Args[3]
            max_intensity = Args[4]
        circle_space = ([min_intensity, circle_space, max_intensity] if not Args else [Args[0][3],circle_space,Args[0][4]])
        count = 0
        for idx, i in enumerate(Circle_list):
            if idx == len(Circle_list) - 1:
                if random.randint(0,100) < intensity_change_odds:
                    if random.randint(0,100) > 50:
                        curve_intensity.append(2)
                    else:
                        curve_intensity.append(0)
                else:
                     curve_intensity.append(1)
            if random.randint(0,100) < intensity_change_odds:
                    if random.randint(0,100) > 50:
                        curve_intensity.append(2)
                        count += 1
                    else:
                        curve_intensity.append(0)
                        count += 1
            else:
                if curve_intensity:
                    if curve_intensity[-1] == 2 and not count+1 > max_duration_intensity:
                        curve_intensity.append(2)
                        count += 1
                        continue
                    elif curve_intensity[-1] == 0 and not count+1 > max_duration_intensity:
                        curve_intensity.append(0)
                        count += 1
                        continue
                    elif count+1 > 2:
                        curve_intensity.append(1)
                        count = 0
                        continue
                    else:
                        curve_intensity.append(1)   
                else:
                    curve_intensity.append(1)
        curve_intensity.reverse()
        if curve_intensity.count(curve_intensity[0]) == len(curve_intensity):
            print("Intensity didn't change")
            return circle_space[1]
        print("\n")
        return [circle_space, curve_intensity]
    

    with this, i obtain 2 list, one with the spacing i specified, and the second one is the list of randomly generated intensity. from there i call another function taking into argument the polyline, the previously specified spacings and the generated intensity:

    def acceleration_algorithm(polyline, circle_space, curve_intensity):
        new_circle_spacing = []
        for idx in range(len(polyline)): #repeat 4 times
            spacing = []
            Length = 0
            best_spacing = 0
            for p_idx in range(len(polyline[idx])-1): #repeat 1000 times / p_idx in [0 ; 1000]
                # Create multiple list containing spacing going from circle_space[curve_intensity[idx-1]] to circle_space[curve_intensity[idx]]
                spacing.append(np.linspace(circle_space[curve_intensity[idx]],circle_space[curve_intensity[idx+1]], p_idx).tolist())
                # Sum distance to find length of curve
                Length += abs(math.sqrt((polyline[idx][p_idx+1][0] - polyline[idx][p_idx][0]) ** 2 + (polyline [idx][p_idx+1][1] - polyline[idx][p_idx][1]) ** 2))
            for s in range(len(spacing)): # probably has 1000 list in 1 list
                length_left = Length # Make sure to reset length for each iteration
                for dist in spacing[s]: # substract the specified int in spacing[s]
                    length_left -= dist 
                if length_left > 0: 
                    best_spacing = s
                else: # Since length < 0, use previous working index (best_spacing), could also jsut do `s-1`
                    if spacing[best_spacing] == []:
                        new_circle_spacing.append([circle_space[1]])
                        continue
                    new_circle_spacing.append(spacing[best_spacing])
                    break
        return new_circle_spacing
    

    with this, i obtain a list with the space between each circles that are going to be placed, from there, i can Call Place_circles() again, and obtain the new stream:

    def Place_circles(polyline, circle_space, cs, DoDrawCircle=True, surface=None):
        Circle_list = []
        curve = []
        next_circle_space = None
        dist = 0
        for c in reversed(range(0, len(polyline))):
            curve = []
            if type(circle_space) == list:
                iter_circle_space = iter(circle_space[c])
                next_circle_space = next(iter_circle_space, circle_space[c][-1])     
            for p in reversed(range(len(polyline[c])-1)):
                dist += math.sqrt((polyline[c][p+1][0] - polyline[c][p][0]) ** 2 + (polyline [c][p+1][1] - polyline[c][p][1]) ** 2)
                if dist > (circle_space if type(circle_space) == int else next_circle_space):
                    dist = 0
                    curve.append(circles.circles(round(polyline[c][p][0]), round(polyline[c][p][1]), cs, DoDrawCircle, surface))
                    if type(circle_space) == list:
                        next_circle_space = next(iter_circle_space, circle_space[c][-1])
            Circle_list.append(curve)
        return Circle_list
    

    the result is a stream with varying space between circles (so accelerating or decelerating), the only issue left to be fixed is pygame not updating the screen with the new set of circle after i call Place_circles(), but that's an issue i'm either going to try to fix myself or ask in another post

    the final code for this feature can be found on my repo : https://github.com/Mrcubix/Osu-StreamGenerator/tree/Acceleration_v02