Skip to content

Commit

Permalink
[orx-shapes] Add Hilbert and Morton point ordering in 2d and 3d
Browse files Browse the repository at this point in the history
  • Loading branch information
edwinRNDR committed Jan 22, 2025
1 parent d1d3af7 commit 0e9e36a
Show file tree
Hide file tree
Showing 9 changed files with 548 additions and 0 deletions.
1 change: 1 addition & 0 deletions orx-shapes/src/commonMain/kotlin/Shapes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package org.openrndr.extra.shapes
26 changes: 26 additions & 0 deletions orx-shapes/src/commonMain/kotlin/ordering/Hilbert2d.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.openrndr.extra.shapes.ordering

@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert2dDecode16Bit(hilbert: UInt): UIntArray {
val morton = hilbertToMorton2d(hilbert, 10)
return morton2dDecode16Bit(morton)
}

@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert2dEncode16Bit(index1: UInt, index2: UInt): UInt {
val morton = morton2dEncode16Bit(index1, index2)
return mortonToHilbert2d(morton, 16)
}


@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert2dDecode5Bit(hilbert: UInt): UIntArray {
val morton = hilbertToMorton2d(hilbert, 5)
return morton2dDecode5Bit(morton)
}

@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert2dEncode5Bit(index1: UInt, index2: UInt): UInt {
val morton = morton2dEncode5Bit(index1, index2)
return mortonToHilbert3d(morton, 5)
}
26 changes: 26 additions & 0 deletions orx-shapes/src/commonMain/kotlin/ordering/Hilbert3d.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.openrndr.extra.shapes.ordering

@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert3dDecode10Bit(hilbert: UInt): UIntArray {
val morton = hilbertToMorton3d(hilbert, 10)
return morton3dDecode10Bit(morton)
}

@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert3dEncode10Bit(index1: UInt, index2: UInt, index3: UInt): UInt {
val morton = morton3dEncode10Bit(index1, index2, index3)
return mortonToHilbert3d(morton, 10)
}


@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert3dDecode5Bit(hilbert: UInt): UIntArray {
val morton = hilbertToMorton3d(hilbert, 5)
return morton3dDecode5Bit(morton)
}

@OptIn(ExperimentalUnsignedTypes::class)
fun hilbert3dEncode5Bit(index1: UInt, index2: UInt, index3: UInt): UInt {
val morton = morton3dEncode5Bit(index1, index2, index3)
return mortonToHilbert3d(morton, 5)
}
75 changes: 75 additions & 0 deletions orx-shapes/src/commonMain/kotlin/ordering/ListVector2Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.openrndr.extra.shapes.ordering

import org.openrndr.math.Vector2
import org.openrndr.math.map
import org.openrndr.shape.Rectangle
import org.openrndr.shape.bounds
import kotlin.math.max
import kotlin.math.pow

enum class Axis2DPermutation {
XY,
YX,
}

fun List<Vector2>.mortonOrder(
scale: Double = 1.0,
permutation: Axis2DPermutation = Axis2DPermutation.XY,
bits: Int = 16,
): List<Vector2> {
val bounds = this.bounds
val md = max(bounds.width, bounds.height) * scale
val rbounds = Rectangle(bounds.corner.x, bounds.corner.y, md, md)
val extend = 2.0.pow(bits.toDouble()) - 1.0
val inputPoints = map {
it.map(
rbounds.position(0.0, 0.0),
rbounds.position(1.0, 1.0),
Vector2(0.0, 0.0),
Vector2(extend, extend)
)
}
val mortonCodes = when (bits) {
5 -> when (permutation) {
Axis2DPermutation.XY -> inputPoints.map { morton2dEncode5Bit(it.x.toUInt(), it.y.toUInt()) }
Axis2DPermutation.YX -> inputPoints.map { morton2dEncode5Bit(it.y.toUInt(), it.x.toUInt()) }
}
16 -> when (permutation) {
Axis2DPermutation.XY -> inputPoints.map { morton2dEncode16Bit(it.x.toUInt(), it.y.toUInt()) }
Axis2DPermutation.YX -> inputPoints.map { morton2dEncode16Bit(it.y.toUInt(), it.x.toUInt()) }
}
else -> error("Only 5 and 16 bit modes are supported.")
}
return (this zip mortonCodes).sortedBy { it.second }.map { it.first }
}

