-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhull_3d_tests.py
More file actions
154 lines (115 loc) · 4.51 KB
/
Copy pathhull_3d_tests.py
File metadata and controls
154 lines (115 loc) · 4.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# hull_2d/test_wrapping3d.py
from __future__ import annotations
import os
import random
from typing import List, Tuple, Dict
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib import animation
from hull_3d.geometry import Point3
from hull_3d.gift_wrapping import convex_hull_3d_wrapping
def generate_random_points_3d_unique(n: int, lo: int = 0, hi: int = 1000) -> List[Point3]:
"""Unique integer 3D points."""
pts = set()
while len(pts) < n:
pts.add((random.randint(lo, hi), random.randint(lo, hi), random.randint(lo, hi)))
return list(pts)
def set_axes_equal(ax) -> None:
"""
Make 3D axes have equal scale so the hull isn't visually distorted.
Matplotlib doesn't do this by default for 3D.
"""
x_limits = ax.get_xlim3d()
y_limits = ax.get_ylim3d()
z_limits = ax.get_zlim3d()
x_range = abs(x_limits[1] - x_limits[0])
y_range = abs(y_limits[1] - y_limits[0])
z_range = abs(z_limits[1] - z_limits[0])
x_middle = (x_limits[0] + x_limits[1]) / 2
y_middle = (y_limits[0] + y_limits[1]) / 2
z_middle = (z_limits[0] + z_limits[1]) / 2
plot_radius = 0.5 * max([x_range, y_range, z_range])
ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])
def build_poly_collection(points: List[Point3], faces: List[Tuple[int, int, int]]) -> Poly3DCollection:
"""
Convert face index triples into a Poly3DCollection for plotting.
"""
polys = []
for (i, j, k) in faces:
polys.append([points[i], points[j], points[k]])
poly = Poly3DCollection(polys, alpha=0.25, edgecolor="k", linewidths=0.3)
return poly
def save_static_plot(points: List[Point3], faces: List[Tuple[int, int, int]], out_png: str) -> None:
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
xs = [p[0] for p in points]
ys = [p[1] for p in points]
zs = [p[2] for p in points]
ax.scatter(xs, ys, zs, s=6)
poly = build_poly_collection(points, faces)
ax.add_collection3d(poly)
ax.set_title("3D Convex Hull (Facet Wrapping)")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
# reasonable initial view
ax.view_init(elev=20, azim=35)
set_axes_equal(ax)
plt.savefig(out_png, dpi=150, bbox_inches="tight")
plt.close(fig)
def save_rotation(points: List[Point3], faces: List[Tuple[int, int, int]], out_gif: str, out_mp4: str) -> None:
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
xs = [p[0] for p in points]
ys = [p[1] for p in points]
zs = [p[2] for p in points]
ax.scatter(xs, ys, zs, s=6)
poly = build_poly_collection(points, faces)
ax.add_collection3d(poly)
ax.set_title("3D Convex Hull (Rotating)")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
set_axes_equal(ax)
# Animation update: rotate azimuth
def update(frame: int):
ax.view_init(elev=20, azim=frame)
return (poly,)
frames = list(range(0, 360, 4)) # 0..356 degrees
anim = animation.FuncAnimation(fig, update, frames=frames, interval=50, blit=False)
# Try GIF first (Pillow), otherwise MP4 (ffmpeg)
saved = False
try:
writer = animation.PillowWriter(fps=20)
anim.save(out_gif, writer=writer)
saved = True
except Exception as e:
print("Could not save GIF (Pillow missing?). Reason:", e)
if not saved:
try:
writer = animation.FFMpegWriter(fps=20)
anim.save(out_mp4, writer=writer)
saved = True
except Exception as e:
print("Could not save MP4 (ffmpeg missing?). Reason:", e)
plt.close(fig)
if __name__ == "__main__":
random.seed(42)
os.makedirs("plots/3d_plots", exist_ok=True)
n = 180
points = generate_random_points_3d_unique(n, 0, 1000)
faces = convex_hull_3d_wrapping(points, return_adjacency=False)
print("Total points:", len(points))
print("Hull faces:", len(faces))
out_png = os.path.join("plots/3d_plots", "hull_static.png")
save_static_plot(points, faces, out_png)
print("Saved:", out_png)
out_gif = os.path.join("plots/3d_plots", "hull_rotate.gif")
out_mp4 = os.path.join("plots/3d_plots", "hull_rotate.mp4")
save_rotation(points, faces, out_gif, out_mp4)
if os.path.exists(out_gif):
print("Saved:", out_gif)
if os.path.exists(out_mp4):
print("Saved:", out_mp4)