Skip to content

Commit b8f7ee6

Browse files
author
pv
committed
Fix align libigl barycentrioc coordinates to compas.
1 parent 8796f43 commit b8f7ee6

File tree

7 files changed

+170
-26
lines changed

7 files changed

+170
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Changed
1313

1414
* Fixed `compas_libigl` plugins are not detected.
15+
* Align barycentric coordinates of libigl to COMPAS.
1516

1617
### Removed
1718

docs/examples/example_intersections.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from compas_viewer import Viewer
88

99
from compas_libigl.intersections import intersection_rays_mesh
10+
from compas_libigl.intersections import barycenter_to_point
1011

1112
# ==============================================================================
1213
# Input geometry
@@ -56,11 +57,12 @@
5657
intersections = []
5758
for ray, hits in zip(rays, hits_per_ray):
5859
if hits:
59-
base, vector = ray
60-
index = hits[0][0]
61-
distance = hits[0][3]
62-
face = index_face[index]
63-
point = base + vector * distance
60+
idx, u, v, w = hits[0]
61+
vertices = mesh.face_vertices(idx)
62+
p1 = mesh.vertex_coordinates(vertices[0])
63+
p2 = mesh.vertex_coordinates(vertices[1])
64+
p3 = mesh.vertex_coordinates(vertices[2])
65+
point = barycenter_to_point(u, v, w, p1, p2, p3)
6466
intersections.append(point)
6567

6668
# ==============================================================================
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import compas.geometry
2+
import compas.datastructures
3+
from compas_libigl.intersections import intersection_rays_mesh, intersection_ray_mesh
4+
from compas_libigl.intersections import barycenter_to_point
5+
from compas_viewer import Viewer
6+
from compas.colors import Color
7+
from compas.geometry import Line
8+
import compas
9+
10+
11+
p0 = compas.geometry.Point(2, 0, 0)
12+
p1 = compas.geometry.Point(3 + 2, 0 - 2, 13)
13+
p2 = compas.geometry.Point(0 - 2, 0 - 2, 10)
14+
p3 = compas.geometry.Point(0 - 2, 2 + 2, 10)
15+
16+
mesh = compas.datastructures.Mesh.from_points([[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z], [p3.x, p3.y, p3.z]])
17+
18+
ray = (p0, compas.geometry.Vector(0, 0, 1))
19+
hits_per_ray = intersection_ray_mesh(ray, mesh.to_vertices_and_faces())
20+
21+
idx, u, v, w = hits_per_ray[0][0], hits_per_ray[0][1], hits_per_ray[0][2], hits_per_ray[0][3]
22+
23+
index_face = {index: face for index, face in enumerate(mesh.faces())}
24+
25+
intersections = []
26+
for hit in hits_per_ray:
27+
idx, u, v, w = hit
28+
point = barycenter_to_point(u, v, w, p1, p2, p3)
29+
intersections.append(point)
30+
31+
bary_coords = compas.geometry.barycentric_coordinates(intersections[0], [p1, p2, p3])
32+
print("libigl barycentric coordinates: ", u, v, w)
33+
print("compas barycentric coordinates: ", *bary_coords)
34+
35+
# ==============================================================================
36+
# Visualisation
37+
# ==============================================================================
38+
39+
viewer = Viewer(width=1600, height=900)
40+
41+
viewer.scene.add(mesh, opacity=0.7, show_points=False)
42+
43+
for intersection in intersections:
44+
viewer.scene.add(Line(p0, intersection), linecolor=Color.blue(), linewidth=3)
45+
46+
viewer.show()

docs/examples/example_mapping.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
# Offset mesh by normals, normals are interpolated from the original mesh.
6262
# ==============================================================================
6363
mesh_mapped_offset = mesh_mapped.copy()
64-
for i in range(mesh_mapped.number_of_vertices()):
65-
mesh_mapped_offset.vertex_attributes(i, "xyz", mesh_mapped.vertex_attributes(i, "xyz") - mn[i]*0.001)
64+
for i in range(mesh_mapped.number_of_vertices()):
65+
mesh_mapped_offset.vertex_attributes(i, "xyz", mesh_mapped.vertex_attributes(i, "xyz") - mn[i] * 0.001)
6666

