Implement playable Mac client and rendering validation
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
extends SceneTree
|
||||
const EffectPlayer = preload("res://fx/effect_player.gd")
|
||||
var failures := 0
|
||||
func check(ok: bool, label: String) -> void:
|
||||
if not ok:
|
||||
failures += 1
|
||||
printerr("FAIL: " + label)
|
||||
func _init() -> void:
|
||||
var fx := EffectPlayer.new()
|
||||
for kind in [0, 2, 3]:
|
||||
fx.build({"particles": [{"particle": {"RotationType": kind, "RotationSpeed": 180,
|
||||
"RotationRandomStartingBegin": 30, "RotationRandomStartingEnd": 60, "BillboardType": 1}}]})
|
||||
var g: GPUParticles3D = fx._emitters[-1]
|
||||
var pm: ParticleProcessMaterial = g.process_material
|
||||
var speed := 0.0 if kind == 0 else (180.0 if kind == 2 else -180.0)
|
||||
check(pm.angular_velocity_min == speed and pm.angular_velocity_max == speed,
|
||||
"constant rotation type %d uses degrees without random slowing" % kind)
|
||||
check(pm.angle_min == 30.0 and pm.angle_max == 60.0, "initial angle range type %d" % kind)
|
||||
check(g.material_override.billboard_mode == BaseMaterial3D.BILLBOARD_PARTICLES,
|
||||
"camera billboard consumes particle rotation type %d" % kind)
|
||||
check(fx._billboard(0) == BaseMaterial3D.BILLBOARD_DISABLED, "non-billboard unchanged")
|
||||
check(fx._billboard(1) == BaseMaterial3D.BILLBOARD_ENABLED, "mesh camera billboard unchanged")
|
||||
check(fx._billboard(2) == BaseMaterial3D.BILLBOARD_FIXED_Y, "source Y enum is two")
|
||||
check(fx._billboard(4) != BaseMaterial3D.BILLBOARD_FIXED_Y, "two-face is not misclassified as Y")
|
||||
fx.free()
|
||||
var path := AssetRoot.path().path_join("PC/ymir work/pc/shaman/effect/7budongbakbu_throw.mse")
|
||||
var spec: Dictionary = preload("res://fx/mse.gd").new().parse_file(path)
|
||||
check(not spec.get("particles", []).is_empty(), "real shaman rotation resource loaded")
|
||||
var count := 0
|
||||
for particle in spec.get("particles", []):
|
||||
var prop: Dictionary = particle.particle.duplicate(true)
|
||||
var kind := int(prop.get("RotationType", 0))
|
||||
if kind != 2 and kind != 3:
|
||||
continue
|
||||
# Isolate rotation/material configuration without loading original textures.
|
||||
prop.erase("TextureFiles")
|
||||
var real_fx := EffectPlayer.new()
|
||||
real_fx.build({"particles": [{"particle": prop}]})
|
||||
var pm: ParticleProcessMaterial = real_fx._emitters[0].process_material
|
||||
var expected := float(prop.get("RotationSpeed", 0)) * (1.0 if kind == 2 else -1.0)
|
||||
check(pm.angular_velocity_min == expected and pm.angular_velocity_max == expected, "real constant speed")
|
||||
check(pm.angle_min == float(prop.get("RotationRandomStartingBegin", 0)) and
|
||||
pm.angle_max == float(prop.get("RotationRandomStartingEnd", 0)), "real initial angle range")
|
||||
count += 1
|
||||
real_fx.free()
|
||||
check(count > 0, "real constant-rotation coverage")
|
||||
print("real rotation tracks: particles=%d" % count)
|
||||
time_event_rotation()
|
||||
real_time_event_rotation()
|
||||
print("effect_rotation_test: failures=%d" % failures)
|
||||
quit(1 if failures else 0)
|
||||
|
||||
func time_event_rotation() -> void:
|
||||
var fx := EffectPlayer.new()
|
||||
fx.build({"particles": [{"particle": {"RotationType": 1, "RotationSpeed": 999,
|
||||
"TimeEventRotation": [[0.0, 180.0], [0.5, -180.0], [1.0, 0.0]],
|
||||
"RotationRandomStartingBegin": 30, "RotationRandomStartingEnd": 30}},
|
||||
{"particle": {"RotationType": 1, "RotationSpeed": 999}}]})
|
||||
var pm: ParticleProcessMaterial = fx._emitters[0].process_material
|
||||
check(pm.angular_velocity_curve is CurveTexture, "TIME_EVENT has a speed curve")
|
||||
if pm.angular_velocity_curve is CurveTexture:
|
||||
var curve: Curve = pm.angular_velocity_curve.curve
|
||||
# Godot 4.7 curve branch integrates sampled value * base rate directly
|
||||
# into CUSTOM.x radians; unlike the constant branch it does not convert.
|
||||
check(is_equal_approx(curve.sample(0.0) * pm.angular_velocity_min, PI), "birth rate is pi radians/sec")
|
||||
check(is_zero_approx(curve.sample(0.25)), "speed passes through zero")
|
||||
check(is_equal_approx(curve.sample(0.75) * pm.angular_velocity_min, -PI / 2.0), "negative rate preserved")
|
||||
check(is_zero_approx(curve.sample(1.0)), "end stops without erasing earlier motion")
|
||||
var angle := deg_to_rad(pm.angle_min)
|
||||
for i in range(2000):
|
||||
angle += curve.sample((float(i) + 0.5) / 2000.0) * pm.angular_velocity_min * 2.0 / 2000.0
|
||||
check(absf(angle - deg_to_rad(-60.0)) < 0.0001, "two-second lifetime integrates speed plus initial angle")
|
||||
var empty: ParticleProcessMaterial = fx._emitters[1].process_material
|
||||
check(empty.angular_velocity_curve is CurveTexture, "empty TIME_EVENT remains an explicit zero curve")
|
||||
if empty.angular_velocity_curve is CurveTexture:
|
||||
check(is_zero_approx(empty.angular_velocity_curve.curve.sample(0.5)), "empty track ignores RotationSpeed fallback")
|
||||
fx.free()
|
||||
|
||||
func real_time_event_rotation() -> void:
|
||||
var count := 0
|
||||
var samples := 0
|
||||
for name in ["jeungsok_blow", "8chojeonbu", "jeungryeok_hand"]:
|
||||
var path := AssetRoot.path().path_join("PC/ymir work/pc/shaman/effect/" + name + ".mse")
|
||||
var spec: Dictionary = preload("res://fx/mse.gd").new().parse_file(path)
|
||||
check(not spec.get("particles", []).is_empty(), "real TIME_EVENT file " + name)
|
||||
for particle in spec.get("particles", []):
|
||||
if int(particle.particle.get("RotationType", 0)) != 1:
|
||||
continue
|
||||
var rows: Array = particle.particle.get("TimeEventRotation", [])
|
||||
var fx := EffectPlayer.new()
|
||||
fx.build({"particles": [{"particle": {"RotationType": 1, "TimeEventRotation": rows}}]})
|
||||
var pm: ParticleProcessMaterial = fx._emitters[0].process_material
|
||||
check(pm.angular_velocity_curve is CurveTexture, "real TIME_EVENT material")
|
||||
if pm.angular_velocity_curve is CurveTexture:
|
||||
for row in rows:
|
||||
if float(row[0]) >= 0.0 and float(row[0]) <= 1.0:
|
||||
var actual: float = pm.angular_velocity_curve.curve.sample(float(row[0])) * pm.angular_velocity_min
|
||||
check(is_equal_approx(actual, deg_to_rad(float(row[1]))), "real TIME_EVENT radians " + name)
|
||||
samples += 1
|
||||
count += 1
|
||||
fx.free()
|
||||
check(count > 0 and samples > 0, "real time-dependent speed coverage")
|
||||
print("real time-event tracks: particles=%d keys=%d" % [count, samples])
|
||||
Reference in New Issue
Block a user