Skip to content

Commit

Permalink
fix(face): Implement final fix for Face3D normal calculation
Browse files Browse the repository at this point in the history
I have used the method here:
https://stackoverflow.com/questions/32274127/how-to-efficiently-determine-the-normal-to-a-polygon-in-3d-space

And it seems more robust than anything else that I have tried so far. I can't believe it took me this long to find this method in my searches.
  • Loading branch information
chriswmackey committed Jul 30, 2021
1 parent 1f6c03a commit e6abc10
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 23 deletions.
41 changes: 19 additions & 22 deletions ladybug_geometry/geometry3d/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -1915,26 +1915,26 @@ def _plane_from_vertices(verts):
verts: The vertices to be used to extract the normal.
"""
try:
# walk around the shape and get cross products and magnitudes
cprod1, d1 = Face3D._normal_from_3pts(verts[-2], verts[-1], verts[0])
cprod2, d2 = Face3D._normal_from_3pts(verts[-1], verts[0], verts[1])
cprods, ds = [cprod1, cprod2], [d1, d2]
# walk around the shape and get cross products
cprods, base_vert = [], verts[0]
for i in range(len(verts) - 2):
cprodx, dx = Face3D._normal_from_3pts(*verts[i:i + 3])
cprods.append(cprodx)
ds.append(dx)
# sum together the cross products of any vertices that are not colinear
min_d = (sum(ds) / len(ds)) * 0.05 # rel tolerance for colinear vertices
verts_3 = (base_vert, verts[i + 1], verts[i + 2])
cprods.append(Face3D._normal_from_3pts(*verts_3))
# sum together the cross products
normal = [0, 0, 0]
for cprodx, dx in zip(cprods, ds):
if dx > min_d:
normal[0] += cprodx[0] / dx
normal[1] += cprodx[1] / dx
normal[2] += cprodx[2] / dx
for cprodx in cprods:
normal[0] += cprodx[0]
normal[1] += cprodx[1]
normal[2] += cprodx[2]
# normalize the vector
if normal != [0, 0, 0]:
ds = math.sqrt(normal[0] ** 2 + normal[1] ** 2 + normal[2] ** 2)
normal_vec = Vector3D(normal[0] / ds, normal[1] / ds, normal[2] / ds)
else: # zero area Face3D; default to positive Z axis
normal_vec = Vector3D(0, 0, 1)
except Exception as e:
raise ValueError('Incorrect vertices input for Face3D:\n\t{}'.format(e))
return Plane(Vector3D(normal[0], normal[1], normal[2]), verts[0]) \
if normal != [0, 0, 0] else Plane(o=verts[0])
return Plane(normal_vec, verts[0])

@staticmethod
def _normal_from_3pts(pt1, pt2, pt3):
Expand All @@ -1949,12 +1949,9 @@ def _normal_from_3pts(pt1, pt2, pt3):
v1 = (pt2.x - pt1.x, pt2.y - pt1.y, pt2.z - pt1.z)
v2 = (pt3.x - pt1.x, pt3.y - pt1.y, pt3.z - pt1.z)
# get the cross product of the two edge vectors
c_prod = (v1[1] * v2[2] - v1[2] * v2[1],
-v1[0] * v2[2] + v1[2] * v2[0],
v1[0] * v2[1] - v1[1] * v2[0])
# normalize the vector to make it a unit vector
d = math.sqrt(c_prod[0] ** 2 + c_prod[1] ** 2 + c_prod[2] ** 2)
return c_prod, d
return (v1[1] * v2[2] - v1[2] * v2[1],
-v1[0] * v2[2] + v1[2] * v2[0],
v1[0] * v2[1] - v1[1] * v2[0])

@staticmethod
def _corner_pt_verts(corner_pt, verts3d, verts2d):
Expand Down
23 changes: 22 additions & 1 deletion tests/face3d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,28 @@ def test_normal_with_jagged_vertices():

assert 0.99 < face_geo.normal.z < 1.01
assert not face_geo.check_planar(0.000001, False)
face_geo.remove_colinear_vertices(0.01) # correct plan ensures removal of verts
face_geo.remove_colinear_vertices(0.01) # correct plane ensures removal of verts


def test_jagged_l_face():
"""Test that shapes with colinear vertices have a relatively close normal."""
geo_file = './tests/json/jagged_l.json'
with open(geo_file, 'r') as fp:
geo_dict = json.load(fp)
face_geo = Face3D.from_dict(geo_dict)

assert -0.99 > face_geo.normal.z > -1.01


def test_face_with_tough_normal():
"""Test that shapes with perfect symmetry to undermine normal calc."""
geo_file = './tests/json/face_with_tough_normal.json'
with open(geo_file, 'r') as fp:
geo_dict = json.load(fp)
face_geo = Face3D.from_dict(geo_dict)

assert 0.99 < face_geo.normal.z < 1.01
face_geo.remove_colinear_vertices(0.01) # correct plane ensures removal of verts


def test_flip():
Expand Down
45 changes: 45 additions & 0 deletions tests/json/face_with_tough_normal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"type": "Face3D",
"boundary": [
[
107.25242534512725,
17.866487910895195,
39.250000000000021
],
[
113.3683215703075,
17.866487910895195,
39.250000000000028
],
[
113.3683215703075,
31.759087049324272,
39.25
],
[
114.2470012451289,
33.880407411265018,
39.25
],
[
116.36832157030744,
34.759087049324265,
39.25
],
[
104.25242534512725,
34.758771519357481,
39.250000000000057
],
[
106.37374570706803,
33.880091844536032,
39.250000000000057
],
[
107.25242534512725,
31.75877151935746,
39.250000000000057
]
]
}
110 changes: 110 additions & 0 deletions tests/json/jagged_l.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"type": "Face3D",
"boundary": [
[
-437.67470345554875,
448.83356213545198,
0.98880353287442802
],
[
-437.67470345554881,
442.39886957379645,
0.9888035328748046
],
[
-444.5537177188728,
442.39886957384624,
0.98880353287477618
],
[
-458.67014426978824,
442.39886957316367,
0.98880353287477618
],
[
-458.67014426905274,
425.631026065789,
0.98880353287477618
],
[
-457.45344876586228,
425.63102606581083,
0.9888035328746021
],
[
-457.45344876574035,
422.2929997622914,
0.98880353287477618
],
[
-458.67014426885544,
422.29299976223831,
0.98880353287477618
],
[
-458.67014426872311,
418.93191119333596,
0.98880353287442979
],
[
-458.67014426858867,
415.51511214043364,
0.9888035328747975
],
[
-460.61884095125691,
415.51511214035691,
0.98880353287469269
],
[
-460.61884095139919,
418.44679102152077,
0.98880353287462164
],
[
-460.61884095153926,
420.85549239157831,
0.98880353287442802
],
[
-460.61884095178226,
426.34027463558721,
0.98880353287442802
],
[
-467.44616830802465,
426.34027463526365,
0.98880353287450617
],
[
-467.42893980187552,
428.27413957398323,
0.98880353287448486
],
[
-462.69727318057494,
428.27413957414274,
0.98880353287473854
],
[
-460.4218559464984,
428.27413957416752,
0.9888035328748046
],
[
-460.4218559464253,
444.34555947569925,
0.9888035328748046
],
[
-444.4644409514276,
444.34555947562603,
0.98880353287477618
],
[
-444.46444095164821,
448.83356213545198,
0.98880353287442802
]
]
}

0 comments on commit e6abc10

Please sign in to comment.