6767
# ==============================================================================
6868
# Get Boundary Polylines
@@ -74,7 +74,7 @@
7474
points = []
7575
for j in range(len(mf[i])):
7676
id = mf[i][j]
77-
points.append(mesh_mapped.vertex_attributes(id, "xyz") + mn[id]*0.002)
77+
points.append(mesh_mapped.vertex_attributes(id, "xyz") + mn[id] * 0.002)
7878
points.append(points[0])
7979
polyline = Polyline(points)
8080
boundaries.append(polyline)

docs/examples/example_mapping_patterns.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99
from compas_libigl.mapping import map_pattern_to_mesh
1010

1111

12-
1312
# ==============================================================================
1413
# Input geometry: 3D Mesh
1514
# ==============================================================================
1615

1716
mesh = Mesh.from_obj(Path(__file__).parent.parent.parent / "data" / "minimal_surface.obj")
1817

1918

20-
2119
for vertex in mesh.vertices():
2220
x, y, z = mesh.vertex_attributes(vertex, "xyz") # type: ignore
2321
mesh.vertex_attributes(vertex, "xyz", [x, -z, y])
@@ -32,7 +30,7 @@
3230

3331
for vertex in mesh.vertices():
3432
x, y, z = mesh.vertex_attributes(vertex, "xyz") # type: ignore
35-
if abs(z-aabb.zmin) < 1e-3 or abs(z-aabb.zmax) < 1e-3:
33+
if abs(z - aabb.zmin) < 1e-3 or abs(z - aabb.zmax) < 1e-3:
3634
fixed_vertices.append(vertex)
3735

3836
# ==============================================================================

pyproject.toml

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,31 @@ authors = [
1212
{ name = "Petras Vestartas", email = "[email protected]" },
1313
]
1414
classifiers = ["License :: OSI Approved :: BSD License"]
15-
dynamic = ['dependencies', 'optional-dependencies', 'version']
15+
dynamic = ['version']
16+
dependencies = [
17+
"compas >=2.0.0",
18+
"tessagon",
19+
]
1620

1721
[project.urls]
1822
Homepage = "https://compas.dev/compas_libigl/latest/"
1923

20-
# ============================================================================
21-
# setuptools config
22-
# ============================================================================
23-
24-
[tool.setuptools.dynamic]
25-
dependencies = { file = "requirements.txt" }
26-
optional-dependencies = { dev = { file = "requirements-dev.txt" } }
24+
[dependency-groups]
25+
dev = [
26+
"ruff",
27+
"pre-commit",
28+
"build",
29+
{ include-group = "tests" },
30+
{ include-group = "docs" },
31+
]
32+
tests = [
33+
"pytest",
34+
"numpy",
35+
]
36+
docs = [
37+
"sphinx",
38+
"sphinx-compas-theme",
39+
]
2740

2841
# ============================================================================
2942
# pytest configuration
@@ -83,8 +96,8 @@ CMAKE_POLICY_DEFAULT_CMP0135 = "NEW"
8396

8497
[tool.cibuildwheel]
8598
build-verbosity = 3
86-
test-requires = ["numpy", "compas", "pytest", "build", "tessagon"]
87-
test-command = "pip install numpy compas && pip list && pytest {project}/tests"
99+
test-groups = ["tests"]
100+
test-command = "pytest {project}/tests"
88101
build-frontend = "pip"
89102
manylinux-x86_64-image = "manylinux2014"
90103
skip = ["*_i686", "*-musllinux_*", "*-win32", "pp*"]
@@ -143,4 +156,4 @@ max-doc-length = 179
143156

144157
[tool.ruff.format]
145158
docstring-code-format = true
146-
docstring-code-line-length = "dynamic"
159+
docstring-code-line-length = "dynamic"

src/compas_libigl/intersections.py

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,69 @@
44
from compas_libigl import _intersections
55

66

