/**************************************************************************/
/*  rendering_light_culler.cpp                                            */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

#include "rendering_light_culler.h"

#include "core/math/plane.h"
#include "core/math/projection.h"
#include "servers/rendering/rendering_server_globals.h"

#ifdef LIGHT_CULLER_DEBUG_FLASH
#include "core/config/engine.h"
#endif

#ifdef RENDERING_LIGHT_CULLER_DEBUG_STRINGS
const char *RenderingLightCuller::Data::string_planes[] = {
	"NEAR",
	"FAR",
	"LEFT",
	"TOP",
	"RIGHT",
	"BOTTOM",
};
const char *RenderingLightCuller::Data::string_points[] = {
	"FAR_LEFT_TOP",
	"FAR_LEFT_BOTTOM",
	"FAR_RIGHT_TOP",
	"FAR_RIGHT_BOTTOM",
	"NEAR_LEFT_TOP",
	"NEAR_LEFT_BOTTOM",
	"NEAR_RIGHT_TOP",
	"NEAR_RIGHT_BOTTOM",
};

String RenderingLightCuller::Data::plane_bitfield_to_string(unsigned int BF) {
	String sz;

	for (int n = 0; n < 6; n++) {
		unsigned int bit = 1 << n;
		if (BF & bit) {
			sz += String(string_planes[n]) + ", ";
		}
	}

	return sz;
}
#endif

void RenderingLightCuller::prepare_directional_light(const RendererSceneCull::Instance *p_instance, int32_t p_directional_light_id) {
	//data.directional_light = p_instance;
	// Something is probably going wrong, we shouldn't have this many directional lights...
	ERR_FAIL_COND(p_directional_light_id > 512);
	DEV_ASSERT(p_directional_light_id >= 0);

	// First make sure we have enough directional lights to hold this one.
	if (p_directional_light_id >= (int32_t)data.directional_cull_planes.size()) {
		data.directional_cull_planes.resize(p_directional_light_id + 1);
	}

	_prepare_light(*p_instance, p_directional_light_id);
}

bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_instance, int32_t p_directional_light_id) {
	if (!data.is_active()) {
		return true;
	}

	LightSource lsource;
	switch (RSG::light_storage->light_get_type(p_instance.base)) {
		case RSE::LIGHT_SPOT:
			lsource.type = LightSource::ST_SPOTLIGHT;
			lsource.angle = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_SPOT_ANGLE);
			lsource.range = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_RANGE);
			break;
		case RSE::LIGHT_OMNI:
			lsource.type = LightSource::ST_OMNI;
			lsource.range = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_RANGE);
			break;
		case RSE::LIGHT_AREA: {
			lsource.type = LightSource::ST_AREA;
			lsource.area_size = RSG::light_storage->light_area_get_size(p_instance.base);
			float half_diagonal = lsource.area_size.length() / 2.0;
			lsource.range = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_RANGE) + half_diagonal;
		} break;
		case RSE::LIGHT_DIRECTIONAL:
			lsource.type = LightSource::ST_DIRECTIONAL;

			lsource.range = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_SHADOW_MAX_DISTANCE);
			switch (RSG::light_storage->light_directional_get_shadow_mode(p_instance.base)) {
				case RSE::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL:
					lsource.cascade_count = 1;
					break;
				case RSE::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS:
					lsource.cascade_count = 2;
					break;
				case RSE::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS:
					lsource.cascade_count = 4;
					break;
				default:
					ERR_FAIL_V_MSG(false, "Only directional lights with 1, 2, or 4 shadow cascades are supported.");
					break;
			}
			lsource.cascade_splits[0] = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET);
			lsource.cascade_splits[1] = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET);
			lsource.cascade_splits[2] = RSG::light_storage->light_get_param(p_instance.base, RSE::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET);
			lsource.blend_splits = RSG::light_storage->light_directional_get_blend_splits(p_instance.base);
			break;
	}

	lsource.pos = p_instance.transform.origin;
	lsource.dir = -p_instance.transform.basis.get_column(2);
	lsource.dir.normalize();

	// In reality there's always going to be at least one cascade, but the compiler can't know that.
	// If SOMEHOW there's actually 0 cascades though, I suppose there isn't going to be anything visible after all.
	bool visible = false;
	if (p_directional_light_id == -1) {
		visible = _add_light_camera_planes(data.regular_cull_planes, lsource, { &data.frustum_planes[0], data.frustum_points });
	} else {
		int used_planes = 1 + lsource.cascade_count; // 2 for ortho (near+far), 3 for pssm2 (near+mid+far), 5 for pssm4 (near+3mids+far).
		Plane boundary_planes[5];
		{
			constexpr const int MAX_PLANES = 5;
			// Orthogonal cameras have fixed max shadow distance, determined by the camera far plane.
			// That must be accounted for, or else GH-120457 happens.
			// For perspective cameras, shadow far must also never be further than the camera far plane (otherwise things break).
			float camera_far = data.camera_projection.get_z_far();
			float shadow_far = data.camera_projection.is_orthogonal() ? camera_far : MIN(lsource.range, camera_far);
			real_t plane_distances[MAX_PLANES] = {
				data.camera_projection.get_z_near(),
				lsource.cascade_splits[0] * shadow_far,
				lsource.cascade_splits[1] * shadow_far,
				lsource.cascade_splits[2] * shadow_far,
				shadow_far,
			};
			// If not 4 cascades, replace last used cascade plane distance with max shadow range (shadow far plane distance).
			plane_distances[used_planes - 1] = shadow_far;
#ifdef LIGHT_CULLER_DEBUG_LOGGING
			if (is_logging()) {
				print_line("cascade split planes (first " + itos(used_planes) + " used): " +
						String(Variant(plane_distances[0])) + "m, " +
						String(Variant(plane_distances[1])) + "m, " +
						String(Variant(plane_distances[2])) + "m, " +
						String(Variant(plane_distances[3])) + "m, " +
						String(Variant(plane_distances[4])) + "m");
			}
#endif
			Vector3 camera_normal = data.camera_transform.basis.xform(Vector3(0, 0, 1)).normalized();
			for (int i = 0; i < used_planes; i++) {
				real_t plane_distance = plane_distances[i];

				//Plane compute
				boundary_planes[i] = Plane(
						camera_normal,
						data.camera_transform.origin + camera_normal * -plane_distance);
			}
		}

		for (int i = 0; i < lsource.cascade_count; i++) {
			/*
			enum PlaneOrder {
				PLANE_NEAR,
				PLANE_FAR,
				PLANE_LEFT,
				PLANE_TOP,
				PLANE_RIGHT,
				PLANE_BOTTOM,
				PLANE_TOTAL,
			};
			*/
			Plane cull_planes[6] = {
				boundary_planes[MAX(i - (lsource.blend_splits ? 1 : 0), 0)],
				Plane(-boundary_planes[i + 1].normal, -boundary_planes[i + 1].d), // Normal flip to ensure far is outward-facing.
				data.frustum_planes[2],
				data.frustum_planes[3],
				data.frustum_planes[4],
				data.frustum_planes[5],
			};

#ifdef LIGHT_CULLER_DEBUG_LOGGING
			if (is_logging()) {
				for (int p = 0; p < 6; p++) {
					print_line("cascade " + itos(i) + " plane " + itos(p) + " : " + String(cull_planes[p]));
				}
			}
#endif

			// Frustum point calculation
			Vector3 frustum_points[8];
			bool success = create_frustum_points(cull_planes, frustum_points);
			ERR_FAIL_COND_V(!success, false);

			// Replace frustum arguments with cascade's.
			LightCullPlanes &destination = data.directional_cull_planes[p_directional_light_id].planes[i];
			visible = _add_light_camera_planes(destination, lsource, { cull_planes, frustum_points });
		}
	}

	if (data.light_culling_active) {
		return visible;
	}
	return true;
}

bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id, int32_t p_cascade) {
	if (!data.is_active() || !is_caster_culling_active()) {
		return true;
	}

	ERR_FAIL_INDEX_V(p_directional_light_id, (int32_t)data.directional_cull_planes.size(), true);

	LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id].planes[p_cascade];

	Vector3 mins = Vector3(p_bound.bounds[0], p_bound.bounds[1], p_bound.bounds[2]);
	Vector3 maxs = Vector3(p_bound.bounds[3], p_bound.bounds[4], p_bound.bounds[5]);
	AABB bb(mins, maxs - mins);

	real_t r_min, r_max;
	for (int p = 0; p < cull_planes.num_cull_planes; p++) {
		bb.project_range_in_plane(cull_planes.cull_planes[p], r_min, r_max);
		if (r_min > 0.0f) {
#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT
			cull_planes.rejected_count++;
#endif

			return false;
		}
	}

	return true;
}

void RenderingLightCuller::cull_regular_light(PagedArray<RendererSceneCull::Instance *> &r_instance_shadow_cull_result) {
	if (!data.is_active() || !is_caster_culling_active()) {
		return;
	}

	// If the light is out of range, no need to check anything, just return 0 casters.
	// Ideally an out of range light should not even be drawn AT ALL (no shadow map, no PCF etc).
	if (data.out_of_range) {
		return;
	}

	// Shorter local alias.
	PagedArray<RendererSceneCull::Instance *> &list = r_instance_shadow_cull_result;

#ifdef LIGHT_CULLER_DEBUG_LOGGING
	uint32_t count_before = r_instance_shadow_cull_result.size();
#endif

	// Go through all the casters in the list (the list will hopefully shrink as we go).
	for (int n = 0; n < (int)list.size(); n++) {
		// World space aabb.
		const AABB &bb = list[n]->transformed_aabb;

#ifdef LIGHT_CULLER_DEBUG_LOGGING
		if (is_logging()) {
			print_line("bb : " + String(bb));
		}
#endif

		real_t r_min, r_max;
		bool show = true;

		for (int p = 0; p < data.regular_cull_planes.num_cull_planes; p++) {
			// As we only need r_min, could this be optimized?
			bb.project_range_in_plane(data.regular_cull_planes.cull_planes[p], r_min, r_max);

#ifdef LIGHT_CULLER_DEBUG_LOGGING
			if (is_logging()) {
				print_line("\tplane " + itos(p) + " : " + String(data.regular_cull_planes.cull_planes[p]) + " r_min " + String(Variant(r_min)) + " r_max " + String(Variant(r_max)));
			}
#endif

			if (r_min > 0.0f) {
				show = false;
				break;
			}
		}

		// Remove.
		if (!show) {
			list.remove_at_unordered(n);

			// Repeat this element next iteration of the loop as it has been removed and replaced by the last.
			n--;

#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT
			data.regular_rejected_count++;
#endif
		}
	}

#ifdef LIGHT_CULLER_DEBUG_LOGGING
	uint32_t removed = r_instance_shadow_cull_result.size() - count_before;
	if (removed) {
		if (((data.debug_count) % 60) == 0) {
			print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(count_before) + " after " + itos(r_instance_shadow_cull_result.size()));
		}
	}
