144 lines
4.4 KiB
Python
144 lines
4.4 KiB
Python
from typing import List
|
|
|
|
import numpy as np
|
|
from specklepy.objects.geometry import Vector
|
|
from specklepy.objects.other import Transform as SpeckleTransform
|
|
|
|
|
|
def calculate_polygon_normal(vertices: List[Vector]) -> Vector:
|
|
"""
|
|
Calculate the normal vector for a polygon represented by a list of vertices.
|
|
|
|
Args:
|
|
vertices (List[Vector]): A list of vertices representing the polygon.
|
|
|
|
Returns:
|
|
Vector: The normal vector of the polygon.
|
|
"""
|
|
normal = Vector.from_list([0.0, 0.0, 0.0])
|
|
num_vertices = len(vertices)
|
|
for i in range(num_vertices):
|
|
curr, nxt = vertices[i], vertices[(i + 1) % num_vertices]
|
|
# Cross product components are accumulated to find the normal.
|
|
normal.x += (curr.y - nxt.y) * (curr.z + nxt.z)
|
|
normal.y += (curr.z - nxt.z) * (curr.x + nxt.x)
|
|
normal.z += (curr.x - nxt.x) * (curr.y + nxt.y)
|
|
|
|
# Normalize the calculated normal vector.
|
|
length = np.sqrt(normal.x**2 + normal.y**2 + normal.z**2)
|
|
normal.x, normal.y, normal.z = (
|
|
normal.x / length,
|
|
normal.y / length,
|
|
normal.z / length,
|
|
)
|
|
return normal
|
|
|
|
|
|
def is_point_within_triangle(pt: Vector, v1: Vector, v2: Vector, v3: Vector) -> bool:
|
|
"""
|
|
Check if a point is inside a given triangle.
|
|
|
|
Args:
|
|
pt (Vector): The point to check.
|
|
v1, v2, v3 (Vector): The vertices of the triangle.
|
|
|
|
Returns:
|
|
bool: True if the point is inside the triangle, False otherwise.
|
|
"""
|
|
|
|
def sign(p1, p2, p3):
|
|
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
|
|
|
|
b1 = sign(pt, v1, v2) < 0.0
|
|
b2 = sign(pt, v2, v3) < 0.0
|
|
b3 = sign(pt, v3, v1) < 0.0
|
|
|
|
return (b1 == b2) and (b2 == b3)
|
|
|
|
|
|
def triangulate_face(vertices: List[Vector]) -> List[List[int]]:
|
|
"""
|
|
Triangulate a polygon defined by a list of vertices.
|
|
|
|
Args:
|
|
vertices (List[Vector]): The vertices of the polygon.
|
|
|
|
Returns:
|
|
List[List[int]]: A list of triangles, each represented as a list of vertex indices.
|
|
"""
|
|
triangles = []
|
|
indices = list(range(len(vertices)))
|
|
normal = calculate_polygon_normal(vertices)
|
|
|
|
# The ear clipping algorithm is used for triangulation.
|
|
while len(indices) > 2:
|
|
for i in range(len(indices)):
|
|
prev, curr, nxt = (
|
|
indices[i - 1],
|
|
indices[i],
|
|
indices[(i + 1) % len(indices)],
|
|
)
|
|
if is_convex(vertices[prev], vertices[curr], vertices[nxt], normal):
|
|
triangles.append([prev, curr, nxt])
|
|
del indices[i]
|
|
break
|
|
|
|
return triangles
|
|
|
|
|
|
def is_convex(a: Vector, b: Vector, c: Vector, normal: Vector) -> bool:
|
|
"""
|
|
Check if a triangle formed by three vertices (a, b, c) is convex with respect to a given normal.
|
|
|
|
Args:
|
|
a (Vector): The first vertex of the triangle.
|
|
b (Vector): The second vertex of the triangle.
|
|
c (Vector): The third vertex of the triangle.
|
|
normal (Vector): The normal vector with respect to which convexity is checked.
|
|
|
|
Returns:
|
|
bool: True if the triangle is convex with respect to the normal, False otherwise.
|
|
"""
|
|
ab = Vector.from_list([b.x - a.x, b.y - a.y, b.z - a.z])
|
|
bc = Vector.from_list([c.x - b.x, c.y - b.y, c.z - b.z])
|
|
cross = Vector.from_list(
|
|
[
|
|
ab.y * bc.z - ab.z * bc.y,
|
|
ab.z * bc.x - ab.x * bc.z,
|
|
ab.x * bc.y - ab.y * bc.x,
|
|
]
|
|
)
|
|
|
|
# Dot product to compare with the face normal
|
|
return cross.x * normal.x + cross.y * normal.y + cross.z * normal.z > 0
|
|
|
|
|
|
def combine_transform_matrices(transforms: List[SpeckleTransform]) -> np.ndarray:
|
|
"""
|
|
Combine multiple transformation matrices into a single matrix.
|
|
|
|
Args:
|
|
transforms (List[SpeckleTransform]): A list of Speckle Transform objects.
|
|
|
|
Returns:
|
|
np.ndarray: A combined 4x4 transformation matrix.
|
|
"""
|
|
combined_matrix = np.identity(4)
|
|
for transform in transforms:
|
|
matrix = convert_speckle_transform_to_matrix(transform)
|
|
combined_matrix = np.dot(combined_matrix, matrix)
|
|
return combined_matrix
|
|
|
|
|
|
def convert_speckle_transform_to_matrix(transform: SpeckleTransform) -> np.ndarray:
|
|
"""
|
|
Convert a Speckle Transform object to a 4x4 NumPy matrix.
|
|
|
|
Args:
|
|
transform (SpeckleTransform): The Speckle Transform object.
|
|
|
|
Returns:
|
|
np.ndarray: A 4x4 transformation matrix.
|
|
"""
|
|
return np.array(transform.value).reshape(4, 4)
|