Animation nodes for geometry line art?

I am trying to see if Animation Nodes can be used to create geometry line art, similar to the various Line Set options in Freestyle. I am not very familiar with animation nodes, so I don't know if the functionality is there, or if it is feasible. So I'm not looking for a detailed explanation, just a simple yes or no of it it works, and perhaps a rough explanation of where to start.

Specifically, here's what it would need to be able to do:

1) Create a spline/curve/vertex strand on marked edges, similar to freestyle's Edge Mark features

2) Create spline/curve/vertex strand on edges based on face angles, similar to Crease.

3) Create them based on contour/external contour, probably based on normal facing on external faces of the mesh.

4) Merge the above together and add modifiers, materials, etc.

Are any of these possible?

UPDATE: Picture examples as requested.

1 and 2 were made as noted, by manually duplicating a selection, separating the object, and converting to a curve. 3 was made by copying the object, fattening it, and moving it back behind the first object. This works for the example of the look, but what I really want to do is draw an outline curve around it based on the view angle.

• It would be better if you add images for the results you want to achieve, a lot of us are not familiar with freestyle, so directly showing desired results will help your question get answered. – Omar Emara Feb 11 '18 at 12:48
• @OmarAhmad I have added an example picture. – Drudge Feb 11 '18 at 21:50

FreeStyle Edge Mark

Animation Nodes have a node Splines From Edges that takes a list of edges and vertices an returns splines that represents those edges. All we have to do is filter the edges by whether they are marked as freestyle or not. The blender API provide access to this data through the edge.use_freestyle_mark where it returns True if the edge is marked as a freestyle and False otherwise. To generate a boolean list of this property, we can use such list comprehension:

[e.use_freestyle_mark for e in object.data.edges]


Animation Nodes have a node Mask List which returns a filtered version of the input list if for each element its corresponding element in the input boolean List is True. So we can use the Mask List node to filter our edges based on the boolean list we generated before using such a node tree:

Edge Crease

The same workflow can be used, however, the boolean list used in filtering will change to be based on an angle threshold. Let us define the angle of the edge as the angle between the normals of the two faces the edge is part of, that, of course, fails for boundary edge that is part of only a single polygon. The problem reduces to finding the indices of the two polygons that each edge is part of. The internal structure of meshes in Blender stores loops which are vertices of polygons, something that you may remember from our study in this answer. A loop also corresponds to an edge that is part of the polygon as well as share the vertex that the loop represents in the polygon, you can study this from the Design Document of the Bmesh structure. So, loops can be used to identify which edges are in each polygon, but we want the inverse of that, we want a function that takes the edge and returns the polygons, this is something that we can solve by using a simple look up table. We can get the indices of the loops through polygon.loop_indices and we can get the index of the loop edge through loop.edge_index, you can study those form the API.The implementation is nested and not direct, so take your time to understand it.

We crate a list of n empty lists where n is the number of edges in the mesh, this can be done by the list comprehension [[] for _ in range(n)]. We will append the index of the polygon to those lists indicating that the polygon is composed of that edge. We will create two loops, the first loop iterates over the polygons and gets the indices of the loops it contains through polygon.loop_indices, that loop in turn hands the indices to another loop which gets the small initially empty list at each of the loop indices and append the polygon index to it, set the new list to the generic list and reassign the generic list. Notice that the polygon index is the loop (iterator) index of the upper loop. Also notice that to get the list after we appended it, we have to enable Output option for the input list:

Now that we have computed the look up table, we can loop over it, get the polygons, compute the angle between them using the dot product or the axis quaternion, if the angle is smaller than a threshold, append True and false otherwise, note that we have to check of the edge is a boundary or not as well. the implementation is simple, if the list contains two elements (It is not boundary) AND if the dot product between the two polygon normals at the two list indices is less than cosine some angle, return True and false otherwise:

Which if we used in filtering the edges, yields:

Blend file for study and practice:

Layering Filters

