ani
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
extends SceneTree
|
||||
|
||||
## Rendered character-art gate for the focused Sakura second-skin pilot.
|
||||
## Captures open/deploying/closed helmet states and verifies that active jet
|
||||
## flames emerge from all four integrated vector-jet mouths.
|
||||
|
||||
var _scene: Node3D
|
||||
var _camera: Camera3D
|
||||
var _models: Array[SkinnedPlayerModel] = []
|
||||
var _jets: Array[PlayerJetVFX] = []
|
||||
var _out_dir := "."
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
if not args.is_empty():
|
||||
_out_dir = args[0]
|
||||
if not _out_dir.is_absolute_path():
|
||||
_out_dir = ProjectSettings.globalize_path(_out_dir)
|
||||
DirAccess.make_dir_recursive_absolute(_out_dir)
|
||||
_run()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_scene = Node3D.new()
|
||||
root.add_child(_scene)
|
||||
current_scene = _scene
|
||||
_build_studio()
|
||||
_add_pilot("default", "sakura",
|
||||
"res://assets/characters/skins/sakura_pilot_hd.glb", 0.0)
|
||||
for _i in 24:
|
||||
await process_frame
|
||||
for model in _models:
|
||||
model.update_state("ground", 0.0, false)
|
||||
model.set_locomotion(0.0, 1.0, 0.0)
|
||||
|
||||
_camera.global_position = Vector3(0.0, 1.05, -3.2)
|
||||
_camera.look_at(Vector3(0.0, 0.92, 0.0), Vector3.UP)
|
||||
await _capture("pilot_open")
|
||||
_camera.global_position = Vector3(0.0, 1.58, -1.15)
|
||||
_camera.look_at(Vector3(0.0, 1.57, 0.0), Vector3.UP)
|
||||
await _capture("helmet_open_closeup")
|
||||
_models[0].set_helmet_closed(true)
|
||||
for _i in 5:
|
||||
await process_frame
|
||||
await _capture("pilot_helmet_deploy")
|
||||
for _i in 40:
|
||||
await process_frame
|
||||
await _capture("pilot_helmet_closed")
|
||||
|
||||
_camera.global_position = Vector3(0.0, 1.08, 3.2)
|
||||
_camera.look_at(Vector3(0.0, 0.92, 0.0), Vector3.UP)
|
||||
await _capture("pilot_rear")
|
||||
for jet in _jets:
|
||||
jet.burst_double_jump()
|
||||
for _i in 4:
|
||||
await process_frame
|
||||
await _capture("pilot_thrusters")
|
||||
|
||||
_models[0].set_wall_side(-1.0)
|
||||
_models[0].set_wall_glide_motion(Vector3.RIGHT * 13.0)
|
||||
for _i in 24:
|
||||
_models[0].update_state("wall_run", 13.0, false)
|
||||
_jets[0].set_wall_glide(true, Vector2.RIGHT)
|
||||
await process_frame
|
||||
_camera.global_position = Vector3(2.4, 1.45, -2.7)
|
||||
_camera.look_at(Vector3(0.0, 1.0, 0.0), Vector3.UP)
|
||||
await _capture("pilot_wall_glide")
|
||||
|
||||
var failed := false
|
||||
if not _models[0].helmet_closed_debug() \
|
||||
or _models[0].helmet_progress_debug() < 0.98:
|
||||
push_error("ANIME_MECHA_CAPTURE helmet failed to finish deployment")
|
||||
failed = true
|
||||
for model_index in _models.size():
|
||||
var model := _models[model_index]
|
||||
var resolved_roles: Array[String] = []
|
||||
var missing_roles: Array[String] = []
|
||||
for role in ["hips", "shoulder.L", "shoulder.R", "upper_arm.L",
|
||||
"upper_arm.R", "forearm.L", "forearm.R", "thigh.L",
|
||||
"thigh.R", "shin.L", "shin.R", "foot.L", "foot.R"]:
|
||||
if model._role_bone(role, []) >= 0:
|
||||
resolved_roles.append(role)
|
||||
else:
|
||||
missing_roles.append(role)
|
||||
print("ANIME_MECHA_CAPTURE %s: %d detail parts, %d nozzles, " % [
|
||||
model.skin_id, model.mecha_detail_part_count_debug(),
|
||||
model.mecha_nozzle_count_debug()], "resolved=", resolved_roles,
|
||||
" missing=", missing_roles)
|
||||
if not missing_roles.is_empty():
|
||||
var bone_names: Array[String] = []
|
||||
if model.skeleton:
|
||||
for bone_index in model.skeleton.get_bone_count():
|
||||
bone_names.append(model.skeleton.get_bone_name(bone_index))
|
||||
print("ANIME_MECHA_CAPTURE unresolved sidecar roles=",
|
||||
model._rig_info.get("roles", {}), " skeleton bones=", bone_names)
|
||||
if model.mecha_detail_part_count_debug() < 180:
|
||||
push_error("ANIME_MECHA_CAPTURE insufficient hardware detail on %s: %d parts" % [
|
||||
model.skin_id, model.mecha_detail_part_count_debug()])
|
||||
failed = true
|
||||
if model.mecha_nozzle_count_debug() != 4:
|
||||
push_error("ANIME_MECHA_CAPTURE missing four visible nozzles on %s" % model.skin_id)
|
||||
failed = true
|
||||
var nozzles := model.get_jet_socket_world_positions()
|
||||
var flames := _jets[model_index].get_flame_world_positions()
|
||||
if nozzles.size() != 4 or flames.size() != 4:
|
||||
push_error("ANIME_MECHA_CAPTURE socket count mismatch on %s" % model.skin_id)
|
||||
failed = true
|
||||
continue
|
||||
for nozzle_index in 4:
|
||||
if nozzles[nozzle_index].distance_to(flames[nozzle_index]) > 0.001:
|
||||
push_error("ANIME_MECHA_CAPTURE flame %d misses %s nozzle" % [
|
||||
nozzle_index, model.skin_id])
|
||||
failed = true
|
||||
print("ANIME_MECHA_CAPTURE %s" % ("FAIL" if failed else "PASS"))
|
||||
quit(1 if failed else 0)
|
||||
|
||||
|
||||
func _build_studio() -> void:
|
||||
var world := WorldEnvironment.new()
|
||||
var environment := Environment.new()
|
||||
environment.background_mode = Environment.BG_COLOR
|
||||
environment.background_color = Color("b8c0cc")
|
||||
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
environment.ambient_light_color = Color("e8edf6")
|
||||
environment.ambient_light_energy = 1.15
|
||||
environment.tonemap_mode = Environment.TONE_MAPPER_FILMIC
|
||||
world.environment = environment
|
||||
_scene.add_child(world)
|
||||
var sun := DirectionalLight3D.new()
|
||||
sun.rotation_degrees = Vector3(-42.0, -28.0, 0.0)
|
||||
sun.light_energy = 1.9
|
||||
_scene.add_child(sun)
|
||||
_camera = Camera3D.new()
|
||||
_camera.fov = 38.0
|
||||
_scene.add_child(_camera)
|
||||
_camera.current = true
|
||||
|
||||
|
||||
func _add_pilot(id: String, theme: String, path: String, x: float) -> void:
|
||||
var model := SkinnedPlayerModel.new()
|
||||
model.skin_id = id
|
||||
model.mecha_theme = theme
|
||||
model.model_path = path
|
||||
model.position.x = x
|
||||
_scene.add_child(model)
|
||||
# Static art QA does not need the expensive full-quality hair/cloth solver;
|
||||
# gameplay and the picker still use their normal distance-driven LOD.
|
||||
if model._spring_mod:
|
||||
model._spring_mod.lod = 3
|
||||
model._spring_mod = null
|
||||
_models.append(model)
|
||||
var jet := PlayerJetVFX.new()
|
||||
jet.name = "JetVFX_%s" % id
|
||||
_scene.add_child(jet)
|
||||
jet.set_visual_model(model)
|
||||
jet.set_render_enabled(true)
|
||||
_jets.append(jet)
|
||||
|
||||
|
||||
func _capture(tag: String) -> void:
|
||||
for _i in 5:
|
||||
await process_frame
|
||||
var path := _out_dir.path_join(tag + ".png")
|
||||
root.get_texture().get_image().save_png(path)
|
||||
print("ANIME_MECHA_CAPTURE saved ", path)
|
||||
Reference in New Issue
Block a user