7+
def _conversion_libigl_to_compas(hits_per_ray):
8+
"""Convert libigl barycentric coordinates to COMPAS barycentric coordinates.
9+
10+
Parameters
11+
----------
12+
hits_per_ray : list[tuple[int, float, float, float]]
13+
Tuples of (face_index, u, v, distance) from libigl ray intersection
14+
15+
Returns
16+
-------
17+
list[tuple[int, float, float, float]]
18+
Tuples of (face_index, w, u, v) in COMPAS barycentric coordinate ordering
19+
20+
Note
21+
----
22+
libigl uses: P = (1-u-v)*v0 + u*v1 + v*v2
23+
This function returns [w, u, v] = [1-u-v, u, v] to match COMPAS ordering
24+
"""
25+
26+
hits_compas = []
27+
for h in hits_per_ray:
28+
idx, u, v, _ = h
29+
w = 1.0 - u - v
30+
hits_compas.append([idx, w, v, u])
31+
return hits_compas
32+
33+
34+
def barycenter_to_point(u, v, w, p1, p2, p3):
35+
"""Convert COMPAS barycentric coordinates to a point.
36+
37+
Parameters
38+
----------
39+
u : float
40+
The u coordinate
41+
v : float
42+
The v coordinate
43+
w : float
44+
The w coordinate
45+
p1 : tuple[float, float, float]
46+
The first point
47+
p2 : tuple[float, float, float]
48+
The second point
49+
p3 : tuple[float, float, float]
50+
The third point
51+
52+
53+
Returns
54+
-------
55+
list[float]
56+
The point at the intersection of the ray and the mesh
57+
58+
Note
59+
----
60+
libigl uses: P = (1-u-v)*v0 + u*v1 + v*v2
61+
This function returns [w, u, v] = [1-u-v, u, v] to match COMPAS ordering
62+
"""
63+
w = 1 - u - v # barycentric coordinates
64+
65+
phit = [u * p1[0] + v * p2[0] + w * p3[0], u * p1[1] + v * p2[1] + w * p3[1], u * p1[2] + v * p2[2] + w * p3[2]]
66+
67+
return phit
68+
69+
770
@plugin(category="intersections")
871
def intersection_ray_mesh(ray, M):
972
"""Compute the intersection(s) between a ray and a mesh.
@@ -24,16 +87,29 @@ def intersection_ray_mesh(ray, M):
2487
2588
0. the index of the intersected face
2689
1. the u coordinate of the intersection in the barycentric coordinates of the face
27-
2. the u coordinate of the intersection in the barycentric coordinates of the face
90+
2. the v coordinate of the intersection in the barycentric coordinates of the face
2891
3. the distance between the ray origin and the hit
92+
93+
Note
94+
----
95+
The barycentric coordinates (u, v) follow the libigl convention where:
96+
- For a triangle with vertices (v0, v1, v2) at face indices F[face_id]
97+
- The intersection point P = (1-u-v)*v0 + u*v1 + v*v2
98+
- This differs from COMPAS barycentric_coordinates which uses a different vertex ordering
2999
"""
30100
point, vector = ray
31101
vertices, faces = M
32102
P = np.asarray(point, dtype=np.float64)
33103
D = np.asarray(vector, dtype=np.float64)
34104
V = np.asarray(vertices, dtype=np.float64)
35105
F = np.asarray(faces, dtype=np.int32)
36-
return _intersections.intersection_ray_mesh(P, D, V, F)
106+
107+
hits_per_ray = _intersections.intersection_ray_mesh(P, D, V, F)
108+
109+
# Convert libigl barycentric coordinates to COMPAS convention
110+
hits_compas = _conversion_libigl_to_compas(hits_per_ray)
111+
112+
return hits_compas
37113

38114

39115
def intersection_rays_mesh(rays, M):
@@ -55,7 +131,7 @@ def intersection_rays_mesh(rays, M):
55131
56132
0. the index of the intersected face
57133
1. the u coordinate of the intersection in the barycentric coordinates of the face
58-
2. the u coordinate of the intersection in the barycentric coordinates of the face
134+
2. the v coordinate of the intersection in the barycentric coordinates of the face
59135
3. the distance between the ray origin and the hit
60136
"""
61137
points, vectors = zip(*rays)
@@ -64,4 +140,12 @@ def intersection_rays_mesh(rays, M):
64140
D = np.asarray(vectors, dtype=np.float64)
65141
V = np.asarray(vertices, dtype=np.float64)
66142
F = np.asarray(faces, dtype=np.int32)
67-
return _intersections.intersection_rays_mesh(P, D, V, F)
143+
144+
hits_per_ray = _intersections.intersection_rays_mesh(P, D, V, F)
145+
146+
# Convert libigl barycentric coordinates to COMPAS convention
147+
hits_per_ray_compas = []
148+
for hit in hits_per_ray:
149+
hits_per_ray_compas.append(_conversion_libigl_to_compas(hit))
150+
151+
return hits_per_ray_compas

0 commit comments

Comments
 (0)