fun List<Vector2>.hilbertOrder(
scale: Double = 1.0,
permutation: Axis2DPermutation = Axis2DPermutation.XY,
bits: Int = 16,
): List<Vector2> {
val bounds = this.bounds
val md = max(bounds.width, bounds.height) * scale
val rbounds = Rectangle(bounds.corner.x, bounds.corner.y, md, md)
val extend = 2.0.pow(bits.toDouble()) - 1.0
val inputPoints = map {
it.map(
rbounds.position(0.0, 0.0),
rbounds.position(1.0, 1.0),
Vector2(0.0, 0.0),
Vector2(extend, extend)
)
}
val hilbertCodes = when (bits) {
5 -> when (permutation) {
Axis2DPermutation.XY -> inputPoints.map { hilbert2dEncode5Bit(it.x.toUInt(), it.y.toUInt()) }
Axis2DPermutation.YX -> inputPoints.map { hilbert2dEncode5Bit(it.y.toUInt(), it.x.toUInt()) }
}
16 -> when (permutation) {
Axis2DPermutation.XY -> inputPoints.map { hilbert2dEncode16Bit(it.x.toUInt(), it.y.toUInt()) }
Axis2DPermutation.YX -> inputPoints.map { hilbert2dEncode16Bit(it.y.toUInt(), it.x.toUInt()) }
}
else -> error("Only 5 and 16 bit modes are supported.")
}
return (this zip hilbertCodes).sortedBy { it.second }.map { it.first }
}
92 changes: 92 additions & 0 deletions orx-shapes/src/commonMain/kotlin/ordering/ListVector3Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.openrndr.extra.shapes.ordering

import org.openrndr.math.Vector3
import org.openrndr.math.map
import org.openrndr.shape.Box
import org.openrndr.shape.bounds
import kotlin.math.max

enum class Axis3DPermutation {
XYZ,
XZY,
YXZ,
YZX,
ZXY,
ZYX
}

fun List<Vector3>.mortonOrder(
scale: Double = 1.0,
permutation: Axis3DPermutation = Axis3DPermutation.XYZ,
bits: Int = 10,
): List<Vector3> {
val bounds = this.bounds
val md = max(max(bounds.width, bounds.height), bounds.depth) * scale
val rbounds = Box(bounds.corner.x, bounds.corner.y, bounds.corner.z, md, md, md)
val inputPoints = map {
it.map(
rbounds.position(0.0, 0.0, 0.0),
rbounds.position(1.0, 1.0, 1.0),
Vector3(0.0, 0.0, 0.0),
Vector3(1023.0, 1023.0, 1023.0)
)
}
val mortonCodes = when (bits) {
5 -> when (permutation) {
Axis3DPermutation.XYZ -> inputPoints.map { morton3dEncode5Bit(it.x.toUInt(), it.y.toUInt(), it.z.toUInt()) }
Axis3DPermutation.XZY -> inputPoints.map { morton3dEncode5Bit(it.x.toUInt(), it.z.toUInt(), it.y.toUInt()) }
Axis3DPermutation.YXZ -> inputPoints.map { morton3dEncode5Bit(it.y.toUInt(), it.x.toUInt(), it.z.toUInt()) }
Axis3DPermutation.YZX -> inputPoints.map { morton3dEncode5Bit(it.y.toUInt(), it.z.toUInt(), it.x.toUInt()) }
Axis3DPermutation.ZXY -> inputPoints.map { morton3dEncode5Bit(it.z.toUInt(), it.x.toUInt(), it.y.toUInt()) }
Axis3DPermutation.ZYX -> inputPoints.map { morton3dEncode5Bit(it.z.toUInt(), it.y.toUInt(), it.x.toUInt()) }
}
10 -> when (permutation) {
Axis3DPermutation.XYZ -> inputPoints.map { morton3dEncode10Bit(it.x.toUInt(), it.y.toUInt(), it.z.toUInt()) }
Axis3DPermutation.XZY -> inputPoints.map { morton3dEncode10Bit(it.x.toUInt(), it.z.toUInt(), it.y.toUInt()) }
Axis3DPermutation.YXZ -> inputPoints.map { morton3dEncode10Bit(it.y.toUInt(), it.x.toUInt(), it.z.toUInt()) }
Axis3DPermutation.YZX -> inputPoints.map { morton3dEncode10Bit(it.y.toUInt(), it.z.toUInt(), it.x.toUInt()) }
Axis3DPermutation.ZXY -> inputPoints.map { morton3dEncode10Bit(it.z.toUInt(), it.x.toUInt(), it.y.toUInt()) }
Axis3DPermutation.ZYX -> inputPoints.map { morton3dEncode10Bit(it.z.toUInt(), it.y.toUInt(), it.x.toUInt()) }
}
else -> error("Only 5 and 10 bit modes are supported.")
}
return (this zip mortonCodes).sortedBy { it.second }.map { it.first }
}

