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: call_deferred("run") func run() -> void: for kind in [4, 5]: var fx := EffectPlayer.new() fx.build({"particles": [{"particle": {"BillboardType": kind, "ColorOperationType": 4}}]}) var emitter: GPUParticles3D = fx._emitters[0] var mesh := emitter.draw_pass_1 var arrays := mesh.surface_get_arrays(0) var angles := [-30.0, 30.0] if kind == 4 else [0.0, -60.0, 60.0] check(arrays[Mesh.ARRAY_VERTEX].size() == angles.size() * 6, "one quad per reference face") for i in angles.size(): for v in 6: check(is_equal_approx(arrays[Mesh.ARRAY_TEX_UV2][i * 6 + v].x, deg_to_rad(angles[i])), "reference face angle") check(emitter.material_override is ShaderMaterial, "custom face shader") check(emitter.material_override.get_shader_parameter("billboard_mode") == 5, "multiface mode") if DisplayServer.get_name() != "headless": await pixels(mesh, emitter.material_override, kind) fx.free() print("effect_faces_test: failures=%d" % failures) quit(1 if failures else 0) func pixels(mesh: Mesh, material: ShaderMaterial, kind: int) -> void: var viewport := SubViewport.new() viewport.size = Vector2i(128, 128) viewport.own_world_3d = true viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS root.add_child(viewport) var world := WorldEnvironment.new() world.environment = Environment.new() world.environment.background_mode = Environment.BG_COLOR world.environment.background_color = Color.BLACK viewport.add_child(world) var image := Image.create(2, 2, false, Image.FORMAT_RGBA8) image.fill(Color.WHITE) material.set_shader_parameter("albedo_texture", ImageTexture.create_from_image(image)) var instance := MeshInstance3D.new() instance.mesh = mesh instance.material_override = material viewport.add_child(instance) var camera := Camera3D.new() camera.projection = Camera3D.PROJECTION_ORTHOGONAL camera.size = 1.28 viewport.add_child(camera) for angle in [0.0, 0.7]: camera.position = Vector3(sin(angle) * 3, 0, cos(angle) * 3) camera.look_at(Vector3.ZERO) await process_frame await RenderingServer.frame_post_draw var pixels := viewport.get_texture().get_image() var width := 0 for x in 128: if pixels.get_pixel(x, 64).r > 0.5: width += 1 # Default width .64m: 2FACE projects cos(30deg), 3FACE includes a full front face. var expected := 64.0 * (cos(PI / 6) if kind == 4 else 1.0) check(absf(width - expected) <= 3.0, "Metal projection kind=%d camera=%f width=%d expected=%f" % [kind, angle, width, expected]) viewport.free() await process_frame