extends SceneTree const Materials = preload("res://fx/particle_color_material.gd") 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: call_deferred("run") func run() -> void: var fx := EffectPlayer.new() var textures: Array[Texture2D] = [] for color in [Color.RED, Color.GREEN, Color.BLUE]: var img := Image.create(4, 4, false, Image.FORMAT_RGBA8) img.fill(color) textures.append(ImageTexture.create_from_image(img)) var frames := fx._texture_array(textures) check(frames != null and frames.get_layers() == 3, "independent texture-array slices") var base := StandardMaterial3D.new() base.blend_mode = BaseMaterial3D.BLEND_MODE_MIX base.albedo_texture = textures[0] var mat := Materials.build(base, 4, frames, {"TexAniType": 1, "TexAniDelay": 0.1}, 1.0) check(mat is ShaderMaterial, "ordinary modulation supports per-particle frames") if DisplayServer.get_name() != "headless": await pixels(mat) else: print("SKIP: texture animation pixels require a renderer") fx.free() print("effect_texture_animation_test: failures=%d" % failures) quit(1 if failures else 0) func pixels(material: ShaderMaterial) -> void: var viewport := SubViewport.new() viewport.size = Vector2i(96, 96) viewport.own_world_3d = true viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS root.add_child(viewport) var environment := WorldEnvironment.new() environment.environment = Environment.new() environment.environment.background_mode = Environment.BG_COLOR environment.environment.background_color = Color.BLACK viewport.add_child(environment) var camera := Camera3D.new() camera.projection = Camera3D.PROJECTION_ORTHOGONAL camera.size = 3.0 camera.position.z = 3.0 viewport.add_child(camera) var instance := MultiMeshInstance3D.new() var mesh := QuadMesh.new() mesh.size = Vector2(0.8, 0.8) var multi := MultiMesh.new() multi.transform_format = MultiMesh.TRANSFORM_3D multi.use_custom_data = true multi.use_colors = true multi.mesh = mesh multi.instance_count = 2 for i in 2: multi.set_instance_transform(i, Transform3D(Basis.IDENTITY, Vector3(-0.6 + i * 1.2, 0, 0))) multi.set_instance_color(i, Color.WHITE) instance.multimesh = multi instance.material_override = material viewport.add_child(instance) # Same material, different ages: old shared AnimatedTexture fails this. for type in [0, 1, 2, 3, 4]: material.set_shader_parameter("animation_type", type) for age in [0.15, 0.25, 0.35]: multi.set_instance_custom_data(0, Color(0, 0, 0.2, 0)) multi.set_instance_custom_data(1, Color(0, age, 0.2, 0)) await process_frame await RenderingServer.frame_post_draw var img := viewport.get_texture().get_image() var newborn := img.get_pixel(29, 48) check(newborn.r > 0.9 and newborn.g < 0.05, "newborn always starts at frame zero type=%d" % type) var aged := img.get_pixel(67, 48) if type in [0, 1, 2, 4]: var step := int(floor(age / 0.1)) var index := 0 if type == 0 else posmod(-step if type == 2 else step, 3) var channels := [aged.r, aged.g, aged.b] check(channels[index] > 0.9, "age selects expected frame type=%d age=%s" % [type, age]) else: check(maxf(aged.r, maxf(aged.g, aged.b)) > 0.9, "random frame selects one valid texture") viewport.free() await process_frame