def __init__(self, point_a=None, point_b=None, point_c=None): self.point_a = point_a.clone() if point_a is not None else Vector( 0.0, 0.0, 0.0) self.point_b = point_b.clone() if point_b is not None else Vector( 0.0, 0.0, 0.0) self.point_c = point_c.clone() if point_c is not None else Vector( 0.0, 0.0, 0.0)
def __init__(self, parent=None): super().__init__(parent) self.tri_mesh_a = TriangleMesh.make_polyhedron(Polyhedron.HEXAHEDRON) radius = (Vector(math.sqrt(2.0), math.sqrt(2.0), 0.0) - Vector(0.0, 1.0, 0.0)).length() self.tri_mesh_b = Sphere(Vector(math.sqrt(2.0), math.sqrt(2.0), 0.0), radius).make_mesh(subdivision_level=2) self.tri_mesh_c = Sphere(Vector(-math.sqrt(2.0), math.sqrt(2.0), 0.0), radius).make_mesh(subdivision_level=2) #transform = AffineTransform(translation=Vector(-1.0, 0.0, 0.0)) #self.tri_mesh_a = transform(self.tri_mesh_a) #transform = AffineTransform(translation=Vector(1.0, 0.0, -0.5)) #self.tri_mesh_b = transform(self.tri_mesh_b) #self.tri_mesh_a = TriangleMesh() #self.tri_mesh_a.add_triangle(Triangle(Vector(0.0, 0.0, 0.0), Vector(5.0, 0.0, 0.0), Vector(0.0, 5.0, 0.0))) #self.tri_mesh_b = TriangleMesh() #self.tri_mesh_b.add_triangle(Triangle(Vector(1.0, 1.0, 3.0), Vector(4.0, 4.0, 3.0), Vector(1.0, 1.0, -2.0))) #self.tri_mesh_b.add_triangle(Triangle(Vector(1.0, 1.0, -2.0), Vector(4.0, 4.0, 3.0), Vector(5.0, 1.0, 0.0))) #self.tri_mesh_b.add_triangle(Triangle(Vector(1.0, 1.0, 3.0), Vector(1.0, 1.0, -2.0), Vector(-2.0, -6.0, -2.0))) self.back_mesh = None self.front_mesh = None self.orient = Vector(0.0, 0.0, 0.0) self.dragging_mouse = False self.drag_pos = None self.zoom = 5.0
def __init__(self, x_axis=None, y_axis=None, z_axis=None): super().__init__() self.x_axis = x_axis.clone() if x_axis is not None else Vector( 1.0, 0.0, 0.0) self.y_axis = y_axis.clone() if y_axis is not None else Vector( 0.0, 1.0, 0.0) self.z_axis = z_axis.clone() if z_axis is not None else Vector( 0.0, 0.0, 1.0)
def from_dict(self, data): super().from_dict(data) self.color = Vector().from_dict(data.get('color', {})) self.alpha = data.get('alpha', 1.0) self.uv_list = [Vector().from_dict(uv) for uv in data.get('uv_list', [])] self.normal_list = [Vector().from_dict(normal) for normal in data.get('normal_list', [])] self.texture_number = data.get('texture_number', -1) self.border_loop_list = data.get('border_loop_list', []) return self
def __init__(self, mesh=None, center=None, axis=None, angle=None, pick_point=None, min_capture_count=None, max_capture_count=None): super().__init__(mesh=mesh) self.center = center if center is not None else Vector(0.0, 0.0, 0.0) self.axis = axis if axis is not None else Vector(0.0, 0.0, 1.0) self.angle = angle if angle is not None else 0.0 self.pick_point = pick_point self.capture_tree_root = None self.min_capture_count = min_capture_count self.max_capture_count = max_capture_count self.fixed_label = ''
def from_dict(self, data): super().from_dict(data) self.center = Vector().from_dict(data.get('center', {})) self.axis = Vector().from_dict(data.get('axis', {})) self.angle = data.get('angle', 0.0) self.pick_point = Vector().from_dict(data.get('pick_point')) if data.get('pick_point') is not None else None self.capture_tree_root = data.get('capture_tree_root') self.min_capture_count = data.get('min_capture_count') self.max_capture_count = data.get('max_capture_count') self.fixed_label = data.get('fixed_label', '') return self
def from_dict(self, data): self.vertex_list = [ Vector().from_dict(vertex) for vertex in data.get('vertex_list', []) ] self.triangle_list = [(triple[0], triple[1], triple[2]) for triple in data.get('triangle_list', [])] return self
def make_mesh(self, subdivision_level=1): from math3d_point_cloud import PointCloud from math3d_transform import AffineTransform spine = self.line_segment.point_b - self.line_segment.point_a spine_length = spine.length() transform = AffineTransform().make_frame(spine, self.line_segment.point_a) count = 4 * (subdivision_level + 1) point_cloud = PointCloud() for i in range(count): angle = 2.0 * math.pi * float(i) / float(count) vertex = Vector(self.radius * math.cos(angle), self.radius * math.sin(angle), 0.0) point_cloud.point_list.append(transform(vertex)) point_cloud.point_list.append( transform(vertex + Vector(0.0, 0.0, spine_length))) return point_cloud.find_convex_hull()
def __init__(self, mesh=None, color=None, alpha=1.0): super().__init__(mesh=mesh) self.color = color if color is not None else Vector(0.0, 0.0, 0.0) self.alpha = alpha self.uv_list = [] self.normal_list = [] self.texture_number = -1 self.border_loop_list = []
def __init__(self, x_axis=None, y_axis=None, z_axis=None, translation=None): super().__init__() self.linear_transform = LinearTransform(x_axis, y_axis, z_axis) self.translation = translation.clone( ) if translation is not None else Vector(0.0, 0.0, 0.0)
def __init__(self, puzzle_class_name, parent=None): super().__init__(parent) self.orient = Vector(0.0, 0.0, 0.0) self.dragging_mouse = False self.drag_pos = None self.zoom = 5.0 self.puzzle_preview = PuzzlePreview(puzzle_class_name)
def calc_vertex_normals(self): normal_list = [] for i, vertex in enumerate(self.vertex_list): normal = Vector(0.0, 0.0, 0.0) for triple in self.triangle_list: if any([triple[j] == i for j in range(3)]): triangle = self.make_triangle(triple) try: plane = triangle.calc_plane() except Exception as ex: error = str(ex) error = None else: normal += plane.unit_normal normal = normal.normalized() if normal is None: normal = Vector(1.0, 0.0, 0.0) normal_list.append(normal) return normal_list
def make_initial_mesh_list(self): # Most, but not all puzzles are based on the cube with the following standard colors. cube_mesh = TriangleMesh().make_polyhedron(Polyhedron.HEXAHEDRON) l_mesh = ColoredMesh(color=Vector(0.0, 0.0, 1.0)) r_mesh = ColoredMesh(color=Vector(0.0, 1.0, 0.0)) d_mesh = ColoredMesh(color=Vector(1.0, 1.0, 1.0)) u_mesh = ColoredMesh(color=Vector(1.0, 1.0, 0.0)) b_mesh = ColoredMesh(color=Vector(1.0, 0.5, 0.0)) f_mesh = ColoredMesh(color=Vector(1.0, 0.0, 0.0)) for triangle in cube_mesh.yield_triangles(): if all([triangle[i].x == -1.0 for i in range(3)]): l_mesh.add_triangle(triangle) elif all([triangle[i].x == 1.0 for i in range(3)]): r_mesh.add_triangle(triangle) elif all([triangle[i].y == -1.0 for i in range(3)]): d_mesh.add_triangle(triangle) elif all([triangle[i].y == 1.0 for i in range(3)]): u_mesh.add_triangle(triangle) elif all([triangle[i].z == -1.0 for i in range(3)]): b_mesh.add_triangle(triangle) elif all([triangle[i].z == 1.0 for i in range(3)]): f_mesh.add_triangle(triangle) return [l_mesh, r_mesh, d_mesh, u_mesh, b_mesh, f_mesh]
def make_standard_cube_faces_using_base_mesh(self, base_mesh): l_mesh = ColoredMesh(mesh=AffineTransform().make_rigid_body_motion(Vector(0.0, 1.0, 0.0), -math.pi / 2.0, Vector(-1.0, 0.0, 0.0))(base_mesh), color=Vector(0.0, 0.0, 1.0)) r_mesh = ColoredMesh(mesh=AffineTransform().make_rigid_body_motion(Vector(0.0, 1.0, 0.0), math.pi / 2.0, Vector(1.0, 0.0, 0.0))(base_mesh), color=Vector(0.0, 1.0, 0.0)) d_mesh = ColoredMesh(mesh=AffineTransform().make_rigid_body_motion(Vector(1.0, 0.0, 0.0), math.pi / 2.0, Vector(0.0, -1.0, 0.0))(base_mesh), color=Vector(1.0, 1.0, 1.0)) u_mesh = ColoredMesh(mesh=AffineTransform().make_rigid_body_motion(Vector(1.0, 0.0, 0.0), -math.pi / 2.0, Vector(0.0, 1.0, 0.0))(base_mesh), color=Vector(1.0, 1.0, 0.0)) b_mesh = ColoredMesh(mesh=AffineTransform().make_rigid_body_motion(Vector(1.0, 0.0, 0.0), math.pi, Vector(0.0, 0.0, -1.0))(base_mesh), color=Vector(1.0, 0.5, 0.0)) f_mesh = ColoredMesh(mesh=AffineTransform().make_rigid_body_motion(Vector(1.0, 0.0, 0.0), 0.0, Vector(0.0, 0.0, 1.0))(base_mesh), color=Vector(1.0, 0.0, 0.0)) return [l_mesh, r_mesh, d_mesh, u_mesh, b_mesh, f_mesh]
def _render_puzzle(self, mesh_list): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) viewport = glGetIntegerv(GL_VIEWPORT) width = viewport[2] height = viewport[3] aspect_ratio = float(width) / float(height) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(60.0, aspect_ratio, 0.1, 1000.0) glMatrixMode(GL_MODELVIEW) glLoadIdentity() gluLookAt(0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) orient = Vector(35.0, -45.0, 0.0) glPushMatrix() glRotatef(orient.x, 1.0, 0.0, 0.0) glRotatef(orient.y, 0.0, 1.0, 0.0) glRotatef(orient.z, 0.0, 0.0, 1.0) #glEnable(GL_LIGHTING) glDisable(GL_LIGHTING) for mesh in mesh_list: if mesh.alpha > 0.0: mesh.render() glDisable(GL_LIGHTING) for mesh in mesh_list: if mesh.alpha > 0.0: mesh.render_border() glPopMatrix() glFlush()
def render(self, random_colors=False): from OpenGL.GL import GL_TRIANGLES, glBegin, glEnd, glVertex3f, glNormal3f, glColor3f glBegin(GL_TRIANGLES) try: for triangle in self.yield_triangles(): plane = triangle.calc_plane() glNormal3f(plane.unit_normal.x, plane.unit_normal.y, plane.unit_normal.z) if random_colors: color = Vector().random() glColor3f(color.x, color.y, color.z) glVertex3f(triangle.point_a.x, triangle.point_a.y, triangle.point_a.z) glVertex3f(triangle.point_b.x, triangle.point_b.y, triangle.point_b.z) glVertex3f(triangle.point_c.x, triangle.point_c.y, triangle.point_c.z) except Exception as ex: error = str(ex) error = None finally: glEnd()
def make_disk(center, unit_normal, radius, sides): from math3d_transform import AffineTransform transform = AffineTransform(translation=center, z_axis=unit_normal) transform.linear_transform.x_axis = unit_normal.perpendicular_vector( ).normalized() transform.linear_transform.y_axis = unit_normal.cross( transform.linear_transform.x_axis) vertex_list = [] for i in range(sides): angle = float(i) / float(sides) * 2.0 * math.pi vertex = Vector(radius * math.cos(angle), radius * math.sin(angle), 0.0) vertex = transform(vertex) vertex_list.append(vertex) mesh = TriangleMesh() for i in range(sides): triangle = Triangle(vertex_list[i], vertex_list[(i + 1) % sides], center) mesh.add_triangle(triangle) return mesh
def make_uniform_scale(self, scale): self.x_axis = Vector(scale, 0.0, 0.0) self.y_axis = Vector(0.0, scale, 0.0) self.z_axis = Vector(0.0, 0.0, scale) return self
def make_polyhedron(polyhedron): from math3d_point_cloud import PointCloud point_cloud = PointCloud() phi = (1.0 + 5.0**0.5) / 2.0 if polyhedron == Polyhedron.TETRAHEDRON: point_cloud.point_list = [ point for point in Vector(1.0, 0.0, -1.0 / math.sqrt(2.0)).sign_permute( True, False, False) ] point_cloud.point_list += [ point for point in Vector(0.0, 1.0, 1.0 / math.sqrt(2.0)).sign_permute( False, True, False) ] elif polyhedron == Polyhedron.HEXAHEDRON: point_cloud.point_list = [ point for point in Vector(1.0, 1.0, 1.0).sign_permute() ] elif polyhedron == Polyhedron.ICOSAHEDRON: point_cloud.point_list = [ point for point in Vector(0.0, 1.0, phi).sign_permute( False, True, True) ] point_cloud.point_list += [ point for point in Vector(phi, 0.0, 1.0).sign_permute( True, False, True) ] point_cloud.point_list += [ point for point in Vector(1.0, phi, 0.0).sign_permute( True, True, False) ] elif polyhedron == Polyhedron.DODECAHEDRON: point_cloud.point_list = [ point for point in Vector(1.0, 1.0, 1.0).sign_permute() ] point_cloud.point_list += [ point for point in Vector(0.0, phi, 1.0 / phi).sign_permute(False, True, True) ] point_cloud.point_list += [ point for point in Vector(1.0 / phi, 0.0, phi).sign_permute( True, False, True) ] point_cloud.point_list += [ point for point in Vector(phi, 1.0 / phi, 0.0).sign_permute(True, True, False) ] elif polyhedron == Polyhedron.ICOSIDODECAHEDRON: point_cloud.point_list = [ point for point in Vector(phi, 0.0, 0.0).sign_permute( True, False, False) ] point_cloud.point_list += [ point for point in Vector(0.0, phi, 0.0).sign_permute( False, True, False) ] point_cloud.point_list += [ point for point in Vector(0.0, 0.0, phi).sign_permute( False, False, True) ] point_cloud.point_list += [ point for point in Vector(0.5, phi / 2.0, phi * phi / 2.0).sign_permute() ] point_cloud.point_list += [ point for point in Vector(phi * phi / 2.0, 0.5, phi / 2.0).sign_permute() ] point_cloud.point_list += [ point for point in Vector(phi / 2.0, phi * phi / 2.0, 0.5).sign_permute() ] elif polyhedron == Polyhedron.TRUNCATED_TETRAHEDRON: point_cloud.point_list = [ Vector(3.0, 1.0, 1.0), Vector(-3.0, -1.0, 1.0), Vector(-3.0, 1.0, -1.0), Vector(3.0, -1.0, -1.0) ] point_cloud.point_list += [ Vector(1.0, 3.0, 1.0), Vector(-1.0, -3.0, 1.0), Vector(-1.0, 3.0, -1.0), Vector(1.0, -3.0, -1.0) ] point_cloud.point_list += [ Vector(1.0, 1.0, 3.0), Vector(-1.0, -1.0, 3.0), Vector(-1.0, 1.0, -3.0), Vector(1.0, -1.0, -3.0) ] elif polyhedron == Polyhedron.TRUNCATED_OCTAHEDRON: point_cloud.point_list += [ point for point in Vector(0.0, 1.0, 2.0).sign_permute() ] return point_cloud.find_convex_hull()
def fit_plane(self): # f(x,y,z) = ax + by + cz + d is the plane equation. # For m points {p_i}, we want to find coefficients a, b, c, d so # that all equations f(p_i)=0 are satisfied or as near satisfied # as they can be in the sense that |f(p_i)| are all minimized. # The least squares method has us define F(a,b,c,d) = Sum_i f^2(p_i), # and then we simply solve for a, b, c, d by setting each of # dF/da, dF/db, dF/dc, dF/dd to zero, which gives us a homogeneous # system of linear equations. We find a non-trivial solution by # looking for an eigenvector with the smallest associated value. import numpy matrix = [[0.0 for i in range(4)] for j in range(4)] sum_xx = sum([point.x * point.x for point in self.point_list]) sum_yy = sum([point.y * point.y for point in self.point_list]) sum_zz = sum([point.z * point.z for point in self.point_list]) sum_xy = sum([point.x * point.y for point in self.point_list]) sum_xz = sum([point.x * point.z for point in self.point_list]) sum_yz = sum([point.y * point.z for point in self.point_list]) sum_x = sum([point.x for point in self.point_list]) sum_y = sum([point.y for point in self.point_list]) sum_z = sum([point.z for point in self.point_list]) matrix[0][0] = sum_xx matrix[0][1] = sum_xy matrix[0][2] = sum_xz matrix[0][3] = sum_x matrix[1][0] = sum_xy matrix[1][1] = sum_yy matrix[1][2] = sum_yz matrix[1][3] = sum_y matrix[2][0] = sum_xz matrix[2][1] = sum_yz matrix[2][2] = sum_zz matrix[2][3] = sum_z matrix[3][0] = sum_x matrix[3][1] = sum_y matrix[3][2] = sum_z matrix[3][3] = float(len(self.point_list)) matrix = numpy.array(matrix) w, v = numpy.linalg.eig(matrix) j = -1 eps = 1e-5 smallest_value = None for i in range(4): value = w[i] if isinstance(value, complex): if abs(value.imag) > eps: continue # Only consider real eigen values. value = value.real if smallest_value is None or abs(value) < abs(smallest_value): smallest_value = value j = i normal = Vector(v[0][j], v[1][j], v[2][j]) normal.x = normal.x.real if isinstance(normal.x, complex) else normal.x normal.y = normal.y.real if isinstance(normal.y, complex) else normal.y normal.z = normal.z.real if isinstance(normal.z, complex) else normal.z length = normal.length() unit_normal = normal / length alpha = v[3][j].real if isinstance(v[3][j], complex) else v[3][j] center = -(alpha / length) * unit_normal plane = Plane(center, unit_normal) return plane
def from_dict(self, data): self.point_list = [ Vector().from_dict(point) for point in data.get('point_list', []) ] return self
def calc_center(self): center = Vector(0.0, 0.0, 0.0) for point in self.point_list: center = center + point center = center * (1.0 / float(len(self.point_list))) return center
def from_dict(self, data): self.center = Vector().from_dict(data.get('center', {})) self.unit_normal = Vector().from_dict(data.get('unit_normal', {})) return self
def make_frame(self, z_axis, translation=None): self.linear_transform.make_frame(z_axis) self.translation = translation.clone( ) if translation is not None else Vector(0.0, 0.0, 0.0) return self
def make_face_meshes(self, mesh): face_mesh_list = [] plane_list = [] color_list = [ Vector(1.0, 0.0, 0.0), Vector(0.0, 1.0, 0.0), Vector(0.0, 0.0, 1.0), Vector(1.0, 1.0, 0.0), Vector(1.0, 0.0, 1.0), Vector(0.0, 1.0, 1.0), Vector(1.0, 0.0, 0.5), Vector(1.0, 0.5, 0.0), Vector(0.0, 1.0, 0.5), Vector(0.5, 1.0, 0.0), Vector(0.0, 0.5, 1.0), Vector(0.5, 0.0, 1.0), Vector(0.5, 0.5, 0.5) ] j = 0 while len(mesh.triangle_list) > 0: triple = mesh.triangle_list.pop(0) triangle = mesh.make_triangle(triple) triangle_list = [triangle] plane = triangle.calc_plane() plane_list.append(plane) i = 0 while i < len(mesh.triangle_list): triangle = mesh.make_triangle(i) if all([plane.side(triangle[i]) == Side.NEITHER for i in range(3)]): triangle_list.append(triangle) del mesh.triangle_list[i] else: i += 1 if j < len(color_list): color = color_list[j] j += 1 else: color = Vector().random() face_mesh = ColoredMesh(color=color, mesh=TriangleMesh().from_triangle_list(triangle_list)) face_mesh_list.append(face_mesh) return face_mesh_list, plane_list
def make_non_uniform_scale(self, x_scale, y_scale, z_scale): self.x_axis = Vector(x_scale, 0.0, 0.0) self.y_axis = Vector(0.0, y_scale, 0.0) self.z_axis = Vector(0.0, 0.0, z_scale) return self
def from_dict(self, data): self.x_axis = Vector().from_dict(data.get('x_axis')) self.y_axis = Vector().from_dict(data.get('y_axis')) self.z_axis = Vector().from_dict(data.get('z_axis')) return self
def calculate_uvs(self, final_mesh_list): class TexturePlane(object): def __init__(self, plane, mesh): if plane.center.dot(plane.unit_normal) < 0.0: plane.unit_normal = -plane.unit_normal self.plane = plane self.mesh_list = [mesh] def is_parallel_with(self, other, eps=1e-7): dot = self.plane.unit_normal.dot(other.plane.unit_normal) dot = min(max(dot, -1.0), 1.0) angle = math.acos(dot) return math.fabs(angle) < eps def is_further_than(self, other): return self.plane.center.length() > other.plane.center.length() def make_texture_space_transform(self): # TODO: Make sure Y-axis is as close to actual Y-axis as possible. x_axis = self.plane.unit_normal.perpendicular_vector().normalized() y_axis = self.plane.unit_normal.cross(x_axis) z_axis = self.plane.unit_normal.clone() transform = AffineTransform(x_axis=x_axis, y_axis=y_axis, z_axis=z_axis, translation=self.plane.center) inverse_transform = transform.calc_inverse() return inverse_transform # Determine all texture planes and assign a list of meshes to each plane. plane_list = [] for face_mesh in final_mesh_list: if face_mesh.alpha == 0.0: continue new_plane = TexturePlane(PointCloud(face_mesh.vertex_list).fit_plane(), face_mesh) for i, plane in enumerate(plane_list): if new_plane.is_parallel_with(plane): if new_plane.is_further_than(plane): new_plane.mesh_list += plane.mesh_list plane_list[i] = new_plane else: plane.mesh_list += new_plane.mesh_list break else: plane_list.append(new_plane) # Process each texture plane. for i, plane in enumerate(plane_list): # Assign a texture number to all meshes associated with the plane. for face_mesh in plane.mesh_list: face_mesh.texture_number = i # Make the transform taking us from model space to texture space. texture_transform = self.make_texture_space_transform_for_plane(plane.plane) if texture_transform is None: texture_transform = plane.make_texture_space_transform() # Calculate the extents of the texture space. x_min = 1000.0 x_max = -1000.0 y_min = 1000.0 y_max = -1000.0 for face_mesh in plane.mesh_list: vertex_list = [texture_transform(vertex) for vertex in face_mesh.vertex_list] for vertex in vertex_list: if vertex.x < x_min: x_min = vertex.x if vertex.x > x_max: x_max = vertex.x if vertex.y < y_min: y_min = vertex.y if vertex.y > y_max: y_max = vertex.y # Fix the aspect ratio of those extents so that the texture is not distorted. x_delta = x_max - x_min y_delta = y_max - y_min if x_delta > y_delta: delta = (x_delta - y_delta) * 0.5 y_min -= delta y_max += delta elif x_delta < y_delta: delta = (y_delta - x_delta) * 0.5 x_min -= delta x_max += delta # Finally, go assign texture coordinates to each face mesh vertex. for face_mesh in plane.mesh_list: face_mesh.uv_list = [] vertex_list = [texture_transform(vertex) for vertex in face_mesh.vertex_list] for vertex in vertex_list: u = (vertex.x - x_min) / (x_max - x_min) v = (vertex.y - y_min) / (y_max - y_min) face_mesh.uv_list.append(Vector(u, v, 0.0))
def make_rotation(self, unit_axis, angle): self.x_axis = Vector(1.0, 0.0, 0.0).rotated(unit_axis, angle) self.y_axis = Vector(0.0, 1.0, 0.0).rotated(unit_axis, angle) self.z_axis = Vector(0.0, 0.0, 1.0).rotated(unit_axis, angle) return self
def make_identity(self): self.x_axis = Vector(1.0, 0.0, 0.0) self.y_axis = Vector(0.0, 1.0, 0.0) self.z_axis = Vector(0.0, 0.0, 1.0) return self