threequick 0.0.1__tar.gz → 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threequick
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A simple python library for creating 3D visualisations
5
5
  Author-email: Hannah Baldwin <contact@hannahbaldwin.net>
6
6
  License: GPL-3.0
@@ -13,6 +13,8 @@ renderable_objects = []
13
13
  tc = Cube(0.2)
14
14
  # pitch roll yaw
15
15
  # x y z
16
+ #tc.set_rotation(0,0,0)
17
+ #tc.set_position(0,0,0)
16
18
  tc.set_rotation(30,10,60)
17
19
  tc.set_position(1,0,0)
18
20
  renderable_objects.append(tc)
@@ -25,7 +27,7 @@ renderable_objects.append(sp)
25
27
 
26
28
  #point_cloud.add_point([0,0.25,0.25], [0,255,0])
27
29
 
28
- cam_dist = 2
30
+ cam_dist = 5
29
31
 
30
32
  camera_context = CameraContext([0,-cam_dist,0], [70,0,30], 90, window)
31
33
  camera_context.position[0] = (math.sin(math.radians(camera_context.rotation[2])) * -cam_dist) * math.sin(math.radians(camera_context.rotation[0]))
@@ -69,6 +71,7 @@ while not done:
69
71
 
70
72
  tc.set_rotation(30,10,60 + cube_angle)
71
73
  tc.set_position(math.sin(math.radians(cube_angle)),-math.cos(math.radians(cube_angle)),0)
74
+ #tc.set_position(0,0,0)
72
75
 
73
76
  # Slow rotation of camera
74
77
  #camera_context.rotation[2] += (last_ticks - current_ticks) / 80
@@ -1 +1 @@
1
- from .threequick import CameraContext, Object3d, Cube, Sphere, PointCloud
1
+ from .threequick import CameraContext, Object3d, Cube, Sphere, PointCloud, Ellipsoid
@@ -1,6 +1,12 @@
1
1
  import pygame
2
+ import pygame.font
2
3
  import numpy as np
3
4
  import math
5
+ import random
6
+ from typing import Optional, Union
7
+
8
+ pygame.font.init()
9
+ sans_font = pygame.font.Font(pygame.font.get_default_font(), 36)
4
10
 
5
11
  def transform_3d_point_4x4_mat(matrix, point):
6
12
  # Convert to homogeneous coordinates
@@ -56,7 +62,7 @@ class CameraContext:
56
62
  self.screen_size = (self.surface.get_width(), self.surface.get_height())
57
63
 
58
64
  self.min_render_distance = 0.1
59
- self.frustum_width = self.min_render_distance * math.radians(self.fov)
65
+ self.frustum_width = self.min_render_distance / (math.pi - math.radians(self.fov))
60
66
  self.frustum_height = self.frustum_width * (self.screen_size[1] / self.screen_size[0])
61
67
  #self.projection_matrix = [[nx/2, 0, 0, (nx-1)/2],
62
68
  # [0, ny/2, 0, (ny-1)/2],
@@ -75,6 +81,8 @@ class CameraContext:
75
81
  [0, 0, c, d],
76
82
  [0, 0,-1, 0]]
77
83
 
84
+ # From https://songho.ca/opengl/gl_transform.html
85
+
78
86
  self.translation_matrix = [[1, 0, 0, self.position[0]],
79
87
  [0, 1, 0, self.position[1]],
80
88
  [0, 0, 1, self.position[2]],
@@ -105,17 +113,33 @@ class Renderable():
105
113
  class Object3d(Renderable):
106
114
  def __init__(self,
107
115
  vertices: list[list[float]],
108
- edges: list[list[int]],
109
- faces: list[list[int]]) -> None:
116
+ faces: list[list[int]],
117
+ color: Optional[list[int]] = None,
118
+ face_color: list[int] = None) -> None:
110
119
  self.__mod_vertices = [None] * len(vertices)
111
120
  #self.__mod_edges = edges
112
121
  #self.__mod_faces = faces
113
122
  self.vertices = vertices
114
- self.edges = edges
115
123
  self.faces = faces
124
+ self.face_color = face_color
116
125
  self.position = [0, 0, 0]
126
+ self.color = color
117
127
  self.rotation_matrix = pry_rot_to_4x4(0, 0, 0)
118
128
  self.update_vertex_cache()
129
+ edges = set()
130
+ for face in self.faces:
131
+ edges.add((face[0], face[1]))
132
+ edges.add((face[1], face[2]))
133
+ edges.add((face[2], face[0]))
134
+ self.edges = list(edges)
135
+ if self.color is None:
136
+ if self.face_color is None:
137
+ self.face_color = []
138
+ for face in self.faces:
139
+ self.face_color.append([random.randint(0,255),random.randint(0,255),random.randint(0,255)])
140
+
141
+ for face_idx in range(len(self.faces)):
142
+ print(str(self.faces[face_idx]) + " => " + str(self.face_color[face_idx]))
119
143
 
