group = "Output"
display_name = "Reflection Probe Base"
output_node = true
render_configs = ["core/stingray_renderer/renderer"]

inputs = {
}

options = {
}

ui = [
]

render_state = {
	reflection_probe = {
		inherit: ["core/stingray_renderer/shader_libraries/common#default"]
		state: {
			z_enable = "true"
			z_write_enable = "false"
			z_func = "greater_equal"

			cull_mode = "cull_ccw"

			blend_enable = "true"
			blend_op = "blend_op_add"
			dest_blend = "blend_one"
			src_blend = "blend_inv_dest_alpha"
		}
	}

	debug_visualization = {
		inherit: ["core/stingray_renderer/shader_libraries/common#opacity"]
		state: {
			"defined(FRONT_CULLING)" = {
				cull_mode = "cull_ccw"
			}
			"defined(BACK_CULLING)" = {
				cull_mode = "cull_cw"
			}
			"defined(WIREFRAME)" = {
				cull_mode = "cull_none"
				fill_mode = "fill_wireframe"	
				"on_renderer(D3D11, D3D12)" = {
					depth_bias = "-1"
					depth_bias_clamp = "-0.00015"
					slope_scale_depth_bias = "-2.0"
				}
			}
			"defined(DRAW_INFLUENCE)" = {
				cull_mode = "cull_none"
				z_func = "greater_equal"
			}
		}
	}
}

sampler_state = {

}

channels = 	{
	vertex_position = { type = "float4" domain = "vertex" }
}

permutation_sets = {
}

shader_contexts = {
	default = {
		passes_sort_mode = "deferred"
		compile_with = [
			{ if: "on_renderer(D3D11, D3D12, GNM, GL)" }
		]
		passes = [
			{ if: "on_renderer(D3D11, D3D12, GNM)" then: [
				{ layer="reflections" code_block="reflection_probe" render_state="reflection_probe" }
				
				// { layer="wireframe" code_block="reflection_probe" defines=["DRAW_INFLUENCE_VOLUME" "FRONT_CULLING"] render_state="dev_volume_visualization"  }

				{ layer="wireframe" code_block="reflection_probe" defines=["DRAW_INFLUENCE"] render_state="debug_visualization" branch_key="dev_volume_visualization" }

				{ layer="wireframe" code_block="reflection_probe" defines=["DRAW_TRACE_VOLUME" "FRONT_CULLING"] render_state="debug_visualization" branch_key="dev_volume_visualization" }
				{ layer="wireframe" code_block="reflection_probe" defines=["DRAW_TRACE_VOLUME" "BACK_CULLING"] render_state="debug_visualization" branch_key="dev_volume_visualization" }
				{ layer="wireframe" code_block="reflection_probe" defines=["DRAW_TRACE_VOLUME" "WIREFRAME"] render_state="debug_visualization" branch_key="dev_volume_visualization" }

				{ layer="wireframe" code_block="reflection_probe" defines=["DRAW_INFLUENCE_VOLUME" "BACK_CULLING"] render_state="debug_visualization" branch_key="dev_volume_visualization" }
				{ layer="wireframe" code_block="reflection_probe" defines=["DRAW_INFLUENCE_VOLUME" "WIREFRAME"] render_state="debug_visualization" branch_key="dev_volume_visualization" }
			] }
		]
	}
}

