From 723e3c4845033f5fdc74692d289a7c7fff0df883 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 10 Oct 2015 01:11:37 -0700 Subject: [PATCH] else projectile parabolas --- ProjectileParabola/README.md | 6 + ProjectileParabola/projectileparabola.py | 171 ++++++++++++++++++++ ProjectileParabola/projectiles.png | 3 + ProjectileParabola/the_butterfly_effect.png | 3 + 4 files changed, 183 insertions(+) create mode 100644 ProjectileParabola/README.md create mode 100644 ProjectileParabola/projectileparabola.py create mode 100644 ProjectileParabola/projectiles.png create mode 100644 ProjectileParabola/the_butterfly_effect.png diff --git a/ProjectileParabola/README.md b/ProjectileParabola/README.md new file mode 100644 index 0000000..4598975 --- /dev/null +++ b/ProjectileParabola/README.md @@ -0,0 +1,6 @@ +Projectile Parabolas +==================== + +Requires `pip install pillow` + +I'd like to thank the academy and [Wikipedia](https://en.wikipedia.org/wiki/Projectile_motion) \ No newline at end of file diff --git a/ProjectileParabola/projectileparabola.py b/ProjectileParabola/projectileparabola.py new file mode 100644 index 0000000..a4af9e5 --- /dev/null +++ b/ProjectileParabola/projectileparabola.py @@ -0,0 +1,171 @@ +import math +import random +import time +from PIL import Image, ImageDraw + +# Do not touch +smallest_x = None +largest_x = None +largest_y = None +label_depths = {} +############## + +def quadratic_formula(a, b, c): + # x = (-b +/- sqrt(b**2 - 4*a*c)) / 2a + discriminant = (b ** 2) - (4 * a * c) + discriminant = math.sqrt(discriminant) + b *= -1 + possible = (b + discriminant, b - discriminant) + possible = [x / (2*a) for x in possible] + return possible + +def time_to_known_distance(velocity, distance, acceleration): + # distance = (0.5 * acceleration * (time**2)) + (velocity * time) + # (0.5 * acceleration * (time**2)) + (velocity * time) - (distance) = 0 + possible = quadratic_formula(a=0.5 * acceleration, b=velocity, c=-distance) + if min(possible) < 0: + return max(possible) + else: + return min(possible) + +def make_throw(starting_x, starting_y, starting_velocity, thrown_angle): + global smallest_x + global largest_x + global largest_y + if thrown_angle in range(1, 179, 1) or thrown_angle in range(-181, -359, -1): + # A positive velocity implies a downward motion, so please reverse this. + starting_velocity *= -1 + + rads = math.radians(thrown_angle) + sin = math.sin(rads) + cos = math.cos(rads) + tan = math.tan(rads) + + throw = {'angle': thrown_angle} + throw['horizontal_component'] = starting_velocity * cos + throw['vertical_component'] = starting_velocity * sin + throw['hang_time'] = time_to_known_distance(throw['vertical_component'], starting_y, acceleration=9.8) + throw['distance'] = abs(throw['hang_time'] * throw['horizontal_component']) + + def parabola(x): + # 100% credit goes to wikipedia authors + # https://en.wikipedia.org/wiki/Projectile_motion#Parabolic_equation + left = tan * x + numerator = 9.8 * (x ** 2) + denominator = 2 * (starting_velocity ** 2) * (math.cos(rads) ** 2) + y = left - (numerator / denominator) + return y + + throw['parabola'] = parabola + throw['parabola_points'] = [] + #print(throw['vertical_component'], throw['hang_time']) + + y = 1 + x = starting_x + backwards = (thrown_angle in range(90, 270)) or (thrown_angle in range(-90, -270)) + while y > 0: + y = throw['parabola'](x) + starting_y + if y < 0: + # To keep a smooth floor of 0, rescale the active x so that + # it looks like it continues in the right direction underground. + previous = throw['parabola_points'][-1] + would_be_length = previous[1] - y + length_scale = previous[1] / would_be_length + x = previous[0] + ((x - previous[0]) * length_scale) + y = 0 + + if (smallest_x is None or x < smallest_x): smallest_x = math.floor(x) + if (largest_x is None or x > largest_x): largest_x = math.ceil(x) + if (largest_y is None or y > largest_y): largest_y = math.ceil(y) + throw['parabola_points'].append([int(x), int(y)]) + if backwards: + x -= PLOT_STEP_X + else: + x += PLOT_STEP_X + return throw + +def get_label_depth(x): + xx = x + LABEL_PAD_HORIZONTAL + for label in label_depths: + #print(label) + if any(v in range(*label) for v in (x, xx)): + label_depths[label] += 1 + return label_depths[label] + label_depths[(x, x+LABEL_PAD_HORIZONTAL)] = 0 + return 0 + + + + + + + +SMART_LABEL_STACK = True +LABEL_PAD_HORIZONTAL = 80 +LABEL_PAD_VERTICAL = 15 +PLOT_PAD_LEFT = 5 + +STARTING_X = 0 +STARTING_Y = 700 +STARTING_VELOCITY = 100 +# Larger step = fewer data points = quicker and less memory +PLOT_STEP_X = 5 + +throws = [] +angle_increment = 15 +angles = [180, 0] +angles = [x * angle_increment for x in range(int(90 / angle_increment))] +#angles += [x+90 for x in angles] +for thrown_angle in (angles): + t = make_throw(STARTING_X, STARTING_Y, STARTING_VELOCITY, thrown_angle) + if len(t['parabola_points']) < 2: + continue + throws.append(t) +throws.sort(key=lambda t: t['distance'], reverse=True) + +# Add some padding on the right edge because labels +# are left-justified and start from the end of each arc +image_width = (largest_x-smallest_x)+LABEL_PAD_HORIZONTAL +image_height = largest_y+(LABEL_PAD_VERTICAL * len(throws)) + +i = Image.new('RGBA', (image_width, image_height)) +d = ImageDraw.Draw(i) + +for (index, t) in enumerate(throws): + # lets avoid making any solid white lines. + r = random.randint(0, 200) + g = random.randint(0, 200) + b = random.randint(0, 200) + color = (r, g, b, 255) + #print(t['angle'], t['distance']) + for pointindex in range(len(t['parabola_points']) - 1): + point_a = t['parabola_points'][pointindex][:] + point_b = t['parabola_points'][pointindex + 1][:] + point_a[0] = (round(point_a[0])) + abs(smallest_x) + PLOT_PAD_LEFT + point_b[0] = (round(point_b[0])) + abs(smallest_x) + PLOT_PAD_LEFT + point_a[1] = (largest_y - round(point_a[1])) + point_b[1] = (largest_y - round(point_b[1])) + try: + # this ensures a solid, smooth line between each of the plotted points. + d.line(point_a + point_b, fill=color) + except: + print('broken:', point) + + # Now that the loop has ended, point_b is the point on the horizon. + label_x = point_b[0] + if SMART_LABEL_STACK: + label_y = largest_y + (LABEL_PAD_VERTICAL * get_label_depth(label_x)) + else: + label_y = largest_y + (LABEL_PAD_VERTICAL * index) + label_text = '%d degrees' % t['angle'] + d.text((label_x, label_y), label_text, fill=color) +d.line((0, largest_y, i.size[0], largest_y), fill=(0, 0, 0, 255)) + +if SMART_LABEL_STACK: + # Earlier we judged the image height by how many tags we would have to add + # if they were stacked all on top of each other. We can crop that excess now. + deepest = max(x[1] for x in label_depths.items()) + 1 + required_image_height = largest_y + (LABEL_PAD_VERTICAL) * deepest + i = i.crop((0, 0, image_width, required_image_height)) +i.save('projectiles.png') +print('saved.') \ No newline at end of file diff --git a/ProjectileParabola/projectiles.png b/ProjectileParabola/projectiles.png new file mode 100644 index 0000000..3f27875 --- /dev/null +++ b/ProjectileParabola/projectiles.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edf0c1089d54756b3fff9bcb39c8433b071196e3625ec60469fc65b63b5be50a +size 28876 diff --git a/ProjectileParabola/the_butterfly_effect.png b/ProjectileParabola/the_butterfly_effect.png new file mode 100644 index 0000000..6e30104 --- /dev/null +++ b/ProjectileParabola/the_butterfly_effect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94151c367c99aa85c2fe159af9904745ce0a24dce36b4e7011efd959c646dc2c +size 15973