Tuesday, April 6, 2021

HOW-TO make a fish scale / mermaid / bun pattern

 


A friend of mine prompted me to make some "nautical" bookmarks, and that drifted off towards fish. I like fish - largely for their dynamic aggregate behaviors (my thesis had to do with flocking behaviors of dinosaurs, not that different from tropical fish, at least in my simulation). 

So, I had this idea of making an organic-looking fish scale design. It was super quick, and it turned out pretty nice, according to me and the people on Twitter who liked my post. This will be a high-level description of how to make something like the above pattern, along with tips on things to try changing.

Step 1: Generate a "grid" of points

If you were here last time for my flow field how-to, you'll recognize this step. I've got a library, or maybe a toolbox, of routines that I like to turn to when I make my plotter drawings. Robert Bridson wrote a short, but super useful (and readable!) paper about generating points that feel organic, but have nice distributions. Read it and come back, it's worth it, and it's short!

https://www.cct.lsu.edu/~fharhad/ganbatte/siggraph2007/CD2/content/sketches/0250.pdf

I'm going to trust you read the paper, but the takeaway is that you make a grid where each square in the grid is sized so that you know that no two points that you generate can be in a single grid cell. And then you can check a neighborhood in O(1) time, which is like the best time. 

Bridson's paper doesn't assume you're working in 2D, but I don't know about you, I do almost all of my drawings in 2D. Sometimes 3D, so it's good to think about a 3D grid, but a 2D grid gets the job done 99% of the time.

So, if you're ok with 2D, there's this variation, which is what I actually use for my plottering:

http://extremelearning.com.au/an-improved-version-of-bridsons-algorithm-n-for-poisson-disc-sampling/

This is faster, and a little bit more tightly packed, which seems good for my stuff. If you decide it's not for you, and you use Bridson Classic, that's cool. The improved version walks the edge of a disc around each point, generating points as tightly as it can, which you'd think might lead to some hints of repeating patterns, but I find it looks good.

Step 2: Sort your points in x

I'm going to trust that you've got this one. Take the points generated by Bridson, and sort them based on their x-coordinate. You could do any other sorting, but what I'm doing here is basically putting all of the points on a slanty surface in x AND Z slanting away from the viewer, so that the points on the right look farther away than the points on the left.

Mix it up, though. Maybe you can do something fancy to get some "wiggle" in the scales. Or an Ouroboros pattern where the scales loop around. Let me know what you come up with.

Step 2a: Let's draw some debug circles


This is just drawing a circle around each point with the radius I used for the Bridson algorithm. You can see that the points are all pretty tightly packed. Nearly a triangle grid, but with some noise in there that makes it feel more interesting.

The thing we're going to do next is some hidden line / hidden surface elimination, which kids for the past 25 years plus have been able to do with hardware z-buffering. But we're not going to do that, because I'm writing in Python, and vector art feels like it's not meant to be all pixel-shader-y. 

If you disagree, maybe you can do something more efficient than I'm doing here, but I'm going to walk the class through use of "The Painter's Algorithm", which is what we had when I was your age.

Step 3: Let's draw some circles one segment at a time

If you looked at that above illustration and reproduced it with a circle primitive in your drawing environment, that's fine. I actually drew it as a "polyline", a series of vertices connected by straight lines. I used a lot of vertices (around 45 for each circle), which is pretty good. Bear in mind that my target is a 6 inch by 1 inch bookmark, so I can get away with not being pixel-perfect.

I'm not here to teach you trigonometry, but a for loop and some sines and cosines, and you can get a polyline that plots to look like a circle.

But not so fast!

We've sorted our circles so that we'll be drawing the circles from left to right. I've got a routine that takes one of these polyline representations of a circle, and "clips" it against another circle, so that any vertices in the polyline that are inside the second circle get removed. It's a simple function, and I don't want to load this post up with code, but since you asked so kindly:


Yeah, that's a picture of code, which makes it hard to just cut and paste. I'm not sorry. You get syntax highlighting this way.

You can probably see what's going on here, I pass in a "path", our polyline of vertices. Also a center and a radius which describe the circle we're clipping against. I keep consecutive sets of verts in "current_path" until I run out, or until I hit a vert that's inside the second disk, at which point, I move "current_path" into "out_paths", and carry on. At the end, I return each little snipped up piece of the polyline. Maybe I could have been smarter, knowing that my path might end up having 0, 1, or 2 pieces, but I was lazy, and this seemed easy.

With this routine, I can take each of the circles to the left (which, recall, are "in front") of this circle and "hide" the parts of the circle that are obscured. It's almost like I'm some sort of oil painter, hence "Painter's Algorithm".

Except that a painter that uses an opaque paint (Bob Ross) would work from the back and move forward, so it's maybe not a perfect analogy. We're sort of manually cutting up each new circle and only drawing the pieces not obscured by those to the left/forward of it.

I could be clever, use the Bridson grid approach to do an O(1) neighborhood check to find previously drawn circles to clip against, but again, I was lazy when i did this, and I just clip against every circle I've already drawn. It's quick enough for what I need, right now.

Step 4: You're done

That's all there is to it, just draw circles, clipped against earlier circles. I thought about doing fancier masking, drawing a bitmap to a temporary buffer, and then clipping my polylines against that - I might do that, too. 

Or, you could do some fancy Signed Distance Field (SDF) stuff, which would be pretty similar to what I did, above. But SDFs are cool, so maybe there's some neat effects you could get from doing it that way.

Step 5: You're still here?

If you turn the picture sideways, maybe it looks like steamed buns or xiao long bao (soup dumplings). 



Maybe not. But if you like the image of dumplings and/or buns, stretching off to the horizon, maybe my work here is done.

No comments:

Post a Comment