fun List<Vector3>.hilbertOrder(
scale: Double = 1.0,
permutation: Axis3DPermutation = Axis3DPermutation.XYZ,
bits: Int
): List<Vector3> {
val bounds = this.bounds
val md = max(max(bounds.width, bounds.height), bounds.depth) * scale
val rbounds = Box(bounds.corner.x, bounds.corner.y, bounds.corner.z, md, md, md)
val inputPoints = map {
it.map(
rbounds.position(0.0, 0.0, 0.0),
rbounds.position(1.0, 1.0, 1.0),
Vector3(0.0, 0.0, 0.0),
Vector3(1023.0, 1023.0, 1023.0)
)
}
val hilbertCodes = when(bits) {
5 -> when (permutation) {
Axis3DPermutation.XYZ -> inputPoints.map { hilbert3dEncode5Bit(it.x.toUInt(), it.y.toUInt(), it.z.toUInt()) }
Axis3DPermutation.XZY -> inputPoints.map { hilbert3dEncode5Bit(it.x.toUInt(), it.z.toUInt(), it.y.toUInt()) }
Axis3DPermutation.YXZ -> inputPoints.map { hilbert3dEncode5Bit(it.y.toUInt(), it.x.toUInt(), it.z.toUInt()) }
Axis3DPermutation.YZX -> inputPoints.map { hilbert3dEncode5Bit(it.y.toUInt(), it.z.toUInt(), it.x.toUInt()) }
Axis3DPermutation.ZXY -> inputPoints.map { hilbert3dEncode5Bit(it.z.toUInt(), it.x.toUInt(), it.y.toUInt()) }
Axis3DPermutation.ZYX -> inputPoints.map { hilbert3dEncode5Bit(it.z.toUInt(), it.y.toUInt(), it.x.toUInt()) }
}
10 -> when (permutation) {
Axis3DPermutation.XYZ -> inputPoints.map { hilbert3dEncode10Bit(it.x.toUInt(), it.y.toUInt(), it.z.toUInt()) }
Axis3DPermutation.XZY -> inputPoints.map { hilbert3dEncode10Bit(it.x.toUInt(), it.z.toUInt(), it.y.toUInt()) }
Axis3DPermutation.YXZ -> inputPoints.map { hilbert3dEncode10Bit(it.y.toUInt(), it.x.toUInt(), it.z.toUInt()) }
Axis3DPermutation.YZX -> inputPoints.map { hilbert3dEncode10Bit(it.y.toUInt(), it.z.toUInt(), it.x.toUInt()) }
Axis3DPermutation.ZXY -> inputPoints.map { hilbert3dEncode10Bit(it.z.toUInt(), it.x.toUInt(), it.y.toUInt()) }
Axis3DPermutation.ZYX -> inputPoints.map { hilbert3dEncode10Bit(it.z.toUInt(), it.y.toUInt(), it.x.toUInt()) }
}
else -> error("Only 5 and 10 bit modes are supported.")
}
return (this zip hilbertCodes).sortedBy { it.second }.map { it.first }
}
88 changes: 88 additions & 0 deletions orx-shapes/src/commonMain/kotlin/ordering/Morton2d.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
@file:OptIn(ExperimentalUnsignedTypes::class)

