Noise-driven fire light rig with bounce, core, embers and glow
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
extends SceneTree
|
||||
## Verifica il rig del fuoco: rumore continuo, assenza di strobing, coerenza fra le
|
||||
## luci e modulazione della fiamma. Il campione di rumore e' puro, quindi il test
|
||||
## simula 60 s a 60 Hz senza dipendere dal frame rate reale.
|
||||
const FireLight = preload("res://scripts/fire_light.gd")
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run_test")
|
||||
|
||||
func run_test() -> void:
|
||||
var world = load("res://scenes/main.tscn").instantiate()
|
||||
root.add_child(world)
|
||||
var rig = world.get_node("OfficeAtmosphere").fire_rig
|
||||
var main_light: OmniLight3D = rig.main_light
|
||||
var bounce: OmniLight3D = rig.bounce_light
|
||||
var core: OmniLight3D = rig.core_light
|
||||
|
||||
# --- limiti, variazione e assenza di strobing -----------------------------
|
||||
var previous := -1.0
|
||||
var maximum_step := 0.0
|
||||
var minimum := INF
|
||||
var maximum := -INF
|
||||
var total := 0.0
|
||||
var squares := 0.0
|
||||
var samples := 0
|
||||
rig._pulse = 0.0
|
||||
for i in 3600: # 60 s a 60 Hz
|
||||
rig._process(1.0 / 60.0)
|
||||
var energy := main_light.light_energy
|
||||
minimum = minf(minimum, energy)
|
||||
maximum = maxf(maximum, energy)
|
||||
total += energy
|
||||
squares += energy * energy
|
||||
samples += 1
|
||||
if previous >= 0.0:
|
||||
maximum_step = maxf(maximum_step, absf(energy - previous))
|
||||
previous = energy
|
||||
var mean := total / samples
|
||||
var deviation := sqrt(squares / samples - mean * mean)
|
||||
assert(minimum >= rig.MIN_ENERGY and maximum <= rig.MAX_ENERGY)
|
||||
assert(deviation > 0.05) # non e' una luce fissa
|
||||
assert(maximum_step < 0.05) # nessun salto: niente strobing
|
||||
assert(maximum - minimum > 0.4) # la gamma viene usata davvero
|
||||
print("PASS: flicker entro [%.2f, %.2f], deviazione %.3f, salto massimo per frame %.3f" % [minimum, maximum, deviation, maximum_step])
|
||||
|
||||
# --- coerenza fra le tre luci ---------------------------------------------
|
||||
assert(bounce.light_energy > 0.0 and core.light_energy > 0.0)
|
||||
assert(bounce.light_energy < main_light.light_energy)
|
||||
assert(core.omni_range < main_light.omni_range)
|
||||
assert(is_equal_approx(main_light.omni_range, rig.BASE_RANGE)) # raggio mai animato
|
||||
rig._pulse = 1.0
|
||||
rig._apply()
|
||||
var bright := [main_light.light_energy, bounce.light_energy, core.light_energy]
|
||||
rig._pulse = -1.0
|
||||
rig._apply()
|
||||
var dim := [main_light.light_energy, bounce.light_energy, core.light_energy]
|
||||
for i in 3:
|
||||
assert(bright[i] > dim[i])
|
||||
print("PASS: principale, rimbalzo e nucleo pulsano insieme e il raggio resta fisso")
|
||||
|
||||
# --- ricetta ufficiale del rimbalzo e unicita' delle ombre ----------------
|
||||
assert(not bounce.shadow_enabled and not core.shadow_enabled)
|
||||
assert(bounce.light_specular == 0.0 and core.light_specular == 0.0)
|
||||
assert(is_equal_approx(bounce.omni_range, rig.BASE_RANGE * rig.BOUNCE_RANGE_FACTOR))
|
||||
assert(bounce.get_parent() == main_light and core.get_parent() == main_light)
|
||||
assert(main_light.omni_shadow_mode == OmniLight3D.SHADOW_CUBE) # il dual paraboloid non esiste in Compatibility
|
||||
assert(main_light.shadow_enabled and not bounce.shadow_enabled)
|
||||
print("PASS: rimbalzo figlio senza ombre ne' specular, raggio +40%, ombre solo sulla principale")
|
||||
|
||||
# --- micro-scarto e fiamma ------------------------------------------------
|
||||
var maximum_offset := 0.0
|
||||
var flame_heights: Array[float] = []
|
||||
rig._time = 0.0
|
||||
for i in 600:
|
||||
rig._process(1.0 / 60.0)
|
||||
maximum_offset = maxf(maximum_offset, Vector2(main_light.position.x, main_light.position.z).length())
|
||||
flame_heights.append(rig._flame_mesh.size.y)
|
||||
assert(maximum_offset <= rig.JITTER * 1.45) # scarto piccolo e limitato
|
||||
assert(maximum_offset > 0.005) # le ombre si muovono davvero
|
||||
assert(flame_heights.max() > flame_heights.min())
|
||||
assert(flame_heights.max() < rig._flame_height * 1.2)
|
||||
print("PASS: scarto massimo %.3f m, altezza fiamma da %.2f a %.2f" % [maximum_offset, flame_heights.min(), flame_heights.max()])
|
||||
|
||||
# --- braci e determinismo -------------------------------------------------
|
||||
assert(rig.embers is CPUParticles3D and rig.embers.amount == 8 and rig.embers.emitting)
|
||||
assert(rig.embers.mesh.material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED)
|
||||
var same := Node3D.new()
|
||||
same.set_script(FireLight)
|
||||
world.add_child(same)
|
||||
var different := false
|
||||
for i in 200:
|
||||
var t := 0.01 * i
|
||||
if not is_equal_approx(rig.pulse_at(t), same.pulse_at(t)):
|
||||
different = true
|
||||
assert(not different) # stesso seed -> stessa sequenza, test riproducibili
|
||||
print("PASS: braci presenti e flicker deterministico con seed fisso")
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
quit()
|
||||
Reference in New Issue
Block a user