120
144
  def set_rotation(self, pitch, roll, yaw):
121
145
  self.rotation_matrix = pry_rot_to_4x4(pitch, roll, yaw)
@@ -130,45 +154,68 @@ class Object3d(Renderable):
130
154
  vertex = transform_3d_point_4x4_mat(self.rotation_matrix, vertex)
131
155
  self.__mod_vertices[vindex] = [vertex[0] + self.position[0], vertex[1] + self.position[1], vertex[2] + self.position[2]]
132
156
 
133
- def draw(self, camera_context: CameraContext) -> None:
157
+ def draw_wireframe(self, camera_context: CameraContext) -> None:
134
158
  scr_center = (camera_context.screen_size[0] / 2, camera_context.screen_size[1] / 2)
135
159
  tpf = camera_context.transform_point
136
160
  for edge in self.edges:
137
161
  p1 = tpf(self.__mod_vertices[edge[0]])
138
162
  p2 = tpf(self.__mod_vertices[edge[1]])
139
163
  if p1 is not None and p2 is not None:
140
- pygame.draw.line(camera_context.surface, (255, 255, 255), p1, p2)
164
+ pygame.draw.line(camera_context.surface, self.color, p1, p2)
165
+
166
+ def draw(self, camera_context: CameraContext) -> None:
167
+ scr_center = (camera_context.screen_size[0] / 2, camera_context.screen_size[1] / 2)
168
+ tpf = camera_context.transform_point
169
+ for idx, face in enumerate(self.faces):
170
+ poly_points = [tpf(self.__mod_vertices[face[0]]),
171
+ tpf(self.__mod_vertices[face[1]]),
172
+ tpf(self.__mod_vertices[face[2]])]
173
+
174
+ dir_sum = 0
175
+ for edgeidx in range(3):
176
+ p1 = poly_points[edgeidx]
177
+ p2 = poly_points[(edgeidx + 1) % 3]
178
+ dir_sum += (p2[0] - p1[0]) * (p2[1] + p1[1])
179
+ backface_cull = dir_sum < 0
180
+
181
+ #if not backface_cull:
182
+ if self.color is None:
183
+ if backface_cull:
184
+ #pygame.draw.lines(camera_context.surface, self.face_color[idx], True, poly_points)
185
+ pass
186
+ else:
187
+ pygame.draw.polygon(camera_context.surface, self.face_color[idx], poly_points)
188
+
189
+
190
+ #for ptidx in range(3):
191
+ # text_surface = sans_font.render(str(face[ptidx]), False, (255,255,255))
192
+ # camera_context.surface.blit(text_surface, poly_points[ptidx])
193
+ else:
194
+ if not backface_cull:
195
+ pygame.draw.polygon(camera_context.surface, self.color, poly_points)
196
+
141
197
 
142
198
  class Cube(Object3d):
143
- edges: list[list[int]] = [
144
- [0, 1],
145
- [1, 2],
146
- [2, 3],
147
- [3, 0],
148
- [4, 5],
149
- [5, 6],
150
- [6, 7],
151
- [7, 4],
152
- [0, 4],
153
- [1, 5],
154
- [2, 6],
155
- [3, 7]
156
- ]
157
199
  faces: list[list[int]] = [
158
- [0, 1, 2],
159
- [1, 2, 3],
160
- [4, 5, 6],
161
- [5, 6, 7],
162
- [0, 1, 5],
163
- [1, 5, 4],
164
- [1, 2, 6],
165
- [2, 6, 5],
166
- [2, 3, 7],
167
- [3, 7, 6],
168
- [3, 0, 4],
169
- [0, 4, 7]
200
+ [0,1,2],
201
+ [0,2,3],
202
+
203
+ [4,7,6],
204
+ [6,5,4],
205
+
206
+ [5,2,1],
207
+ [6,2,5],
208
+
209
+ [5,1,4],
210
+ [4,1,0],
211
+
212
+ [7,0,3],
213
+ [4,0,7],
214
+
215
+ [7,3,6],
216
+ [6,3,2]
170
217
  ]
171
- def __init__(self, size) -> None:
218
+ def __init__(self, size, color = None) -> None:
172
219
  size = size/2
173
220
  vertices: list[list[float]] = [
174
221
  [-size, -size, -size],
@@ -180,17 +227,17 @@ class Cube(Object3d):
180
227
  [size, size, size],
181
228
  [-size, size, size]
182
229
  ]
