clean up
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
import os
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Scanline polygon fill for Blender Image pixels
|
||||
# ----------------------------------------------------------------------
|
||||
def fill_polygon(image, pixels, width, height, polygon, color):
|
||||
poly = [(int(round(x)), int(round(y))) for x, y in polygon]
|
||||
if len(poly) < 3:
|
||||
return
|
||||
|
||||
min_x = max(0, min(p[0] for p in poly))
|
||||
max_x = min(width-1, max(p[0] for p in poly))
|
||||
min_y = max(0, min(p[1] for p in poly))
|
||||
max_y = min(height-1, max(p[1] for p in poly))
|
||||
|
||||
for y in range(min_y, max_y + 1):
|
||||
intersections = []
|
||||
for i in range(len(poly)):
|
||||
x1, y1 = poly[i]
|
||||
x2, y2 = poly[(i+1) % len(poly)]
|
||||
if y1 == y2:
|
||||
continue
|
||||
if (y1 <= y < y2) or (y2 <= y < y1):
|
||||
t = (y - y1) / (y2 - y1)
|
||||
x = x1 + t * (x2 - x1)
|
||||
intersections.append(x)
|
||||
intersections.sort()
|
||||
for k in range(0, len(intersections), 2):
|
||||
if k+1 >= len(intersections):
|
||||
break
|
||||
x_start = int(round(intersections[k]))
|
||||
x_end = int(round(intersections[k+1]))
|
||||
x_start = max(0, min(width-1, x_start))
|
||||
x_end = max(0, min(width-1, x_end))
|
||||
for x in range(x_start, x_end + 1):
|
||||
idx = (y * width + x) * 4
|
||||
pixels[idx:idx+4] = color
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Get interpolated UV from face and hit location (fixed version)
|
||||
# ----------------------------------------------------------------------
|
||||
def get_uv_from_face(obj, face_index, location):
|
||||
me = obj.data
|
||||
local_loc = obj.matrix_world.inverted() @ location
|
||||
|
||||
poly = me.polygons[face_index]
|
||||
vert_indices = poly.vertices
|
||||
loop_indices = poly.loop_indices
|
||||
|
||||
if len(vert_indices) < 3:
|
||||
return None
|
||||
|
||||
v0 = me.vertices[vert_indices[0]].co
|
||||
v1 = me.vertices[vert_indices[1]].co
|
||||
v2 = me.vertices[vert_indices[2]].co
|
||||
|
||||
uv_layer = me.uv_layers.active.data
|
||||
uv0 = uv_layer[loop_indices[0]].uv
|
||||
uv1 = uv_layer[loop_indices[1]].uv
|
||||
uv2 = uv_layer[loop_indices[2]].uv
|
||||
|
||||
def tri_area(a, b, c):
|
||||
return 0.5 * ((b - a).cross(c - a)).length
|
||||
|
||||
total_area = tri_area(v0, v1, v2)
|
||||
if total_area == 0:
|
||||
return None
|
||||
|
||||
w0 = tri_area(local_loc, v1, v2) / total_area
|
||||
w1 = tri_area(local_loc, v2, v0) / total_area
|
||||
w2 = 1.0 - w0 - w1
|
||||
|
||||
uv = w0 * uv0 + w1 * uv1 + w2 * uv2
|
||||
return Vector((uv.x, uv.y))
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Color generation functions (unchanged)
|
||||
# ----------------------------------------------------------------------
|
||||
def merge_intervals(intervals):
|
||||
if not intervals:
|
||||
return []
|
||||
intervals.sort(key=lambda x: x[0])
|
||||
merged = [intervals[0]]
|
||||
for current in intervals[1:]:
|
||||
last = merged[-1]
|
||||
if current[0] <= last[1]:
|
||||
merged[-1] = (last[0], max(last[1], current[1]))
|
||||
else:
|
||||
merged.append(current)
|
||||
return merged
|
||||
|
||||
def get_allowed_intervals(neighbor_values, delta):
|
||||
forbidden = []
|
||||
for c in neighbor_values:
|
||||
low = max(0, c - delta)
|
||||
high = min(1, c + delta)
|
||||
forbidden.append((low, high))
|
||||
merged = merge_intervals(forbidden)
|
||||
allowed = []
|
||||
if merged:
|
||||
if merged[0][0] > 0:
|
||||
allowed.append((0, merged[0][0]))
|
||||
for i in range(1, len(merged)):
|
||||
allowed.append((merged[i-1][1], merged[i][0]))
|
||||
if merged[-1][1] < 1:
|
||||
allowed.append((merged[-1][1], 1))
|
||||
else:
|
||||
allowed.append((0, 1))
|
||||
return allowed
|
||||
|
||||
def sample_from_allowed(allowed):
|
||||
lengths = [end - start for start, end in allowed]
|
||||
total_length = sum(lengths)
|
||||
if total_length <= 0:
|
||||
return None
|
||||
point = np.random.rand() * total_length
|
||||
cumulative = 0
|
||||
for (start, end), length in zip(allowed, lengths):
|
||||
cumulative += length
|
||||
if point <= cumulative:
|
||||
return np.random.uniform(start, end)
|
||||
return allowed[-1][0]
|
||||
|
||||
def sample_channel(neighbor_values, delta):
|
||||
allowed = get_allowed_intervals(neighbor_values, delta)
|
||||
if not allowed:
|
||||
return None
|
||||
return sample_from_allowed(allowed)
|
||||
|
||||
def generate_valid_color(neighbors, r):
|
||||
delta = np.sqrt(r)
|
||||
color = np.zeros(3)
|
||||
for channel in range(3):
|
||||
neighbor_channel = [n[channel] for n in neighbors]
|
||||
sampled = sample_channel(neighbor_channel, delta)
|
||||
if sampled is None:
|
||||
sampled = np.random.rand()
|
||||
color[channel] = sampled
|
||||
return color
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Main function
|
||||
# ----------------------------------------------------------------------
|
||||
def generate_fingerprint_texture(
|
||||
obj_name=None,
|
||||
camera_name="Camera",
|
||||
nx=40,
|
||||
ny=30,
|
||||
r=0.05,
|
||||
texture_width=2048,
|
||||
texture_height=2048,
|
||||
output_path="//finger_texture.png"
|
||||
):
|
||||
scene = bpy.context.scene
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
if obj_name:
|
||||
obj = bpy.data.objects[obj_name]
|
||||
else:
|
||||
obj = bpy.context.active_object
|
||||
if obj is None or obj.type != 'MESH':
|
||||
raise ValueError("No valid mesh object selected")
|
||||
|
||||
cam = bpy.data.objects[camera_name]
|
||||
if cam is None or cam.type != 'CAMERA':
|
||||
raise ValueError("Camera not found")
|
||||
|
||||
if not obj.data.uv_layers.active:
|
||||
obj.data.uv_layers.new(name="UVMap")
|
||||
|
||||
render = scene.render
|
||||
res_x = render.resolution_x
|
||||
res_y = render.resolution_y
|
||||
|
||||
cam_data = cam.data
|
||||
if cam_data.sensor_fit == 'HORIZONTAL':
|
||||
sensor_width = cam_data.sensor_width
|
||||
sensor_height = sensor_width / (res_x / res_y)
|
||||
else:
|
||||
sensor_height = cam_data.sensor_height
|
||||
sensor_width = sensor_height * (res_x / res_y)
|
||||
focal = cam_data.lens
|
||||
frame_width = sensor_width / focal
|
||||
frame_height = sensor_height / focal
|
||||
|
||||
patch_uv_polygons = []
|
||||
patch_colors = np.zeros((ny, nx, 3), dtype=np.float32)
|
||||
|
||||
for j in range(ny):
|
||||
v_start = j / ny
|
||||
v_end = (j+1) / ny
|
||||
for i in range(nx):
|
||||
u_start = i / nx
|
||||
u_end = (i+1) / nx
|
||||
|
||||
corners_ndc = [
|
||||
(u_start, v_start),
|
||||
(u_end, v_start),
|
||||
(u_end, v_end),
|
||||
(u_start, v_end)
|
||||
]
|
||||
|
||||
uv_poly = []
|
||||
for (u_ndc, v_ndc) in corners_ndc:
|
||||
sx = (u_ndc - 0.5) * frame_width
|
||||
sy = (v_ndc - 0.5) * frame_height
|
||||
direction_cam = Vector((sx, sy, -1.0))
|
||||
direction_world = cam.matrix_world.to_quaternion() @ direction_cam
|
||||
direction_world.normalize()
|
||||
origin = cam.matrix_world.translation
|
||||
|
||||
hit, location, normal, face_index, hit_obj, matrix = scene.ray_cast(depsgraph, origin, direction_world)
|
||||
if hit and hit_obj == obj:
|
||||
uv = get_uv_from_face(obj, face_index, location)
|
||||
if uv is not None:
|
||||
uv_poly.append(uv)
|
||||
|
||||
if len(uv_poly) < 3:
|
||||
patch_uv_polygons.append(None)
|
||||
continue
|
||||
patch_uv_polygons.append(uv_poly)
|
||||
|
||||
neighbors = []
|
||||
if j > 0:
|
||||
neighbors.append(patch_colors[j-1, i])
|
||||
if i > 0:
|
||||
neighbors.append(patch_colors[j, i-1])
|
||||
|
||||
if len(neighbors) == 0:
|
||||
color = np.random.rand(3)
|
||||
else:
|
||||
color = generate_valid_color(neighbors, r)
|
||||
patch_colors[j, i] = color
|
||||
|
||||
img_name = "FingerprintTexture"
|
||||
if img_name in bpy.data.images:
|
||||
bpy.data.images.remove(bpy.data.images[img_name])
|
||||
image = bpy.data.images.new(img_name, width=texture_width, height=texture_height, alpha=True)
|
||||
pixels = list(image.pixels)
|
||||
|
||||
idx = 0
|
||||
for j in range(ny):
|
||||
for i in range(nx):
|
||||
uv_poly = patch_uv_polygons[idx]
|
||||
idx += 1
|
||||
if uv_poly is None:
|
||||
continue
|
||||
pixel_poly = [(u * (texture_width - 1), v * (texture_height - 1)) for (u, v) in uv_poly]
|
||||
color = patch_colors[j, i]
|
||||
rgba = (color[0], color[1], color[2], 1.0)
|
||||
fill_polygon(image, pixels, texture_width, texture_height, pixel_poly, rgba)
|
||||
|
||||
image.pixels[:] = pixels
|
||||
image.update()
|
||||
|
||||
abs_path = bpy.path.abspath(output_path)
|
||||
image.filepath_raw = abs_path
|
||||
image.file_format = 'PNG'
|
||||
image.save()
|
||||
print(f"Texture saved to {abs_path}")
|
||||
return image
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Run
|
||||
# ----------------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
generate_fingerprint_texture(
|
||||
obj_name="FingerMesh", # Change to your mesh name
|
||||
nx=50, ny=40,
|
||||
r=0.05,
|
||||
texture_width=4096,
|
||||
texture_height=4096,
|
||||
output_path="//finger_texture.png"
|
||||
)
|
||||
Reference in New Issue
Block a user