Skip to content

Commit de6033a

Browse files
author
pv
committed
FIX barycentric coordinates and output a point
1 parent b8f7ee6 commit de6033a

File tree

4 files changed

+85
-58
lines changed

4 files changed

+85
-58
lines changed

CHANGELOG.md

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

1414
* Fixed `compas_libigl` plugins are not detected.
1515
* Align barycentric coordinates of libigl to COMPAS.
16+
* Ray mesh intersection now returns a point.
1617

1718
### Removed
1819

docs/examples/example_intersections.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@
1717

1818
trimesh = mesh.copy()
1919
trimesh.quads_to_triangles()
20-
2120
# ==============================================================================
2221
# Rays
2322
# ==============================================================================
2423

2524
base = Point(*mesh.centroid())
2625
base.z = 0
2726

28-
theta = np.linspace(0, np.pi, 20, endpoint=False)
29-
phi = np.linspace(0, 2 * np.pi, 20, endpoint=False)
27+
theta = np.linspace(0, np.pi, 5, endpoint=False)
28+
phi = np.linspace(0, 2 * np.pi, 5, endpoint=False)
3029
theta, phi = np.meshgrid(theta, phi)
3130
theta = theta.ravel()
3231
phi = phi.ravel()
@@ -39,11 +38,13 @@
3938
mask = xyz[:, 2] > 0
4039
hemi = xyz[mask]
4140

41+
lines = []
4242
rays = []
4343
for x, y, z in hemi:
4444
point = Point(x, y, z)
4545
vector = point - base
4646
vector.unitize()
47+
lines.append(Line(base, base + vector))
4748
rays.append((base, vector))
4849

4950
# ==============================================================================
@@ -52,18 +53,13 @@
5253

5354
index_face = {index: face for index, face in enumerate(mesh.faces())}
5455

55-
hits_per_ray = intersection_rays_mesh(rays, mesh.to_vertices_and_faces())
56+
hits_per_rays = intersection_rays_mesh(rays, trimesh.to_vertices_and_faces())
5657

57-
intersections = []
58-
for ray, hits in zip(rays, hits_per_ray):
59-
if hits:
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)
66-
intersections.append(point)
58+
intersection_points = []
59+
for hit in hits_per_rays:
60+
if hit:
61+
pt, idx, u, v, w = hit[0]
62+
intersection_points.append(pt)
6763

6864
# ==============================================================================
6965
# Visualisation
@@ -73,7 +69,12 @@
7369

7470
viewer.scene.add(mesh, opacity=0.7, show_points=False)
7571

76-
for intersection in intersections:
77-
viewer.scene.add(Line(base, intersection), linecolor=Color.blue(), linewidth=3)
72+
for point in intersection_points:
73+
viewer.scene.add(Line(base, point), linecolor=Color.blue(), linewidth=3)
74+
viewer.scene.add(point, pointcolor=Color.red(), pointsize=10)
75+
76+
for line in lines:
77+
for i in range(20):
78+
viewer.scene.add(line.point_at(i / 20), pointcolor=Color.red(), pointsize=5)
7879

7980
viewer.show()

docs/examples/example_intersections_barycentric.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,23 @@
1818
ray = (p0, compas.geometry.Vector(0, 0, 1))
1919
hits_per_ray = intersection_ray_mesh(ray, mesh.to_vertices_and_faces())
2020

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]
21+
point, idx, u, v, w = hits_per_ray[0][0], hits_per_ray[0][1], hits_per_ray[0][2], hits_per_ray[0][3], hits_per_ray[0][4]
2222

2323
index_face = {index: face for index, face in enumerate(mesh.faces())}
2424

2525
intersections = []
2626
for hit in hits_per_ray:
27-
idx, u, v, w = hit
27+
point, idx, w, u, v = hit
2828
point = barycenter_to_point(u, v, w, p1, p2, p3)
2929
intersections.append(point)
3030

3131
bary_coords = compas.geometry.barycentric_coordinates(intersections[0], [p1, p2, p3])
32-
print("libigl barycentric coordinates: ", u, v, w)
32+
print("libigl barycentric coordinates: ", w, u, v)
3333
print("compas barycentric coordinates: ", *bary_coords)
3434

35+
print(barycenter_to_point(bary_coords[0], bary_coords[1], bary_coords[2], p1, p2, p3))
36+
print(barycenter_to_point(w, u, v, p1, p2, p3))
37+
3538
# ==============================================================================
3639
# Visualisation
3740
# ==============================================================================
@@ -43,4 +46,6 @@
4346
for intersection in intersections:
4447
viewer.scene.add(Line(p0, intersection), linecolor=Color.blue(), linewidth=3)
4548

49+
viewer.scene.add(point, pointcolor=Color.red(), pointsize=10)
50+
4651
viewer.show()

src/compas_libigl/intersections.py

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,86 @@
11
import numpy as np
2+
from compas.geometry import Point
23
from compas.plugins import plugin
34

45
from compas_libigl import _intersections
56

67

7-
def _conversion_libigl_to_compas(hits_per_ray):
8+
def _conversion_libigl_to_compas(hits_per_ray, M):
89
"""Convert libigl barycentric coordinates to COMPAS barycentric coordinates.
910
1011
Parameters
1112
----------
1213
hits_per_ray : list[tuple[int, float, float, float]]
1314
Tuples of (face_index, u, v, distance) from libigl ray intersection
15+
M : tuple[list[list[float]], list[list[int]]]
16+
A mesh represented by a tuple of (vertices, faces)
17+
where vertices are 3D points and faces are triangles
1418
1519
Returns
1620
-------
17-
list[tuple[int, float, float, float]]
18-
Tuples of (face_index, w, u, v) in COMPAS barycentric coordinate ordering
21+
list[tuple[list[float], int, float, float, float]]
22+
Tuples of (point, face_index, u, v, w) in COMPAS barycentric coordinate ordering
1923
2024
Note
2125
----
2226
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
27+
COMPAS uses: P = u*v0 + v*v1 + w*v2 where u + v + w = 1
28+
This function converts libigl coordinates to match COMPAS barycentric coordinate ordering
2429
"""
30+
vertices = M[0]
31+
faces = M[1]
2532

