prochitecture / bpypolyskel

A port of Botffy/polyskel library for Blender that outputs polygons formed by a straight skeleton. 'pip install mathutils' to use it as a general purpose library independent of Blender.
GNU General Public License v3.0
51 stars 5 forks source link

Bugs in first version of bpypolyskel #5

Open polarkernel opened 3 years ago

polarkernel commented 3 years ago

I just have commited a first version of the bpypolyskel modules. The demo-module requires the mathutils library to be installed in your interpreter using 'pip install mathutils'. A demo running in Blender will follow later.

Please report and discuss eventual bugs or otpimizations here.

vvoovv commented 3 years ago

A quite simple polygon. What's wrong with it?

bpypolyskel.py.txt

polarkernel commented 3 years ago

A quite simple polygon. What's wrong with it?

Oh no !! This issue is produced by me. I implemented polygonize() expecting a clockwise order of vertices and not counter-clockwise, as defined. I will fix that as soon as possible, sorry.

vvoovv commented 3 years ago

Yes, the order of vertices is counter-clockwise for the outer ring The order of vertices is clockwise for the holes.

polarkernel commented 3 years ago

I will fix that as soon as possible,

I am sorry, but this will take a while. The only proper way is to change the vertex order in Botffy's code. In the original paper, the vertex order is anti-clockwise in a coordinate system as we like to use it. But I assume that he used the coordinate system of the images he introduced for debugging. In many image libraries, the y-axis goes from top to bottom.

Reversing vertices lists and indices in polygonize() leads immediately to an unreadable code and I like to avoid this. I think it is better if I take the time to change skeletonize(). It will even help me to understand the algorithm for the straight skeleton a bit better, I assume.

vvoovv commented 3 years ago

Ok. No problem.

polarkernel commented 3 years ago

Yes, the order of vertices is counter-clockwise for the outer ring The order of vertices is clockwise for the holes.

Fixed and commited. This was really an intensive exercise in computational geometry, but I hope it works now.

vvoovv commented 3 years ago

I was away for a few days. I'll try it today or tomorrow.

vvoovv commented 3 years ago

Got a weird result.

(1) bpypolyskel_test1.py.txt bpypolyskel_test1


The same footprint, but imported relative to another reference point. (2) bpypolyskel_test2.py.txt bpypolyskel_test2

polarkernel commented 3 years ago

Got a weird result.

The same footprint, but imported relative to another reference point.

Fixed. Was damned diffcult to find. Finally, it required only a '>' instead of a '<'.

vvoovv commented 3 years ago

Great! Thank you so much! I've fully integrated bpypolyskel with the addon. Hipped roofs with textures are now generated for polygons without a hole.

image

polarkernel commented 3 years ago

Fantastic! The buildings look really very natural. I hope, the package will work also for buildings with holes. I tested only one example of them.

vvoovv commented 3 years ago

I notices the faces are returned in the arbitrary order rather than follow the order of vertices in the verts input parameter,

Would it be possible to return the faces in the order of the verts without sorting? If not possible, no problem at all. I can cope with it.

polarkernel commented 3 years ago

notices the faces are returned in the arbitrary order rather than follow the order of vertices in the verts input parameter,

I can't find this issue. The vertices in faces should always start with an edge of the original polygon (or hole). Thus, to follow the order of vertices in the verts, their number should always increase, except for the edge from the last vertice to the first. If I take your example _bpypolyskeltest1.py above and print faces, I get the following values:

[[10, 11, 21, 20], [8, 9, 17, 16], [15, 8, 16], [13, 14, 18, 19], [14, 15, 16, 17, 18], 
 [9, 10, 20, 19, 18, 17], [11, 12, 21], [12, 13, 19, 20, 21]]

As you can see, the numbers are always increasing, except for the last (15) to the first (8) vertice. In my example with the Blender addon in the repo, the face normals are all looking outside, as it should be:

FaceNormals

Do you have an example that shows this issue?

vvoovv commented 3 years ago

So the vertices of the polygon have the indices [8, 9, 10, 11, 12, 13, 14, 15]

Under the same order I meant the following result:

[
[8, 9, 17, 16],
[9, 10, 20, 19, 18, 17],
[10, 11, 21, 20],
[11, 12, 21],
[12, 13, 19, 20, 21],
[13, 14, 18, 19],
[14, 15, 16, 17, 18],
[15, 8, 16]
]

Note the first vertex in each of the resulting face. It is the vertex of the original polygon for the same index.

If sorting is required to get that kind of result, then keep the resulting faces as they are now.

polarkernel commented 3 years ago

Oh my god. what a stupid misunderstanding. I wondered because the order of the vertices is given as counterclockwise, but I didn't get the self-evident meaning, sorry.

