Object Occlusion Culling

This commit is contained in:
2026-10-15 02:41:03 -07:00
parent 3273054766
commit 612a3d0a09
33 changed files with 3629 additions and 84 deletions

23
lib/radix/LICENSE_1_0.txt Normal file
View File

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

23
lib/radix/README.md Normal file
View File

@ -0,0 +1,23 @@
# Radix
Haxe port: in-place MSD radix sort (American flag sort) with an insertion sort fallback for small sub-ranges implemented in the Leenkx SDK by Riccardo Mazzucco
Original reference source Malte Skarupke's [ska_sort](https://github.com/skarupke/ska_sort) - https://github.com/skarupke/ska_sort
## Usage
```haxe
import radix.Radix;
// Stable sort: counting-sort scatter between `a` and `buffer`.
// Equal keys keep input order - matches haxe Array.sort semantics.
Radix.sortCopyKeyed(meshes, keys, meshesBuf, keysBuf);
// In-place MSD sort on the same (element, keys) arrays.
// No scratch buffer needed, but NOT stable - equal keys may reorder.
Radix.sortKeyed(meshes, keys);
// Or let the sorter build keys from an extractor
Radix.sort(meshes, function(m) {
return (m.depthRead ? 0x80000000 : 0) | (FPHelper.floatToI32(m.cameraDistance) >>> 1);
});
```

View File

