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:
@@ -0,0 +1,187 @@
|
||||
# Tattoo viewer: Quaternius Regular Male with six baked tattoo marks.
|
||||
# Saves verification shots to shots/, then hands over to manual control:
|
||||
# left-drag orbits the camera around the model, scroll wheel zooms in/out.
|
||||
extends Node3D
|
||||
|
||||
const GLTF_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/Regular_Male_FullBody.gltf"
|
||||
const BAKED_PATH := "C:/Users/CAN/tinqs-ltd/tattoo-test/baked_body.png"
|
||||
const NORMAL_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/T_Regular_Male_Normal_png.png"
|
||||
const EYE_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/T_Eye_Brown.png"
|
||||
const HAIR_PATH := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/source-models/T_Hair_1_BaseColor.png"
|
||||
const SHOT_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/shots/"
|
||||
const TAKE_SHOTS := true
|
||||
|
||||
var model: Node3D
|
||||
var skel: Skeleton3D
|
||||
var cam: Camera3D
|
||||
|
||||
# manual orbit state (active once shots are done)
|
||||
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()
|
||||
var err := doc.append_from_file(GLTF_PATH, state)
|
||||
if err != OK:
|
||||
push_error("GLTF load failed")
|
||||
return
|
||||
model = doc.generate_scene(state)
|
||||
add_child(model)
|
||||
|
||||
var baked := ImageTexture.create_from_image(Image.load_from_file(BAKED_PATH))
|
||||
var body_normal := ImageTexture.create_from_image(Image.load_from_file(NORMAL_PATH))
|
||||
var eye_tex := ImageTexture.create_from_image(Image.load_from_file(EYE_PATH))
|
||||
var hair_tex := ImageTexture.create_from_image(Image.load_from_file(HAIR_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:
|
||||
var lname := String(n.name).to_lower()
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.roughness = 0.85
|
||||
if lname.contains("regularmale"):
|
||||
mat.albedo_texture = baked
|
||||
mat.normal_enabled = true
|
||||
mat.normal_texture = body_normal
|
||||
elif lname.contains("eye") and not lname.contains("brow"):
|
||||
mat.albedo_texture = eye_tex
|
||||
else:
|
||||
mat.albedo_texture = hair_tex
|
||||
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")
|
||||
|
||||
# hand over to manual orbit
|
||||
model.rotation.y = 0.0
|
||||
interactive = true
|
||||
_update_cam()
|
||||
|
||||
# ── manual orbit: left-drag rotates, scroll wheel zooms ─────────────────────
|
||||
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)
|
||||
|
||||
# ── verification shots ───────────────────────────────────────────────────────
|
||||
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")
|
||||
|
||||
# torso closeups
|
||||
model.rotation.y = 0.0
|
||||
await get_tree().process_frame
|
||||
cam.look_at_from_position(Vector3(0, 1.3, 0.9), Vector3(0, 1.3, 0))
|
||||
await _snap("03_chest_star_eye")
|
||||
model.rotation.y = PI
|
||||
await get_tree().process_frame
|
||||
cam.look_at_from_position(Vector3(0, 1.25, 0.9), Vector3(0, 1.25, 0))
|
||||
await _snap("04_back_hearth_warmth")
|
||||
|
||||
# arms (front view of each arm)
|
||||
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("05_arm_r_chiefs_mark")
|
||||
var mid_l := _limb_mid("upperarm_l", "hand_l")
|
||||
cam.look_at_from_position(mid_l + Vector3(0, 0.12, 1.0), mid_l)
|
||||
await _snap("06_arm_l_iron_spine")
|
||||
|
||||
# legs (front + outer side of each leg)
|
||||
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("07_leg_r_voyagers_front")
|
||||
cam.look_at_from_position(leg_r + Vector3(-1.0, 0.05, 0.3), leg_r)
|
||||
await _snap("08_leg_r_voyagers_outer")
|
||||
var leg_l := _limb_mid("thigh_l", "foot_l")
|
||||
cam.look_at_from_position(leg_l + Vector3(0, 0.05, 1.1), leg_l)
|
||||
await _snap("09_leg_l_storm_front")
|
||||
cam.look_at_from_position(leg_l + Vector3(1.0, 0.05, 0.3), leg_l)
|
||||
await _snap("10_leg_l_storm_outer")
|
||||
model.rotation.y = PI
|
||||
await get_tree().process_frame
|
||||
cam.look_at_from_position(leg_r + Vector3(0, 0.05, 1.1), leg_r)
|
||||
await _snap("11_legs_back")
|
||||
model.rotation.y = 0.0
|
||||
Reference in New Issue
Block a user