No, it is not possible to create this order without sorting. It gets porduced by the construction of a so called counterclockwise embedding in the graph class. It is based on a set of edges, every edge with both directions. A set in Python is by defintion unsorted, may be due to hash values used to identify the items in it. But I don't know the underlying algorithm. However, it makes finding faces in counterclockwise order very fast. It could be changed, but I think this would result in even more computation time than sorting.

vvoovv commented 3 years ago

Ok, thank you for the explanation. Let's preserve the current realisation.

vvoovv commented 3 years ago

Unit vectors as the input parameter don't work for me: bpypolyskel_test.py.txt

polarkernel commented 3 years ago

Unit vectors as the input parameter don't work for me:

Fixed. Accidentally, a unit vector for all vertices in verts was expected , which was clearly wrong.

vvoovv commented 3 years ago

Fixed. Accidentally, a unit vector for all vertices in verts was expected , which was clearly wrong.

Thank you! It works now.

vvoovv commented 3 years ago

Is it possible to solve the problem of the extra edge created for a t-shaped polygon? t_shape_extra_edge.py.txt image

polarkernel commented 3 years ago

Is it possible to solve the problem of the extra edge created for a t-shaped polygon?

Difficult. I wouldn't touch skeltonize(), because its task is to deliver one straight skeleton and not two partial skeletons. Maybe some post-processing could solve this issue: Find adjacent coplanar faces and merge them. Could maybe Blender solve this? As far as I know, there exists a built-in operator

bpy.ops.mesh.select_similar(type='COPLANAR', threshold=???)

I don't know if it could be applied already to the mesh of polygon faces. Maybe Blender could then merge (dissolve egde(s)?) these. However, my skills in Blender are very limited, if this idea is not feasible, I could try to code some simple (=fast) post-processing, but in Python this could be slower than in Blender.

polarkernel commented 3 years ago

Is it possible to solve the problem of the extra edge created for a t-shaped polygon?

Wait, before you waist time on the Blender solution proposal. I got an idea how this could quite simply be solved in polygonize(), but I have to elaborate it. This will take some time.

vvoovv commented 3 years ago

I think that extra edge is erroneously delivered by skeletonize(..).

polarkernel commented 3 years ago

I know and try to debug. In the skeleton there is more than one erroneous edge. Even one as loop from a node to itself.

polarkernel commented 3 years ago

Is it possible to solve the problem of the extra edge created for a t-shaped polygon?

I give up!! I can't find a real bug in skeletonize(). It seems that the algorithm in the corresponding paper is realized without bugs. Maybe the original algorithm even didn't solve this issue. Whatever, I couldn't solve it. :-(

As an intermediate solution, I have introduced a function _cleanskeleton(), which solves issues with extra edges at the end of skeltonize() by cleaning up the skeleton. Let me explain its idea using your example t_shape_extra_edge.py.txt:

One of the skeleton nodes in the result of skeletonize() is node (60,50). One of its sinks points to itself and forms a graph loop. Two other sinks, one is (40,50) and the other is (60,70), form two parallel skeleton edges, the first to another skeleton node and the second to a polygon vertex. Cleaning means now removing the loop, moving the sink (60,70) of the polygon vertex to the sinks list of the node (40,50), the first edge points to, and remove it from the sinks list of (60,50).

Like this, there are no more extra edges in the skeleton. Its not for free, but I think it works quite fast.

vvoovv commented 3 years ago

I tried t_shape_extra_edge.py.txt again. The extra edge was still there.

polarkernel commented 3 years ago

I tried t_shape_extra_edge.py.txt again. The extra edge was still there.

I removed two extra edges and you are still not happy?

Today, I studied some literature on straight skeletons and have found the expression ghost edge for this type of extra edge. Unfortunately, I couldn't find any solution to remove them properly, except by a completely new algorithm (see weighted straight skeleton).

As a workaround, I have removed this edge in my solution. However, I don't know, if this can be generalized for all types of ghost edges. Let us see if you encounter any new example, where this type of issue (or a side effect of my solution) appears.

vvoovv commented 3 years ago

Still not happy. Sorry. There is an extra vertex. Would it possible to get rid of it as well?

image

vvoovv commented 3 years ago

Another interesting case: t_shape_extra_edge_2.py.txt Everything is ok here, except of the fact that I'd prefer to have a quadrangle instead of the pentagon for the polygon with the selected vertices.

vvoovv commented 3 years ago

A brute force way to solve the problem of the extra vertices would be to iterate through the resulting faces that are polygons with at least 5 vertices and check if there are vertices with collinear adjacent edges.

Since those candidates for extra vertices are known in advance, would it be possible to optimize the task?

polarkernel commented 3 years ago

Still not happy. Sorry. There is an extra vertex. Would it possible to get rid of it as well?

I continue until you are happy! I extended the test for pairs of parallel edges to pairs of anti-parallel edges (was almost already included by the dot-product). Two anti-parallel edges from the same node get replaced by one long edge between their sinks. This works for your examples. And I still hope it will not produce side effects.

