godot/servers/rendering/rendering_light_culler.cpp

/**************************************************************************/
/*  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 "rendering_server_globals.h"

#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) {}

bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_instance, int32_t p_directional_light_id) {}

bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id) {}

void RenderingLightCuller::cull_regular_light(PagedArray<RendererSceneCull::Instance *> &r_instance_shadow_cull_result) {}

void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &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) {}

bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source) {}

bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {}

RenderingLightCuller::RenderingLightCuller() {}

/* clang-format off */
uint8_t RenderingLightCuller::Data::LUT_entry_sizes[LUT_SIZE] =;

// 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] =;

/* 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