Not sure if you got through clipping, but it was one of those things I had to go through first at some point in the mid 90s myself. I feel your pain, but after having implemented it about 5-10 times in various situations, variants and languages, I can promise it gets a lot easier.
In my experience it is most elegant to clip against the 6 planes of the view-frustrum in succession (one plane at a time). Preferably clipping against the near-plane first, as that reduces the set of triangles the most for subsequent clips.
Your triangles can turn into convex polygons after a clip. So it is convenient to start with a generic convex polygon vs. plane-clipping algorithm; The thing to be careful about here is that points can (and will) lie on the plane.
Use the plane equation (f(x,y,z)=ax+by+cz+d) to determine if a point is on one side, on the plane, or the other side.
It is convenient to use a "mask" to designate the side a point v=(x,y,z) is on.
So:
1 := inside_plane (f(x,y,z)>eps)
2 := outside_plane (f(x,y,z)=eps && f(x,y,z)When you go through each edge of the convex polygon (v_i->v_{i+1}), you can check if you should clip the edge using the mask. I.e.:
if (m(v_i)&m(v_{i+1})==0 the points are on opposite side => clip [determine intersection point].
Since you are just clipping to the frustrum, just return a list of the points that are inside or on the plane (i.e. m(v_i)&1==1) and the added intersection points.
There are lots of potential for optimization, of course, but I wouldn't worry about that. There are lots of other places to optimize a software rasterizer with more potential, in my experience.