Spirals are done... enough. Still have some tweaks that I want to implement, but I'll wait on those. Here's a description of how I designed spiral galaxies in 3D:

I realized early on that I would have to generate the core and arms separately, as there's no single geometric shape that easily describes an entire spiral galaxy. The core would be a disk that bulged a bit toward the center, while the arms could be described by half-ellipsoids--think of them as looking like footballs that are pulled to be longer and then cut in half. One of those halves can roughly represent the volume of a galactic arm.

When you cut an ellipsoid in half, you get a flat "butt" to it. In order to join this flat region to the side of the disk-shaped core, I decided to cut away at the sides of the core to transform it from a disk with rounded edges to a polygon-disk. In other words, a spiral galaxy with 4 arms would have a square core (though it would still have height in the z-direction) and the flat sides of the square would be the locations where the flat butt of the arm ellipsoids would join.

But how do you cut away at the central disk to create the flat joins? Answer: Don't cut away, just build it that way in the first place. Rather than fill in a disk using a disk point-picking method, fill in a series of triangles and rotate them to the appropriate angle. Think of it this way--a hexagon can be broken into 6 triangles, a square into 4 triangles, and a larger triangle can be broken into 3 smaller triangles. I used a triangle point picking method to create each "triangle sector" of the core. In the end, I was left with the desired shape of either a big triangle (3 arms), a square (2 or 4 arms), or a hexagon (6 arms).

(Edit: Forgot to mention that I also had to use a triangle interior detection algorithm when doing the triangle point picking, since the point picking actually puts the points in a quadrilateral and you have to determine which sector they are in.)

After that I just used the disk point picking method again to create each arm, multiplying the (x, y) coordinates for each point by the (a,b) dimensions of the arm ellipsoid. Then I had to rotate the arm to align with the proper flat join on the core and translate it outward so it lined up perfectly.

I have some code in place to allow a single arm to have branches that split off and run for some length, but it was getting somewhat complicated to implement. I might come back to it at some point, but for now I'm happy with just having singular, non-branching arms.

I recorded a couple videos of the new spirals. My recording software can only capture at about 15 frames/second, so there's a bit of chop.

Videos:

3 Arm Spiral, 3000 Stars

4 Arm Spiral, 3000 Stars

6 Arm Spiral, 3000 Stars