2633
hits_compas = []
2734
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])
35+
idx, u_libigl, v_libigl, _ = h
36+
w = 1.0 - u_libigl - v_libigl # libigl's (1-u-v) coefficient
37+
u = u_libigl # libigl's u coefficient
38+
v = v_libigl # libigl's v coefficient
39+
40+
face = faces[idx]
41+
p1, p2, p3 = vertices[face[0]], vertices[face[1]], vertices[face[2]]
42+
point = barycenter_to_point(u, v, w, p1, p2, p3)
43+
44+
# To match COMPAS barycentric coordinates exactly:
45+
# COMPAS expects coordinates in order [p1_weight, p2_weight, p3_weight]
46+
# Our formula is P = w*p1 + u*p2 + v*p3, so COMPAS order should be [w, u, v]
47+
hits_compas.append([point, idx, u, v, w])
3148
return hits_compas
3249

3350

3451
def barycenter_to_point(u, v, w, p1, p2, p3):
35-
"""Convert COMPAS barycentric coordinates to a point.
52+
"""Convert barycentric coordinates to a point using the working interpolation formula.
3653
3754
Parameters
3855
----------
3956
u : float
40-
The u coordinate
57+
The u coordinate (weight for p2)
4158
v : float
42-
The v coordinate
59+
The v coordinate (weight for p3)
4360
w : float
44-
The w coordinate
61+
The w coordinate (weight for p1)
4562
p1 : tuple[float, float, float]
46-
The first point
63+
The first vertex
4764
p2 : tuple[float, float, float]
48-
The second point
65+
The second vertex
4966
p3 : tuple[float, float, float]
50-
The third point
51-
67+
The third vertex
5268
5369
Returns
5470
-------
55-
list[float]
56-
The point at the intersection of the ray and the mesh
71+
Point
72+
The interpolated point
5773
5874
Note
5975
----
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
76+
Uses barycentric interpolation: P = w*p1 + u*p2 + v*p3
77+
where w + u + v = 1
6278
"""
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]]
79+
phit = [w * p1[0] + u * p2[0] + v * p3[0],
80+
w * p1[1] + u * p2[1] + v * p3[1],
81+
w * p1[2] + u * p2[2] + v * p3[2]]
6682

67-
return phit
83+
return Point(*phit)
6884

6985

7086
@plugin(category="intersections")
@@ -81,21 +97,23 @@ def intersection_ray_mesh(ray, M):
8197
8298
Returns
8399
-------
84-
list[tuple[int, float, float, float]]
100+
list[tuple[list[float], int, float, float, float]]
85101
The array contains a tuple per intersection of the ray with the mesh.
86102
Each tuple contains:
87103
88-
0. the index of the intersected face
89-
1. 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
91-
3. the distance between the ray origin and the hit
104+
0. the point of intersection
105+
1. the index of the intersected face
106+
2. the u coordinate of the intersection in COMPAS barycentric coordinates
107+
3. the v coordinate of the intersection in COMPAS barycentric coordinates
108+
4. the w coordinate of the intersection in COMPAS barycentric coordinates
109+
92110
93111
Note
94112
----
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
113+
The returned barycentric coordinates follow COMPAS convention where:
114+
- For a triangle with vertices (p1, p2, p3) at face indices F[face_id]
115+
- The intersection point P = u*p1 + v*p2 + w*p3 where u + v + w = 1
116+
- These coordinates match those returned by compas.geometry.barycentric_coordinates
99117
"""
100118
point, vector = ray
101119
vertices, faces = M
@@ -107,7 +125,7 @@ def intersection_ray_mesh(ray, M):
107125
hits_per_ray = _intersections.intersection_ray_mesh(P, D, V, F)
108126

109127
# Convert libigl barycentric coordinates to COMPAS convention
110-
hits_compas = _conversion_libigl_to_compas(hits_per_ray)
128+
hits_compas = _conversion_libigl_to_compas(hits_per_ray, M)
111129

112130
return hits_compas
113131

@@ -125,14 +143,16 @@ def intersection_rays_mesh(rays, M):
125143
126144
Returns
127145
-------
128-
list[list[tuple[int, float, float, float]]]
146+
list[list[tuple[list[float], int, float, float, float]]]
129147
List of intersection results, one per ray.
130148
Each intersection result contains tuples with:
131149
132-
0. the index of the intersected face
133-
1. 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
135-
3. the distance between the ray origin and the hit
150+
0. the point of intersection
151+
1. the index of the intersected face
152+
2. the u coordinate of the intersection in COMPAS barycentric coordinates
153+
3. the v coordinate of the intersection in COMPAS barycentric coordinates
154+
4. the w coordinate of the intersection in COMPAS barycentric coordinates
155+
136156
"""
137157
points, vectors = zip(*rays)
138158
vertices, faces = M
@@ -146,6 +166,6 @@ def intersection_rays_mesh(rays, M):
146166
# Convert libigl barycentric coordinates to COMPAS convention
147167
hits_per_ray_compas = []
148168
for hit in hits_per_ray:
149-
hits_per_ray_compas.append(_conversion_libigl_to_compas(hit))
169+
hits_per_ray_compas.append(_conversion_libigl_to_compas(hit, M))
150170

151171
return hits_per_ray_compas

0 commit comments

Comments
 (0)