Open polarkernel opened 4 years ago
The skeletonization code stores polygon and hole edges in the class LineSegment2, constructed in the method _frompolygon() of the class _LAV and collected in the class _LAVertex. Whenever a unit vector of an edge is required, it gets computed applying the method normalized() on the direction vector v in LineSegment2, which is very inefficient.
We could provide the line segments of polygon and holes already as input to skeletonize() and compute the unit vector only once per segment and store it in LineSegment2. It would then be available within the computation of the skeleton and also as output of polygonize().
How should the output of polygonize() look like to be convenient for the usage in blender-osm?
I suggest to add input additional parameters:
I suggest to add input additional parameters:
unitVectors (a Python list), the order of unitVectors for the polygon edges is the same as the order of vertices in the input Python list verts. Should it be an optional or obligatory parameter?
edgeLengths (a Python list). Are edge lengths used inside polyskel.skeletonize(..) method?
I dont think it makes any sense to return unit vectors for the skeleton edges on calling polyskel.polygonize(..), as their direction is different for different faces?
- unitVectors: OK. Can be optional for callers that didn't yet compute unitVectors, just to make the function run in this case. But I don't propose to return them as a result. When required by the caller, they should get computed there.
Ok, agreed
- edgeLengths: Not required, edge lengths are used nowhere in polyskel.skeletonize(..) method.
Ok. No edge lengths as inputs.
I dont think it makes any sense to return unit vectors for the skeleton edges on calling polyskel.polygonize(..), as their direction is different for different faces?
Unit vectors for the skeleton edges aren't needed by the addon. Only the ones for the polygon edges (including holes) are needed by the addon.
OK, then the new signature will be
bpypolyskel.polygonize(verts, firstVertIndex, numVerts, unitVectors=None, numVertsHoles=None, height=0., tan=0., faces=None)
I will update the new code soon.
I updated the first message at #2.
I will update the new code soon.
This has been done now, the new code is commited to the repository.
Thank you! I'll test it in the coming days.
Do we need logging in bpypolyskel.py? From the original code of skeletonize(), we actuall import a logger and use many instructions like
log.info("%.2f Peak event at intersection %s from <%s,%s,%s> in %s", event.distance,
event.intersection_point, event.vertex_a, event.vertex_b, event.vertex_a.prev, lav)
Additionally, every class, also those in the euclid replacement, needs to implement __str__()
and __repr__()
like
def __str__(self):
return "LAV {}".format(id(self))
def __repr__(self):
return "{} = {}".format(str(self), [vertex for vertex in self])
which provide string representations of the object.
During my 'exercise' in computational geometry I preferred to debug using mathplotlib in order to see what's going on. I know, it's not very much code, but we could save some memory.
Hi @polarkernel,
I think it's ok to remove the logging code.
In https://github.com/prochitecture/bpypolyskel/issues/5#issuecomment-732998743 I mentioned that using double-precision floating point computations in cross- and dot-products increased the quality of the skeleton. At this time it was unknown, how much the performance decreases by this change. I did now some tests to get an impression of this subject.
As an example, I used the data of _potsdam_grosse_weinmeisterstrasse49d, which is some kind of average sized building. On my computer, one execution of polygonize() with this data requires 10.8 ms (average of 500 runs). Note that on a Windows computer the timing is not very accurate, but I think it will be enough to get an idea. In this example, 436 cross-products and 293 dot-products were computed for one run.
The setup for my measurements for cross- and dot-products was as follows:
from mathutils import Vector
a = Vector((1.7,5.8))
b = Vector((9.1,3.7))
def dot(a, b):
return a.x * b.x + a.y * b.y
def cross(a, b):
return a.x * b.y - a.y * b.x
Then I used three different test snippets to measure the execution time (here shown for the cross-product and similar for the dot-product):
1) c = a.cross(b) # mathutils
2) c = cross(a,b) # function call
3) c = a.x * b.y - a.y * b.x # inline
The results were as follows:
Conclusions:
Note: Using double precision will not solve the issues of floating point precision for computational geometry, it will only shift them to another level.
EDIT: It is not true that _prague_wenzigova458 looks better than with single-precision arithmetic. I forgot to remove a decreased mergeRange in my test code of bpypolyskel.py with double-precision cross- and dot-products. I like to postpone this proposition for now.
Anyway, it would be good to have several variants of calculations in the bpypolyskel library if there will be more than one of them.
This issue shall be used to discuss propositions for optimizations of the project.