extends SceneTree ## Dev tool: close-up of the hip/thigh region through a run cycle. ## ## godot --path . --windowed --resolution 900x900 -s res://debug/skirt_closeup.gd -- [skin] ## ## debug/anim_capture.gd frames the whole character in 1280x720, which is far ## too small to judge whether a thigh is poking through a skirt — the question ## this exists to answer. Camera sits ~0.9 m from the hips at hip height and ## saves front and side views at several points across the stride. var _frames := 0 var _out := "." var _model: SkinnedPlayerModel = null var _cam: Camera3D = null var _shots := 0 const SHOT_EVERY := 7 func _initialize() -> void: var args := OS.get_cmdline_user_args() _out = args[0] if args.size() > 0 else "." var path := "res://assets/characters/skins/taila.glb" if args.size() > 1: path = "res://assets/characters/skins/%s.glb" % args[1] var scene := Node3D.new() root.add_child(scene) current_scene = scene var env := WorldEnvironment.new() var e := Environment.new() e.background_mode = Environment.BG_COLOR e.background_color = Color(0.15, 0.15, 0.2) e.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR e.ambient_light_color = Color(1, 1, 1) e.ambient_light_energy = 1.6 env.environment = e scene.add_child(env) var sun := DirectionalLight3D.new() sun.rotation_degrees = Vector3(-40, 35, 0) sun.light_energy = 1.4 scene.add_child(sun) _model = SkinnedPlayerModel.new() _model.model_path = path scene.add_child(_model) _cam = Camera3D.new() _cam.fov = 26.0 scene.add_child(_cam) _cam.current = true func _process(_delta: float) -> bool: _frames += 1 if _frames < 10 or not _model.loaded: return false _model.update_state("ground", 9.0, false) _model.set_locomotion(0.0, 1.0, 0.0) # Frame the hips: that is where a skirt meets a thigh. var hips := 0.95 if _model.skeleton: var h := _model.skeleton.find_bone("DEF-spine") if h >= 0: hips = _model.skeleton.get_bone_global_pose(h).origin.y # Position on one frame, capture on the next: awaiting inside _process turns # it into a coroutine and the SceneTree stops driving it. if _frames > 20 and _shots < 8: var phase := _frames % SHOT_EVERY if phase == 0: var side := (_shots % 2) == 1 if side: _cam.position = Vector3(0.85, hips - 0.02, 0.0) else: # NEGATIVE Z is the FRONT. SkinnedPlayerModel spins the imported scene 180 # degrees (`facing_flip`: glTF forward is +Z, players face -Z), so a camera # on +Z looks at the character's BACK. Every tool in here used to sit on +Z, # and every "front" judgement made from them was of the back of the skirt. _cam.position = Vector3(0.0, hips - 0.02, -0.85) _cam.look_at(Vector3(0, hips - 0.16, 0), Vector3.UP) elif phase == 1: var tag := "side" if (_shots % 2) == 1 else "front" root.get_texture().get_image().save_png( "%s/skirt_%s_%d.png" % [_out, tag, _shots]) print("saved skirt_%s_%d.png" % [tag, _shots]) _shots += 1 if _shots >= 8: return true if _frames > 200: return true return false