Implement playable Mac client and rendering validation
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
extends SceneTree
|
||||
|
||||
## 鬼木林 / 赤鬼木林树怪的离线资源和 CPU/GPU 动作验收。
|
||||
## 这不是服务器刷新证明;真实地图中的选中、战斗和掉落由 playable_live_test 承担。
|
||||
|
||||
const MobView = preload("res://ui/mob_view.gd")
|
||||
const RACES := [2301, 2302, 2303, 2304, 2305, 2306, 2307, 2311, 2312, 2313, 2314, 2315]
|
||||
const STATES := ["wait", "run", "attack", "damage", "dead"]
|
||||
const SIZE := 640
|
||||
|
||||
var _output := ""
|
||||
var _failures: Array[String] = []
|
||||
var _pose_bounds := {}
|
||||
var _results: Array[Dictionary] = []
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func _fail(message: String) -> void:
|
||||
_failures.append(message)
|
||||
printerr("FOREST MOB FAIL: " + message)
|
||||
|
||||
func run() -> void:
|
||||
_output = OS.get_environment("MT_RENDER_OUTPUT")
|
||||
if _output.is_empty():
|
||||
_output = ProjectSettings.globalize_path("res://../build/rendering/forest-mobs-%d" % Time.get_unix_time_from_system())
|
||||
DirAccess.make_dir_recursive_absolute(_output)
|
||||
root.size = Vector2i(SIZE, SIZE)
|
||||
_build_lighting()
|
||||
var cam := Camera3D.new()
|
||||
cam.fov = 38.0
|
||||
cam.near = 0.02
|
||||
root.add_child(cam)
|
||||
cam.make_current()
|
||||
for gpu in ["0", "1"]:
|
||||
OS.set_environment("MTGODOT_GPUSKIN", gpu)
|
||||
for race in RACES:
|
||||
await _check_race(race, gpu, cam)
|
||||
OS.set_environment("MTGODOT_GPUSKIN", "0")
|
||||
var report := {"passed": _failures.is_empty(), "failures": _failures, "races": RACES,
|
||||
"states": STATES, "gpu_modes": ["0", "1"], "results": _results,
|
||||
"assets": AssetRoot.path(), "viewport": str(root.size),
|
||||
"headless": DisplayServer.get_name() == "headless",
|
||||
"scope": "offline tree-monster assembly; no server spawn or 40250 pixel parity"}
|
||||
var file := FileAccess.open(_output.path_join("report.json"), FileAccess.WRITE)
|
||||
if file:
|
||||
file.store_string(JSON.stringify(report, "\t"))
|
||||
file.close()
|
||||
print("FOREST MOB RENDER: ", JSON.stringify({"passed": _failures.is_empty(), "failures": _failures.size(), "results": _results.size(), "output": _output}))
|
||||
quit(0 if _failures.is_empty() else 1)
|
||||
|
||||
func _check_race(race: int, gpu: String, cam: Camera3D) -> void:
|
||||
var view := MobView.new()
|
||||
view.name = "ForestMob_%d_%s" % [race, gpu]
|
||||
root.add_child(view)
|
||||
if not view.build(AssetRoot.path(), null, race):
|
||||
_fail("race=%d GPU=%s build returned false" % [race, gpu])
|
||||
view.queue_free()
|
||||
await process_frame
|
||||
return
|
||||
var row := {"race": race, "gpu_skin": gpu == "1", "states": {}}
|
||||
for state in STATES:
|
||||
var requested := view.get_motion_path(state)
|
||||
if requested.is_empty() or not FileAccess.file_exists(requested):
|
||||
_fail("race=%d GPU=%s state=%s has no resolved .msa" % [race, gpu, state])
|
||||
continue
|
||||
view.set_anim_state(state)
|
||||
if view.anim == null:
|
||||
_fail("race=%d GPU=%s state=%s has no animation player" % [race, gpu, state])
|
||||
continue
|
||||
view.anim.set_process(true)
|
||||
await process_frame
|
||||
var actual := String(view.anim.get("anim_path"))
|
||||
if actual != requested:
|
||||
_fail("race=%d GPU=%s state=%s resolved %s but played %s" % [race, gpu, state, requested, actual])
|
||||
var duration := float(view.anim.call("get_duration")) if view.anim.has_method("get_duration") else 1.0
|
||||
var fraction := 0.5
|
||||
if state == "dead":
|
||||
fraction = 0.95
|
||||
view.anim.call("set_time", maxf(0.0, duration * fraction))
|
||||
view.anim.set_process(false)
|
||||
await process_frame
|
||||
var bounds: AABB = view.model.call("get_visual_aabb")
|
||||
if not bounds.position.is_finite() or not bounds.size.is_finite() or bounds.size.length() <= 0.0:
|
||||
_fail("race=%d GPU=%s state=%s has invalid visual AABB" % [race, gpu, state])
|
||||
_pose_bounds["%d/%s" % [race, state]] = bounds if gpu == "0" else _pose_bounds.get("%d/%s" % [race, state], AABB())
|
||||
if gpu == "1":
|
||||
var cpu_bounds: AABB = _pose_bounds.get("%d/%s" % [race, state], AABB())
|
||||
var tolerance := maxf(0.01, maxf(cpu_bounds.size.length(), bounds.size.length()) * 0.005)
|
||||
if cpu_bounds.size.length() <= 0.0 or cpu_bounds.position.distance_to(bounds.position) > tolerance or cpu_bounds.size.distance_to(bounds.size) > tolerance:
|
||||
_fail("race=%d state=%s CPU/GPU AABB differs beyond %.4fm" % [race, state, tolerance])
|
||||
var image_name := ""
|
||||
if DisplayServer.get_name() != "headless":
|
||||
var centre: Vector3 = view.model.global_transform * bounds.get_center()
|
||||
var visual_size: float = bounds.size.length() * view.model.scale.length() / sqrt(3.0)
|
||||
var distance: float = maxf(3.0, visual_size * 1.8)
|
||||
cam.position = centre + Vector3(distance * 0.35, distance * 0.22, distance)
|
||||
cam.look_at(centre, Vector3.UP)
|
||||
await RenderingServer.frame_post_draw
|
||||
image_name = "mob-%d-%s.png" % [race, state] if gpu == "1" else "mob-%d-%s-cpu.png" % [race, state]
|
||||
if root.get_texture().get_image().save_png(_output.path_join(image_name)) != OK:
|
||||
_fail("race=%d state=%s screenshot save failed" % [race, state])
|
||||
row.states[state] = {"requested_motion": requested, "resolved_motion": actual,
|
||||
"duration": duration, "sample_fraction": fraction,
|
||||
"aabb_position": [bounds.position.x, bounds.position.y, bounds.position.z],
|
||||
"aabb_size": [bounds.size.x, bounds.size.y, bounds.size.z], "screenshot": image_name}
|
||||
_results.append(row)
|
||||
view.queue_free()
|
||||
await process_frame
|
||||
|
||||
func _build_lighting() -> void:
|
||||
var sun := DirectionalLight3D.new()
|
||||
sun.rotation_degrees = Vector3(-42, 155, 0)
|
||||
sun.light_energy = 1.1
|
||||
root.add_child(sun)
|
||||
var env := WorldEnvironment.new()
|
||||
var settings := Environment.new()
|
||||
settings.background_mode = Environment.BG_COLOR
|
||||
settings.background_color = Color(0.08, 0.1, 0.14)
|
||||
settings.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
settings.ambient_light_color = Color(0.55, 0.58, 0.65)
|
||||
settings.ambient_light_energy = 0.8
|
||||
env.environment = settings
|
||||
root.add_child(env)
|
||||
Reference in New Issue
Block a user