fix cli redundant images
finger-xynova first design completed
This commit is contained in:
@@ -1,8 +1,57 @@
|
||||
"""Blender adapter for evaluated, camera-local loop-triangle fragments."""
|
||||
from patch_geometry import cell_fragments, interpolate_uv
|
||||
import math
|
||||
|
||||
from patch_geometry import barycentric, cell_fragments, interpolate_uv
|
||||
|
||||
|
||||
def build_patch_geometry(obj, cam, scene, depsgraph, uv_names, nx, ny, edge_sample_grid):
|
||||
def _polygon_area_3d(points):
|
||||
"""Area of a planar polygon given its vertices in order (fan from vertex 0)."""
|
||||
if len(points) < 3:
|
||||
return 0.0
|
||||
total = 0.0
|
||||
for k in range(1, len(points) - 1):
|
||||
total += (points[k] - points[0]).cross(points[k + 1] - points[0]).length
|
||||
return total * 0.5
|
||||
|
||||
|
||||
def _point_segment_distance(point, a, b):
|
||||
"""Shortest 3D distance from ``point`` to the segment ``a``-``b``."""
|
||||
ab = b - a
|
||||
denom = ab.dot(ab)
|
||||
if denom <= 0.0:
|
||||
return (point - a).length
|
||||
t = (point - a).dot(ab) / denom
|
||||
t = 0.0 if t < 0.0 else (1.0 if t > 1.0 else t)
|
||||
return (point - (a + ab * t)).length
|
||||
|
||||
|
||||
def _min_seam_distance(point, segments):
|
||||
"""Shortest distance from ``point`` to any of the seam segments."""
|
||||
best = None
|
||||
for a, b in segments:
|
||||
d = _point_segment_distance(point, a, b)
|
||||
if best is None or d < best:
|
||||
best = d
|
||||
return best if best is not None else float("inf")
|
||||
|
||||
|
||||
def build_patch_geometry(obj, cam, scene, depsgraph, uv_names, nx, ny, edge_sample_grid,
|
||||
fill_unresolved_leaves=False, seam_segments=None,
|
||||
seam_search_mm=0.0):
|
||||
"""Evaluated loop-triangle fragments for one camera, per UV target.
|
||||
|
||||
``fill_unresolved_leaves`` accepts fragments whose visibility could not be resolved at
|
||||
the finest subdivision instead of leaving them unpainted. It trades a slight stretch at
|
||||
silhouette-adjacent leaves for a marker with no holes; the default is the conservative
|
||||
behaviour, so single-view artifacts stay byte-identical.
|
||||
|
||||
``seam_segments`` is the authored seam as world-space ``(a, b)`` pairs. When given, every
|
||||
fragment is measured against it and the distance is reported as
|
||||
``fragment_seam_distance_mm``, which is what the seam margin filters on. Only triangles
|
||||
whose nearest vertex is within ``seam_search_mm`` plus the triangle's own radius are
|
||||
measured exactly; everything else is reported as ``inf`` (it cannot be inside a margin of
|
||||
that size, and the exact number would be wasted work on the whole surface).
|
||||
"""
|
||||
from mathutils import Vector
|
||||
if cam.data.type not in {'PERSP', 'ORTHO'}:
|
||||
raise ValueError('Patch projection requires a perspective or orthographic camera')
|
||||
@@ -24,6 +73,30 @@ def build_patch_geometry(obj, cam, scene, depsgraph, uv_names, nx, ny, edge_samp
|
||||
forward = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
output = {name: {'mesh': [], 'patches': []} for name in uv_names}
|
||||
ray_count = 0
|
||||
|
||||
# Triangle-level and fragment-level records, shared by every UV target: the
|
||||
# fragment walk is identical for all of them (they differ only in which layer
|
||||
# the barycentrics are resolved against), so recording it per target would be
|
||||
# the same numbers repeated. Consumed by ownership assignment; the single-view
|
||||
# path never reads them.
|
||||
triangle_vertices = []
|
||||
triangle_area_mm2 = []
|
||||
fragment_triangle = []
|
||||
fragment_incidence_deg = []
|
||||
fragment_footprint_mm2 = []
|
||||
fragment_seam_distance_mm = []
|
||||
triangle_seam_distance_mm = []
|
||||
if seam_segments:
|
||||
# Per-vertex distance to the seam polyline, computed once. A fragment's distance is
|
||||
# then only worth measuring exactly where its triangle can plausibly reach the
|
||||
# margin, which keeps this off the whole surface.
|
||||
vertex_seam_distance = [
|
||||
_min_seam_distance(evaluated.matrix_world @ vertex.co, seam_segments)
|
||||
for vertex in mesh.vertices
|
||||
]
|
||||
else:
|
||||
vertex_seam_distance = None
|
||||
|
||||
for triangle in mesh.loop_triangles:
|
||||
world = [evaluated.matrix_world @ mesh.vertices[i].co for i in triangle.vertices]
|
||||
clips = [tuple(clip_matrix @ p.to_4d()) for p in world]
|
||||
@@ -31,6 +104,35 @@ def build_patch_geometry(obj, cam, scene, depsgraph, uv_names, nx, ny, edge_samp
|
||||
for name, layer in layers.items()}
|
||||
for name in uv_names:
|
||||
output[name]['mesh'].append(uvs[name])
|
||||
|
||||
triangle_vertices.append(tuple(triangle.vertices))
|
||||
triangle_area_mm2.append(_polygon_area_3d(world))
|
||||
|
||||
if vertex_seam_distance is not None:
|
||||
triangle_near = min(vertex_seam_distance[i] for i in triangle.vertices)
|
||||
node = sum(world, Vector()) / 3.0
|
||||
radius = max((p - node).length for p in world)
|
||||
triangle_seam_distance_mm.append(triangle_near)
|
||||
else:
|
||||
triangle_near = radius = None
|
||||
|
||||
# Incidence of this triangle to the camera. ``abs`` on purpose: the mesh
|
||||
# carries inward-facing normals on most faces, and ownership cares about the
|
||||
# angle between the surface and the view ray, not about winding. Whether the
|
||||
# winding is a defect is reported separately, not decided here.
|
||||
normal = (world[1] - world[0]).cross(world[2] - world[0])
|
||||
centroid = (world[0] + world[1] + world[2]) / 3.0
|
||||
if normal.length > 1e-15:
|
||||
normal.normalize()
|
||||
to_camera = (origin - centroid) if camera.data.type == 'PERSP' else -forward
|
||||
if to_camera.length > 1e-15:
|
||||
cosine = min(1.0, max(0.0, abs(normal.dot(to_camera.normalized()))))
|
||||
incidence_deg = math.degrees(math.acos(cosine))
|
||||
else:
|
||||
incidence_deg = 90.0
|
||||
else:
|
||||
incidence_deg = 90.0
|
||||
|
||||
cache = {}
|
||||
|
||||
# ``cache`` and ``world`` are bound as defaults: ``cache`` is rebound
|
||||
@@ -62,10 +164,40 @@ def build_patch_geometry(obj, cam, scene, depsgraph, uv_names, nx, ny, edge_samp
|
||||
cache[key] = result
|
||||
return result
|
||||
|
||||
for i, j, fragment in cell_fragments(clips, nx, ny, visible, edge_sample_grid):
|
||||
for i, j, fragment in cell_fragments(clips, nx, ny, visible, edge_sample_grid,
|
||||
fill_unresolved_leaves):
|
||||
# Fragments are triangles (cell_fragments fans and refines to triples),
|
||||
# so the surface footprint is exact, not an approximation.
|
||||
points = [sum((p * w for p, w in zip(world, barycentric(p))), Vector())
|
||||
for p in fragment]
|
||||
fragment_triangle.append(triangle.index)
|
||||
fragment_incidence_deg.append(incidence_deg)
|
||||
fragment_footprint_mm2.append(_polygon_area_3d(points))
|
||||
if vertex_seam_distance is not None:
|
||||
if triangle_near <= seam_search_mm + radius:
|
||||
fragment_seam_distance_mm.append(
|
||||
min(_min_seam_distance(point, seam_segments) for point in points)
|
||||
)
|
||||
else:
|
||||
# Cannot be inside any margin this size; skip the exact measurement.
|
||||
fragment_seam_distance_mm.append(float("inf"))
|
||||
for name in uv_names:
|
||||
polygon = [interpolate_uv(p, uvs[name]) for p in fragment]
|
||||
output[name]['patches'].append((i, j, polygon))
|
||||
|
||||
output['_meta'] = {
|
||||
'triangle_count': len(mesh.loop_triangles),
|
||||
'triangle_vertices': triangle_vertices,
|
||||
'triangle_area_mm2': triangle_area_mm2,
|
||||
'fragment_triangle': fragment_triangle,
|
||||
'fragment_incidence_deg': fragment_incidence_deg,
|
||||
'fragment_footprint_mm2': fragment_footprint_mm2,
|
||||
# None (rather than a list) when no seam was supplied, so a consumer can tell
|
||||
# "not measured" from "measured and far away" without guessing.
|
||||
'fragment_seam_distance_mm': fragment_seam_distance_mm or None,
|
||||
'triangle_seam_distance_mm': triangle_seam_distance_mm or None,
|
||||
'camera_type': camera.data.type,
|
||||
}
|
||||
print(f'Evaluated {len(mesh.loop_triangles)} loop triangles; {ray_count} visibility rays; '
|
||||
f'{len(output[uv_names[0]]["patches"])} local fragments per UV target')
|
||||
print('Visibility sampling: seven probes per fragment; uniformly probed '
|
||||
|
||||
Reference in New Issue
Block a user