From fba1e5b61a8c49f7c225bfe7f8eac9df2a461c71 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Sun, 20 Oct 2024 14:14:50 +0200 Subject: [PATCH] [orx-noise] Add hash functions --- .../src/commonMain/kotlin/Composition.kt | 17 +++-- .../src/jvmDemo/kotlin/DemoADSRTracker02.kt | 6 +- .../typed/TestTypedCompiledExpression.kt | 2 +- .../src/jvmDemo/kotlin/DemoFilter01.kt | 2 +- .../src/jvmDemo/kotlin/DemoHashGrid01.kt | 2 +- .../src/commonMain/kotlin/mesh/Box.kt | 72 +++++++++++++++++++ .../src/jvmDemo/kotlin/decal/DemoDecal01.kt | 1 + .../src/jvmDemo/kotlin/decal/DemoDecal02.kt | 1 + .../jvmDemo/kotlin/tangents/DemoTangents01.kt | 39 ++++++++++ orx-mesh-noise/build.gradle.kts | 1 + .../src/commonMain/kotlin/MeshNoise.kt | 56 +++++++++++++++ .../src/jvmDemo/kotlin/DemoMeshNoise02.kt | 48 +++++++++++++ orx-noise/src/commonMain/kotlin/ShapeNoise.kt | 32 +++------ .../src/commonMain/kotlin/TriangleNoise.kt | 26 ------- .../src/commonMain/kotlin/UniformRandom.kt | 24 ++++++- orx-noise/src/commonMain/kotlin/shapes/Box.kt | 28 ++++++++ .../src/commonMain/kotlin/shapes/Circle.kt | 27 +++++++ .../src/commonMain/kotlin/shapes/Rectangle.kt | 24 +++++++ .../src/commonMain/kotlin/shapes/Triangle.kt | 58 +++++++++++++++ .../src/jvmDemo/kotlin/DemoCircleHash01.kt | 33 +++++++++ .../src/jvmDemo/kotlin/DemoRectangleHash01.kt | 29 ++++++++ .../src/jvmDemo/kotlin/DemoTriangleNoise01.kt | 7 +- .../primitives/DemoRectangleIntersection01.kt | 1 + 23 files changed, 473 insertions(+), 63 deletions(-) create mode 100644 orx-mesh-generators/src/commonMain/kotlin/mesh/Box.kt create mode 100644 orx-mesh-generators/src/jvmDemo/kotlin/tangents/DemoTangents01.kt create mode 100644 orx-mesh-noise/src/jvmDemo/kotlin/DemoMeshNoise02.kt delete mode 100644 orx-noise/src/commonMain/kotlin/TriangleNoise.kt create mode 100644 orx-noise/src/commonMain/kotlin/shapes/Box.kt create mode 100644 orx-noise/src/commonMain/kotlin/shapes/Circle.kt create mode 100644 orx-noise/src/commonMain/kotlin/shapes/Rectangle.kt create mode 100644 orx-noise/src/commonMain/kotlin/shapes/Triangle.kt create mode 100644 orx-noise/src/jvmDemo/kotlin/DemoCircleHash01.kt create mode 100644 orx-noise/src/jvmDemo/kotlin/DemoRectangleHash01.kt diff --git a/orx-composition/src/commonMain/kotlin/Composition.kt b/orx-composition/src/commonMain/kotlin/Composition.kt index 122f42a38..0f52bf682 100644 --- a/orx-composition/src/commonMain/kotlin/Composition.kt +++ b/orx-composition/src/commonMain/kotlin/Composition.kt @@ -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 { diff --git a/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt b/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt index 21d250f65..ee07cb169 100644 --- a/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt +++ b/orx-envelopes/src/jvmDemo/kotlin/DemoADSRTracker02.kt @@ -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) diff --git a/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedCompiledExpression.kt b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedCompiledExpression.kt index 919048429..29548c26e 100644 --- a/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedCompiledExpression.kt +++ b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedCompiledExpression.kt @@ -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 diff --git a/orx-hash-grid/src/jvmDemo/kotlin/DemoFilter01.kt b/orx-hash-grid/src/jvmDemo/kotlin/DemoFilter01.kt index 59acc24d3..9c39067a6 100644 --- a/orx-hash-grid/src/jvmDemo/kotlin/DemoFilter01.kt +++ b/orx-hash-grid/src/jvmDemo/kotlin/DemoFilter01.kt @@ -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() { diff --git a/orx-hash-grid/src/jvmDemo/kotlin/DemoHashGrid01.kt b/orx-hash-grid/src/jvmDemo/kotlin/DemoHashGrid01.kt index d6f55f720..598f4d943 100644 --- a/orx-hash-grid/src/jvmDemo/kotlin/DemoHashGrid01.kt +++ b/orx-hash-grid/src/jvmDemo/kotlin/DemoHashGrid01.kt @@ -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() { diff --git a/orx-mesh-generators/src/commonMain/kotlin/mesh/Box.kt b/orx-mesh-generators/src/commonMain/kotlin/mesh/Box.kt new file mode 100644 index 000000000..e5539c72b --- /dev/null +++ b/orx-mesh-generators/src/commonMain/kotlin/mesh/Box.kt @@ -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() + 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 + ) +} \ No newline at end of file diff --git a/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal01.kt b/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal01.kt index 11f105278..c98ca3c12 100644 --- a/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal01.kt +++ b/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal01.kt @@ -13,6 +13,7 @@ import java.io.File /** * Demonstrate decal generator as an object slicer + * @see */ fun main() { application { diff --git a/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal02.kt b/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal02.kt index 32c73a4a4..e0bf39ffd 100644 --- a/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal02.kt +++ b/orx-mesh-generators/src/jvmDemo/kotlin/decal/DemoDecal02.kt @@ -15,6 +15,7 @@ import kotlin.math.PI /** * Demonstrate decal generation and rendering + * @see */ fun main() { application { diff --git a/orx-mesh-generators/src/jvmDemo/kotlin/tangents/DemoTangents01.kt b/orx-mesh-generators/src/jvmDemo/kotlin/tangents/DemoTangents01.kt new file mode 100644 index 000000000..cde4c609d --- /dev/null +++ b/orx-mesh-generators/src/jvmDemo/kotlin/tangents/DemoTangents01.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/orx-mesh-noise/build.gradle.kts b/orx-mesh-noise/build.gradle.kts index 33f235401..bb148adc6 100644 --- a/orx-mesh-noise/build.gradle.kts +++ b/orx-mesh-noise/build.gradle.kts @@ -10,6 +10,7 @@ kotlin { api(libs.openrndr.math) api(libs.openrndr.shape) api(project(":orx-mesh")) + implementation(project(":orx-noise")) } } diff --git a/orx-mesh-noise/src/commonMain/kotlin/MeshNoise.kt b/orx-mesh-noise/src/commonMain/kotlin/MeshNoise.kt index 7c87c261e..cb35bd8f4 100644 --- a/orx-mesh-noise/src/commonMain/kotlin/MeshNoise.kt +++ b/orx-mesh-noise/src/commonMain/kotlin/MeshNoise.kt @@ -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) @@ -62,4 +95,27 @@ fun IMeshData.uniform(count: Int, random: Random = Random.Default): List { + val triangulated = triangulate() + val result = mutableListOf() + 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 } \ No newline at end of file diff --git a/orx-mesh-noise/src/jvmDemo/kotlin/DemoMeshNoise02.kt b/orx-mesh-noise/src/jvmDemo/kotlin/DemoMeshNoise02.kt new file mode 100644 index 000000000..f4aa5c1b3 --- /dev/null +++ b/orx-mesh-noise/src/jvmDemo/kotlin/DemoMeshNoise02.kt @@ -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) + } + } + } + } + } +} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/ShapeNoise.kt b/orx-noise/src/commonMain/kotlin/ShapeNoise.kt index 781224c6e..f7b21f77e 100644 --- a/orx-noise/src/commonMain/kotlin/ShapeNoise.kt +++ b/orx-noise/src/commonMain/kotlin/ShapeNoise.kt @@ -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 { + 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 = shape.triangulation.uniform(sampleCount, random) - +fun ShapeProvider.hash(pointCount: Int, seed: Int, x: Int): List { + return shape.triangulation.hash(pointCount, seed, x) +} /** * Returns a list of pairs in which the first component is a radius and the diff --git a/orx-noise/src/commonMain/kotlin/TriangleNoise.kt b/orx-noise/src/commonMain/kotlin/TriangleNoise.kt deleted file mode 100644 index a35945a12..000000000 --- a/orx-noise/src/commonMain/kotlin/TriangleNoise.kt +++ /dev/null @@ -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.uniform(count: Int, random: Random = Random.Default): List { - val totalArea = this.sumOf { it.area } - val randoms = (0 until count).map { - Double.uniform(0.0, totalArea, random = random) - }.sorted() - val result = mutableListOf() - 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 -} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/UniformRandom.kt b/orx-noise/src/commonMain/kotlin/UniformRandom.kt index 6c79fc848..5747a02d4 100644 --- a/orx-noise/src/commonMain/kotlin/UniformRandom.kt +++ b/orx-noise/src/commonMain/kotlin/UniformRandom.kt @@ -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 diff --git a/orx-noise/src/commonMain/kotlin/shapes/Box.kt b/orx-noise/src/commonMain/kotlin/shapes/Box.kt new file mode 100644 index 000000000..44eaa9f7b --- /dev/null +++ b/orx-noise/src/commonMain/kotlin/shapes/Box.kt @@ -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) +} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/shapes/Circle.kt b/orx-noise/src/commonMain/kotlin/shapes/Circle.kt new file mode 100644 index 000000000..20c817b08 --- /dev/null +++ b/orx-noise/src/commonMain/kotlin/shapes/Circle.kt @@ -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 +} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/shapes/Rectangle.kt b/orx-noise/src/commonMain/kotlin/shapes/Rectangle.kt new file mode 100644 index 000000000..fb33cb067 --- /dev/null +++ b/orx-noise/src/commonMain/kotlin/shapes/Rectangle.kt @@ -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) +} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/shapes/Triangle.kt b/orx-noise/src/commonMain/kotlin/shapes/Triangle.kt new file mode 100644 index 000000000..7f317ebfd --- /dev/null +++ b/orx-noise/src/commonMain/kotlin/shapes/Triangle.kt @@ -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.uniform(count: Int, random: Random = Random.Default): List { + val totalArea = this.sumOf { it.area } + val randoms = (0 until count).map { + Double.uniform(0.0, totalArea, random = random) + }.sorted() + val result = mutableListOf() + 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.hash(count: Int, seed: Int = 0, x: Int = 0): List { + 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() + 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) +} diff --git a/orx-noise/src/jvmDemo/kotlin/DemoCircleHash01.kt b/orx-noise/src/jvmDemo/kotlin/DemoCircleHash01.kt new file mode 100644 index 000000000..8610bcdb9 --- /dev/null +++ b/orx-noise/src/jvmDemo/kotlin/DemoCircleHash01.kt @@ -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) + + } + + } + + } + } +} \ No newline at end of file diff --git a/orx-noise/src/jvmDemo/kotlin/DemoRectangleHash01.kt b/orx-noise/src/jvmDemo/kotlin/DemoRectangleHash01.kt new file mode 100644 index 000000000..eb53df289 --- /dev/null +++ b/orx-noise/src/jvmDemo/kotlin/DemoRectangleHash01.kt @@ -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) + + } + + } + + } + } +} \ No newline at end of file diff --git a/orx-noise/src/jvmDemo/kotlin/DemoTriangleNoise01.kt b/orx-noise/src/jvmDemo/kotlin/DemoTriangleNoise01.kt index b0f5c92ee..7a3d5f0cb 100644 --- a/orx-noise/src/jvmDemo/kotlin/DemoTriangleNoise01.kt +++ b/orx-noise/src/jvmDemo/kotlin/DemoTriangleNoise01.kt @@ -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) diff --git a/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIntersection01.kt b/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIntersection01.kt index 38ada841e..8328ed5a5 100644 --- a/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIntersection01.kt +++ b/orx-shapes/src/jvmDemo/kotlin/primitives/DemoRectangleIntersection01.kt @@ -6,6 +6,7 @@ import org.openrndr.extra.shapes.primitives.intersection /** * Demonstrate rectangle-rectangle intersection + * @see */ fun main() { application {