[orx-noise] Add hash functions

This commit is contained in:
Edwin Jakobs
2024-10-20 14:14:50 +02:00
parent 6e1e161726
commit fba1e5b61a
23 changed files with 473 additions and 63 deletions

View File

@@ -186,11 +186,16 @@ class ShapeNode(var shape: Shape) : CompositionNode() {
}
}
/**
* apply transforms of all ancestor nodes and return a new detached shape node with identity transform and transformed Shape
* @param composition use viewport transform
*/
fun flatten(): ShapeNode {
return ShapeNode(shape.transform(transform(this))).also {
fun flatten(composition: Composition? = null): ShapeNode {
val viewport = composition?.calculateViewportTransform() ?: Matrix44.IDENTITY
return ShapeNode(shape.transform(viewport * transform(this))).also {
it.id = id
it.parent = parent
it.style = effectiveStyle
@@ -293,10 +298,10 @@ data class CompositionDimensions(val x: Length, val y: Length, val width: Length
// but otherwise equality checks will never succeed
override fun equals(other: Any?): Boolean {
return other is CompositionDimensions
&& x.value == other.x.value
&& y.value == other.y.value
&& width.value == other.width.value
&& height.value == other.height.value
&& x.value == other.x.value
&& y.value == other.y.value
&& width.value == other.width.value
&& height.value == other.height.value
}
override fun hashCode(): Int {

View File

@@ -1,7 +1,7 @@
import org.openrndr.application
import org.openrndr.draw.loadFont
import org.openrndr.extra.envelopes.ADSRTracker
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.noise.shapes.uniform
import org.openrndr.shape.Rectangle
fun main() {
@@ -15,13 +15,13 @@ fun main() {
keyboard.keyDown.listen {
if (it.name == "t") {
val center = drawer.bounds.uniform(distanceToEdge = 30.0)
val center = drawer.bounds.offsetEdges(-30.0).uniform()
tracker.triggerOn(0) { time, value, position ->
drawer.circle(center, value * 100.0)
}
}
if (it.name == "r") {
val center = drawer.bounds.uniform(distanceToEdge = 30.0)
val center = drawer.bounds.offsetEdges(-30.0).uniform()
tracker.triggerOn(1) { time, value, position ->
val r = Rectangle.fromCenter(center, width = value * 100.0, height = value * 100.0)
drawer.rectangle(r)

View File

@@ -1,7 +1,7 @@
package typed
import org.openrndr.extra.expressions.typed.compileFunction1OrNull
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.noise.shapes.uniform
import org.openrndr.math.Vector2
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@@ -1,6 +1,6 @@
import org.openrndr.application
import org.openrndr.extra.hashgrid.filter
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.noise.shapes.uniform
import kotlin.random.Random
fun main() {

View File

@@ -1,7 +1,7 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.hashgrid.HashGrid
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.noise.shapes.uniform
import kotlin.random.Random
fun main() {

View File

@@ -0,0 +1,72 @@
package org.openrndr.extra.mesh.generators
import org.openrndr.extra.mesh.*
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
class MeshBuilder {
val vertexData = MutableVertexData()
val polygons = mutableListOf<IndexedPolygon>()
val mesh = MutableMeshData(vertexData, polygons)
}
fun box(): MeshData {
val positions = listOf(
Vector3(-0.5, -0.5, -0.5),
Vector3(0.5, -0.5, -0.5),
Vector3(-0.5, 0.5, -0.5),
Vector3(0.5, 0.5, -0.5),
Vector3(-0.5, -0.5, 0.5),
Vector3(0.5, -0.5, 0.5),
Vector3(-0.5, 0.5, 0.5),
Vector3(0.5, 0.5, 0.5),
)
val textureCoords = listOf(
Vector2(0.0, 0.0),
Vector2(1.0, 0.0),
Vector2(0.0, 1.0),
Vector2(1.0, 1.0),
)
val normals = listOf(
Vector3(-1.0, 0.0, 0.0),
Vector3(1.0, 0.0, 0.0),
Vector3(0.0, -1.0, 0.0),
Vector3(0.0, 1.0, 0.0),
Vector3(0.0, 0.0, -1.0),
Vector3(0.0, 0.0, 1.0)
)
val polygons = listOf(
// -x
IndexedPolygon(
positions = listOf(0, 2, 4, 6),
textureCoords = listOf(0, 1, 3, 2),
colors = emptyList(),
normals = listOf(0, 0, 0, 0),
tangents = listOf(5, 5, 5, 5),
bitangents = listOf(3, 3, 3, 3)
),
// +x
IndexedPolygon(
positions = listOf(1, 3, 5, 7),
textureCoords = listOf(0, 1, 3, 2),
colors = emptyList(),
normals = listOf(1, 1, 1, 1),
tangents = listOf(4, 4, 4, 4),
bitangents = listOf(3, 3, 3, 3)
)
)
return MeshData(
VertexData(
positions = positions,
textureCoords = textureCoords,
normals = normals,
tangents = normals,
bitangents = normals
), polygons
)
}

View File

@@ -13,6 +13,7 @@ import java.io.File
/**
* Demonstrate decal generator as an object slicer
* @see <img src="https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/decal-DemoDecal01Kt.png">
*/
fun main() {
application {

View File

@@ -15,6 +15,7 @@ import kotlin.math.PI
/**
* Demonstrate decal generation and rendering
* @see <img src="https://raw.githubusercontent.com/openrndr/orx/media/orx-mesh-generators/images/decal-DemoDecal02Kt.png">
*/
fun main() {
application {

View File

@@ -0,0 +1,39 @@
package tangents
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.mesh.toVertexBuffer
import org.openrndr.extra.meshgenerators.tangents.estimateTangents
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.math.Vector3
import java.io.File
fun main() = application {
program {
val obj = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData().triangulate()
val tangentObj = obj.estimateTangents()
val objVB = tangentObj.toVertexBuffer()
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = """
vec3 viewTangent = (u_viewNormalMatrix * u_modelNormalMatrix * vec4(va_tangent, 0.0)).xyz;
vec3 viewBitangent = (u_viewNormalMatrix * u_modelNormalMatrix * vec4(va_bitangent, 0.0)).xyz;
float c = cos(100.0*dot(v_worldPosition, va_normal)) * 0.5 + 0.5;
//x_fill.rgb = normalize(viewTangent)*0.5+0.5;
x_fill.rgb = vec3(c);
""".trimIndent()
}
drawer.vertexBuffer(objVB, DrawPrimitive.TRIANGLES)
}
}
}

View File

@@ -10,6 +10,7 @@ kotlin {
api(libs.openrndr.math)
api(libs.openrndr.shape)
api(project(":orx-mesh"))
implementation(project(":orx-noise"))
}
}

View File

@@ -3,6 +3,9 @@ package org.openrndr.extra.mesh.noise
import org.openrndr.extra.mesh.IIndexedPolygon
import org.openrndr.extra.mesh.IMeshData
import org.openrndr.extra.mesh.IVertexData
import org.openrndr.extra.noise.fhash1D
import org.openrndr.extra.noise.uhash11
import org.openrndr.extra.noise.uhash1D
import org.openrndr.math.Vector3
import kotlin.math.sqrt
import kotlin.random.Random
@@ -20,6 +23,23 @@ fun uniformBarycentric(random: Random = Random.Default): Vector3 {
return Vector3(b0, b1, 1.0 - b0 - b1)
}
/**
* Generate a uniformly distributed barycentric coordinate
* @param random a random number generator
*/
fun hashBarycentric(seed: Int, x: Int): Vector3 {
val u = fhash1D(seed, x)
val v = fhash1D(seed, u.toRawBits().toInt() - x)
val su0 = sqrt(u)
val b0 = 1.0 - su0
val b1 = v * su0
return Vector3(b0, b1, 1.0 - b0 - b1)
}
/**
* Generate a uniformly distributed point that lies inside this [IIndexedPolygon]
* @param vertexData vertex data used to resolve positions
@@ -33,6 +53,19 @@ fun IIndexedPolygon.uniform(vertexData: IVertexData, random: Random = Random.Def
return x[0] * b.x + x[1] * b.y + x[2] * b.z
}
/**
* Generate a uniformly distributed point that lies inside this [IIndexedPolygon]
* @param vertexData vertex data used to resolve positions
* @param random a random number generator
*/
fun IIndexedPolygon.hash(vertexData: IVertexData, seed:Int, x: Int): Vector3 {
require(positions.size == 3) { "polygon must be a triangle"}
val s = vertexData.positions.slice(positions)
val b = hashBarycentric(seed, x)
return s[0] * b.x + s[1] * b.y + s[2] * b.z
}
internal fun IIndexedPolygon.area(vertexData: IVertexData): Double {
require(positions.size == 3) { "polygon must be a triangle"}
val x = vertexData.positions.slice(positions)
@@ -63,3 +96,26 @@ fun IMeshData.uniform(count: Int, random: Random = Random.Default): List<Vector3
}
return result
}
/**
* Generate points on the surface described by the mesh data
*/
fun IMeshData.hash(count: Int, seed:Int, x: Int): List<Vector3> {
val triangulated = triangulate()
val result = mutableListOf<Vector3>()
val totalArea = triangulated.polygons.sumOf { it.area(vertexData) }
val randoms = (0 until count).map {
Pair(x + it, fhash1D(seed, x + it) * totalArea)
}.sortedBy { it.second }
var idx = 0
var sum = 0.0
for (t in triangulated.polygons) {
sum += t.area(vertexData)
while (idx <= randoms.lastIndex && sum > randoms[idx].second) {
result.add(t.hash(vertexData, seed xor 0x7f7f7f, randoms[idx].first))
idx++
}
}
return result
}

View File

@@ -0,0 +1,48 @@
import org.openrndr.application
import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.isolated
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.camera.Orbital
import org.openrndr.extra.mesh.noise.hash
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.mesh.noise.uniform
import org.openrndr.extra.meshgenerators.sphereMesh
import org.openrndr.math.Vector3
import java.io.File
import kotlin.math.cos
import kotlin.random.Random
/**
* Demonstrate uniform point on mesh generation using hash functions
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val mesh = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData()
val sphere = sphereMesh(radius = 0.01)
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
}
extend {
val points = mesh.hash((1000 + (cos(seconds)*0.5+0.5)*9000).toInt(), 808, (seconds*10000).toInt())
drawer.shadeStyle = shadeStyle {
fragmentTransform = "x_fill = vec4(v_viewNormal*0.5+0.5, 1.0);"
}
for (point in points) {
drawer.isolated {
drawer.translate(point)
drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES)
}
}
}
}
}
}

View File

@@ -1,35 +1,25 @@
package org.openrndr.extra.noise
import org.openrndr.extra.hashgrid.HashGrid
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.extra.noise.shapes.uniform
import org.openrndr.math.Vector2
import org.openrndr.shape.*
import kotlin.random.Random
/**
* Returns a random [Vector2] point located inside a [ShapeProvider] while
* maintaining a distance to the edge of the shape of [distanceToEdge] units.
* Generates specified amount of random points that lie inside the [Shape].
*
* @param pointCount The number of points to generate.
* @param random The [Random] number generator to use, defaults to [Random.Default].
*/
fun ShapeProvider.uniform(distanceToEdge: Double = 0.0, random: Random = Random.Default): Vector2 {
val shape = shape
require(!shape.empty)
var attempts = 0
val innerBounds = shape.bounds.offsetEdges(-distanceToEdge.coerceAtLeast(0.0))
return Vector2.uniformSequence(innerBounds, random).first {
attempts++
require(attempts < 100)
if (distanceToEdge == 0.0) {
shape.contains(it)
} else {
shape.contains(it) && shape.contours.minOf { c -> c.nearest(it).position.distanceTo(it) } > distanceToEdge
}
}
fun ShapeProvider.uniform(pointCount: Int, random: Random = Random.Default): List<Vector2> {
return shape.triangulation.uniform(pointCount, random)
}
/**
* Generate [sampleCount] uniformly distributed points inside the area of [ShapeProvider]
*/
fun ShapeProvider.uniform(sampleCount: Int, random: Random = Random.Default) : List<Vector2> = shape.triangulation.uniform(sampleCount, random)
fun ShapeProvider.hash(pointCount: Int, seed: Int, x: Int): List<Vector2> {
return shape.triangulation.hash(pointCount, seed, x)
}
/**
* Returns a list of pairs in which the first component is a radius and the

View File

@@ -1,26 +0,0 @@
package org.openrndr.extra.noise
import org.openrndr.math.Vector2
import org.openrndr.shape.Triangle
import kotlin.random.Random
/**
* Generate [count] uniform samples from a list of [Triangle]s
*/
fun List<Triangle>.uniform(count: Int, random: Random = Random.Default): List<Vector2> {
val totalArea = this.sumOf { it.area }
val randoms = (0 until count).map {
Double.uniform(0.0, totalArea, random = random)
}.sorted()
val result = mutableListOf<Vector2>()
var idx = 0
var sum = 0.0
for (t in this) {
sum += t.area
while (idx <= randoms.lastIndex && sum > randoms[idx]) {
result.add(t.randomPoint(random))
idx++
}
}
return result
}

View File

@@ -23,6 +23,12 @@ fun Double.Companion.uniform(
) =
(random.nextDouble() * (max - min)) + min
fun Double.Companion.hash(
seed: Int, x: Int,
min: Double = -1.0, max: Double = 1.0
) = fhash1D(seed, x) * (max - min) + min
fun Vector2.Companion.uniform(
min: Vector2 = -ONE, max: Vector2 = ONE,
random: Random = Random.Default
@@ -32,12 +38,28 @@ fun Vector2.Companion.uniform(
Double.uniform(min.y, max.y, random)
)
fun Vector2.Companion.hash(
seed: Int, x: Int,
min: Vector2 = -ONE, max: Vector2 = ONE
) =
Vector2(
Double.hash(seed, x, min.x, max.x),
Double.hash(seed xor 0x7f7f7f7f, x, min.y, max.y)
)
fun Vector2.Companion.uniform(
min: Double = -1.0, max: Double = 1.0,
random: Random = Random.Default
) =
Vector2.uniform(Vector2(min, min), Vector2(max, max), random)
fun Vector2.Companion.hash(
seed: Int, x: Int,
min: Double = -1.0, max: Double = 1.0,
) =
Vector2.hash(seed, x, Vector2(min, min), Vector2(max, max))
fun Vector2.Companion.uniform(
rect: Rectangle,
random: Random = Random.Default
@@ -78,7 +100,7 @@ fun Vector2.Companion.uniformRing(
val eps = 1E-6
if ( abs(innerRadius - outerRadius) < eps) {
if (abs(innerRadius - outerRadius) < eps) {
val angle = Double.uniform(-180.0, 180.0, random)
return Polar(angle, innerRadius).cartesian

View File

@@ -0,0 +1,28 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.uhash11
import org.openrndr.math.Vector3
import org.openrndr.shape.Box
import kotlin.random.Random
fun Box.uniform(random: Random = Random.Default): Vector3 {
val x = random.nextDouble() * width + corner.x
val y = random.nextDouble() * height + corner.y
val z = random.nextDouble() * depth + corner.z
return Vector3(x, y ,z)
}
fun Box.hash(seed: Int, x: Int): Vector3 {
val ux = uhash11(seed.toUInt() + uhash11(x.toUInt()))
val uy = uhash11(ux + x.toUInt())
val uz = uhash11(uy + x.toUInt())
val fx = ux.toDouble() / UInt.MAX_VALUE.toDouble()
val fy = uy.toDouble() / UInt.MAX_VALUE.toDouble()
val fz = uz.toDouble() / UInt.MAX_VALUE.toDouble()
val x = fx * width + corner.x
val y = fy * height + corner.y
val z = fz * depth + corner.z
return Vector3(x, y, z)
}

View File

@@ -0,0 +1,27 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.fhash1D
import org.openrndr.extra.noise.hash
import org.openrndr.math.Polar
import org.openrndr.math.Vector2
import org.openrndr.shape.Circle
import kotlin.math.sqrt
import kotlin.random.Random
/**
* Generate a uniformly distributed random point inside [Circle]
*/
fun Circle.hash(seed: Int, x: Int): Vector2 {
val r = radius * sqrt(fhash1D(seed, x))
val phi = 360.0 * fhash1D(seed xor 0x7f7f_7f7f, x)
return Polar(phi, r).cartesian + center
}
/**
* Generate a uniformly distributed random point inside [Circle]
*/
fun Circle.uniform(random: Random = Random.Default): Vector2 {
val r = radius * sqrt(random.nextDouble())
val phi = 360.0 * random.nextDouble()
return Polar(phi, r).cartesian + center
}

View File

@@ -0,0 +1,24 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.uhash11
import org.openrndr.math.Vector2
import org.openrndr.shape.Rectangle
import kotlin.random.Random
fun Rectangle.uniform(random: Random = Random.Default): Vector2 {
val x = random.nextDouble() * width + corner.x
val y = random.nextDouble() * height + corner.y
return Vector2(x, y)
}
fun Rectangle.hash(seed: Int, x: Int): Vector2 {
val ux = uhash11(seed.toUInt() + uhash11(x.toUInt()))
val uy = uhash11(ux + x.toUInt())
val fx = ux.toDouble() / UInt.MAX_VALUE.toDouble()
val fy = uy.toDouble() / UInt.MAX_VALUE.toDouble()
val x = fx * width + corner.x
val y = fy * height + corner.y
return Vector2(x, y)
}

View File

@@ -0,0 +1,58 @@
package org.openrndr.extra.noise.shapes
import org.openrndr.extra.noise.fhash1D
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2
import org.openrndr.shape.Triangle
import kotlin.random.Random
/**
* Generate [count] uniform samples from a list of [Triangle]s
*/
fun List<Triangle>.uniform(count: Int, random: Random = Random.Default): List<Vector2> {
val totalArea = this.sumOf { it.area }
val randoms = (0 until count).map {
Double.uniform(0.0, totalArea, random = random)
}.sorted()
val result = mutableListOf<Vector2>()
var idx = 0
var sum = 0.0
for (t in this) {
sum += t.area
while (idx <= randoms.lastIndex && sum > randoms[idx]) {
result.add(t.uniform(random))
idx++
}
}
return result
}
fun List<Triangle>.hash(count: Int, seed: Int = 0, x: Int = 0): List<Vector2> {
val totalArea = this.sumOf { it.area }
val randoms = (0 until count).map {
Pair(x + it, fhash1D(seed, x + it) * totalArea)
}.sortedBy { it.second }
val result = mutableListOf<Vector2>()
var idx = 0
var sum = 0.0
for (t in this) {
sum += t.area
while (idx <= randoms.lastIndex && sum > randoms[idx].second) {
result.add(t.hash(seed, randoms[idx].first))
idx++
}
}
return result
}
/** Generates a random point that lies inside the [Triangle]. */
fun Triangle.uniform(random: Random = Random.Default): Vector2 {
return position(random.nextDouble(), random.nextDouble())
}
fun Triangle.hash(seed: Int, x: Int): Vector2 {
val u = fhash1D(seed, x)
val v = fhash1D(seed, u.toRawBits().toInt() + x)
return position(u, v)
}

View File

@@ -0,0 +1,33 @@
import org.openrndr.application
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.extra.noise.shapes.uniform
import org.openrndr.shape.Circle
import kotlin.random.Random
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
val b = drawer.bounds
val b0 = b.sub(0.0, 0.0, 0.5, 1.0).offsetEdges(-10.0)
val b1 = b.sub(0.5, 0.0, 1.0, 1.0).offsetEdges(-10.0)
val c0 = Circle(b0.center, b0.width/2.0)
val c1 = Circle(b1.center, b1.width/2.0)
val r = Random(0)
for (i in 0 until 2000) {
drawer.circle(c0.uniform(r), 2.0)
drawer.circle(c1.hash(909, i),2.0)
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
import org.openrndr.application
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.extra.noise.shapes.uniform
import kotlin.random.Random
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
val b = drawer.bounds
val b0 = b.sub(0.0, 0.0, 0.5, 1.0).offsetEdges(-10.0)
val b1 = b.sub(0.5, 0.0, 1.0, 1.0).offsetEdges(-10.0)
val r = Random(0)
for (i in 0 until 20000) {
drawer.circle(b0.uniform(r), 2.0)
drawer.circle(b1.hash(909, i),2.0)
}
}
}
}
}

View File

@@ -1,8 +1,7 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.noise.shapes.hash
import org.openrndr.shape.Triangle
import kotlin.random.Random
/**
* Demonstrate the generation of uniformly distributed points inside a list of triangles
@@ -17,8 +16,10 @@ fun main() {
program {
val r = drawer.bounds.offsetEdges(-100.0)
val triangle = Triangle(r.position(0.5, 0.0), r.position(0.0, 1.0), r.position(1.0, 1.0))
val pts = listOf(triangle).uniform(1000, Random(0))
//val pts = listOf(triangle).uniform(1000, Random(0))
extend {
val pts = listOf(triangle).hash(1000, 0, (seconds*500.0).toInt())
drawer.clear(ColorRGBa.PINK)
drawer.stroke = null
drawer.contour(triangle.contour)

View File

@@ -6,6 +6,7 @@ import org.openrndr.extra.shapes.primitives.intersection
/**
* Demonstrate rectangle-rectangle intersection
* @see <img src="https://raw.githubusercontent.com/openrndr/orx/media/orx-shapes/images/primitives-DemoRectangleIntersection01Kt.png">
*/
fun main() {
application {