Files
AnyImage/src/snapform/operators/balloon/poisson.py
nan 13ef705226 Add operators for model downloading and surface generation
- Implemented `DownloadModel` and `DownloadRequiredModels` operators for downloading models.
- Created `ClearModels` operator to clear model resources.
- Added `ConvertToSurface` operator for converting image empties into textured surfaces.
- Introduced `GenerateDa3Surface` and `GenerateMoge2Surface` operators for generating surfaces using DA3 and MoGe-2 models respectively.
- Developed utility functions for handling image mesh data, assigning attributes, and managing geometry nodes.
- Established runtime configuration and post-installation model downloading in `runtime.py`.
2026-08-20 01:56:04 +08:00

124 lines
4.1 KiB
Python

import math
def cotangent_poisson_field(
planar_points,
planar_faces,
boundary_mask,
tolerance=1e-8,
):
import numpy as np
points = np.asarray(planar_points, dtype=np.float64)
boundary_mask = np.asarray(boundary_mask, dtype=bool)
loads = np.zeros(len(points), dtype=np.float64)
edge_weights = {}
def add_edge(start, end, weight):
edge = tuple(sorted((int(start), int(end))))
edge_weights[edge] = edge_weights.get(edge, 0.0) + weight
for face in planar_faces:
first, second, third = (int(index) for index in face)
a, b, c = points[[first, second, third]]
cross = abs(
(b[0] - a[0]) * (c[1] - a[1])
- (b[1] - a[1]) * (c[0] - a[0])
)
if cross <= 1e-14:
continue
area = cross * 0.5
loads[[first, second, third]] += area / 3.0
cotangent_a = float((b - a).dot(c - a) / cross)
cotangent_b = float((a - b).dot(c - b) / cross)
cotangent_c = float((a - c).dot(b - c) / cross)
add_edge(second, third, cotangent_a * 0.5)
add_edge(first, third, cotangent_b * 0.5)
add_edge(first, second, cotangent_c * 0.5)
interior = np.flatnonzero(~boundary_mask)
field = np.zeros(len(points), dtype=np.float64)
if not len(interior):
return field
compact = np.full(len(points), -1, dtype=np.int64)
compact[interior] = np.arange(len(interior), dtype=np.int64)
diagonal = np.zeros(len(interior), dtype=np.float64)
edge_starts = []
edge_ends = []
weights = []
for (start, end), weight in edge_weights.items():
start_compact = compact[start]
end_compact = compact[end]
if start_compact >= 0:
diagonal[start_compact] += weight
if end_compact >= 0:
diagonal[end_compact] += weight
if start_compact >= 0 and end_compact >= 0:
edge_starts.append(start_compact)
edge_ends.append(end_compact)
weights.append(weight)
if np.any(diagonal <= 1e-14):
raise ValueError("Unable to build a stable Poisson Balloon field")
edge_starts = np.asarray(edge_starts, dtype=np.int64)
edge_ends = np.asarray(edge_ends, dtype=np.int64)
weights = np.asarray(weights, dtype=np.float64)
def multiply(values):
result = diagonal * values
if len(weights):
np.add.at(result, edge_starts, -weights * values[edge_ends])
np.add.at(result, edge_ends, -weights * values[edge_starts])
return result
right_hand_side = loads[interior]
solution = np.zeros(len(interior), dtype=np.float64)
residual = right_hand_side.copy()
preconditioned = residual / diagonal
direction = preconditioned.copy()
residual_product = float(residual.dot(preconditioned))
target = max(
float(np.linalg.norm(right_hand_side)) * tolerance,
1e-12,
)
max_iterations = max(64, min(len(interior) * 4, 4000))
for _iteration in range(max_iterations):
if float(np.linalg.norm(residual)) <= target:
break
multiplied = multiply(direction)
denominator = float(direction.dot(multiplied))
if denominator <= 1e-20 or not math.isfinite(denominator):
raise ValueError("Unable to solve the Poisson Balloon field")
step = residual_product / denominator
solution += step * direction
residual -= step * multiplied
preconditioned = residual / diagonal
next_product = float(residual.dot(preconditioned))
if not math.isfinite(next_product):
raise ValueError("Unable to solve the Poisson Balloon field")
if abs(residual_product) <= 1e-30:
break
direction = preconditioned + (
next_product / residual_product
) * direction
residual_product = next_product
field[interior] = np.maximum(solution, 0.0)
return field
def poisson_balloon_heights(
planar_points,
planar_faces,
boundary_mask,
inflation,
):
import numpy as np
field = cotangent_poisson_field(
planar_points,
planar_faces,
boundary_mask,
)
return 2.0 * inflation * np.sqrt(field)