Tattoo test bed: Quaternius v3-v6 bakes + Lena pipeline (bake, de-mirror, re-UV, viewers)

Session 2026-07-23/24 additions on top of the existing Quaternius bed:
- bake_lena.gd: 6-piece bake on QuatSkin Lena (chiefs-mark/iron-spine arms,
  voyagers/storm-bearer legs, star-eye chest, hearth-warmth back); global-axis
  wrap frame + 3cm junction blend kill the elbow split line
- demirror_lena.gd: tri-level split of mirror-shared limb UVs (arms 100%->0%,
  legs 99%->5% crotch-only residual)
- reuv_lena.gd: Quaternius-style semantic re-UV (16 islands, cylinder limbs +
  cylinder head/torso with planar caps) + texture transfer; fixes armpit/chin/
  cheek planar smears; single 4096 atlas, per-side tattoos native
- main_lena / anim_lena viewers (18 UAL clips, U light toggle) + probes + shots

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Can Narin
2026-07-24 00:47:12 +03:00
commit ce4c4e7d13
105 changed files with 5090 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
# Tattoo viewer: QuatSkin Lena with the chiefs-mark v3 right-arm sleeve baked in.
# Saves verification shots to shots-lena/, then hands over to manual control:
# left-drag orbits the camera around the model, scroll wheel zooms in/out.
extends Node3D
const GLB_PATH := "C:/Users/CAN/tinqs-ltd/tattoo-test/lena_clean_uv.glb"
const BAKED_PATH := "C:/Users/CAN/tinqs-ltd/tattoo-test/baked_lena_body.png"
const SHOT_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/shots-lena/"
const TAKE_SHOTS := true
var model: Node3D
var skel: Skeleton3D
var cam: Camera3D
var interactive := false
var dragging := false
var orbit_yaw := 0.0
var orbit_pitch := 0.08
var orbit_dist := 3.4
var orbit_target := Vector3(0, 1.0, 0)
func _ready() -> void:
DirAccess.make_dir_recursive_absolute(SHOT_DIR)
var env := Environment.new()
env.background_mode = Environment.BG_COLOR
env.background_color = Color(0.12, 0.13, 0.16)
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.75, 0.78, 0.85)
env.ambient_light_energy = 0.7
env.tonemap_mode = Environment.TONE_MAPPER_FILMIC
var we := WorldEnvironment.new()
we.environment = env
add_child(we)
var sun := DirectionalLight3D.new()
sun.rotation_degrees = Vector3(-40, -35, 0)
sun.light_energy = 1.5
add_child(sun)
var fill := DirectionalLight3D.new()
fill.rotation_degrees = Vector3(-15, 140, 0)
fill.light_energy = 0.6
add_child(fill)
var under := DirectionalLight3D.new()
under.rotation_degrees = Vector3(35, 0, 0)
under.light_energy = 0.35
add_child(under)
var doc := GLTFDocument.new()
var state := GLTFState.new()
if doc.append_from_file(GLB_PATH, state) != OK:
push_error("GLB load failed")
return
model = doc.generate_scene(state)
add_child(model)
var baked := ImageTexture.create_from_image(Image.load_from_file(BAKED_PATH))
var stack: Array = [model]
while not stack.is_empty():
var n: Node = stack.pop_back()
if n is Skeleton3D and skel == null:
skel = n
if n is MeshInstance3D and String(n.name).to_lower().contains("lena"):
var mat := StandardMaterial3D.new()
mat.roughness = 0.85
mat.albedo_texture = baked
mat.cull_mode = BaseMaterial3D.CULL_BACK
for s in n.mesh.get_surface_count():
n.set_surface_override_material(s, mat)
for c in n.get_children():
stack.push_back(c)
cam = Camera3D.new()
cam.fov = 50
add_child(cam)
cam.current = true
if TAKE_SHOTS:
await _take_shots()
print("SHOTS_DONE")
model.rotation.y = 0.0
interactive = true
_update_cam()
func _unhandled_input(event: InputEvent) -> void:
if not interactive:
return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
dragging = event.pressed
elif event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_UP:
orbit_dist = clampf(orbit_dist * 0.90, 0.35, 8.0)
_update_cam()
elif event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
orbit_dist = clampf(orbit_dist / 0.90, 0.35, 8.0)
_update_cam()
elif event is InputEventMouseMotion and dragging:
orbit_yaw -= event.relative.x * 0.008
orbit_pitch = clampf(orbit_pitch + event.relative.y * 0.008, -1.3, 1.3)
_update_cam()
func _update_cam() -> void:
var dir := Vector3(
sin(orbit_yaw) * cos(orbit_pitch),
sin(orbit_pitch),
cos(orbit_yaw) * cos(orbit_pitch))
cam.look_at_from_position(orbit_target + dir * orbit_dist, orbit_target)
func _bone_point(bone: String) -> Vector3:
return (skel.global_transform * skel.get_bone_global_rest(skel.find_bone(bone)).origin)
func _limb_mid(a: String, b: String) -> Vector3:
return (_bone_point(a) + _bone_point(b)) * 0.5
func _snap(shot_name: String) -> void:
await get_tree().process_frame
await get_tree().process_frame
await RenderingServer.frame_post_draw
var img := get_viewport().get_texture().get_image()
img.save_png(SHOT_DIR + shot_name + ".png")
print("shot saved: ", SHOT_DIR + shot_name + ".png")
func _take_shots() -> void:
# full body front / back
model.rotation.y = 0.0
cam.look_at_from_position(Vector3(0, 1.1, 3.4), Vector3(0, 1.0, 0))
await _snap("01_full_front")
model.rotation.y = PI
await get_tree().process_frame
await _snap("02_full_back")
# right arm: front, outer side, back
model.rotation.y = 0.0
await get_tree().process_frame
var mid_r := _limb_mid("upperarm_r", "hand_r")
cam.look_at_from_position(mid_r + Vector3(0, 0.12, 1.0), mid_r)
await _snap("03_arm_r_front")
cam.look_at_from_position(mid_r + Vector3(-1.0, 0.12, 0.15), mid_r)
await _snap("04_arm_r_outer")
model.rotation.y = PI
await get_tree().process_frame
var mid_r_back := _limb_mid("upperarm_r", "hand_r")
mid_r_back.x = -mid_r_back.x
mid_r_back.z = -mid_r_back.z
cam.look_at_from_position(mid_r_back + Vector3(0, 0.12, 1.0), mid_r_back)
await _snap("05_arm_r_back")
# inner (underside) of the arm — where the elbow bend is concave
model.rotation.y = 0.0
await get_tree().process_frame
cam.look_at_from_position(mid_r + Vector3(0, -0.85, 0.5), mid_r)
await _snap("06_arm_r_inner")
# torso closeups
cam.look_at_from_position(Vector3(0, 1.25, 0.9), Vector3(0, 1.25, 0))
await _snap("07_chest")
model.rotation.y = PI
await get_tree().process_frame
cam.look_at_from_position(Vector3(0, 1.2, 0.9), Vector3(0, 1.2, 0))
await _snap("08_back")
# legs front / outer / back
model.rotation.y = 0.0
await get_tree().process_frame
var leg_r := _limb_mid("thigh_r", "foot_r")
cam.look_at_from_position(leg_r + Vector3(0, 0.05, 1.1), leg_r)
await _snap("09_leg_r_front")
cam.look_at_from_position(leg_r + Vector3(-1.0, 0.05, 0.3), leg_r)
await _snap("10_leg_r_outer")
model.rotation.y = PI
await get_tree().process_frame
var leg_b := Vector3(-leg_r.x, leg_r.y, -leg_r.z)
cam.look_at_from_position(leg_b + Vector3(0, 0.05, 1.1), leg_b)
await _snap("11_legs_back")
# problem-area closeups: armpit, under-chin, cheek/ear
model.rotation.y = 0.0
await get_tree().process_frame
var pit_r := _bone_point("upperarm_r") + Vector3(-0.02, -0.05, 0)
cam.look_at_from_position(pit_r + Vector3(-0.12, -0.28, 0.42), pit_r)
await _snap("12_armpit_r")
var head_p := _bone_point("Head")
var chin := head_p + Vector3(0, -0.05, 0.06)
cam.look_at_from_position(chin + Vector3(0, -0.22, 0.40), chin)
await _snap("13_under_chin")
var ear_r := head_p + Vector3(-0.06, 0.04, 0)
cam.look_at_from_position(ear_r + Vector3(-0.38, 0.02, 0.16), ear_r)
await _snap("14_ear_cheek_r")
model.rotation.y = 0.0