#endif
}

void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &p) {
	ERR_FAIL_COND(num_cull_planes >= MAX_CULL_PLANES);
	cull_planes[num_cull_planes++] = p;
}

// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third
// points are derived differently.
bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) {
	uint32_t lookup = 0;
	r_cull_planes.num_cull_planes = 0;

	const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes;

	// Directional light, we will use dot against the light direction to determine back facing planes.
	for (int n = 0; n < 6; n++) {
		float dot = cull_frustum_planes[n].normal.dot(p_light_source.dir);
		if (dot > 0.0f) {
			lookup |= 1 << n;

			// Add backfacing camera frustum planes.
			r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
		}
	}

	ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);

	// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
	// then we will add the camera frustum planes to clip the light volume .. there is no need to
	// render shadow casters outside the frustum as shadows can never re-enter the frustum.

	// Should never happen with directional light?? This may be able to be removed.
	if (lookup == 63) {
		r_cull_planes.num_cull_planes = 0;
		for (int n = 0; n < 6; n++) {
			r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
		}

		return true;
	}

// Each edge forms a plane.
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
	const LocalVector<uint8_t> &entry = _calculated_LUT[lookup];

	// each edge forms a plane
	int n_edges = entry.size() - 1;
#else
	uint8_t *entry = &data.LUT_entries[lookup][0];
	int n_edges = data.LUT_entry_sizes[lookup] - 1;
#endif

	const Vector3 *const frustum_points = p_cull_frustum.frustum_points;

	for (int e = 0; e < n_edges; e++) {
		int i0 = entry[e];
		int i1 = entry[e + 1];

		const Vector3 &pt0 = frustum_points[i0];
		const Vector3 &pt1 = frustum_points[i1];

		// Create a third point from the light direction.
		Vector3 pt2 = pt0 - p_light_source.dir;

		if (!_is_colinear_tri(pt0, pt1, pt2)) {
			// Create plane from 3 points.
			Plane p(pt0, pt1, pt2);
			r_cull_planes.add_cull_plane(p);
		}
	}

	// Last to 0 edge.
	if (n_edges) {
		int i0 = entry[n_edges]; // Last.
		int i1 = entry[0]; // First.

		const Vector3 &pt0 = frustum_points[i0];
		const Vector3 &pt1 = frustum_points[i1];

		// Create a third point from the light direction.
		Vector3 pt2 = pt0 - p_light_source.dir;

		if (!_is_colinear_tri(pt0, pt1, pt2)) {
			// Create plane from 3 points.
			Plane p(pt0, pt1, pt2);
			r_cull_planes.add_cull_plane(p);
		}
	}

#ifdef LIGHT_CULLER_DEBUG_LOGGING
	if (is_logging()) {
		print_line("lcam.pos is " + String(p_light_source.pos));
	}
#endif

	return true;
}

bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) {
	if (!data.is_active()) {
		return true;
	}

	const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes;

	switch (p_light_source.type) {
		case LightSource::ST_SPOTLIGHT:
		case LightSource::ST_OMNI:
		case LightSource::ST_AREA:
			break;
		case LightSource::ST_DIRECTIONAL:
			return add_light_camera_planes_directional(r_cull_planes, p_light_source, p_cull_frustum);
			break;
		default:
			return false; // not yet supported
			break;
	}

	// Start with 0 cull planes.
	r_cull_planes.num_cull_planes = 0;
	data.out_of_range = false;
	uint32_t lookup = 0;

	// Find which of the camera planes are facing away from the light.
	// We can also test for the situation where the light max range means it cannot
	// affect the camera frustum. This is absolutely worth doing because it is relatively
	// cheap, and if the entire light can be culled this can vastly improve performance
	// (much more than just culling casters).

	// POINT LIGHT (spotlight, omni)
	// Instead of using dot product to compare light direction to plane, we can simply
	// find out which side of the plane the camera is on. By definition this marks the point at which the plane
	// becomes invisible.

	// OMNIS
	if (p_light_source.type == LightSource::ST_OMNI) {
		for (int n = 0; n < 6; n++) {
			float dist = cull_frustum_planes[n].distance_to(p_light_source.pos);
			if (dist < 0.0f) {
				lookup |= 1 << n;

				// Add backfacing camera frustum planes.
				r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
			} else {
				// Is the light out of range?
				// This is one of the tests. If the point source is more than range distance from a frustum plane, it can't
				// be seen.
				if (dist >= p_light_source.range) {
					// If the light is out of range, no need to do anything else, everything will be culled.
					data.out_of_range = true;
					return false;
				}
			}
		}
	} else if (p_light_source.type == LightSource::ST_AREA) {
		for (int n = 0; n < 6; n++) {
			float dist = data.frustum_planes[n].distance_to(p_light_source.pos);
			float half_diagonal = p_light_source.area_size.length() / 2.0;
			if (dist < 0.0f) {
				lookup |= 1 << n;

				// Add backfacing camera frustum planes.
				r_cull_planes.add_cull_plane(data.frustum_planes[n]);
			} else {
				// Is the light out of range?
				if (dist >= p_light_source.range + half_diagonal) {
					// If the light is out of range, no need to do anything else, everything will be culled.
					data.out_of_range = true;
					return false;
				}
			}
		}
	} else {
		// SPOTLIGHTs, more complex to cull.
		Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range);

		// This is the radius of the cone at distance 1.
		float radius_at_dist_one = Math::tan(Math::deg_to_rad(p_light_source.angle));

		// The worst case radius of the cone at the end point can be calculated
		// (the radius will scale linearly with length along the cone).
		float end_cone_radius = radius_at_dist_one * p_light_source.range;

		for (int n = 0; n < 6; n++) {
			float dist = cull_frustum_planes[n].distance_to(p_light_source.pos);
			if (dist < 0.0f) {
				// Either the plane is backfacing or we are inside the frustum.
				lookup |= 1 << n;

				// Add backfacing camera frustum planes.
				r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
			} else {
				// The light is in front of the plane.

				// Is the light out of range?
				if (dist >= p_light_source.range) {
					data.out_of_range = true;
					return false;
				}

				// For a spotlight, we can use an extra test
				// at this point the cone start is in front of the plane...
				// If the cone end point is further than the maximum possible distance to the plane
				// we can guarantee that the cone does not cross the plane, and hence the cone
				// is outside the frustum.
				float dist_end = cull_frustum_planes[n].distance_to(pos_end);

				if (dist_end >= end_cone_radius) {
					data.out_of_range = true;
					return false;
				}
			}
		}
	}

	// The lookup should be within the LUT, logic should prevent this.
	ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);

	// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
	// then we will add the camera frustum planes to clip the light volume .. there is no need to
	// render shadow casters outside the frustum as shadows can never re-enter the frustum.
	if (lookup == 63) {
		r_cull_planes.num_cull_planes = 0;
		for (int n = 0; n < 6; n++) {
			r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
		}

		return true;
	}

	// Each edge forms a plane.
	uint8_t *entry = &data.LUT_entries[lookup][0];
	int n_edges = data.LUT_entry_sizes[lookup] - 1;

	const Vector3 *const frustum_points = p_cull_frustum.frustum_points;

	const Vector3 &pt2 = p_light_source.pos;

	for (int e = 0; e < n_edges; e++) {
		int i0 = entry[e];
		int i1 = entry[e + 1];
		const Vector3 &pt0 = frustum_points[i0];
		const Vector3 &pt1 = frustum_points[i1];

		if (!_is_colinear_tri(pt0, pt1, pt2)) {
			// Create plane from 3 points.
			Plane p(pt0, pt1, pt2);
			r_cull_planes.add_cull_plane(p);
		}
	}

	// Last to 0 edge.
	if (n_edges) {
		int i0 = entry[n_edges]; // Last.
		int i1 = entry[0]; // First.

		const Vector3 &pt0 = frustum_points[i0];
		const Vector3 &pt1 = frustum_points[i1];

		if (!_is_colinear_tri(pt0, pt1, pt2)) {
			// Create plane from 3 points.
			Plane p(pt0, pt1, pt2);
			r_cull_planes.add_cull_plane(p);
		}
	}

#ifdef LIGHT_CULLER_DEBUG_LOGGING
	if (is_logging()) {
		print_line("lsource.pos is " + String(p_light_source.pos));
	}
#endif

	return true;
}

bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {
	data.debug_count++;
	if (data.debug_count >= 120) {
		data.debug_count = 0;
	}

	// For debug flash off and on.
#ifdef LIGHT_CULLER_DEBUG_FLASH
	if (!Engine::get_singleton()->is_editor_hint()) {
		int dc = Engine::get_singleton()->get_process_frames() / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;
		bool bnew_active;
		bnew_active = (dc % 2) == 0;

		if (bnew_active != data.light_culling_active) {
			data.light_culling_active = bnew_active;
			print_line("switching light culler " + String(Variant(data.light_culling_active)));
		}
	}
#endif

	if (!data.is_active()) {
		return false;
	}
	// These are needed later to build per-cascade cull frustums for directional lights.
	data.camera_transform = p_cam_transform;
	data.camera_projection = p_cam_matrix;

	// Get the camera frustum planes in world space.
	data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);
	DEV_CHECK_ONCE(data.frustum_planes.size() == 6);

	data.regular_cull_planes.num_cull_planes = 0;

#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT
	if (is_logging()) {
		for (uint32_t n = 0; n < data.directional_cull_planes.size(); n++) {
			print_line("LightCuller directional light " + itos(n) + " rejected " + itos(data.directional_cull_planes[n].rejected_count) + " instances.");
		}
	}