code_blocks = {
	reflection_probe = {
		include:[
			"core/stingray_renderer/shader_libraries/common#common",
			"core/stingray_renderer/shader_libraries/common#gbuffer_access",
			"core/stingray_renderer/shader_libraries/lighting_common#brdf",
			"core/stingray_renderer/shader_libraries/shadow_map_common#shadow_bias"
			]

		samplers = {
			gbuffer1 = {
				sampler_state = "core/stingray_renderer/shader_libraries/common#clamp_point"
				source = "resource_set"
				slot_name = "gbuffer1"
				type = "2d"
			}

			linear_depth = {
				sampler_state = "core/stingray_renderer/shader_libraries/common#clamp_point"
				source = "resource_set"
				slot_name = "linear_depth"
				type = "2d"
			}

			reflection_map = {
				display_name = "Reflection Map"
				type = "cube"
				sampler_state = "core/stingray_renderer/shader_libraries/common#clamp_linear"
				source = "material"
				slot_name = "reflection_map"
			}

			radiation_map = {
				display_name = "Reflection Map"
				type = "cube"
				sampler_state = "core/stingray_renderer/shader_libraries/common#clamp_linear"
				source = "material"
				slot_name = "radiation_map"
			}	
		}

		code = {
			hlsl = """
				DECLARE_SAMPLER_2D(gbuffer1);
				DECLARE_SAMPLER_2D(linear_depth);
				DECLARE_SAMPLER_CUBE(reflection_map);
				DECLARE_SAMPLER_CUBE(radiation_map);

				struct VS_INPUT {
					float4 position : POSITION;
					GRAPH_VERTEX_INPUT
				};

				struct PS_INPUT {
					float4 position : SV_POSITION;
					float4 w : TEXCOORD1;
					GRAPH_PIXEL_INPUT
				};

				CBUFFER_START(c0)
					float4x4 world;
					float4x4 inv_world;
					float4x4 world_view_proj;
					GRAPH_MATERIAL_EXPORTS
				CBUFFER_END

				CBUFFER_START(light)
					float3 light_position;
					float3 light_proxy_scale;
					float3 light_box_min;
					float3 light_box_max;

					// controlled from script data on probe unit propagated through the light material.
					float3 trace_box_min;
					float3 trace_box_max;
					float3 falloff;
				CBUFFER_END

				DEFAULT_ROOT_SIGNATURE_ATTRIBUTE
				PS_INPUT vs_main(VS_INPUT input) {
					PS_INPUT o;

					GraphVertexParams params;
					GraphVertexResults results;

					// Write automatic params
					GRAPH_VERTEX_WRITE_PARAMS(params, input);

					#if defined(DRAW_TRACE_VOLUME)
						float3 scale = trace_box_max - trace_box_min;
						float4 position = float4(input.position.xyz * scale, 1);
						position.xyz += (trace_box_min + trace_box_max) * 0.5f;
					#else
						float4 position = float4(input.position.xyz * light_proxy_scale, 1);
						position.xyz += (light_box_min + light_box_max) * 0.5f;
					#endif
					GRAPH_VERTEX_PARAM(params, vertex_position) = position;

					o.position = mul(position, world_view_proj);
					#if !defined(STENCIL_MARK)
						o.w = encode_world_pos(o.position); 
					#endif

					// Evaluate all pieces of the graph that should run per-vertex.
					GRAPH_EVALUATE_VERTEX(results, params);

					// Write results
					GRAPH_VERTEX_WRITE(o, results, params);

					return o;
				}

				#if defined(DRAW_INFLUENCE_VOLUME) || defined(DRAW_TRACE_VOLUME)
					DEFAULT_ROOT_SIGNATURE_ATTRIBUTE
					float4 ps_main() : SV_TARGET0
					{
						#if defined(WIREFRAME)
							#if defined(DRAW_INFLUENCE_VOLUME)
								return float4(1,0.8,0.8,0.5);
							#else
								return float4(0.8,1,0.8,0.5);
							#endif
						#elif defined(DRAW_INFLUENCE_VOLUME)
							return float4(1,0,0,0.1);
						#else
							return float4(0,1,0,0.1);
						#endif
					}
				#else
					// TODO: does not support scaling atm.
					
					#if defined(DRAW_INFLUENCE)
						DEFAULT_ROOT_SIGNATURE_ATTRIBUTE
						float4 ps_main(PS_INPUT input
							#if defined(GL2)
								, float2 wpos : VPOS
							#endif
							) : SV_TARGET0
					#else
						struct PS_OUTPUT {
							float4 reflection : SV_TARGET0;
							float4 radiation : SV_TARGET1;
						};

						DEFAULT_ROOT_SIGNATURE_ATTRIBUTE
						PS_OUTPUT ps_main(PS_INPUT input
							#if defined(GL2)
								, float2 wpos : VPOS
							#endif
							)
					#endif
					{
						#if defined(GL2)
							half2 uv = wpos.xy / back_buffer_size;
						#else
							half2 uv = input.position.xy / back_buffer_size;
						#endif
						float d = gbuffer_decode_depth(TEX2D(linear_depth, uv));

						float3 wp = decode_world_pos(input.w, d);
						float3 op = mul(float4(wp, 1), inv_world).xyz;
						float3 V = mul(normalize(camera_world._m30_m31_m32 - wp), (float3x3)inv_world);

						#if defined(HAS_LIGHT_COLOR)
							GraphPixelParams params;
							GraphPixelResults graph;
							GRAPH_PIXEL_WRITE_PARAMS(params, input);
							GRAPH_EVALUATE_PIXEL(graph, params);							
						#else
							//float3 light_col = light_color;
						#endif

						half4 gbuffer_1 = TEX2D(gbuffer1, uv);

						float3 gbuffer_normal = gbuffer_decode_normal(gbuffer_1);
						float3 N = mul(gbuffer_normal, (float3x3)inv_world);
						half roughness = gbuffer_decode_roughness(gbuffer_1);
						float mipmap_index = roughness * 7;

						float3 reflection_dir;
						{
							reflection_dir = reflect(-V, N);
							float3 r_max = (trace_box_max - op) / reflection_dir;
							float3 r_min = (trace_box_min - op) / reflection_dir;
							float3 r_min_max = max(r_max, r_min); 
							float intersect_dist = min(min(r_min_max.x, r_min_max.y), r_min_max.z);
							reflection_dir = mul(op + reflection_dir * intersect_dist, (float3x3)world);
						}

						float3 reflection = rgbm_decode(TEXCUBELOD(reflection_map, reflection_dir, mipmap_index));
						
						float3 radiation_dir = gbuffer_normal;
						/*{
							radiation_dir = N;
							float3 r_max = (trace_box_max - op) / radiation_dir;
							float3 r_min = (trace_box_min - op) / radiation_dir;
							float3 r_min_max = max(r_max, r_min); 
							float intersect_dist = min(min(r_min_max.x, r_min_max.y), r_min_max.z);
							radiation_dir = mul(op + radiation_dir * intersect_dist, (float3x3)world);
						}*/

						// TODO: this might be wanted, but requires more probes in order to get good result.
						// float diff_attn = saturate(dot(gbuffer_normal, normalize(world._m30_m31_m32 - wp)));
						float3 radiation = rgbm_decode(TEXCUBELOD(radiation_map, radiation_dir, 0));

						// Attenuate
						float3 distances = min(op - light_box_min, light_box_max - op) / falloff;
						float attn = saturate(min(distances.x, min(distances.y, distances.z)));

						#if defined(DRAW_INFLUENCE)
							bool3 inside = (distances > 0.0);
							if(!all(inside))
								discard;
							return float4(0, 0, 1, (1.0 - attn) * 0.5);
						#else
							PS_OUTPUT o;
							o.reflection = float4(reflection * attn, attn);
							o.radiation = float4(radiation * attn, attn);
							return o;
						#endif
					}
				#endif


			"""
		}
	}
}