Layering filters is as easy as combing the boolean lists using what ever logic you want to use, for instance, if I want to use the edge mark OR the Crease, we can add the two boolean lists (Addition is OR in boolean algebra):

Sky is the limit when it comes to Animation Nodes.

External Contour

As for External Contour, a screen space processing might be your best option. For instance, construct a signed distance field in screen space and them march a spline starting from a known edges point, or use marching squares algorithm to polygonize it, project point on the surface and convert it into a spline. I don't have enough time to research the topic further, so i will leave it to you.

• Thank you for the very detailed answer! I'm working on recreating the Edge Crease section, but my lack of knowledge of animation nodes is tripping me up. I am having trouble telling what the List Type is for some of the Loop inputs/outputs as you seem to have renamed them. Specifically, Lopp Indices, Edges of Polygons, and Polygon Index. Can you clarify what these inputs/outputs are? – Drudge Feb 13 '18 at 1:04
• @Drudge Loop Indices and Polygon Index are integer lists. Edges Of Polygons are Generic Lists. A generic list can hold any type of data including lists. Notice that in order to convert between a generic and a non generic type, Convert Node should be used. – Omar Emara Feb 13 '18 at 14:53
• I am still having trouble getting it to work. Append to List in the Loops loop is giving a 'does not work' error. I am also getting convert nodes popping up that are not in your screenshots. Is the Polygon Index input of Loops supposed to be an Integer List or Integer? Is the Append to List node in Loops supposed to be a Generic list or something else? Is append to list supposed to output to anything? Thanks. – Drudge Feb 15 '18 at 2:30
• Not entirely sure what is happening with Append Node, it should never return that. It is ok for convert nodes to pop up, AN is trying to fix something with them, however, avoid automatic addition by adding them manually, choosing the type, and locking it using the lock button. Polygon Index is an Integer. The appended to list should be generic. The append node not not suppose to output to anything. – Omar Emara Feb 15 '18 at 15:58
• @Drudge I added the blend file and extra information to understand the implementation better. – Omar Emara Feb 15 '18 at 18:33

Thank you Omar for the very helpful and detailed answers on Animation Nodes, here and on other questions.

I've been working on the "external contour" problem (I call it a silhouette) for a while now, and have developed a good general solution using Animation Nodes. Hope it helps!

To see the external contour, the viewing angle matters, as the visible edges and vectors change depending on your viewing location.

For this example, I'm using an object which is several cubes combined into a single object mesh, and which has a complex silhouette depending on the viewing angle.

Solving for the silhouette is really two problems:

1) Find all vertices / edges which are on the "outside edge" of the object. That is, vertices/edges which are visible from the vantage point, and have nothing in front of or behind them.

2) If an outside edge is partially obscured, (i.e. one vertex is visible, but the other is hidden or in front of something else,) recreate that edge to show only the visible portion.

In this image, the small square stands alone, so the silhouette for the small square in the back is simple to draw, because all of the vertices are already known. The vertices 12 and 10 are excluded, because 10 is obscured and 12, while visible to the camera, has something behind it and would thus be hidden in the silhouette.

The silhouette for the two larger squares is harder to determine, because they overlap. Most of the vertices for the 'external edge' are already known, but what about the 16-17 edge? Because the front square partially covers the edge, we need a new vertex for that edge instead of 17. Same problem for edge 21-17.

With more objects, and animation, this problem gets even more complicated.

The outline of my solution is:

1. Create a BVH tree from the original object mesh
2. (separately,) build a list of all of the edges in the object
3. Subdivide the edges into smaller sub-edge segments
4. Using the BVH tree from 1, test all of the sub-edges to see if they're part of the silhouette
5. Recombine just the silhouette sub-edges into longer edges, to simplify the final object
6. Hide the original object, display the silhouette edges

Here are the Nodes:

