Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/servers/rendering/rendering_light_culler.cpp
20873 views
1
/**************************************************************************/
2
/* rendering_light_culler.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "rendering_light_culler.h"
32
33
#include "core/math/plane.h"
34
#include "core/math/projection.h"
35
#include "rendering_server_globals.h"
36
37
#ifdef RENDERING_LIGHT_CULLER_DEBUG_STRINGS
38
const char *RenderingLightCuller::Data::string_planes[] = {
39
"NEAR",
40
"FAR",
41
"LEFT",
42
"TOP",
43
"RIGHT",
44
"BOTTOM",
45
};
46
const char *RenderingLightCuller::Data::string_points[] = {
47
"FAR_LEFT_TOP",
48
"FAR_LEFT_BOTTOM",
49
"FAR_RIGHT_TOP",
50
"FAR_RIGHT_BOTTOM",
51
"NEAR_LEFT_TOP",
52
"NEAR_LEFT_BOTTOM",
53
"NEAR_RIGHT_TOP",
54
"NEAR_RIGHT_BOTTOM",
55
};
56
57
String RenderingLightCuller::Data::plane_bitfield_to_string(unsigned int BF) {
58
String sz;
59
60
for (int n = 0; n < 6; n++) {
61
unsigned int bit = 1 << n;
62
if (BF & bit) {
63
sz += String(string_planes[n]) + ", ";
64
}
65
}
66
67
return sz;
68
}
69
#endif
70
71
void RenderingLightCuller::prepare_directional_light(const RendererSceneCull::Instance *p_instance, int32_t p_directional_light_id) {
72
//data.directional_light = p_instance;
73
// Something is probably going wrong, we shouldn't have this many directional lights...
74
ERR_FAIL_COND(p_directional_light_id > 512);
75
DEV_ASSERT(p_directional_light_id >= 0);
76
77
// First make sure we have enough directional lights to hold this one.
78
if (p_directional_light_id >= (int32_t)data.directional_cull_planes.size()) {
79
data.directional_cull_planes.resize(p_directional_light_id + 1);
80
}
81
82
_prepare_light(*p_instance, p_directional_light_id);
83
}
84
85
bool RenderingLightCuller::_prepare_light(const RendererSceneCull::Instance &p_instance, int32_t p_directional_light_id) {
86
if (!data.is_active()) {
87
return true;
88
}
89
90
LightSource lsource;
91
switch (RSG::light_storage->light_get_type(p_instance.base)) {
92
case RS::LIGHT_SPOT:
93
lsource.type = LightSource::ST_SPOTLIGHT;
94
lsource.angle = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SPOT_ANGLE);
95
lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);
96
break;
97
case RS::LIGHT_OMNI:
98
lsource.type = LightSource::ST_OMNI;
99
lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_RANGE);
100
break;
101
case RS::LIGHT_DIRECTIONAL:
102
lsource.type = LightSource::ST_DIRECTIONAL;
103
104
lsource.range = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_MAX_DISTANCE);
105
switch (RSG::light_storage->light_directional_get_shadow_mode(p_instance.base)) {
106
case RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL:
107
lsource.cascade_count = 1;
108
break;
109
case RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_2_SPLITS:
110
lsource.cascade_count = 2;
111
break;
112
case RS::LIGHT_DIRECTIONAL_SHADOW_PARALLEL_4_SPLITS:
113
lsource.cascade_count = 4;
114
break;
115
default:
116
ERR_FAIL_V_MSG(false, "Only directional lights with 1, 2, or 4 shadow cascades are supported.");
117
break;
118
}
119
lsource.cascade_splits[0] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_1_OFFSET);
120
lsource.cascade_splits[1] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET);
121
lsource.cascade_splits[2] = RSG::light_storage->light_get_param(p_instance.base, RS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET);
122
lsource.blend_splits = RSG::light_storage->light_directional_get_blend_splits(p_instance.base);
123
break;
124
}
125
126
lsource.pos = p_instance.transform.origin;
127
lsource.dir = -p_instance.transform.basis.get_column(2);
128
lsource.dir.normalize();
129
130
// In reality there's always going to be at least one cascade, but the compiler can't know that.
131
// If SOMEHOW there's actually 0 cascades though, I suppose there isn't going to be anything visible after all.
132
bool visible = false;
133
if (p_directional_light_id == -1) {
134
visible = _add_light_camera_planes(data.regular_cull_planes, lsource, { &data.frustum_planes[0], data.frustum_points });
135
} else {
136
int used_planes = 1 + lsource.cascade_count; // 2 for ortho (near+far), 3 for pssm2 (near+mid+far), 5 for pssm4 (near+3mids+far).
137
Plane boundary_planes[5];
138
{
139
constexpr const int MAX_PLANES = 5;
140
real_t plane_distances[MAX_PLANES] = {
141
data.camera_projection.get_z_near(),
142
lsource.cascade_splits[0] * lsource.range,
143
lsource.cascade_splits[1] * lsource.range,
144
lsource.cascade_splits[2] * lsource.range,
145
lsource.range,
146
};
147
//If not 4 cascades, replace last used cascade plane distance with max shadow range (shadow far plane distance).
148
plane_distances[used_planes - 1] = lsource.range;
149
#ifdef LIGHT_CULLER_DEBUG_LOGGING
150
if (is_logging()) {
151
print_line("cascade split planes (first " + itos(used_planes) + " used): " +
152
String(Variant(plane_distances[0])) + "m, " +
153
String(Variant(plane_distances[1])) + "m, " +
154
String(Variant(plane_distances[2])) + "m, " +
155
String(Variant(plane_distances[3])) + "m, " +
156
String(Variant(plane_distances[4])) + "m");
157
}
158
#endif
159
Vector3 camera_normal = data.camera_transform.basis.xform(Vector3(0, 0, 1)).normalized();
160
for (int i = 0; i < used_planes; i++) {
161
real_t plane_distance = plane_distances[i];
162
163
//Plane compute
164
boundary_planes[i] = Plane(
165
camera_normal,
166
data.camera_transform.origin + camera_normal * -plane_distance);
167
}
168
}
169
170
for (int i = 0; i < lsource.cascade_count; i++) {
171
/*
172
enum PlaneOrder {
173
PLANE_NEAR,
174
PLANE_FAR,
175
PLANE_LEFT,
176
PLANE_TOP,
177
PLANE_RIGHT,
178
PLANE_BOTTOM,
179
PLANE_TOTAL,
180
};
181
*/
182
Plane cull_planes[6] = {
183
boundary_planes[MAX(i - (lsource.blend_splits ? 1 : 0), 0)],
184
Plane(-boundary_planes[i + 1].normal, -boundary_planes[i + 1].d), // Normal flip to ensure far is outward-facing.
185
data.frustum_planes[2],
186
data.frustum_planes[3],
187
data.frustum_planes[4],
188
data.frustum_planes[5],
189
};
190
191
#ifdef LIGHT_CULLER_DEBUG_LOGGING
192
if (is_logging()) {
193
for (int p = 0; p < 6; p++) {
194
print_line("cascade " + itos(i) + " plane " + itos(p) + " : " + String(cull_planes[p]));
195
}
196
}
197
#endif
198
199
// Frustum point calculation
200
Vector3 frustum_points[8];
201
bool success = create_frustum_points(cull_planes, frustum_points);
202
ERR_FAIL_COND_V(!success, false);
203
204
// Replace frustum arguments with cascade's.
205
LightCullPlanes &destination = data.directional_cull_planes[p_directional_light_id].planes[i];
206
visible = _add_light_camera_planes(destination, lsource, { cull_planes, frustum_points });
207
}
208
}
209
210
if (data.light_culling_active) {
211
return visible;
212
}
213
return true;
214
}
215
216
bool RenderingLightCuller::cull_directional_light(const RendererSceneCull::InstanceBounds &p_bound, int32_t p_directional_light_id, int32_t p_cascade) {
217
if (!data.is_active() || !is_caster_culling_active()) {
218
return true;
219
}
220
221
ERR_FAIL_INDEX_V(p_directional_light_id, (int32_t)data.directional_cull_planes.size(), true);
222
223
LightCullPlanes &cull_planes = data.directional_cull_planes[p_directional_light_id].planes[p_cascade];
224
225
Vector3 mins = Vector3(p_bound.bounds[0], p_bound.bounds[1], p_bound.bounds[2]);
226
Vector3 maxs = Vector3(p_bound.bounds[3], p_bound.bounds[4], p_bound.bounds[5]);
227
AABB bb(mins, maxs - mins);
228
229
real_t r_min, r_max;
230
for (int p = 0; p < cull_planes.num_cull_planes; p++) {
231
bb.project_range_in_plane(cull_planes.cull_planes[p], r_min, r_max);
232
if (r_min > 0.0f) {
233
#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT
234
cull_planes.rejected_count++;
235
#endif
236
237
return false;
238
}
239
}
240
241
return true;
242
}
243
244
void RenderingLightCuller::cull_regular_light(PagedArray<RendererSceneCull::Instance *> &r_instance_shadow_cull_result) {
245
if (!data.is_active() || !is_caster_culling_active()) {
246
return;
247
}
248
249
// If the light is out of range, no need to check anything, just return 0 casters.
250
// Ideally an out of range light should not even be drawn AT ALL (no shadow map, no PCF etc).
251
if (data.out_of_range) {
252
return;
253
}
254
255
// Shorter local alias.
256
PagedArray<RendererSceneCull::Instance *> &list = r_instance_shadow_cull_result;
257
258
#ifdef LIGHT_CULLER_DEBUG_LOGGING
259
uint32_t count_before = r_instance_shadow_cull_result.size();
260
#endif
261
262
// Go through all the casters in the list (the list will hopefully shrink as we go).
263
for (int n = 0; n < (int)list.size(); n++) {
264
// World space aabb.
265
const AABB &bb = list[n]->transformed_aabb;
266
267
#ifdef LIGHT_CULLER_DEBUG_LOGGING
268
if (is_logging()) {
269
print_line("bb : " + String(bb));
270
}
271
#endif
272
273
real_t r_min, r_max;
274
bool show = true;
275
276
for (int p = 0; p < data.regular_cull_planes.num_cull_planes; p++) {
277
// As we only need r_min, could this be optimized?
278
bb.project_range_in_plane(data.regular_cull_planes.cull_planes[p], r_min, r_max);
279
280
#ifdef LIGHT_CULLER_DEBUG_LOGGING
281
if (is_logging()) {
282
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)));
283
}
284
#endif
285
286
if (r_min > 0.0f) {
287
show = false;
288
break;
289
}
290
}
291
292
// Remove.
293
if (!show) {
294
list.remove_at_unordered(n);
295
296
// Repeat this element next iteration of the loop as it has been removed and replaced by the last.
297
n--;
298
299
#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT
300
data.regular_rejected_count++;
301
#endif
302
}
303
}
304
305
#ifdef LIGHT_CULLER_DEBUG_LOGGING
306
uint32_t removed = r_instance_shadow_cull_result.size() - count_before;
307
if (removed) {
308
if (((data.debug_count) % 60) == 0) {
309
print_line("[" + itos(data.debug_count) + "] linear cull before " + itos(count_before) + " after " + itos(r_instance_shadow_cull_result.size()));
310
}
311
}
312
#endif
313
}
314
315
void RenderingLightCuller::LightCullPlanes::add_cull_plane(const Plane &p) {
316
ERR_FAIL_COND(num_cull_planes >= MAX_CULL_PLANES);
317
cull_planes[num_cull_planes++] = p;
318
}
319
320
// Directional lights are different to points, as the origin is infinitely in the distance, so the plane third
321
// points are derived differently.
322
bool RenderingLightCuller::add_light_camera_planes_directional(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) {
323
uint32_t lookup = 0;
324
r_cull_planes.num_cull_planes = 0;
325
326
const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes;
327
328
// Directional light, we will use dot against the light direction to determine back facing planes.
329
for (int n = 0; n < 6; n++) {
330
float dot = cull_frustum_planes[n].normal.dot(p_light_source.dir);
331
if (dot > 0.0f) {
332
lookup |= 1 << n;
333
334
// Add backfacing camera frustum planes.
335
r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
336
}
337
}
338
339
ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);
340
341
// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
342
// then we will add the camera frustum planes to clip the light volume .. there is no need to
343
// render shadow casters outside the frustum as shadows can never re-enter the frustum.
344
345
// Should never happen with directional light?? This may be able to be removed.
346
if (lookup == 63) {
347
r_cull_planes.num_cull_planes = 0;
348
for (int n = 0; n < 6; n++) {
349
r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
350
}
351
352
return true;
353
}
354
355
// Each edge forms a plane.
356
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
357
const LocalVector<uint8_t> &entry = _calculated_LUT[lookup];
358
359
// each edge forms a plane
360
int n_edges = entry.size() - 1;
361
#else
362
uint8_t *entry = &data.LUT_entries[lookup][0];
363
int n_edges = data.LUT_entry_sizes[lookup] - 1;
364
#endif
365
366
const Vector3 *const frustum_points = p_cull_frustum.frustum_points;
367
368
for (int e = 0; e < n_edges; e++) {
369
int i0 = entry[e];
370
int i1 = entry[e + 1];
371
372
const Vector3 &pt0 = frustum_points[i0];
373
const Vector3 &pt1 = frustum_points[i1];
374
375
// Create a third point from the light direction.
376
Vector3 pt2 = pt0 - p_light_source.dir;
377
378
if (!_is_colinear_tri(pt0, pt1, pt2)) {
379
// Create plane from 3 points.
380
Plane p(pt0, pt1, pt2);
381
r_cull_planes.add_cull_plane(p);
382
}
383
}
384
385
// Last to 0 edge.
386
if (n_edges) {
387
int i0 = entry[n_edges]; // Last.
388
int i1 = entry[0]; // First.
389
390
const Vector3 &pt0 = frustum_points[i0];
391
const Vector3 &pt1 = frustum_points[i1];
392
393
// Create a third point from the light direction.
394
Vector3 pt2 = pt0 - p_light_source.dir;
395
396
if (!_is_colinear_tri(pt0, pt1, pt2)) {
397
// Create plane from 3 points.
398
Plane p(pt0, pt1, pt2);
399
r_cull_planes.add_cull_plane(p);
400
}
401
}
402
403
#ifdef LIGHT_CULLER_DEBUG_LOGGING
404
if (is_logging()) {
405
print_line("lcam.pos is " + String(p_light_source.pos));
406
}
407
#endif
408
409
return true;
410
}
411
412
bool RenderingLightCuller::_add_light_camera_planes(LightCullPlanes &r_cull_planes, const LightSource &p_light_source, const CullFrustumData &p_cull_frustum) {
413
if (!data.is_active()) {
414
return true;
415
}
416
417
const Plane *const cull_frustum_planes = p_cull_frustum.frustum_planes;
418
419
switch (p_light_source.type) {
420
case LightSource::ST_SPOTLIGHT:
421
case LightSource::ST_OMNI:
422
break;
423
case LightSource::ST_DIRECTIONAL:
424
return add_light_camera_planes_directional(r_cull_planes, p_light_source, p_cull_frustum);
425
break;
426
default:
427
return false; // not yet supported
428
break;
429
}
430
431
// Start with 0 cull planes.
432
r_cull_planes.num_cull_planes = 0;
433
data.out_of_range = false;
434
uint32_t lookup = 0;
435
436
// Find which of the camera planes are facing away from the light.
437
// We can also test for the situation where the light max range means it cannot
438
// affect the camera frustum. This is absolutely worth doing because it is relatively
439
// cheap, and if the entire light can be culled this can vastly improve performance
440
// (much more than just culling casters).
441
442
// POINT LIGHT (spotlight, omni)
443
// Instead of using dot product to compare light direction to plane, we can simply
444
// find out which side of the plane the camera is on. By definition this marks the point at which the plane
445
// becomes invisible.
446
447
// OMNIS
448
if (p_light_source.type == LightSource::ST_OMNI) {
449
for (int n = 0; n < 6; n++) {
450
float dist = cull_frustum_planes[n].distance_to(p_light_source.pos);
451
if (dist < 0.0f) {
452
lookup |= 1 << n;
453
454
// Add backfacing camera frustum planes.
455
r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
456
} else {
457
// Is the light out of range?
458
// This is one of the tests. If the point source is more than range distance from a frustum plane, it can't
459
// be seen.
460
if (dist >= p_light_source.range) {
461
// If the light is out of range, no need to do anything else, everything will be culled.
462
data.out_of_range = true;
463
return false;
464
}
465
}
466
}
467
} else {
468
// SPOTLIGHTs, more complex to cull.
469
Vector3 pos_end = p_light_source.pos + (p_light_source.dir * p_light_source.range);
470
471
// This is the radius of the cone at distance 1.
472
float radius_at_dist_one = Math::tan(Math::deg_to_rad(p_light_source.angle));
473
474
// The worst case radius of the cone at the end point can be calculated
475
// (the radius will scale linearly with length along the cone).
476
float end_cone_radius = radius_at_dist_one * p_light_source.range;
477
478
for (int n = 0; n < 6; n++) {
479
float dist = cull_frustum_planes[n].distance_to(p_light_source.pos);
480
if (dist < 0.0f) {
481
// Either the plane is backfacing or we are inside the frustum.
482
lookup |= 1 << n;
483
484
// Add backfacing camera frustum planes.
485
r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
486
} else {
487
// The light is in front of the plane.
488
489
// Is the light out of range?
490
if (dist >= p_light_source.range) {
491
data.out_of_range = true;
492
return false;
493
}
494
495
// For a spotlight, we can use an extra test
496
// at this point the cone start is in front of the plane...
497
// If the cone end point is further than the maximum possible distance to the plane
498
// we can guarantee that the cone does not cross the plane, and hence the cone
499
// is outside the frustum.
500
float dist_end = cull_frustum_planes[n].distance_to(pos_end);
501
502
if (dist_end >= end_cone_radius) {
503
data.out_of_range = true;
504
return false;
505
}
506
}
507
}
508
}
509
510
// The lookup should be within the LUT, logic should prevent this.
511
ERR_FAIL_COND_V(lookup >= LUT_SIZE, true);
512
513
// Deal with special case... if the light is INSIDE the view frustum (i.e. all planes face away)
514
// then we will add the camera frustum planes to clip the light volume .. there is no need to
515
// render shadow casters outside the frustum as shadows can never re-enter the frustum.
516
if (lookup == 63) {
517
r_cull_planes.num_cull_planes = 0;
518
for (int n = 0; n < 6; n++) {
519
r_cull_planes.add_cull_plane(cull_frustum_planes[n]);
520
}
521
522
return true;
523
}
524
525
// Each edge forms a plane.
526
uint8_t *entry = &data.LUT_entries[lookup][0];
527
int n_edges = data.LUT_entry_sizes[lookup] - 1;
528
529
const Vector3 *const frustum_points = p_cull_frustum.frustum_points;
530
531
const Vector3 &pt2 = p_light_source.pos;
532
533
for (int e = 0; e < n_edges; e++) {
534
int i0 = entry[e];
535
int i1 = entry[e + 1];
536
const Vector3 &pt0 = frustum_points[i0];
537
const Vector3 &pt1 = frustum_points[i1];
538
539
if (!_is_colinear_tri(pt0, pt1, pt2)) {
540
// Create plane from 3 points.
541
Plane p(pt0, pt1, pt2);
542
r_cull_planes.add_cull_plane(p);
543
}
544
}
545
546
// Last to 0 edge.
547
if (n_edges) {
548
int i0 = entry[n_edges]; // Last.
549
int i1 = entry[0]; // First.
550
551
const Vector3 &pt0 = frustum_points[i0];
552
const Vector3 &pt1 = frustum_points[i1];
553
554
if (!_is_colinear_tri(pt0, pt1, pt2)) {
555
// Create plane from 3 points.
556
Plane p(pt0, pt1, pt2);
557
r_cull_planes.add_cull_plane(p);
558
}
559
}
560
561
#ifdef LIGHT_CULLER_DEBUG_LOGGING
562
if (is_logging()) {
563
print_line("lsource.pos is " + String(p_light_source.pos));
564
}
565
#endif
566
567
return true;
568
}
569
570
bool RenderingLightCuller::prepare_camera(const Transform3D &p_cam_transform, const Projection &p_cam_matrix) {
571
data.debug_count++;
572
if (data.debug_count >= 120) {
573
data.debug_count = 0;
574
}
575
576
// For debug flash off and on.
577
#ifdef LIGHT_CULLER_DEBUG_FLASH
578
if (!Engine::get_singleton()->is_editor_hint()) {
579
int dc = Engine::get_singleton()->get_process_frames() / LIGHT_CULLER_DEBUG_FLASH_FREQUENCY;
580
bool bnew_active;
581
bnew_active = (dc % 2) == 0;
582
583
if (bnew_active != data.light_culling_active) {
584
data.light_culling_active = bnew_active;
585
print_line("switching light culler " + String(Variant(data.light_culling_active)));
586
}
587
}
588
#endif
589
590
if (!data.is_active()) {
591
return false;
592
}
593
// These are needed later to build per-cascade cull frustums for directional lights.
594
data.camera_transform = p_cam_transform;
595
data.camera_projection = p_cam_matrix;
596
597
// Get the camera frustum planes in world space.
598
data.frustum_planes = p_cam_matrix.get_projection_planes(p_cam_transform);
599
DEV_CHECK_ONCE(data.frustum_planes.size() == 6);
600
601
data.regular_cull_planes.num_cull_planes = 0;
602
603
#ifdef LIGHT_CULLER_DEBUG_DIRECTIONAL_LIGHT
604
if (is_logging()) {
605
for (uint32_t n = 0; n < data.directional_cull_planes.size(); n++) {
606
print_line("LightCuller directional light " + itos(n) + " rejected " + itos(data.directional_cull_planes[n].rejected_count) + " instances.");
607
}
608
}
609
#endif
610
#ifdef LIGHT_CULLER_DEBUG_REGULAR_LIGHT
611
if (data.regular_rejected_count) {
612
print_line("LightCuller regular lights rejected " + itos(data.regular_rejected_count) + " instances.");
613
}
614
data.regular_rejected_count = 0;
615
#endif
616
617
data.directional_cull_planes.resize(0);
618
619
#ifdef LIGHT_CULLER_DEBUG_LOGGING
620
if (is_logging()) {
621
for (int p = 0; p < 6; p++) {
622
print_line("plane " + itos(p) + " : " + String(data.frustum_planes[p]));
623
}
624
}
625
#endif
626
627
return create_frustum_points(&data.frustum_planes[0], data.frustum_points);
628
}
629
630
bool RenderingLightCuller::create_frustum_points(const Plane *p_frustum_planes, Vector3 *r_result) const {
631
// We want to calculate the frustum corners in a specific order.
632
const Projection::Planes intersections[8][3] = {
633
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
634
{ Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
635
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
636
{ Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
637
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
638
{ Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
639
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
640
{ Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
641
};
642
643
for (int i = 0; i < 8; i++) {
644
// 3 plane intersection, gives us a point.
645
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]);
646
647
// What happens with a zero frustum? NYI - deal with this.
648
ERR_FAIL_COND_V(!res, false);
649
650
#ifdef LIGHT_CULLER_DEBUG_LOGGING
651
if (is_logging()) {
652
print_line("point " + itos(i) + " -> " + String(result[i]));
653
}
654
#endif
655
}
656
return true;
657
}
658
659
RenderingLightCuller::RenderingLightCuller() {
660
// Used to determine which frame to give debug output.
661
data.debug_count = -1;
662
663
// Uncomment below to switch off light culler in the editor.
664
// data.caster_culling_active = Engine::get_singleton()->is_editor_hint() == false;
665
666
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
667
create_LUT();
668
#endif
669
}
670
671
/* clang-format off */
672
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, };
673
674
// The lookup table used to determine which edges form the silhouette of the camera frustum,
675
// depending on the viewing angle (defined by which camera planes are backward facing).
676
uint8_t RenderingLightCuller::Data::LUT_entries[LUT_SIZE][8] = {
677
{0, 0, 0, 0, 0, 0, 0, 0, },
678
{7, 6, 4, 5, 0, 0, 0, 0, },
679
{1, 0, 2, 3, 0, 0, 0, 0, },
680
{0, 0, 0, 0, 0, 0, 0, 0, },
681
{1, 5, 4, 0, 0, 0, 0, 0, },
682
{1, 5, 7, 6, 4, 0, 0, 0, },
683
{4, 0, 2, 3, 1, 5, 0, 0, },
684
{5, 7, 6, 4, 0, 2, 3, 1, },
685
{0, 4, 6, 2, 0, 0, 0, 0, },
686
{0, 4, 5, 7, 6, 2, 0, 0, },
687
{6, 2, 3, 1, 0, 4, 0, 0, },
688
{2, 3, 1, 0, 4, 5, 7, 6, },
689
{0, 1, 5, 4, 6, 2, 0, 0, },
690
{0, 1, 5, 7, 6, 2, 0, 0, },
691
{6, 2, 3, 1, 5, 4, 0, 0, },
692
{2, 3, 1, 5, 7, 6, 0, 0, },
693
{2, 6, 7, 3, 0, 0, 0, 0, },
694
{2, 6, 4, 5, 7, 3, 0, 0, },
695
{7, 3, 1, 0, 2, 6, 0, 0, },
696
{3, 1, 0, 2, 6, 4, 5, 7, },
697
{0, 0, 0, 0, 0, 0, 0, 0, },
698
{2, 6, 4, 0, 1, 5, 7, 3, },
699
{7, 3, 1, 5, 4, 0, 2, 6, },
700
{0, 0, 0, 0, 0, 0, 0, 0, },
701
{2, 0, 4, 6, 7, 3, 0, 0, },
702
{2, 0, 4, 5, 7, 3, 0, 0, },
703
{7, 3, 1, 0, 4, 6, 0, 0, },
704
{3, 1, 0, 4, 5, 7, 0, 0, },
705
{2, 0, 1, 5, 4, 6, 7, 3, },
706
{2, 0, 1, 5, 7, 3, 0, 0, },
707
{7, 3, 1, 5, 4, 6, 0, 0, },
708
{3, 1, 5, 7, 0, 0, 0, 0, },
709
{3, 7, 5, 1, 0, 0, 0, 0, },
710
{3, 7, 6, 4, 5, 1, 0, 0, },
711
{5, 1, 0, 2, 3, 7, 0, 0, },
712
{7, 6, 4, 5, 1, 0, 2, 3, },
713
{3, 7, 5, 4, 0, 1, 0, 0, },
714
{3, 7, 6, 4, 0, 1, 0, 0, },
715
{5, 4, 0, 2, 3, 7, 0, 0, },
716
{7, 6, 4, 0, 2, 3, 0, 0, },
717
{0, 0, 0, 0, 0, 0, 0, 0, },
718
{3, 7, 6, 2, 0, 4, 5, 1, },
719
{5, 1, 0, 4, 6, 2, 3, 7, },
720
{0, 0, 0, 0, 0, 0, 0, 0, },
721
{3, 7, 5, 4, 6, 2, 0, 1, },
722
{3, 7, 6, 2, 0, 1, 0, 0, },
723
{5, 4, 6, 2, 3, 7, 0, 0, },
724
{7, 6, 2, 3, 0, 0, 0, 0, },
725
{3, 2, 6, 7, 5, 1, 0, 0, },
726
{3, 2, 6, 4, 5, 1, 0, 0, },
727
{5, 1, 0, 2, 6, 7, 0, 0, },
728
{1, 0, 2, 6, 4, 5, 0, 0, },
729
{3, 2, 6, 7, 5, 4, 0, 1, },
730
{3, 2, 6, 4, 0, 1, 0, 0, },
731
{5, 4, 0, 2, 6, 7, 0, 0, },
732
{6, 4, 0, 2, 0, 0, 0, 0, },
733
{3, 2, 0, 4, 6, 7, 5, 1, },
734
{3, 2, 0, 4, 5, 1, 0, 0, },
735
{5, 1, 0, 4, 6, 7, 0, 0, },
736
{1, 0, 4, 5, 0, 0, 0, 0, },
737
{0, 0, 0, 0, 0, 0, 0, 0, },
738
{3, 2, 0, 1, 0, 0, 0, 0, },
739
{5, 4, 6, 7, 0, 0, 0, 0, },
740
{0, 0, 0, 0, 0, 0, 0, 0, },
741
};
742
743
/* clang-format on */
744
745
#ifdef RENDERING_LIGHT_CULLER_CALCULATE_LUT
746
747
// See e.g. http://lspiroengine.com/?p=153 for reference.
748
// Principles are the same, but differences to the article:
749
// * Order of planes / points is different in Godot.
750
// * We use a lookup table at runtime.
751
void RenderingLightCuller::create_LUT() {
752
// Each pair of planes that are opposite can have an edge.
753
for (int plane_0 = 0; plane_0 < PLANE_TOTAL; plane_0++) {
754
// For each neighbor of the plane.
755
PlaneOrder neighs[4];
756
get_neighbouring_planes((PlaneOrder)plane_0, neighs);
757
758
for (int n = 0; n < 4; n++) {
759
int plane_1 = neighs[n];
760
761
// If these are opposite we need to add the 2 points they share.
762
PointOrder pts[2];
763
get_corners_of_planes((PlaneOrder)plane_0, (PlaneOrder)plane_1, pts);
764
765
add_LUT(plane_0, plane_1, pts);
766
}
767
}
768
769
for (uint32_t n = 0; n < LUT_SIZE; n++) {
770
compact_LUT_entry(n);
771
}
772
773
debug_print_LUT();
774
debug_print_LUT_as_table();
775
}
776
777
// we can pre-create the entire LUT and store it hard coded as a static inside the executable!
778
// it is only small in size, 64 entries with max 8 bytes per entry
779
void RenderingLightCuller::debug_print_LUT_as_table() {
780
print_line("\nLIGHT VOLUME TABLE BEGIN\n");
781
782
print_line("Copy this to LUT_entry_sizes:\n");
783
String sz = "{";
784
for (int n = 0; n < LUT_SIZE; n++) {
785
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
786
787
sz += itos(entry.size()) + ", ";
788
}
789
sz += "}";
790
print_line(sz);
791
print_line("\nCopy this to LUT_entries:\n");
792
793
for (int n = 0; n < LUT_SIZE; n++) {
794
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
795
796
String sz = "{";
797
798
// First is the number of points in the entry.
799
int s = entry.size();
800
801
for (int p = 0; p < 8; p++) {
802
if (p < s) {
803
sz += itos(entry[p]);
804
} else {
805
sz += "0"; // just a spacer
806
}
807
808
sz += ", ";
809
}
810
811
sz += "},";
812
print_line(sz);
813
}
814
815
print_line("\nLIGHT VOLUME TABLE END\n");
816
}
817
818
void RenderingLightCuller::debug_print_LUT() {
819
for (int n = 0; n < LUT_SIZE; n++) {
820
String sz;
821
sz = "LUT" + itos(n) + ":\t";
822
823
sz += Data::plane_bitfield_to_string(n);
824
print_line(sz);
825
826
const LocalVector<uint8_t> &entry = _calculated_LUT[n];
827
828
sz = "\t" + string_LUT_entry(entry);
829
830
print_line(sz);
831
}
832
}
833
834
String RenderingLightCuller::string_LUT_entry(const LocalVector<uint8_t> &p_entry) {
835
String string;
836
837
for (uint32_t n = 0; n < p_entry.size(); n++) {
838
uint8_t val = p_entry[n];
839
DEV_ASSERT(val < 8);
840
const char *sz_point = Data::string_points[val];
841
string += sz_point;
842
string += ", ";
843
}
844
845
return string;
846
}
847
848
String RenderingLightCuller::debug_string_LUT_entry(const LocalVector<uint8_t> &p_entry, bool p_pair) {
849
String string;
850
851
for (uint32_t i = 0; i < p_entry.size(); i++) {
852
int pt_order = p_entry[i];
853
if (p_pair && ((i % 2) == 0)) {
854
string += itos(pt_order) + "-";
855
} else {
856
string += itos(pt_order) + ", ";
857
}
858
}
859
860
return string;
861
}
862
863
void RenderingLightCuller::add_LUT(int p_plane_0, int p_plane_1, PointOrder p_pts[2]) {
864
// Note that some entries to the LUT will be "impossible" situations,
865
// because it contains all combinations of plane flips.
866
uint32_t bit0 = 1 << p_plane_0;
867
uint32_t bit1 = 1 << p_plane_1;
868
869
// All entries of the LUT that have plane 0 set and plane 1 not set.
870
for (uint32_t n = 0; n < 64; n++) {
871
// If bit0 not set...
872
if (!(n & bit0)) {
873
continue;
874
}
875
876
// If bit1 set...
877
if (n & bit1) {
878
continue;
879
}
880
881
// Meets criteria.
882
add_LUT_entry(n, p_pts);
883
}
884
}
885
886
void RenderingLightCuller::add_LUT_entry(uint32_t p_entry_id, PointOrder p_pts[2]) {
887
DEV_ASSERT(p_entry_id < LUT_SIZE);
888
LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];
889
890
entry.push_back(p_pts[0]);
891
entry.push_back(p_pts[1]);
892
}
893
894
void RenderingLightCuller::compact_LUT_entry(uint32_t p_entry_id) {
895
DEV_ASSERT(p_entry_id < LUT_SIZE);
896
LocalVector<uint8_t> &entry = _calculated_LUT[p_entry_id];
897
898
int num_pairs = entry.size() / 2;
899
900
if (num_pairs == 0) {
901
return;
902
}
903
904
LocalVector<uint8_t> temp;
905
906
String string;
907
string = "Compact LUT" + itos(p_entry_id) + ":\t";
908
string += debug_string_LUT_entry(entry, true);
909
print_line(string);
910
911
// Add first pair.
912
temp.push_back(entry[0]);
913
temp.push_back(entry[1]);
914
unsigned int BFpairs = 1;
915
916
string = debug_string_LUT_entry(temp) + " -> ";
917
print_line(string);
918
919
// Attempt to add a pair each time.
920
for (int done = 1; done < num_pairs; done++) {
921
string = "done " + itos(done) + ": ";
922
// Find a free pair.
923
for (int p = 1; p < num_pairs; p++) {
924
unsigned int bit = 1 << p;
925
// Is it done already?
926
if (BFpairs & bit) {
927
continue;
928
}
929
930
// There must be at least 1 free pair.
931
// Attempt to add.
932
int a = entry[p * 2];
933
int b = entry[(p * 2) + 1];
934
935
string += "[" + itos(a) + "-" + itos(b) + "], ";
936
937
int found_a = temp.find(a);
938
int found_b = temp.find(b);
939
940
// Special case, if they are both already in the list, no need to add
941
// as this is a link from the tail to the head of the list.
942
if ((found_a != -1) && (found_b != -1)) {
943
string += "foundAB link " + itos(found_a) + ", " + itos(found_b) + " ";
944
BFpairs |= bit;
945
goto found;
946
}
947
948
// Find a.
949
if (found_a != -1) {
950
string += "foundA " + itos(found_a) + " ";
951
temp.insert(found_a + 1, b);
952
BFpairs |= bit;
953
goto found;
954
}
955
956
// Find b.
957
if (found_b != -1) {
958
string += "foundB " + itos(found_b) + " ";
959
temp.insert(found_b, a);
960
BFpairs |= bit;
961
goto found;
962
}
963
964
} // Check each pair for adding.
965
966
// If we got here before finding a link, the whole set of planes is INVALID
967
// e.g. far and near plane only, does not create continuous sillouhette of edges.
968
print_line("\tINVALID");
969
entry.clear();
970
return;
971
972
found:;
973
print_line(string);
974
string = "\ttemp now : " + debug_string_LUT_entry(temp);
975
print_line(string);
976
}
977
978
// temp should now be the sorted entry .. delete the old one and replace by temp.
979
entry.clear();
980
entry = temp;
981
}
982
983
void RenderingLightCuller::get_neighbouring_planes(PlaneOrder p_plane, PlaneOrder r_neigh_planes[4]) const {
984
// Table of neighboring planes to each.
985
static const PlaneOrder neigh_table[PLANE_TOTAL][4] = {
986
{ // LSM_FP_NEAR
987
PLANE_LEFT,
988
PLANE_RIGHT,
989
PLANE_TOP,
990
PLANE_BOTTOM },
991
{ // LSM_FP_FAR
992
PLANE_LEFT,
993
PLANE_RIGHT,
994
PLANE_TOP,
995
PLANE_BOTTOM },
996
{ // LSM_FP_LEFT
997
PLANE_TOP,
998
PLANE_BOTTOM,
999
PLANE_NEAR,
1000
PLANE_FAR },
1001
{ // LSM_FP_TOP
1002
PLANE_LEFT,
1003
PLANE_RIGHT,
1004
PLANE_NEAR,
1005
PLANE_FAR },
1006
{ // LSM_FP_RIGHT
1007
PLANE_TOP,
1008
PLANE_BOTTOM,
1009
PLANE_NEAR,
1010
PLANE_FAR },
1011
{ // LSM_FP_BOTTOM
1012
PLANE_LEFT,
1013
PLANE_RIGHT,
1014
PLANE_NEAR,
1015
PLANE_FAR },
1016
};
1017
1018
for (int n = 0; n < 4; n++) {
1019
r_neigh_planes[n] = neigh_table[p_plane][n];
1020
}
1021
}
1022
1023
// Given two planes, returns the two points shared by those planes. The points are always
1024
// returned in counter-clockwise order, assuming the first input plane is facing towards
1025
// the viewer.
1026
1027
// param p_plane_a The plane facing towards the viewer.
1028
// param p_plane_b A plane neighboring p_plane_a.
1029
// param r_points An array of exactly two elements to be filled with the indices of the points
1030
// on return.
1031
1032
void RenderingLightCuller::get_corners_of_planes(PlaneOrder p_plane_a, PlaneOrder p_plane_b, PointOrder r_points[2]) const {
1033
static const PointOrder fp_table[PLANE_TOTAL][PLANE_TOTAL][2] = {
1034
{
1035
// LSM_FP_NEAR
1036
{
1037
// LSM_FP_NEAR
1038
PT_NEAR_LEFT_TOP, PT_NEAR_RIGHT_TOP // Invalid combination.
1039
},
1040
{
1041
// LSM_FP_FAR
1042
PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP // Invalid combination.
1043
},
1044
{
1045
// LSM_FP_LEFT
1046
PT_NEAR_LEFT_TOP,
1047
PT_NEAR_LEFT_BOTTOM,
1048
},
1049
{
1050
// LSM_FP_TOP
1051
PT_NEAR_RIGHT_TOP,
1052
PT_NEAR_LEFT_TOP,
1053
},
1054
{
1055
// LSM_FP_RIGHT
1056
PT_NEAR_RIGHT_BOTTOM,
1057
PT_NEAR_RIGHT_TOP,
1058
},
1059
{
1060
// LSM_FP_BOTTOM
1061
PT_NEAR_LEFT_BOTTOM,
1062
PT_NEAR_RIGHT_BOTTOM,
1063
},
1064
},
1065
1066
{
1067
// LSM_FP_FAR
1068
{
1069
// LSM_FP_NEAR
1070
PT_FAR_LEFT_TOP, PT_FAR_RIGHT_TOP // Invalid combination.
1071
},
1072
{
1073
// LSM_FP_FAR
1074
PT_FAR_RIGHT_TOP, PT_FAR_LEFT_TOP // Invalid combination.
1075
},
1076
{
1077
// LSM_FP_LEFT
1078
PT_FAR_LEFT_BOTTOM,
1079
PT_FAR_LEFT_TOP,
1080
},
1081
{
1082
// LSM_FP_TOP
1083
PT_FAR_LEFT_TOP,
1084
PT_FAR_RIGHT_TOP,
1085
},
1086
{
1087
// LSM_FP_RIGHT
1088
PT_FAR_RIGHT_TOP,
1089
PT_FAR_RIGHT_BOTTOM,
1090
},
1091
{
1092
// LSM_FP_BOTTOM
1093
PT_FAR_RIGHT_BOTTOM,
1094
PT_FAR_LEFT_BOTTOM,
1095
},
1096
},
1097
1098
{
1099
// LSM_FP_LEFT
1100
{
1101
// LSM_FP_NEAR
1102
PT_NEAR_LEFT_BOTTOM,
1103
PT_NEAR_LEFT_TOP,
1104
},
1105
{
1106
// LSM_FP_FAR
1107
PT_FAR_LEFT_TOP,
1108
PT_FAR_LEFT_BOTTOM,
1109
},
1110
{
1111
// LSM_FP_LEFT
1112
PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.
1113
},
1114
{
1115
// LSM_FP_TOP
1116
PT_NEAR_LEFT_TOP,
1117
PT_FAR_LEFT_TOP,
1118
},
1119
{
1120
// LSM_FP_RIGHT
1121
PT_FAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.
1122
},
1123
{
1124
// LSM_FP_BOTTOM
1125
PT_FAR_LEFT_BOTTOM,
1126
PT_NEAR_LEFT_BOTTOM,
1127
},
1128
},
1129
1130
{
1131
// LSM_FP_TOP
1132
{
1133
// LSM_FP_NEAR
1134
PT_NEAR_LEFT_TOP,
1135
PT_NEAR_RIGHT_TOP,
1136
},
1137
{
1138
// LSM_FP_FAR
1139
PT_FAR_RIGHT_TOP,
1140
PT_FAR_LEFT_TOP,
1141
},
1142
{
1143
// LSM_FP_LEFT
1144
PT_FAR_LEFT_TOP,
1145
PT_NEAR_LEFT_TOP,
1146
},
1147
{
1148
// LSM_FP_TOP
1149
PT_NEAR_LEFT_TOP, PT_FAR_LEFT_TOP // Invalid combination.
1150
},
1151
{
1152
// LSM_FP_RIGHT
1153
PT_NEAR_RIGHT_TOP,
1154
PT_FAR_RIGHT_TOP,
1155
},
1156
{
1157
// LSM_FP_BOTTOM
1158
PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM // Invalid combination.
1159
},
1160
},
1161
1162
{
1163
// LSM_FP_RIGHT
1164
{
1165
// LSM_FP_NEAR
1166
PT_NEAR_RIGHT_TOP,
1167
PT_NEAR_RIGHT_BOTTOM,
1168
},
1169
{
1170
// LSM_FP_FAR
1171
PT_FAR_RIGHT_BOTTOM,
1172
PT_FAR_RIGHT_TOP,
1173
},
1174
{
1175
// LSM_FP_LEFT
1176
PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM // Invalid combination.
1177
},
1178
{
1179
// LSM_FP_TOP
1180
PT_FAR_RIGHT_TOP,
1181
PT_NEAR_RIGHT_TOP,
1182
},
1183
{
1184
// LSM_FP_RIGHT
1185
PT_FAR_RIGHT_BOTTOM, PT_FAR_RIGHT_BOTTOM // Invalid combination.
1186
},
1187
{
1188
// LSM_FP_BOTTOM
1189
PT_NEAR_RIGHT_BOTTOM,
1190
PT_FAR_RIGHT_BOTTOM,
1191
},
1192
},
1193
1194
// ==
1195
1196
// P_NEAR,
1197
// P_FAR,
1198
// P_LEFT,
1199
// P_TOP,
1200
// P_RIGHT,
1201
// P_BOTTOM,
1202
1203
{
1204
// LSM_FP_BOTTOM
1205
{
1206
// LSM_FP_NEAR
1207
PT_NEAR_RIGHT_BOTTOM,
1208
PT_NEAR_LEFT_BOTTOM,
1209
},
1210
{
1211
// LSM_FP_FAR
1212
PT_FAR_LEFT_BOTTOM,
1213
PT_FAR_RIGHT_BOTTOM,
1214
},
1215
{
1216
// LSM_FP_LEFT
1217
PT_NEAR_LEFT_BOTTOM,
1218
PT_FAR_LEFT_BOTTOM,
1219
},
1220
{
1221
// LSM_FP_TOP
1222
PT_NEAR_LEFT_BOTTOM, PT_FAR_LEFT_BOTTOM // Invalid combination.
1223
},
1224
{
1225
// LSM_FP_RIGHT
1226
PT_FAR_RIGHT_BOTTOM,
1227
PT_NEAR_RIGHT_BOTTOM,
1228
},
1229
{
1230
// LSM_FP_BOTTOM
1231
PT_FAR_LEFT_BOTTOM, PT_NEAR_LEFT_BOTTOM // Invalid combination.
1232
},
1233
},
1234
1235
// ==
1236
1237
};
1238
r_points[0] = fp_table[p_plane_a][p_plane_b][0];
1239
r_points[1] = fp_table[p_plane_a][p_plane_b][1];
1240
}
1241
1242
#endif
1243
1244