Files
tattoo-test/anim_lena.gd
T

244 lines
7.6 KiB
GDScript
Raw Normal View History

# Animation test: the tattooed QuatSkin Lena driven by UAL1 clips.
# Controls:
# ←/→ or A/D previous / next animation
# Space pause / resume
# U lighting ON / FLAT (kills the sun+fill lights, boosts ambient)
# left-drag orbit camera, scroll wheel zooms
# Saves a few mid-animation shots to shots-anim-lena/ first, then goes interactive.
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 UAL1 := "C:/Users/CAN/tinqs-ltd/ariki-game/assets/quaternius/anim/UAL1.glb"
const SHOT_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/shots-anim-lena/"
const TAKE_SHOTS := false
# gallery order: locomotion first, then actions
const CLIPS := [
"Idle_Loop", "Walk_Loop", "Jog_Fwd_Loop", "Sprint_Loop",
"Crouch_Idle_Loop", "Crouch_Fwd_Loop", "Jump_Loop",
"Swim_Idle_Loop", "Swim_Fwd_Loop",
"Dance_Loop", "Punch_Jab", "Punch_Cross", "Roll", "Interact",
"Fixing_Kneeling", "Sitting_Idle_Loop", "Spell_Simple_Shoot", "Death01",
]
var model: Node3D
var skel: Skeleton3D
var cam: Camera3D
var player: AnimationPlayer
var hud: Label
var clip_idx := 0
var lights: Array[Light3D] = []
var env: Environment
var lights_on := true
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)
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)
lights = [sun, fill, under]
# ground plane so locomotion reads against something
var ground := MeshInstance3D.new()
var plane := PlaneMesh.new()
plane.size = Vector2(12, 12)
ground.mesh = plane
var gmat := StandardMaterial3D.new()
gmat.albedo_color = Color(0.16, 0.17, 0.21)
gmat.roughness = 1.0
ground.set_surface_override_material(0, gmat)
add_child(ground)
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)
_load_ual_clips()
cam = Camera3D.new()
cam.fov = 50
add_child(cam)
cam.current = true
hud = Label.new()
hud.position = Vector2(16, 12)
hud.add_theme_font_size_override("font_size", 22)
hud.add_theme_color_override("font_color", Color(0.95, 0.95, 0.9))
add_child(hud)
_play_clip(0)
_update_cam()
if TAKE_SHOTS:
await _take_shots()
print("SHOTS_DONE")
_play_clip(0)
interactive = true
# pulls the wanted UAL1 clips onto a local AnimationPlayer, remapping the
# "Armature/Skeleton3D" track prefix to this scene's skeleton (game approach,
# see PlayerController.LoadUalLibrary)
func _load_ual_clips() -> void:
var doc := GLTFDocument.new()
var state := GLTFState.new()
if doc.append_from_file(UAL1, state) != OK:
push_error("UAL1 load failed")
return
var ual := doc.generate_scene(state)
var src: AnimationPlayer = null
var stack: Array = [ual]
while not stack.is_empty():
var n: Node = stack.pop_back()
if n is AnimationPlayer:
src = n
break
for c in n.get_children():
stack.push_back(c)
if src == null:
push_error("no AnimationPlayer in UAL1")
return
player = AnimationPlayer.new()
add_child(player)
var skel_path := String(get_path_to(skel))
var lib := AnimationLibrary.new()
for clip_name in CLIPS:
if not src.has_animation(clip_name):
push_warning("UAL1 missing clip: " + clip_name)
continue
var anim := src.get_animation(clip_name).duplicate() as Animation
anim.loop_mode = Animation.LOOP_LINEAR
for i in anim.get_track_count():
var path := String(anim.track_get_path(i))
if path.contains("Armature/Skeleton3D"):
anim.track_set_path(i, path.replace("Armature/Skeleton3D", skel_path))
lib.add_animation(clip_name, anim)
player.add_animation_library("", lib)
print("loaded %d clips" % lib.get_animation_list().size())
ual.queue_free()
func _play_clip(idx: int) -> void:
clip_idx = wrapi(idx, 0, CLIPS.size())
var clip: String = CLIPS[clip_idx]
if player and player.has_animation(clip):
player.play(clip)
_update_hud()
func _set_lights(on: bool) -> void:
lights_on = on
for l in lights:
l.visible = on
# flat mode: strong even ambient so the texture reads unshaded
env.ambient_light_energy = 0.7 if on else 1.6
_update_hud()
func _update_hud() -> void:
if hud:
hud.text = "[%d/%d] %s light: %s\n←/→ switch Space pause U light drag rotate scroll zoom" \
% [clip_idx + 1, CLIPS.size(), CLIPS[clip_idx], "SUN" if lights_on else "FLAT"]
func _unhandled_input(event: InputEvent) -> void:
if not interactive:
return
if event is InputEventKey and event.pressed and not event.echo:
match event.keycode:
KEY_RIGHT, KEY_D:
_play_clip(clip_idx + 1)
KEY_LEFT, KEY_A:
_play_clip(clip_idx - 1)
KEY_SPACE:
if player.is_playing():
player.pause()
else:
player.play()
KEY_U:
_set_lights(not lights_on)
elif 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 _snap(shot_name: String) -> void:
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:
for spec in [["Jog_Fwd_Loop", 0.4], ["Dance_Loop", 1.2], ["Punch_Jab", 0.5], ["Interact", 0.6]]:
var clip: String = spec[0]
if not player.has_animation(clip):
continue
player.play(clip)
await get_tree().create_timer(spec[1]).timeout
await _snap("anim_" + clip.to_lower())
# verify the U-key light toggle path: one flat-lit shot, then restore
_set_lights(false)
await _snap("anim_light_flat")
_set_lights(true)