#endif
#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT
	if (data.regular_rejected_count) {
		print_line("LightCuller regular lights rejected " + itos(data.regular_rejected_count) + " instances.");
	}
	data.regular_rejected_count = 0;
#endif

	data.directional_cull_planes.resize(0);

#ifdef LIGHT_CULLER_DEBUG_LOGGING
	if (is_logging()) {
		for (int p = 0; p < 6; p++) {
			print_line("plane " + itos(p) + " : " + String(data.frustum_planes[p]));
		}
	}
#endif

	return create_frustum_points(&data.frustum_planes[0], data.frustum_points);
}

bool RenderingLightCuller::create_frustum_points(const Plane *p_frustum_planes, Vector3 *r_result) const {
	// We want to calculate the frustum corners in a specific order.
	const Projection::Planes intersections[8][3] = {
		{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
		{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
		{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
		{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
		{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
		{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
		{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
		{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
	};

	for (int i = 0; i < 8; i++) {
		// 3 plane intersection, gives us a point.
		bool res = p_frustum_planes[intersections[i][0]].intersect_3(p_frustum_planes[intersections[i][1]], p_frustum_planes[intersections[i][2]], &r_result[i]);

		// What happens with a zero frustum? NYI - deal with this.
		ERR_FAIL_COND_V(!res, false);

#ifdef LIGHT_CULLER_DEBUG_LOGGING
		if (is_logging()) {
			print_line("point " + itos(i) + " -> " + String(result[i]));
		}
#endif
	}
	return true;
}

RenderingLightCuller::RenderingLightCuller() {
	// Used to determine which frame to give debug output.
	data.debug_count = -1;

	// Uncomment below to switch off light culler in the editor.
	// data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;

#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
	create_LUT();
#endif
}

/* clang-format off */
uint8_t RenderingLightCuller::Data::LUT_entry_sizes[LUT_SIZE] = {0, 4, 4, 0, 4, 6, 6, 8, 4, 6, 6, 8, 6, 6, 6, 6, 4, 6, 6, 8, 0, 8, 8, 0, 6, 6, 6, 6, 8, 6, 6, 4, 4, 6, 6, 8, 6, 6, 6, 6, 0, 8, 8, 0, 8, 6, 6, 4, 6, 6, 6, 6, 8, 6, 6, 4, 8, 6, 6, 4, 0, 4, 4, 0, };

// The lookup table used to determine which edges form the silhouette of the camera frustum,
// depending on the viewing angle (defined by which camera planes are backward facing).
uint8_t RenderingLightCuller::Data::LUT_entries[LUT_SIZE][8] = {
{0, 0, 0, 0, 0, 0, 0, 0, },
{7, 6, 4, 5, 0, 0, 0, 0, },
{1, 0, 2, 3, 0, 0, 0, 0, },
{0, 0, 0, 0, 0, 0, 0, 0, },
{1, 5, 4, 0, 0, 0, 0, 0, },
{1, 5, 7, 6, 4, 0, 0, 0, },
{4, 0, 2, 3, 1, 5, 0, 0, },
{5, 7, 6, 4, 0, 2, 3, 1, },
{0, 4, 6, 2, 0, 0, 0, 0, },
{0, 4, 5, 7, 6, 2, 0, 0, },
{6, 2, 3, 1, 0, 4, 0, 0, },
{2, 3, 1, 0, 4, 5, 7, 6, },
{0, 1, 5, 4, 6, 2, 0, 0, },
{0, 1, 5, 7, 6, 2, 0, 0, },
{6, 2, 3, 1, 5, 4, 0, 0, },
{2, 3, 1, 5, 7, 6, 0, 0, },
{2, 6, 7, 3, 0, 0, 0, 0, },
{2, 6, 4, 5, 7, 3, 0, 0, },
{7, 3, 1, 0, 2, 6, 0, 0, },
{3, 1, 0, 2, 6, 4, 5, 7, },
{0, 0, 0, 0, 0, 0, 0, 0, },
{2, 6, 4, 0, 1, 5, 7, 3, },
{7, 3, 1, 5, 4, 0, 2, 6, },
{0, 0, 0, 0, 0, 0, 0, 0, },
{2, 0, 4, 6, 7, 3, 0, 0, },
{2, 0, 4, 5, 7, 3, 0, 0, },
{7, 3, 1, 0, 4, 6, 0, 0, },
{3, 1, 0, 4, 5, 7, 0, 0, },
{2, 0, 1, 5, 4, 6, 7, 3, },
{2, 0, 1, 5, 7, 3, 0, 0, },
{7, 3, 1, 5, 4, 6, 0, 0, },
{3, 1, 5, 7, 0, 0, 0, 0, },
{3, 7, 5, 1, 0, 0, 0, 0, },
{3, 7, 6, 4, 5, 1, 0, 0, },
{5, 1, 0, 2, 3, 7, 0, 0, },
{7, 6, 4, 5, 1, 0, 2, 3, },
{3, 7, 5, 4, 0, 1, 0, 0, },
{3, 7, 6, 4, 0, 1, 0, 0, },
{5, 4, 0, 2, 3, 7, 0, 0, },
{7, 6, 4, 0, 2, 3, 0, 0, },
{0, 0, 0, 0, 0, 0, 0, 0, },
{3, 7, 6, 2, 0, 4, 5, 1, },
{5, 1, 0, 4, 6, 2, 3, 7, },
{0, 0, 0, 0, 0, 0, 0, 0, },
{3, 7, 5, 4, 6, 2, 0, 1, },
{3, 7, 6, 2, 0, 1, 0, 0, },
{5, 4, 6, 2, 3, 7, 0, 0, },
{7, 6, 2, 3, 0, 0, 0, 0, },
{3, 2, 6, 7, 5, 1, 0, 0, },
{3, 2, 6, 4, 5, 1, 0, 0, },
{5, 1, 0, 2, 6, 7, 0, 0, },
{1, 0, 2, 6, 4, 5, 0, 0, },
{3, 2, 6, 7, 5, 4, 0, 1, },
{3, 2, 6, 4, 0, 1, 0, 0, },
{5, 4, 0, 2, 6, 7, 0, 0, },
{6, 4, 0, 2, 0, 0, 0, 0, },
{3, 2, 0, 4, 6, 7, 5, 1, },
{3, 2, 0, 4, 5, 1, 0, 0, },
{5, 1, 0, 4, 6, 7, 0, 0, },
{1, 0, 4, 5, 0, 0, 0, 0, },
{0, 0, 0, 0, 0, 0, 0, 0, },
{3, 2, 0, 1, 0, 0, 0, 0, },
{5, 4, 6, 7, 0, 0, 0, 0, },
{0, 0, 0, 0, 0, 0, 0, 0, },
};

/* clang-format on */

#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT

// See e.g. http://lspiroengine.com/?p=153 for reference.
// Principles are the same, but differences to the article:
// * Order of planes / points is different in Godot.
// * We use a lookup table at runtime.
void RenderingLightCuller::create_LUT() {
	// Each pair of planes that are opposite can have an edge.
	for (int plane_0 = 0; plane_0 < PLANE_TOTAL; plane_0++) {
		// For each neighbor of the plane.
		PlaneOrder neighs[4];
		get_neighbouring_planes((PlaneOrder)plane_0, neighs);

		for (int n = 0; n < 4; n++) {
			int plane_1 = neighs[n];

			// If these are opposite we need to add the 2 points they share.
			PointOrder pts[2];
			get_corners_of_planes((PlaneOrder)plane_0, (PlaneOrder)plane_1, pts);

			add_LUT(plane_0, plane_1, pts);
		}
	}

	for (uint32_t n = 0; n < LUT_SIZE; n++) {
		compact_LUT_entry(n);
	}

	debug_print_LUT();
	debug_print_LUT_as_table();
}

// we can pre-create the entire LUT and store it hard coded as a static inside the executable!
// it is only small in size, 64 entries with max 8 bytes per entry
void RenderingLightCuller::debug_print_LUT_as_table() {
	print_line("\nLIGHT VOLUME TABLE BEGIN\n");

	print_line("Copy this to LUT_entry_sizes:\n");
	String sz = "{";
	for (int n = 0; n < LUT_SIZE; n++) {
		const LocalVector<uint8_t> &entry = _calculated_LUT[n];

		sz += itos(entry.size()) + ", ";
	}
	sz += "}";
	print_line(sz);
	print_line("\nCopy this to LUT_entries:\n");

	for (int n = 0; n < LUT_SIZE; n++) {
		const LocalVector<uint8_t> &entry = _calculated_LUT[n];

		String sz = "{";

		// First is the number of points in the entry.
		int s = entry.size();

		for (int p = 0; p < 8; p++) {
			if (p < s) {
				sz += itos(entry[p]);
			} else {
				sz += "0"; // just a spacer
			}

			sz += ", ";
		}

		sz += "},";
		print_line(sz);
	}

	print_line("\nLIGHT VOLUME TABLE END\n");
}

void RenderingLightCuller::debug_print_LUT() {
	for (int n = 0; n < LUT_SIZE; n++) {
		String sz;
		sz = "LUT" + itos(n) + ":\t";

		sz += Data::plane_bitfield_to_string(n);
		print_line(sz);

		const LocalVector<uint8_t> &entry = _calculated_LUT[n];

		sz = "\t" + string_LUT_entry(entry);

		print_line(sz);
	}
}

String RenderingLightCuller::string_LUT_entry(const LocalVector<uint8_t> &p_entry) {
	String string;

	for (uint32_t n = 0; n < p_entry.size(); n++) {
		uint8_t val = p_entry[n];
		DEV_ASSERT(val < 8);
		const char *sz_point = Data::string_points[val];
		string += sz_point;
		string += ", ";
	}

	return string;
}

String RenderingLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {
	String string;

	for (uint32_t i = 0; i < p_entry.size(); i++) {
		int pt_order = p_entry[i];
		if (p_pair && ((i % 2) == 0)) {
			string += itos(pt_order) + "-";
		} else {
			string += itos(pt_order) + ", ";
		}
	}

	return string;
}

void RenderingLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {
	// Note that some entries to the LUT will be "impossible" situations,
	// because it contains all combinations of plane flips.
	uint32_t bit0 = 1 << p_plane_0;
	uint32_t bit1 = 1 << p_plane_1;

	// All entries of the LUT that have plane 0 set and plane 1 not set.
	for (uint32_t n = 0; n < 64; n++) {
		// If bit0 not set...
		if (!(n & bit0)) {
			continue;
		}

		// If bit1 set...
		if (n & bit1) {
			continue;
		}

		// Meets criteria.
		add_LUT_entry(n, p_pts);
	}
}

void RenderingLightCuller::add_LUT_entry(uint32_t p_entry_id, PointOrder p_pts[2]) {
	DEV_ASSERT(p_entry_id < LUT_SIZE);
	LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];

	entry.push_back(p_pts[0]);
	entry.push_back(p_pts[1]);
}

void RenderingLightCuller::compact_LUT_entry(uint32_t p_entry_id) {
	DEV_ASSERT(p_entry_id < LUT_SIZE);
	LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];

	int num_pairs = entry.size() / 2;

	if (num_pairs == 0) {
		return;
	}

	LocalVector<uint8_t> temp;

	String string;
	string = "Compact LUT" + itos(p_entry_id) + ":\t";
	string += debug_string_LUT_entry(entry, true);
	print_line(string);

	// Add first pair.
	temp.push_back(entry[0]);
	temp.push_back(entry[1]);
	unsigned int BFpairs = 1;

	string = debug_string_LUT_entry(temp) + " -> ";
	print_line(string);

	// Attempt to add a pair each time.
	for (int done = 1; done < num_pairs; done++) {
		string = "done " + itos(done) + ": ";
		// Find a free pair.
		for (int p = 1; p < num_pairs; p++) {
			unsigned int bit = 1 << p;
			// Is it done already?
			if (BFpairs & bit) {
				continue;
			}

			// There must be at least 1 free pair.
			// Attempt to add.
			int a = entry[p * 2];
			int b = entry[(p * 2) + 1];

			string += "[" + itos(a) + "-" + itos(b) + "], ";

			int found_a = temp.find(a);
			int found_b = temp.find(b);

			// Special case, if they are both already in the list, no need to add
			// as this is a link from the tail to the head of the list.
			if ((found_a != -1) && (found_b != -1)) {
				string += "foundAB link " + itos(found_a) + ", " + itos(found_b) + " ";
				BFpairs |= bit;
				goto found;
			}

			// Find a.
			if (found_a != -1) {
				string += "foundA " + itos(found_a) + " ";
				temp.insert(found_a + 1, b);
				BFpairs |= bit;
				goto found;
			}

			// Find b.
			if (found_b != -1) {
				string += "foundB " + itos(found_b) + " ";
				temp.insert(found_b, a);
				BFpairs |= bit;
				goto found;
			}

		} // Check each pair for adding.

		// If we got here before finding a link, the whole set of planes is INVALID
		// e.g. far and near plane only, does not create continuous sillouhette of edges.
		print_line("\tINVALID");
		entry.clear();
		return;

	found:;
		print_line(string);
		string = "\ttemp now : " + debug_string_LUT_entry(temp);
		print_line(string);
	}

	// temp should now be the sorted entry .. delete the old one and replace by temp.
	entry.clear();
	entry = temp;
}

void RenderingLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {
	// Table of neighboring planes to each.
	static const PlaneOrder neigh_table[PLANE_TOTAL][4] = {
		{ // LSM_FP_NEAR
				PLANE_LEFT,
				PLANE_RIGHT,
				PLANE_TOP,
				PLANE_BOTTOM },
		{ // LSM_FP_FAR
				PLANE_LEFT,
				PLANE_RIGHT,
				PLANE_TOP,
				PLANE_BOTTOM },
		{ // LSM_FP_LEFT
				PLANE_TOP,
				PLANE_BOTTOM,
				PLANE_NEAR,
				PLANE_FAR },
		{ // LSM_FP_TOP
				PLANE_LEFT,
				PLANE_RIGHT,
				PLANE_NEAR,
				PLANE_FAR },
		{ // LSM_FP_RIGHT
				PLANE_TOP,
				PLANE_BOTTOM,
				PLANE_NEAR,
				PLANE_FAR },
		{ // LSM_FP_BOTTOM
				PLANE_LEFT,
				PLANE_RIGHT,
				PLANE_NEAR,
				PLANE_FAR },
	};

	for (int n = 0; n < 4; n++) {
		r_neigh_planes[n] = neigh_table[p_plane][n];
	}
}

// Given two planes, returns the two points shared by those planes.  The points are always
// returned in counter-clockwise order, assuming the first input plane is facing towards
// the viewer.

// param p_plane_a The plane facing towards the viewer.
// param p_plane_b A plane neighboring p_plane_a.
// param r_points An array of exactly two elements to be filled with the indices of the points
// on return.

void RenderingLightCuller::get_corners_of_planes(PlaneOrder p_plane_a, PlaneOrder p_plane_b, PointOrder r_points[2]) const {
	static const PointOrder fp_table[PLANE_TOTAL][PLANE_TOTAL][2] = {
		{
				// LSM_FP_NEAR
				{
						// LSM_FP_NEAR
						PT_NEAR_LEFT_TOP, PT_NEAR_RIGHT_TOP // Invalid combination.
				},
				{
						// LSM_FP_FAR
						PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP // Invalid combination.
				},
				{
						// LSM_FP_LEFT
						PT_NEAR_LEFT_TOP,
						PT_NEAR_LEFT_BOTTOM,
				},
				{
						// LSM_FP_TOP
						PT_NEAR_RIGHT_TOP,
						PT_NEAR_LEFT_TOP,
				},
				{
						// LSM_FP_RIGHT
						PT_NEAR_RIGHT_BOTTOM,
						PT_NEAR_RIGHT_TOP,
				},
				{
						// LSM_FP_BOTTOM
						PT_NEAR_LEFT_BOTTOM,
						PT_NEAR_RIGHT_BOTTOM,
				},
		},

		{
				// LSM_FP_FAR
				{
						// LSM_FP_NEAR
						PT_FAR_LEFT_TOP, PT_FAR_RIGHT_TOP // Invalid combination.
				},
				{
						// LSM_FP_FAR
						PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP // Invalid combination.
				},
				{
						// LSM_FP_LEFT
						PT_FAR_LEFT_BOTTOM,
						PT_FAR_LEFT_TOP,
				},
				{
						// LSM_FP_TOP
						PT_FAR_LEFT_TOP,
						PT_FAR_RIGHT_TOP,
				},
				{
						// LSM_FP_RIGHT
						PT_FAR_RIGHT_TOP,
						PT_FAR_RIGHT_BOTTOM,
				},
				{
						// LSM_FP_BOTTOM
						PT_FAR_RIGHT_BOTTOM,
						PT_FAR_LEFT_BOTTOM,
				},
		},

		{
				// LSM_FP_LEFT
				{
						// LSM_FP_NEAR
						PT_NEAR_LEFT_BOTTOM,
						PT_NEAR_LEFT_TOP,
				},
				{
						// LSM_FP_FAR
						PT_FAR_LEFT_TOP,
						PT_FAR_LEFT_BOTTOM,
				},
				{
						// LSM_FP_LEFT
						PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.
				},
				{
						// LSM_FP_TOP
						PT_NEAR_LEFT_TOP,
						PT_FAR_LEFT_TOP,
				},
				{
						// LSM_FP_RIGHT
						PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.
				},
				{
						// LSM_FP_BOTTOM
						PT_FAR_LEFT_BOTTOM,
						PT_NEAR_LEFT_BOTTOM,
				},
		},

		{
				// LSM_FP_TOP
				{
						// LSM_FP_NEAR
						PT_NEAR_LEFT_TOP,
						PT_NEAR_RIGHT_TOP,
				},
				{
						// LSM_FP_FAR
						PT_FAR_RIGHT_TOP,
						PT_FAR_LEFT_TOP,
				},
				{
						// LSM_FP_LEFT
						PT_FAR_LEFT_TOP,
						PT_NEAR_LEFT_TOP,
				},
				{
						// LSM_FP_TOP
						PT_NEAR_LEFT_TOP, PT_FAR_LEFT_TOP // Invalid combination.
				},
				{
						// LSM_FP_RIGHT
						PT_NEAR_RIGHT_TOP,
						PT_FAR_RIGHT_TOP,
				},
				{
						// LSM_FP_BOTTOM
						PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM // Invalid combination.
				},
		},

		{
				// LSM_FP_RIGHT
				{
						// LSM_FP_NEAR
						PT_NEAR_RIGHT_TOP,
						PT_NEAR_RIGHT_BOTTOM,
				},
				{
						// LSM_FP_FAR
						PT_FAR_RIGHT_BOTTOM,
						PT_FAR_RIGHT_TOP,
				},
				{
						// LSM_FP_LEFT
						PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM // Invalid combination.
				},
				{
						// LSM_FP_TOP
						PT_FAR_RIGHT_TOP,
						PT_NEAR_RIGHT_TOP,
				},
				{
						// LSM_FP_RIGHT
						PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM // Invalid combination.
				},
				{
						// LSM_FP_BOTTOM
						PT_NEAR_RIGHT_BOTTOM,
						PT_FAR_RIGHT_BOTTOM,
				},
		},

		// ==

		//	P_NEAR,
		//	P_FAR,
		//	P_LEFT,
		//	P_TOP,
		//	P_RIGHT,
		//	P_BOTTOM,

		{
				// LSM_FP_BOTTOM
				{
						// LSM_FP_NEAR
						PT_NEAR_RIGHT_BOTTOM,
						PT_NEAR_LEFT_BOTTOM,
				},
				{
						// LSM_FP_FAR
						PT_FAR_LEFT_BOTTOM,
						PT_FAR_RIGHT_BOTTOM,
				},
				{
						// LSM_FP_LEFT
						PT_NEAR_LEFT_BOTTOM,
						PT_FAR_LEFT_BOTTOM,
				},
				{
						// LSM_FP_TOP
						PT_NEAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.
				},
				{
						// LSM_FP_RIGHT
						PT_FAR_RIGHT_BOTTOM,
						PT_NEAR_RIGHT_BOTTOM,
				},
				{
						// LSM_FP_BOTTOM
						PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM // Invalid combination.
				},
		},

		// ==

	};
	r_points[0] = fp_table[p_plane_a][p_plane_b][0];
	r_points[1] = fp_table[p_plane_a][p_plane_b][1];
}

#endif