vvoovv commented 3 years ago

I continue until you are happy!

It will be probably a long way!

In the example below the extra edge is still there: t_shape_extra_edge_3.py.txt

image

vvoovv commented 3 years ago

There is still a extra vertex in the example below: t_shape_extra_edge.py.txt image

polarkernel commented 3 years ago

It will be probably a long way!

I am very patient.

extra edges in _t_shape_extraedge.py and _t_shape_extra_edge3.py

Fixed. I didn't think on possible rounding errors. Removing of singular vertices didn't work within _cleanskeleton(). I moved it at the end of skeletonize() now.

vvoovv commented 3 years ago

Now a degenerated polygon is generated for the case below (red vertices); t_shape_extra_edge_2.py.txt image

vvoovv commented 3 years ago

A rectangle with a symmetrical hole doesn't work: hole_symmetrical.py.txt However @Botffy provided a fix for that case: image

polarkernel commented 3 years ago

Now a degenerated polygon is generated for the case below (red vertices); t_shape_extra_edge_2.py.txt

In the skeleton it is not possible to distinguish, whether an edge will later be used by a polygon face or not. The replacement of anti-parallel edges by one longer edge degenerated one polygon. Therefore, I removed this task from skeletonize().

polygonize() delivers then correct faces, however, with this unpleasent extra vertice. Therefore, a fix that replaces adjacent parallel edges in faces has been introduced at the end of polygonize(), which removes this vertice. All _t_shape_extraedge issues should now be fixed.

polarkernel commented 3 years ago

A rectangle with a symmetrical hole doesn't work: hole_symmetrical.py.txt However @Botffy provided a fix for that case.

Update by Botffy integrated, adapted to our right-handed coordinate system. Really a stupid bug, could be one of mine. ;-)

However, now something is wrong with the polygonizer. I'll check this.

polarkernel commented 3 years ago

However, now something is wrong with the polygonizer. I'll check this.

Fixed now. Again, there were several issues with float precision decisions.

vvoovv commented 3 years ago

All three cases for the T-shape work now! That's a greate milestone! Congratulations!

The vertices of the most interesting polygons are in red in the images below:

image

image

image

vvoovv commented 3 years ago

But...

A number of extra edges are present on a real OSM building footprint: bldg_nikolskaya_street.py.txt

Shall I somehow localize the problems?

image

vvoovv commented 3 years ago

I localized the extra edge in the leftmost wing of the building. It appears to be a face rather than edge probably caused by the non-rectangular footprint:

image

vvoovv commented 3 years ago

Sorry to bother you. I need to explore the latter case more thoroughly. I'll inform you if there is a real problem.

vvoovv commented 3 years ago

Found the exact problem. There is a very weird face in the example below. The face is marked with the red color. bldg_nikolskaya_street.py.txt

The index of the problem face in the resulting Python list faces is 8.

The face consists of the vertices [59, 58, 88, 89, 91, 77, 85, 80, 53, 52, 89, 88, 87, 86, 82, 81, 79, 78, 84, 83, 76, 77, 91, 93, 98, 97, 96, 99, 34, 67, 85, 77, 76]. The indices 88, 89, 91, 85, 76 are used twice. The index 77 is used thrice.

Let me know how I can help debugging the problem.

image

polarkernel commented 3 years ago

Found the exact problem.

Will take some time. I will first prepare some tools to ease debugging of such a large number of vertices. I will notice you should I need your help.

vvoovv commented 3 years ago

It seems that the order of indices was changed. The normals of the roof faces in Blender are pointing inwards now. UV-mapping is now distorted.

For example here are the indices for a hipped roof: [9, 8, 13] [6, 11, 14] [7, 6, 14, 15] [8, 7, 15, 12, 13] [11, 10, 12, 15, 14] [10, 9, 13, 12]

The first two vertices in each roof face are from the original polygon. They should have the order N, N+1 rather than N+1, N.

polarkernel commented 3 years ago

It seems that the order of indices was changed.

This was a bug, fixed now. However, this does not solve the isuue in _bldg_nikolskayastreet. I am out today for most of the day, so its solution will get delayed a bit, sorry.

polarkernel commented 3 years ago

Found the exact problem.

What I already can tell is that the issue comes from skeletonize(). Skeleton edges are forbidden to cross each other. However, they do in this example. For instance at the right end of the footprint, there is an intersection (yellow: polygon, blue: skeleton): Bild1 The region marked by the red arrow shows this clearly when magnified below, the edge (76,83) gets crossed by another edge: Bild2

vvoovv commented 3 years ago

This was a bug, fixed now. However, this does not solve the isuue in _bldg_nikolskayastreet.

Thanks, the correct UV-mapping is back now!

polarkernel commented 3 years ago

What I already can tell is that the issue comes from skeletonize().

The bug appears already in Botffy's original code. It is very difficult to localize it, this will take a long time.