@ -0,0 +1,350 @@
package radix;
import haxe.ds.Vector;
import haxe.io.FPHelper;
/**
In-place MSD radix sort - Haxe port of Malte Skarupke's ska_sort
(ref/ska_sort.hpp, Boost Software License 1.0). Original:
https://github.com/skarupke/ska_sort
**/
class Radix {
static inline var STD_SORT_THRESHOLD = 128;
static inline var AMERICAN_FLAG_SORT_THRESHOLD = 1024;
public static inline function intToUnsigned(i:Int):Int {
return i ^ 0x80000000;
}
public static inline function floatToUnsigned(f:Float):Int {
var u = FPHelper.floatToI32(f);
var signBit = u >> 31;
return u ^ (signBit | 0x80000000);
}
public static inline function uLess(a:Int, b:Int):Bool {
return (a ^ 0x80000000) < (b ^ 0x80000000);
}
public static function sort<T>(a:Array<T>, extractKey:T->Int, ?keys:Array<Int>):Void {
if (keys == null) keys = new Array<Int>();
for (i in 0...a.length) keys[i] = extractKey(a[i]);
sortKeyed(a, keys);
}
public static function sortFloat<T>(a:Array<T>, extractKey:T->Float, ?keys:Array<Int>):Void {
if (keys == null) keys = new Array<Int>();
for (i in 0...a.length) keys[i] = floatToUnsigned(extractKey(a[i]));
sortKeyed(a, keys);
}
public static function sortKeyed<T>(a:Array<T>, keys:Array<Int>):Void {
var n = a.length;
if (stdSortIfLessThanThreshold(a, keys, 0, n)) return;
msdSort(a, keys, 0, n, 24, new RadixScratch());
}
static function msdSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int, shift:Int, scratch:RadixScratch):Void {
var numElements = end - begin;
if (numElements < AMERICAN_FLAG_SORT_THRESHOLD)
americanFlagSort(a, keys, begin, end, shift, scratch);
else
skaByteSort(a, keys, begin, end, shift, scratch);
}
static function stdSortIfLessThanThreshold<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int):Bool {
var numElements = end - begin;
if (numElements <= 1) return true;
if (numElements >= STD_SORT_THRESHOLD) return false;
insertionSort(a, keys, begin, end);
return true;
}
static function insertionSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int):Void {
var i = begin + 1;
while (i < end) {
var key = keys[i];
var v = a[i];
var j = i - 1;
while (j >= begin && uLess(key, keys[j])) {
keys[j + 1] = keys[j];
a[j + 1] = a[j];
j--;
}
keys[j + 1] = key;
a[j + 1] = v;
i++;
}
}
static function americanFlagSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int, shift:Int, scratch:RadixScratch):Void {
var frame = scratch.frame(3 - (shift >> 3));
var offset = frame.offset;
var nextOffset = frame.nextOffset;
var remaining = frame.remaining;
zeroTable(offset);
for (i in begin...end) {
offset[(keys[i] >>> shift) & 0xff]++;
}
var numPartitions = 0;
var total = 0;
for (i in 0...256) {
var count = offset[i];
if (count == 0) continue;
offset[i] = total;
total += count;
nextOffset[i] = total;
remaining[numPartitions] = i;
numPartitions++;
}
if (numPartitions > 1) {
var curIdx = 0;
var cur = remaining[0];
var lastIdx = numPartitions - 1;
var it = begin;
var blockEnd = begin + nextOffset[cur];
var lastElement = end - 1;
var done = false;
while (true) {
var block = (keys[it] >>> shift) & 0xff;
if (block == cur) {
it++;
if (it == lastElement) break;
if (it == blockEnd) {
while (true) {
curIdx++;
if (curIdx == lastIdx) {
done = true;
break;
}
cur = remaining[curIdx];
if (offset[cur] != nextOffset[cur]) break;
}
if (done) break;
it = begin + offset[cur];
blockEnd = begin + nextOffset[cur];
}
}
else {
var t = offset[block];
offset[block] = t + 1;
swapAt(a, keys, it, begin + t);
}
}
}
if (shift > 0) {
var startOffset = 0;
var partitionBegin = begin;
for (i in 0...numPartitions) {
var endOffset = nextOffset[remaining[i]];
var partitionEnd = begin + endOffset;
if (!stdSortIfLessThanThreshold(a, keys, partitionBegin, partitionEnd)) {
msdSort(a, keys, partitionBegin, partitionEnd, shift - 8, scratch);
}
startOffset = endOffset;
partitionBegin = partitionEnd;
}
}
}
static function skaByteSort<T>(a:Array<T>, keys:Array<Int>, begin:Int, end:Int, shift:Int, scratch:RadixScratch):Void {
var frame = scratch.frame(3 - (shift >> 3));
var offset = frame.offset;
var nextOffset = frame.nextOffset;
var remaining = frame.remaining;
zeroTable(offset);
for (i in begin...end) {
offset[(keys[i] >>> shift) & 0xff]++;
}
var numPartitions = 0;
var total = 0;
for (i in 0...256) {
var count = offset[i];
if (count != 0) {
offset[i] = total;
total += count;
remaining[numPartitions] = i;
numPartitions++;
}
nextOffset[i] = total;
}
var lastRemaining = numPartitions;
while (lastRemaining > 1) {
var b = 0;
while (b < lastRemaining && processPending(a, keys, offset, nextOffset, remaining[b], begin, shift)) b++;
var it = b + 1;
while (it < lastRemaining) {
if (processPending(a, keys, offset, nextOffset, remaining[it], begin, shift)) {
var t = remaining[b];
remaining[b] = remaining[it];
remaining[it] = t;
b++;
}
it++;
}
lastRemaining = b;
}
if (shift > 0) {
var i = numPartitions;
while (i > 0) {
i--;
var partition = remaining[i];
var start = partition == 0 ? 0 : nextOffset[partition - 1];
var endOff = nextOffset[partition];
if (!stdSortIfLessThanThreshold(a, keys, begin + start, begin + endOff)) {
msdSort(a, keys, begin + start, begin + endOff, shift - 8, scratch);
}
}
}
}
static inline function newTable():Vector<Int> {
var v = new Vector<Int>(256);
#if !(cpp || hl || java || cs || flash)
for (i in 0...256) v[i] = 0;
#end
return v;
}
static function processPending<T>(a:Array<T>, keys:Array<Int>, offset:Vector<Int>, nextOffset:Vector<Int>, partition:Int, begin:Int, shift:Int):Bool {
var endOff = nextOffset[partition];
if (offset[partition] == endOff) return false;
var it = begin + offset[partition];
var count = endOff - offset[partition];
while (count >= 4) {
placeAt(a, keys, offset, it, begin, shift); it++;
placeAt(a, keys, offset, it, begin, shift); it++;
placeAt(a, keys, offset, it, begin, shift); it++;
placeAt(a, keys, offset, it, begin, shift); it++;
count -= 4;
}
while (count > 0) {
placeAt(a, keys, offset, it, begin, shift);
it++;
count--;
}
return offset[partition] != endOff;
}
static inline function placeAt<T>(a:Array<T>, keys:Array<Int>, offset:Vector<Int>, it:Int, begin:Int, shift:Int):Void {
var p = (keys[it] >>> shift) & 0xff;
var t = offset[p];
offset[p] = t + 1;
swapAt(a, keys, it, begin + t);
}
static inline function zeroTable(v:Vector<Int>):Void {
for (i in 0...256) v[i] = 0;
}
static inline function swapAt<T>(a:Array<T>, keys:Array<Int>, i:Int, j:Int):Void {
var ta = a[i];
a[i] = a[j];
a[j] = ta;
var tk = keys[i];
keys[i] = keys[j];
keys[j] = tk;
}
public static function sortCopy<T>(a:Array<T>, buffer:Array<T>, extractKey:T->Int):Bool {
var numElements = a.length;
if (numElements < STD_SORT_THRESHOLD) {
sort(a, extractKey);
return false;
}
var keys = new Array<Int>();
var bufKeys = new Array<Int>();
for (i in 0...numElements) keys[i] = extractKey(a[i]);
sortCopyKeyed(a, keys, buffer, bufKeys);
return false;
}
public static function sortCopyKeyed<T>(a:Array<T>, keys:Array<Int>, buffer:Array<T>, bufKeys:Array<Int>):Void {
var n = a.length;
var counts0 = newTable();
var counts1 = newTable();
var counts2 = newTable();
var counts3 = newTable();
for (i in 0...n) {
var key = keys[i];
counts0[key & 0xff]++;
counts1[(key >>> 8) & 0xff]++;
counts2[(key >>> 16) & 0xff]++;
counts3[(key >>> 24) & 0xff]++;
}
var total0 = 0, total1 = 0, total2 = 0, total3 = 0;
for (i in 0...256) {
var old0 = counts0[i];
var old1 = counts1[i];
var old2 = counts2[i];
var old3 = counts3[i];
counts0[i] = total0;
counts1[i] = total1;
counts2[i] = total2;
counts3[i] = total3;
total0 += old0;
total1 += old1;
total2 += old2;
total3 += old3;
}
for (i in 0...n) {
var b = keys[i] & 0xff;
var t = counts0[b];
counts0[b] = t + 1;
buffer[t] = a[i];
bufKeys[t] = keys[i];
}
for (i in 0...n) {
var b = (bufKeys[i] >>> 8) & 0xff;
var t = counts1[b];
counts1[b] = t + 1;
a[t] = buffer[i];
keys[t] = bufKeys[i];
}
for (i in 0...n) {
var b = (keys[i] >>> 16) & 0xff;
var t = counts2[b];
counts2[b] = t + 1;
buffer[t] = a[i];
bufKeys[t] = keys[i];
}
for (i in 0...n) {
var b = (bufKeys[i] >>> 24) & 0xff;
var t = counts3[b];
counts3[b] = t + 1;
a[t] = buffer[i];
keys[t] = bufKeys[i];
}
}
}
private class RadixFrame {
public var offset:Vector<Int>;
public var nextOffset:Vector<Int>;
public var remaining:Vector<Int>;
public function new() {
offset = new Vector<Int>(256);
nextOffset = new Vector<Int>(256);
remaining = new Vector<Int>(256);
}
}
private class RadixScratch {
public var frames:Array<RadixFrame>;
public function new() {
frames = [];
}
public inline function frame(level:Int):RadixFrame {
var f = frames[level];
if (f == null) {
f = new RadixFrame();
frames[level] = f;
}
return f;
}
}

11
lib/radix/haxelib.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "radix",
"url": "https://github.com/skarupke/ska_sort",
"license": "BSL-1.0",
"tags": ["sort", "radix", "msd", "in-place"],
"description": "Haxe port : in-place MSD radix sort (American flag sort) for Leenkx SDK from Malte Skarupke's ska_sort with insertion sort fallback and stable LSD copy path",
"version": "1.0.0",
"classPath": "Sources/",
"releasenote": "Leenkx HAXE port",
"contributors": ["Riccardo Mazzucco"]
}