package org.openrndr.extra.shapes.ordering

fun morton2dEncode5Bit(index1: UInt, index2: UInt): UInt {
// pack 2 5-bit indices into a 10-bit Morton code
var index1: UInt = index1
var index2: UInt = index2
index1 = index1 and 0x0000001fu
index2 = index2 and 0x0000001fu
index1 *= 0x01041041u
index2 *= 0x01041041u
index1 = index1 and 0x10204081u
index2 = index2 and 0x10204081u
index1 *= 0x00108421u
index2 *= 0x00108421u
index1 = index1 and 0x15500000u
index2 = index2 and 0x15500000u
return ((index1 shr 20) or (index2 shr 19))
}

fun morton2dDecode5Bit(morton: UInt): UIntArray { // unpack 2 5-bit indices from a 10-bit Morton code
var value1 = morton;
var value2 = (value1 shr 1)
value1 = value1 and 0x00000155u
value2 = value2 and 0x00000155u
value1 = value1 or (value1 shr 1)
value2 = value2 or (value2 shr 1)
value1 = value1 and 0x00000133u
value2 = value2 and 0x00000133u
value1 = value1 or (value1 shr 2)
value2 = value2 or (value2 shr 2)
value1 = value1 and 0x0000010fu
value2 = value2 and 0x0000010fu
value1 = value1 or (value1 shr 4)
value2 = value2 or (value2 shr 4)
value1 = value1 and 0x0000001fu
value2 = value2 and 0x0000001fu
return uintArrayOf(value1, value2)
}

fun morton2dEncode16Bit(index1: UInt, index2: UInt): UInt { // pack 2 16-bit indices into a 32-bit Morton code
var index1: UInt = index1
var index2: UInt = index2
index1 = index1 and 0x0000ffffu
index2 = index2 and 0x0000ffffu
index1 = index1 or (index1 shl 8)
index2 = index2 or (index2 shl 8)
index1 = index1 and 0x00ff00ffu
index2 = index2 and 0x00ff00ffu
index1 = index1 or (index1 shl 4)
index2 = index2 or (index2 shl 4)
index1 = index1 and 0x0f0f0f0fu
index2 = index2 and 0x0f0f0f0fu
index1 = index1 or (index1 shl 2)
index2 = index2 or (index2 shl 2)
index1 = index1 and 0x33333333u
index2 = index2 and 0x33333333u
index1 = index1 or (index1 shl 1)
index2 = index2 or (index2 shl 1)
index1 = index1 and 0x55555555u
index2 = index2 and 0x55555555u
return (index1 or (index2 shl 1))
}

fun morton2dDecode16Bit(morton: UInt): UIntArray { // unpack 2 16-bit indices from a 32-bit Morton code
var value1 = morton;
var value2 = (value1 shr 1);
value1 = value1 and 0x55555555u
value2 = value2 and 0x55555555u
value1 = value1 or (value1 shr 1)
value2 = value2 or (value2 shr 1)
value1 = value1 and 0x33333333u
value2 = value2 and 0x33333333u
value1 = value1 or (value1 shr 2)
value2 = value2 or (value2 shr 2)
value1 = value1 and 0x0f0f0f0fu
value2 = value2 and 0x0f0f0f0fu
value1 = value1 or (value1 shr 4)
value2 = value2 or (value2 shr 4)
value1 = value1 and 0x00ff00ffu
value2 = value2 and 0x00ff00ffu
value1 = value1 or (value1 shr 8)
value2 = value2 or (value2 shr 8)
value1 = value1 and 0x0000ffffu
value2 = value2 and 0x0000ffffu
return uintArrayOf(value1, value2)
}
Loading

0 comments on commit 0e9e36a

Please sign in to comment.