183
- super().__init__(vertices, Cube.edges, Cube.faces)
230
+ super().__init__(vertices, Cube.faces, color)
184
231
 
185
- class Sphere(Object3d):
186
- def __init__(self, n_meridians, n_parallels, diameter) -> None:
232
+ class Ellipsoid(Object3d):
233
+ def __init__(self, n_meridians, n_parallels, diameter, flattening, color = None) -> None:
234
+ compression_factor = 1 - flattening
187
235
  radius = diameter/2
188
- vertices = [[0,0,radius],[0,0,-radius]]
189
- edges = []
236
+ vertices = [[0,0,radius * compression_factor],[0,0,-radius * compression_factor]]
190
237
  faces = []
191
238
  for ipa in range(n_parallels):
192
239
  vang = ((ipa + 1) / (n_parallels + 1)) * math.pi
193
- vpos = -math.cos(vang) * radius
240
+ vpos = -math.cos(vang) * radius * compression_factor
194
241
  for ime in range(n_meridians):
195
242
  ang = (ime * math.pi * 2) / n_meridians
196
243
  sa = math.sin(ang)
@@ -200,24 +247,29 @@ class Sphere(Object3d):
200
247
 
201
248
  # bottom cap
202
249
  for ime in range(n_meridians):
203
- edges.append([1,2+ime])
250
+ faces.append([2+ime,1,2+((ime+1)%n_meridians)])
204
251
 
205
252
  # top cap
206
253
  for ime in range(n_meridians):
207
- edges.append([0,2+ime+(n_meridians * (n_parallels - 1))])
254
+ bc_offset = (n_meridians * (n_parallels - 1))
255
+ faces.append([0,2+ime+bc_offset,2+((ime+1)%n_meridians)+bc_offset])
208
256
 
209
- # meridians
257
+ # panels
210
258
  for ipa in range(n_parallels - 1):
211
259
  for ime in range(n_meridians):
212
- edges.append([2+ime+(n_meridians * (ipa + 1)),2+ime+(n_meridians * ipa)])
213
-
214
- # parallels
215
- for ipa in range(n_parallels):
216
- for ime in range(n_meridians):
217
- edges.append([2+ime+(n_meridians * ipa),2+((ime + 1)%n_meridians)+(n_meridians * ipa)])
218
-
219
- super().__init__(vertices, edges, faces)
220
-
260
+ pt1 = 2+((ime + 0)%n_meridians)+(n_meridians * ipa)
261
+ pt2 = 2+((ime + 0)%n_meridians)+(n_meridians * (ipa + 1))
262
+ pt3 = 2+((ime + 1)%n_meridians)+(n_meridians * ipa)
263
+ pt4 = 2+((ime + 1)%n_meridians)+(n_meridians * (ipa + 1))
264
+ faces.append([pt2, pt1, pt3])
265
+ faces.append([pt2, pt3, pt4])
266
+ #edges.append([2+ime+(n_meridians * (ipa + 1)),2+ime+(n_meridians * ipa)])
267
+
268
+ super().__init__(vertices, faces, color)
269
+
270
+ class Sphere(Ellipsoid):
271
+ def __init__(self, n_meridians, n_parallels, diameter, color = None) -> None:
272
+ super().__init__(n_meridians, n_parallels, diameter, 0, color)
221
273
 
222
274
  class PointCloud(Renderable):
223
275
  def __init__(self, diameter = 2) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threequick
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A simple python library for creating 3D visualisations
5
5
  Author-email: Hannah Baldwin <contact@hannahbaldwin.net>
6
6
  License: GPL-3.0
@@ -10,4 +10,6 @@ src/threequick.egg-info/PKG-INFO
10
10
  src/threequick.egg-info/SOURCES.txt
11
11
  src/threequick.egg-info/dependency_links.txt
12
12
  src/threequick.egg-info/requires.txt
13
+ src/threequick.egg-info/scm_file_list.json
14
+ src/threequick.egg-info/scm_version.json
13
15
  src/threequick.egg-info/top_level.txt
@@ -0,0 +1,12 @@
1
+ {
2
+ "files": [
3
+ "README.md",
4
+ "pyproject.toml",
5
+ "LICENSE",
6
+ ".gitignore",
7
+ "scripts/build-install-test.sh",
8
+ "scripts/example.py",
9
+ "src/threequick/__init__.py",
10
+ "src/threequick/threequick.py"
11
+ ]
12
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.0.2",
3
+ "distance": 0,
4
+ "node": "g1c2fe6dc0ad76f3a32cf39120b3f25647deecce2",
5
+ "dirty": false,
6
+ "branch": "main",
7
+ "node_date": "2026-07-10"
8
+ }
File without changes
File without changes
File without changes
File without changes
File without changes