MAIN - The main routine has two inputs (an object and a camera) and one output (a target object which will contain the silhouette edges. The object is used to create a list of edges to be subdivided, as well as a BVH tree for raycast hit testing.

Subdivide Edges: This loop iterates through the object's original edges, subdividing them into shorter edge segments. The "scale" variable is the desired sub-edge length, but the subdividing is limited to a max of 20 vertices (19 segments) for performance. These two variables can be tuned depending on the model.

The resulting list of vertices and edge indices is a new mesh, which will be used for silhouette testing.

Find Silhouette - This loop has two sections. The first is just a loop that iterates through all the edges created in the subdivision step above. For each edge, it tests the beginning and ending vertex to see if it meets the silhouette criteria. If both the beginning and end vertex are part of the silhouette, it flags that edge with a boolean "yes" for later.

The second section is Silhouette Detection, which is run on each vertex. In order for a vertex to be part of the silhouette, it needs to meet two criteria:

• a ray cast from the vertex to the camera location does NOT hit any other objects in the BVH tree. It can be seen by the camera.
• a ray cast from the vertex AWAY FROM the camera location (the inverted vector) does NOT hit any other objects in the BVH tree. The camera can look through that vertex into empty space.

In the example image from above, vertex 16 WOULD be part of the silhouette, but 7 would not. 7 passes the first test for silhouette, but not the second. Vertex 17 also fails, because it is hidden behind the foreground square.

Because we subdivided Edge 16-17, there are a number of vertices between 16 and 17, so some of the sub-edge segments will be visible.

The output of this loop is a boolean list, with YES for only those edges that are part of the silhouette.

Recombine Edges - The final step is to recombine the silhouette sub segments into longer edges. It does this by testing whether the "start" vertex of one edge is the same as the "end" of the previous edge. If so, it just combines the two into a single edge. The output is a new mesh, with all of the original vertices, but just a few edge indices.

Recombining the small edges is not strictly necessary, as the shorter line segments will draw the same lines. For my purposes, I needed a simpler mesh.

Also note that this final mesh is just vertices and edges, and has NO faces. Thus you may need to tweak further w/ modifiers or whatever to make it useful for you.

Last, the precision of the line segment lengths is a function of the scale and segment limit variables. Tweak to taste.

BONUS: This same set of nodes can also be used to display a wireframe of the model, excluding the hidden edges. Just disable the second silhouette test (the raycast AWAY from the camera) as shown above, and the node system will only test for visible line segments.

Blend file was created in Blender 2.79, using Animation Nodes 2.0.4. I'm sure there are more efficient approaches, and I'm open to any suggestions. HTH

• Excellent! I am planning to put all these together into a useable mesh lineart generator when I have a break on some other projects, and this was the big missing piece I was stuck on to start doing that. – Drudge Jul 16 '18 at 0:46
• Glad to help! I've learned a lot around here, time to pay it forward a bit... I'm interested to see your line art mesh generator when it's done. I use blender to make Oscilloscope Music, which can only use simple wireframes. I like the vibe and simplicity of the freestyle options, but can't use any rendered blender output. Would be cool to have some similar tools for wireframes. – zippy Jul 16 '18 at 1:06
• Very Nice! I will experiment with this as soon as I get my computer back. – Omar Emara Jul 16 '18 at 9:21
• Hello again, I've been experimenting with this again recently. Do you know if there's a way to fix the frequent line breaks, especially on more complex objects? I've also run into a wall trying to port this to 2.1. Although it's probably best to revisit it after animation nodes gets updated for 2.8 – Drudge Oct 17 '18 at 23:43
• After recombining the edges, I suppose one could take the resulting silhouette edges and somehow reorder them to find edges with common end/begin vertices, then put those in a sequence to reduce discontinuities. I'm not a coder, however, so that feels a little beyond what I could accomplish w/ AN. Not really sure it's possible to avoid frequent line breaks on really complex objects, as the little line segments bridge overlaps / gaps, etc. HTH – zippy Oct 18 '18 at 3:47