1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063 |
- /**************************************************************************/
- /* visual_server_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 "visual_server_light_culler.h"
- #include "core/math/camera_matrix.h"
- #include "core/math/plane.h"
- #include "scene/3d/camera.h"
- #include "visual_server_globals.h"
- #include "visual_server_scene.h"
- #ifdef VISUAL_SERVER_LIGHT_CULLER_DEBUG_STRINGS
- const char *VisualServerLightCuller::Data::string_planes[] = {
- "NEAR",
- "FAR",
- "LEFT",
- "TOP",
- "RIGHT",
- "BOTTOM",
- };
- const char *VisualServerLightCuller::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 VisualServerLightCuller::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
- bool VisualServerLightCuller::prepare_light(const VisualServerScene::Instance &p_instance) {
- if (!data.is_active()) {
- return true;
- }
- LightSource lsource;
- switch (VSG::storage->light_get_type(p_instance.base)) {
- case VS::LIGHT_SPOT:
- lsource.type = LightSource::ST_SPOTLIGHT;
- lsource.angle = VSG::storage->light_get_param(p_instance.base, VS::LIGHT_PARAM_SPOT_ANGLE);
- lsource.range = VSG::storage->light_get_param(p_instance.base, VS::LIGHT_PARAM_RANGE);
- break;
- case VS::LIGHT_OMNI:
- lsource.type = LightSource::ST_OMNI;
- lsource.range = VSG::storage->light_get_param(p_instance.base, VS::LIGHT_PARAM_RANGE);
- break;
- case VS::LIGHT_DIRECTIONAL:
- lsource.type = LightSource::ST_DIRECTIONAL;
- // Could deal with a max directional shadow range here? NYI
- // LIGHT_PARAM_SHADOW_MAX_DISTANCE
- break;
- }
- lsource.pos = p_instance.transform.origin;
- lsource.dir = -p_instance.transform.basis.get_axis(2);
- lsource.dir.normalize();
- bool visible = _add_light_camera_planes(lsource);
- if (data.light_culling_active) {
- return visible;
- }
- return true;
- }
- int VisualServerLightCuller::cull(int p_count, VisualServerScene::Instance **p_result_array) {
- if (!data.is_active() || !is_caster_culling_active()) {
- return p_count;
- }
- // 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 0;
- }
- int new_count = p_count;
- // Go through all the casters in the list (the list will hopefully shrink as we go).
- for (int n = 0; n < new_count; n++) {
- // World space aabb.
- const AABB &bb = p_result_array[n]->transformed_aabb;
- #ifdef LIGHT_CULLER_DEBUG_LOGGING
- if (is_logging()) {
- print_line("bb : " + String(bb));
- }
- #endif
- float r_min, r_max;
- bool show = true;
- for (int p = 0; p < data.num_cull_planes; p++) {
- // As we only need r_min, could this be optimized?
- bb.project_range_in_plane(data.cull_planes[p], r_min, r_max);
- #ifdef LIGHT_CULLER_DEBUG_LOGGING
- if (is_logging()) {
- print_line("\tplane " + itos(p) + " : " + String(data.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) {
- // Quick unsorted remove - swap last element and reduce count.
- p_result_array[n] = p_result_array[new_count - 1];
- new_count--;
- // Repeat this element next iteration of the loop as it has been removed and replaced by the last.
- n--;
- }
- }
- #ifdef LIGHT_CULLER_DEBUG_LOGGING
- int removed = p_count - new_count;
- if (removed) {
- if (((data.debug_count) % 60) == 0) {
- print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(p_count) + " after " + itos(new_count));
- }
- }
- #endif
- return new_count;
- }
- void VisualServerLightCuller::add_cull_plane(const Plane &p) {
- ERR_FAIL_COND(data.num_cull_planes >= MAX_CULL_PLANES);
- data.cull_planes[data.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 VisualServerLightCuller::add_light_camera_planes_directional(const LightSource &p_light_source) {
- uint32_t lookup = 0;
- // Directional light, we will use dot against the light direction to determine back facing planes.
- for (int n = 0; n < 6; n++) {
- float dot = data.frustum_planes[n].normal.dot(p_light_source.dir);
- if (dot > 0.0f) {
- lookup |= 1 << n;
- // Add backfacing camera frustum planes.
- add_cull_plane(data.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) {
- data.num_cull_planes = 0;
- for (int n = 0; n < data.frustum_planes.size(); n++) {
- add_cull_plane(data.frustum_planes[n]);
- }
- return true;
- }
- // Each edge forms a plane.
- #ifdef VISUAL_SERVER_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
- for (int e = 0; e < n_edges; e++) {
- int i0 = entry[e];
- int i1 = entry[e + 1];
- const Vector3 &pt0 = data.frustum_points[i0];
- const Vector3 &pt1 = data.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);
- 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 = data.frustum_points[i0];
- const Vector3 &pt1 = data.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);
- 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 VisualServerLightCuller::_add_light_camera_planes(const LightSource &p_light_source) {
- if (!data.is_active()) {
- return true;
- }
- // We should have called prepare_camera before this.
- ERR_FAIL_COND_V(data.frustum_planes.size() != 6, true);
- // Start with 0 cull planes.
- data.num_cull_planes = 0;
- data.out_of_range = false;
- switch (p_light_source.type) {
- case LightSource::ST_SPOTLIGHT:
- case LightSource::ST_OMNI:
- break;
- case LightSource::ST_DIRECTIONAL:
- return add_light_camera_planes_directional(p_light_source);
- break;
- default:
- return false; // not yet supported
- break;
- }
- 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 = data.frustum_planes[n].distance_to(p_light_source.pos);
- if (dist < 0.0f) {
- lookup |= 1 << n;
- // Add backfacing camera frustum planes.
- add_cull_plane(data.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 {
- // 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::deg2rad(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 = data.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.
- add_cull_plane(data.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 = data.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) {
- data.num_cull_planes = 0;
- for (int n = 0; n < data.frustum_planes.size(); n++) {
- add_cull_plane(data.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 &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 = data.frustum_points[i0];
- const Vector3 &pt1 = data.frustum_points[i1];
- if (!_is_colinear_tri(pt0, pt1, pt2)) {
- // Create plane from 3 points.
- Plane p(pt0, pt1, pt2);
- 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 = data.frustum_points[i0];
- const Vector3 &pt1 = data.frustum_points[i1];
- if (!_is_colinear_tri(pt0, pt1, pt2)) {
- // Create plane from 3 points.
- Plane p(pt0, pt1, pt2);
- 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 VisualServerLightCuller::prepare_camera(const Transform &p_cam_transform, const CameraMatrix &p_cam_matrix) {
- data.debug_count++;
- // For debug flash off and on.
- #ifdef LIGHT_CULLER_DEBUG_FLASH
- if (!Engine::get_singleton()->is_editor_hint()) {
- int dc = data.debug_count / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;
- bool bnew_active;
- bnew_active = (dc % 2) == 0;
- if (bnew_active != data.active) {
- data.active = bnew_active;
- print_line("switching light culler " + String(Variant(data.active)));
- }
- }
- #endif
- if (!data.is_active()) {
- return false;
- }
- // Get the camera frustum planes in world space.
- data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);
- data.num_cull_planes = 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
- // We want to calculate the frustum corners in a specific order.
- const CameraMatrix::Planes intersections[8][3] = {
- { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_TOP },
- { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_BOTTOM },
- { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_TOP },
- { CameraMatrix::PLANE_FAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_BOTTOM },
- { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_TOP },
- { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_LEFT, CameraMatrix::PLANE_BOTTOM },
- { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_TOP },
- { CameraMatrix::PLANE_NEAR, CameraMatrix::PLANE_RIGHT, CameraMatrix::PLANE_BOTTOM },
- };
- for (int i = 0; i < 8; i++) {
- // 3 plane intersection, gives us a point.
- bool res = data.frustum_planes[intersections[i][0]].intersect_3(data.frustum_planes[intersections[i][1]], data.frustum_planes[intersections[i][2]], &data.frustum_points[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(data.frustum_points[i]));
- }
- #endif
- }
- return true;
- }
- VisualServerLightCuller::VisualServerLightCuller() {
- // Used to determine which frame to give debug output.
- data.debug_count = -1;
- // bactive is switching on and off the light culler
- data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;
- #ifdef VISUAL_SERVER_LIGHT_CULLER_CALCULATE_LUT
- create_LUT();
- #endif
- }
- /* clang-format off */
- uint8_t VisualServerLightCuller::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 VisualServerLightCuller::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 VISUAL_SERVER_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 VisualServerLightCuller::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 neighbour 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 VisualServerLightCuller::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 VisualServerLightCuller::debug_print_LUT() {
- for (uint32_t 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 VisualServerLightCuller::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 VisualServerLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {
- String string;
- for (int 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 VisualServerLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {
- 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 VisualServerLightCuller::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 VisualServerLightCuller::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 VisualServerLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {
- // Table of neighbouring 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 VisualServerLightCuller::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
|