diff --git a/README.md b/README.md index f9773db5..5fbd7c0e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,13 @@ Hi! I'm [Alexandre Villares](https://abav.lugaralgum.com), let's see if I can ma If you enjoy this, be a [patreon](https://patreon.com/arteprog) or make a donation [here](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HCGAKACDMVNV2) + +--- + +![s315](s315/s315.png) + +315: [GIF](s315/s315.gif) [code](https://github.com/villares/sketch-a-day/tree/master/s315) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)] + --- ![s314](s314/s314.png) diff --git a/s315/gif_exporter.py b/s315/gif_exporter.py new file mode 100644 index 00000000..9b48b504 --- /dev/null +++ b/s315/gif_exporter.py @@ -0,0 +1,42 @@ +""" +Alexandre B A Villares http://abav.lugaralgum.com - GPL v3 + +A helper for the Processing gifAnimation library https://github.com/extrapixel/gif-animation/tree/3.0 +Download from https://github.com/villares/processing-play/blob/master/export_GIF/unzip_and_move_to_libraries_GifAnimation.zip +This helper was inspired by an example by Art Simon https://github.com/APCSPrinciples/AnimatedGIF/ + +# add at the start of your sketch: + add_library('gifAnimation') + from gif_exporter import gif_export +# add at the end of draw(): + gif_export(GifMaker) +""" + +def gif_export(GifMaker, # gets a reference to the library + filename="exported", # .gif will be added + repeat=0, # 0 makes it an "endless" animation + quality=255, # quality range 0 - 255 + delay=200, # this is quick + frames=0, # 0 will stop on keyPressed or frameCount >= 100000 + finish=False): # force stop + global gifExporter + try: + gifExporter + except NameError: + gifExporter = GifMaker(this, filename + ".gif") + gifExporter.setRepeat(repeat) + gifExporter.setQuality(quality) + gifExporter.setDelay(delay) + + gifExporter.addFrame() + + if frames == 0: + if keyPressed or frameCount >= 100000: + finish = True + elif frameCount >= frames: + finish = True + + if finish: + gifExporter.finish() + print("gif saved") + exit() diff --git a/s315/line_geometry.py b/s315/line_geometry.py new file mode 100644 index 00000000..e8468e59 --- /dev/null +++ b/s315/line_geometry.py @@ -0,0 +1,146 @@ + +def create_points(non_intersecting=True): + background(200) + done = False + while not done: + poly_points = [PVector(random(BORDER, width - BORDER), + random(BORDER, height - BORDER) + ) + for _ in range(NUM)] + ed = edges(poly_points) + done = True + if non_intersecting: + for p1, p2 in ed[::-1]: + for p3, p4 in ed[2::]: + # test only non consecutive edges + if (p1 != p3) and (p2 != p3) and (p1 != p4): + if line_instersect(Line(p1, p2), Line(p3, p4)): + done = False + break + return poly_points + +def is_inside(x, y, poly_points): + min_, max_ = min_max(poly_points) + if x < min_.x or y < min_.y or x > max_.x or y > max_.y: + return False + + a = PVector(x, min_.y) + b = PVector(x, max_.y) + v_lines = inter_lines(Line(a, b), poly_points) + if not v_lines: + return False + + a = PVector(min_.x, y) + b = PVector(max_.x, y) + h_lines = inter_lines(Line(a, b), poly_points) + if not h_lines: + return False + + for v in v_lines: + for h in h_lines: + if line_instersect(v, h): + return True + + return False + + +def inter_lines(L, poly_points): + inter_points = [] + for p1, p2 in edges(poly_points): + inter = line_instersect(Line(p1, p2), L) + if inter: + inter_points.append(inter) + if not inter_points: + return [] + inter_lines = [] + if len(inter_points) > 1: + inter_points.sort() + pairs = zip(inter_points[::2], inter_points[1::2]) + for p1, p2 in pairs: + if p2: + inter_lines.append(Line(PVector(p1.x, p1.y), + PVector(p2.x, p2.y))) + return inter_lines + + +class Line(): + """ I should change this to a named tuple... """ + def __init__(self, p1, p2): + self.p1 = p1 + self.p2 = p2 + + def plot(self): + line(self.p1.x, self.p1.y, self.p2.x, self.p2.y) + + def lerp(self, other, t): + p1 = PVector.lerp(self.p1, other.p1, t) + p2 = PVector.lerp(self.p2, other.p2, t) + return Line(p1, p2) + +def line_instersect(line_a, line_b): + """ + code adapted from Bernardo Fontes + https://github.com/berinhard/sketches/ + """ + + x1, y1 = line_a.p1.x, line_a.p1.y + x2, y2 = line_a.p2.x, line_a.p2.y + x3, y3 = line_b.p1.x, line_b.p1.y + x4, y4 = line_b.p2.x, line_b.p2.y + + try: + uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); + uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1)); + except ZeroDivisionError: + return + + if not(0 <= uA <= 1 and 0 <= uB <= 1): + return + + x = line_a.p1.x + uA * (line_a.p2.x - line_a.p1.x) + y = line_a.p1.y + uA * (line_a.p2.y - line_a.p1.y) + + return PVector(x, y) + + +def edges(poly_points): + return pairwise(poly_points) + [(poly_points[-1], poly_points[0])] + +def pairwise(iterable): + import itertools + "s -> (s0,s1), (s1,s2), (s2, s3), ..." + a, b = itertools.tee(iterable) + next(b, None) + return zip(a, b) + +def min_max(points): + points = iter(points) + try: + p = points.next() + min_x, min_y = max_x, max_y = p.x, p.y + except StopIteration: + raise ValueError, "min_max requires at least one point" + for p in points: + if p.x < min_x: + min_x = p.x + elif p.x > max_x: + max_x = p.x + if p.y < min_y: + min_y = p.y + elif p.y > max_y: + max_y = p.y + return (PVector(min_x, min_y), + PVector(max_x, max_y)) + +def par_hatch(points, divisions, *sides): + vectors = [PVector(p.x, p.y) for p in points] + lines = [] + if not sides: sides = [0] + for s in sides: + a, b = vectors[-1 + s], vectors[+0 + s] + d, c = vectors[-2 + s], vectors[-3 + s] + for i in range(1, divisions): + s0 = PVector.lerp(a, b, i/float(divisions)) + s1 = PVector.lerp(d, c, i/float(divisions)) + lines.append(Line(s0, s1)) + return lines diff --git a/s315/s315.gif b/s315/s315.gif new file mode 100644 index 00000000..d552ab30 Binary files /dev/null and b/s315/s315.gif differ diff --git a/s315/s315.png b/s315/s315.png new file mode 100644 index 00000000..b1465d4f Binary files /dev/null and b/s315/s315.png differ diff --git a/s315/s315.pyde b/s315/s315.pyde new file mode 100644 index 00000000..5ae4de2f --- /dev/null +++ b/s315/s315.pyde @@ -0,0 +1,151 @@ +# Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day +SKETCH_NAME = "s315" # 20181109 +OUTPUT = ".png" +GRID_SIZE = 36 + +from line_geometry import Line +from line_geometry import par_hatch +# add_library('gifAnimation') +# from gif_exporter import gif_export + +# this rule was used to choose nodes that would move on +# earlier regular grid deformation sketches (now it's just a random selection) +rule = lambda x, y: random(40) < 20 + +def setup(): + strokeCap(SQUARE) + size(700, 700) + init_grid(GRID_SIZE) + background(200) + +def draw(): + f = frameCount/10. + t = cos(f)/2 + + for c in Cell.cells: + c.plot(t) + + # if frameCount % 10 == 0: + # print(t) + # for c in Cell.cells: + # c.plot(t) + + if 0 < f < TWO_PI: + pass + # gif_export(GifMaker, SKETCH_NAME) + else: + # for i in range(60): + # gif_export(GifMaker, SKETCH_NAME) + noLoop() + pass + +def init_grid(grid_size): + Cell.border = 100. + Cell.spacing = (width - Cell.border *2) / grid_size + Cell.cells = [] + for x in range(0, grid_size, 2): + for y in range(0, grid_size, 4): + new_cell = Cell(x, y) + Cell.cells.append(new_cell) + Cell.grid[x, y] = new_cell + + randomSeed(frameCount+2) + for x in range(-1, grid_size+1, 2): + for y in range(-1, grid_size+1, 2): + Node.grid0[x, y] = Node(x, y) # extrarir do dict + Node.grid1[x, y] = Node(x, y) # extrarir do dict + + + for c in Cell.cells: + c.update_vers() + +class Node(): + grid0 = dict() + grid1 = dict() + + def __init__(self, x, y): + self.ix = x + self.iy = y + self.px = Cell.border + Cell.spacing + x * Cell.spacing + self.py = Cell.border + Cell.spacing + y * Cell.spacing + if rule(x, y): + mx, my = width/2, height/2 + self.px += (self.px - mx) * 0.15 + self.py += (self.py - my) * 0.15 + self.x = self.px + self.y = self.py + +class Cell(): + cells = [] + grid = dict() + + def __init__(self, x, y): + self.ix = x + self.iy = y + self.px = Cell.border + Cell.spacing + x * Cell.spacing + self.py = Cell.border + Cell.spacing + y * Cell.spacing + self.vers = [] + self.num_hatches = int(random(3, 6))#int(random(5, 12)) + self.type_hatches = random(10) + + def plot(self, t): + L0, V0 = self.lines, self.vers + L1, V1 = self.lines_f, self.vers_f + + strokeWeight(1) + c = lerpColor(color(160, 0, 0), + color(0, 0, 160, 0), t) + stroke(c) + for l0, l1 in zip(L0, L1): + l = l0.lerp(l1, t) + l.plot() + beginShape() + noFill() + #strokeWeight(1) + #stroke(100, 0, 100) + for p0, p1 in zip(V0, V1): + vertex(lerp(p0.x, p1.x, t), lerp(p0.y, p1.y, t) ) + endShape(CLOSE) + + def update_vers(self): + self.v0 = Node.grid0.get((self.ix-1, self.iy-1)) + self.v1 = Node.grid0.get((self.ix-1, self.iy+1)) + self.v3 = Node.grid0.get((self.ix+1, self.iy-1)) + self.v2 = Node.grid0.get((self.ix+1, self.iy+1)) + self.vers = [self.v0, self.v1, self.v2, self.v3] + self.v0f = Node.grid1.get((self.ix-1, self.iy-1)) + self.v1f = Node.grid1.get((self.ix-1, self.iy+1)) + self.v3f = Node.grid1.get((self.ix+1, self.iy-1)) + self.v2f = Node.grid1.get((self.ix+1, self.iy+1)) + self.vers_f = [self.v0f, self.v1f, self.v2f, self.v3f] + self.hatch() + + def hatch(self): + self.lines = [] + self.lines_f = [] + n = self.num_hatches + r = self.type_hatches + if r > 2: + self.lines.extend(par_hatch(self.vers, n, 0)) + self.lines_f.extend(par_hatch(self.vers_f, n, 0)) + if r < 8: + self.lines.extend(par_hatch(self.vers, n, 1)) + self.lines_f.extend(par_hatch(self.vers_f, n, 1)) + +def keyPressed(): + if key == "n" or key == CODED: + init_grid(GRID_SIZE) + background(200) + loop() + #saveFrame("###.png") + if key == "s": saveFrame("###.png") + +# print text to add to the project's README.md +def settings(): + println( +""" +![{0}]({0}/{0}{2}) + +{1}: [code](https://github.com/villares/sketch-a-day/tree/master/{0}) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)] +""".format(SKETCH_NAME, SKETCH_NAME[1:], OUTPUT) + )