[orx-mesh-noise] Refactor barycentric and mesh sampling logic into specialized files.

This commit is contained in:
Edwin Jakobs
2025-01-25 13:57:39 +01:00
parent e32fcba77b
commit c1d2ea4ecc
9 changed files with 562 additions and 252 deletions

View File

@@ -0,0 +1,65 @@
package org.openrndr.extra.mesh.noise
import org.openrndr.extra.noise.fhash1D
import org.openrndr.math.Vector3
import kotlin.math.sqrt
import kotlin.random.Random
typealias Barycentric = Vector3
/**
* Generates a 3D vector with components representing uniform barycentric coordinates
* over a standard triangle. The barycentric coordinates are computed based on two
* input parameters, `u` and `v`, which are random values typically ranging between 0 and 1.
*
* @param u A random value used to compute the barycentric coordinate.
* @param v A random value used to compute the barycentric coordinate.
* @return A [Vector3] instance representing uniform barycentric coordinates, where each component
* corresponds to the weight of a vertex in the triangle.
*/
fun uniformBarycentric(u: Double, v: Double): Barycentric {
val su0 = sqrt(u)
val b0 = 1.0 - su0
val b1 = v * su0
return Barycentric(b0, b1, 1.0 - b0 - b1)
}
/**
* Adjusts a barycentric coordinate based on weights for each component and normalizes the result.
*
* @param barycentric The original barycentric coordinate to be adjusted.
* @param weight0 The weight for the first component of the barycentric coordinate.
* @param weight1 The weight for the second component of the barycentric coordinate.
* @param weight2 The weight for the third component of the barycentric coordinate.
* @return A new barycentric coordinate with components adjusted by their corresponding weights and normalized.
*/
fun weightBarycentric(barycentric: Barycentric, weight0: Double, weight1: Double, weight2: Double): Barycentric {
val b0 = barycentric.x * weight0
val b1 = barycentric.y * weight1
val b2 = barycentric.z * weight2
val totalWeight = b0 + b1 + b2
return Barycentric(b0 / totalWeight, b1 / totalWeight, b2 / totalWeight)
}
/**
* Computes a barycentric coordinate vector derived from hashing operations.
*
* This function generates a barycentric coordinate for a point within a triangle
* by utilizing hash-based techniques to ensure uniform randomization. The result
* is expressed as a 3D vector where the components represent the barycentric
* weights.
*
* @param seed An integer seed used for the hash function to ensure reproducibility.
* @param x An integer value that contributes to the hash-based computation.
* @return A `Vector3` instance containing the computed barycentric coordinates.
*/
fun hashBarycentric(seed: Int, x: Int): Barycentric {
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)
}

View File

@@ -0,0 +1,24 @@
package org.openrndr.extra.mesh.noise
import org.openrndr.extra.mesh.IIndexedPolygon
import org.openrndr.extra.mesh.IVertexData
/**
* Calculates the area of the triangular polygon.
*
* The method assumes that the polygon is a triangle and computes its area
* using the cross product formula. The computed area is a positive value as it
* represents the absolute area of the triangle.
*
* @param vertexData the vertex data containing positional information of the polygon vertices
* @return the area of the triangle as a Double
* @throws IllegalArgumentException if the polygon is not a triangle (i.e., does not have exactly 3 vertices)
*/
internal fun IIndexedPolygon.area(vertexData: IVertexData): Double {
require(positions.size == 3) { "polygon must be a triangle" }
val x = vertexData.positions.slice(positions)
val u = x[1] - x[0]
val v = x[2] - x[0]
return u.areaBetween(v) / 2.0
}

View File

@@ -0,0 +1,273 @@
package org.openrndr.extra.mesh.noise
import org.openrndr.extra.mesh.*
import org.openrndr.extra.noise.fhash1D
import org.openrndr.extra.noise.hammersley.hammersley2D
import org.openrndr.extra.noise.rsequence.rSeq2D
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import kotlin.random.Random
/**
* Represents a type alias for a function that processes data associated with a polygon and vertex data,
* producing a result of type [T].
*
* This function takes the following parameters:
* - `polygon`: An instance of [IIndexedPolygon] representing the polygon in 3D space.
* - `vertexData`: An instance of [IVertexData] containing the associated vertex attributes,
* such as positions, texture coordinates, normals, etc.
* - `barycentric`: A [Barycentric] representation defining the barycentric coordinates for interpolation.
*/
typealias SampleFunction<T> = (polygon: IIndexedPolygon, vertexData: IVertexData, barycentric: Barycentric) -> T
/**
* A lambda function that computes the 3D position within an indexed polygon given vertex data
* and barycentric coordinates.
*
* This function utilizes the `position` method of an `IIndexedPolygon` instance to calculate
* a specific point in the 3D space based on the barycentric weights and vertex positions.
*
* @property positionSampleFunction A functional instance of type `SampleFunction` where:
* - `t` is the `IIndexedPolygon`, representing the polygon.
* - `v` is the `IVertexData`, representing the vertex attributes.
* - `b` is the barycentric coordinate used for interpolation.
* The lambda computes the resulting position by invoking `t.position(v, b)`.
*/
val positionSampleFunction: SampleFunction<Vector3> =
{ t: IIndexedPolygon, v: IVertexData, b: Barycentric -> t.position(v, b) }
/**
* A lambda function that extracts a [Point] from a given [IIndexedPolygon], [IVertexData], and [Barycentric] coordinate.
*
* This function utilizes the `point` method of [IIndexedPolygon] to compute the [Point] in 3D space.
*
* @property t An instance of [IIndexedPolygon] representing the polygon from which the point is derived.
* @property v An instance of [IVertexData] representing the vertex data associated with the polygon.
* @property b An instance of [Barycentric] representing the barycentric coordinates used to interpolate the point.
* @return A [Point] representing the interpolated 3D position and attributes at the specified barycentric coordinate.
*/
val pointSampleFunction: SampleFunction<Point> = { t: IIndexedPolygon, v: IVertexData, b: Barycentric -> t.point(v, b) }
/**
* Generates a list of uniformly distributed points on the surface of the given mesh.
*
* The method uses triangulation and computes areas of triangular polygons to ensure
* uniform distribution of points across the surface.
*
* @param count the number of points to generate
* @param random a random number generator instance, defaulting to [Random.Default]
* @return a list of [Vector3] points uniformly distributed across the mesh surface
*/
fun IMeshData.uniform(count: Int, random: Random = Random.Default): List<Vector3> {
return nonuniformEx(
(0 until count).map { random.nextDouble() },
(0 until count).map { Vector2(random.nextDouble(), random.nextDouble()) },
identityWeightFunction,
positionSampleFunction
)
}
/**
* Generates a uniformly distributed set of points over the surface of the mesh.
*
* The method creates a specific number of points distributed across the mesh,
* based on the given count, using random values to determine their positions.
*
* @param count The number of points to generate and distribute across the mesh.
* @param random The random number generator used for creating the distribution of points (default: [Random.Default]).
* @return A list of [Point] instances representing the uniformly distributed points across the mesh.
*/
fun IMeshData.uniformPoints(count: Int, random: Random = Random.Default): List<Point> {
return nonuniformEx(
(0 until count).map { random.nextDouble() },
(0 until count).map { Vector2(random.nextDouble(), random.nextDouble()) },
identityWeightFunction,
pointSampleFunction
)
}
/**
* Generates a list of points within the mesh using a non-uniform distribution.
* The points are sampled according to a provided weight function, where each point
* is influenced by the weights of the vertices of the triangulated polygons.
*
* @param count the number of points to generate within the mesh
* @param random the random number generator used for sampling; defaults to [Random.Default]
* @param weightFunction a function that calculates the weight for a given vertex of a polygon
* @return a list of [Vector3] instances representing the sampled points within the mesh
*/
fun IMeshData.nonuniform(count: Int, random: Random = Random.Default, weightFunction: WeightFunction): List<Vector3> {
return nonuniformEx(
(0 until count).map { random.nextDouble() },
(0 until count).map { Vector2(random.nextDouble(), random.nextDouble()) },
weightFunction,
positionSampleFunction
)
}
/**
* Generates a list of non-uniformly distributed points on the mesh based on a specified weight function.
*
* @param count The number of points to generate.
* @param random A [Random] instance used to generate random values for the distribution. Defaults to [Random.Default].
* @param weightFunction A function used to calculate weighting for vertices in a triangle, affecting the distribution of points.
* @return A list of [Point] instances representing non-uniformly distributed points on the mesh.
*/
fun IMeshData.nonuniformPoints(
count: Int,
random: Random = Random.Default,
weightFunction: WeightFunction
): List<Point> {
return nonuniformEx(
(0 until count).map { random.nextDouble() },
(0 until count).map { Vector2(random.nextDouble(), random.nextDouble()) },
weightFunction,
pointSampleFunction
)
}
/**
* Generates a list of 3D points distributed on a mesh surface using a nonuniform sampling strategy
* with Hammersley sequence and a custom weight function.
*
* The Hammersley sequence is used to generate an initial set of 2D points that are mapped to the
* surface of the mesh based on the provided weight function. Random numbers are also utilized
* to introduce variability in the sampling process.
*
* @param count The number of points to sample on the mesh surface.
* @param random An optional random number generator used for shuffling the Hammersley points
* and generating random values for the sampling. Defaults to `Random.Default`.
* @param weightFunction A function that determines the weight of individual components of the mesh,
* influencing the distribution of generated points.
* @return A list of 3D points (`Vector3`) distributed across the mesh surface based on the provided
* weight function and sampling method.
*/
fun IMeshData.nonuniformHammersley(
count: Int,
shuffle: Boolean = true,
random: Random = Random.Default,
weightFunction: WeightFunction
): List<Vector3> {
return nonuniformEx(
(0 until count).map { random.nextDouble() },
(0 until count).map { hammersley2D(it, count) }.let { if (shuffle) it.shuffled(random) else it },
weightFunction,
positionSampleFunction
)
}
/**
* Generates a non-uniform sequence of 3D points based on the provided weight function, using
* a combination of randomized values and the R2 low-discrepancy quasirandom sequence.
*
* @param count The number of points to generate.
* @param shuffle Determines whether the resulting R2 sequence should be shuffled. Default is true.
* @param random The source of randomness used for shuffling and random value generation. Default is [Random.Default].
* @param weightFunction A function that computes the weight for a given vertex data and indexed polygon. This influences
* the distribution of points across the mesh.
* @return A list of [Vector3] objects representing the generated sequence of 3D points.
*/
fun IMeshData.nonuniformRSeq(
count: Int,
shuffle: Boolean = true,
random: Random = Random.Default,
weightFunction: WeightFunction
): List<Vector3> {
return nonuniformEx(
(0 until count).map { random.nextDouble() },
(0 until count).map { rSeq2D(it) }.let { if (shuffle) it.shuffled(random) else it },
weightFunction,
positionSampleFunction
)
}
/**
* Generates a list of samples by distributing random points across the mesh
* using weighted areas of triangles and a provided sample function.
*
* @param T The type of the sample produced by the sample function.
* @param randomsUnsorted A list of random values in the range [0, 1), which are used to
* distribute points across the mesh based on weighted triangle areas.
* @param randomPoints A list of random 2D points in barycentric coordinates, used to
* determine the position of generated samples within a triangle.
* @param weightFunction A function that calculates weights for vertices in a triangle,
* modifying the distribution of random points based on these weights.
* @param sampleFunction A function that generates a sample of type [T] given a triangle
* (as an [IIndexedPolygon]), associated vertex data, and barycentric coordinates.
* @return A list of samples of type [T], generated by applying the sample function
* to distributed random points across the mesh.
*/
fun <T> IMeshData.nonuniformEx(
randomValuesUnsorted: List<Double>,
randomPoints: List<Vector2>,
weightFunction: WeightFunction,
sampleFunction: (IIndexedPolygon, IVertexData, Barycentric) -> T
): List<T> {
val triangulated = triangulate()
val defaultWeights = doubleArrayOf(1.0, 1.0, 1.0)
val result = ArrayList<T>(randomPoints.size)
// Helper function to calculate triangle weights
fun calculateWeights(triangle: IIndexedPolygon): DoubleArray =
if (weightFunction !== identityWeightFunction)
doubleArrayOf(
weightFunction(vertexData, triangle, 0),
weightFunction(vertexData, triangle, 1),
weightFunction(vertexData, triangle, 2)
)
else defaultWeights
// Helper function to calculate total area
fun calculateTotalArea(): Double =
triangulated.polygons.sumOf { triangle ->
val weights = calculateWeights(triangle)
triangle.area(vertexData) * weights.sum()
}
val totalArea = calculateTotalArea()
val randomValues = randomValuesUnsorted.sorted().map { it * totalArea }
var sum = 0.0
var idx = 0
// Iterate through each triangle in the triangulated mesh
for (triangle in triangulated.polygons) {
val weights = calculateWeights(triangle)
sum += triangle.area(vertexData) * weights.sum()
// Distribute random points across the triangle
while (idx <= randomValues.lastIndex && sum > randomValues[idx]) {
val barycentricCoords = if (weightFunction !== identityWeightFunction) {
weightBarycentric(
uniformBarycentric(randomPoints[idx].x, randomPoints[idx].y),
weights[0], weights[1], weights[2]
)
} else {
uniformBarycentric(randomPoints[idx].x, randomPoints[idx].y)
}
result.add(sampleFunction(triangle, vertexData, barycentricCoords))
idx++
}
}
return result
}
/**
* Generate points on the surface described by the mesh data
*/
fun IMeshData.hash(count: Int, seed: Int, x: Int): List<Vector3> {
val randoms = (0 until count).map {
fhash1D(seed, x + it)
}
val randomPoints = (0 until count).map {
val x = x + it
val u = fhash1D(seed xor 0x7f7f7f7f, x)
val v = fhash1D(seed xor 0x7f7f7f7f, u.toRawBits().toInt() - x)
Vector2(u, v)
}
return nonuniformEx(
randoms, randomPoints, identityWeightFunction, positionSampleFunction
)
}

View File

@@ -1,201 +0,0 @@
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
/**
* Generate a uniformly distributed barycentric coordinate
* @param random a random number generator
*/
fun uniformBarycentric(random: Random = Random.Default): Vector3 {
val u = random.nextDouble()
val v = random.nextDouble()
val su0 = sqrt(u)
val b0 = 1.0 - su0
val b1 = v * su0
return Vector3(b0, b1, 1.0 - b0 - b1)
}
/**
* Generate a non-uniformly distributed barycentric coordinate
* @param random a random number generator
*/
fun nonUniformBarycentric(weight0: Double, weight1: Double, weight2: Double, random: Random = Random.Default): Vector3 {
val b = uniformBarycentric()
var b0 = b.x / weight0
var b1 = b.y / weight1
var b2 = b.z / weight2
val b = uniformBarycentric(random)
var b0 = b.x * weight0
var b1 = b.y * weight1
var b2 = b.z * weight2
val totalWeight = b0 + b1 + b2
b0 /= totalWeight
b1 /= totalWeight
b2 /= totalWeight
return Vector3(b0, b1, b2)
}
/**
* 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
* @param random a random number generator
*/
fun IIndexedPolygon.uniform(vertexData: IVertexData, random: Random = Random.Default): Vector3 {
require(positions.size == 3) { "polygon must be a triangle" }
val x = vertexData.positions.slice(positions)
val b = uniformBarycentric(random)
return x[0] * b.x + x[1] * b.y + x[2] * b.z
}
/**
* Computes a point within a triangle defined by the current indexed polygon. The point is determined
* through non-uniform barycentric coordinates, which are influenced by the specified weights.
*
* @param vertexData the vertex data containing positions and other attributes
* @param weight0 the weight associated with the first vertex of the triangle
* @param weight1 the weight associated with the second vertex of the triangle
* @param weight2 the weight associated with the third vertex of the triangle
* @param random an optional random number generator used for generating the barycentric coordinates
* @return a 3D vector representing a point within the triangle specified by the barycentric coordinates
*/
fun IIndexedPolygon.nonUniform(
vertexData: IVertexData,
weight0: Double,
weight1: Double,
weight2: Double,
random: Random = Random.Default
): Vector3 {
require(positions.size == 3) { "polygon must be a triangle" }
val x = vertexData.positions.slice(positions)
val b = nonUniformBarycentric(weight0, weight1, weight2, random)
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
}
/**
* Calculates the area of the triangular polygon.
*
* The method assumes that the polygon is a triangle and computes its area
* using the cross product formula. The computed area is a positive value as it
* represents the absolute area of the triangle.
*
* @param vertexData the vertex data containing positional information of the polygon vertices
* @return the area of the triangle as a Double
* @throws IllegalArgumentException if the polygon is not a triangle (i.e., does not have exactly 3 vertices)
*/
internal fun IIndexedPolygon.area(vertexData: IVertexData): Double {
require(positions.size == 3) { "polygon must be a triangle" }
val x = vertexData.positions.slice(positions)
val u = x[1] - x[0]
val v = x[2] - x[0]
return u.areaBetween(v) / 2.0
}
/**
* Computes the weighted area of a triangular polygon by scaling its area with the average of the given weights.
*
* @param vertexData the vertex data containing position information of the polygon vertices
* @param weight0 the weight associated with the first vertex of the polygon
* @param weight1 the weight associated with the second vertex of the polygon
* @param weight2 the weight associated with the third vertex of the polygon
* @return the weighted area of the triangular polygon
*/
internal fun IIndexedPolygon.weightedArea(
vertexData: IVertexData,
weight0: Double,
weight1: Double,
weight2: Double
): Double {
return area(vertexData) * (weight0 + weight1 + weight2) / 3.0
}
/**
* Generates a list of uniformly distributed points on the surface of the given mesh.
*
* The method uses triangulation and computes areas of triangular polygons to ensure
* uniform distribution of points across the surface.
*
* @param count the number of points to generate
* @param random a random number generator instance, defaulting to [Random.Default]
* @return a list of [Vector3] points uniformly distributed across the mesh surface
*/
fun IMeshData.uniform(count: Int, random: Random = Random.Default): List<Vector3> {
val triangulated = triangulate()
val result = mutableListOf<Vector3>()
val totalArea = triangulated.polygons.sumOf { it.area(vertexData) }
val randoms = (0 until count).map {
random.nextDouble(totalArea)
}.sorted()
var idx = 0
var sum = 0.0
for (t in triangulated.polygons) {
sum += t.area(vertexData)
while (idx <= randoms.lastIndex && sum > randoms[idx]) {
result.add(t.uniform(vertexData, random))
idx++
}
}
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,26 @@
package org.openrndr.extra.mesh.noise
import org.openrndr.extra.mesh.IIndexedPolygon
import org.openrndr.extra.mesh.IVertexData
/**
* A type alias representing a weight function for vertices in a polygon.
*
* The function calculates the weight of a vertex within a given polygon based on its vertex data,
* the polygon's geometry, and the specific vertex index.
*
* @param vertexData The vertex data containing attributes such as positions, normals, and other properties.
* @param polygon The indexed polygon for which the weight is being calculated.
* @param vertexIndex The index of the vertex within the polygon for which the weight is being computed.
* @return The computed weight as a Double.
*/
typealias WeightFunction = (vertexData: IVertexData, polygon: IIndexedPolygon, vertexIndex: Int) -> Double
/**
* A constant weight function for barycentric coordinates that always returns a weight of 1.0.
*
* This function can be used in scenarios where uniform weighting is required across
* all components of a barycentric coordinate, effectively resulting in no modification
* to the original weights of the components.
*/
val identityWeightFunction: WeightFunction = { _, _, _ -> 1.0 }

View File

@@ -1,3 +1,4 @@
import org.openrndr.WindowMultisample
import org.openrndr.application import org.openrndr.application
import org.openrndr.draw.DrawPrimitive import org.openrndr.draw.DrawPrimitive
import org.openrndr.draw.isolated import org.openrndr.draw.isolated
@@ -26,31 +27,28 @@ import kotlin.random.Random
* The application runs with a window size of 720x720 pixels and positions the camera * The application runs with a window size of 720x720 pixels and positions the camera
* in front of the scene using the "Orbital" extension. * in front of the scene using the "Orbital" extension.
*/ */
fun main() { fun main() = application {
application { configure {
configure { width = 720
width = 720 height = 720
height = 720 multisample = WindowMultisample.SampleCount(8)
} }
program { program {
val mesh = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData() val mesh = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData()
val points = mesh.uniform(1000, Random(0)) val points = mesh.uniform(1000, Random(0))
val sphere = sphereMesh(radius = 0.1) val sphere = sphereMesh(radius = 0.1)
extend(Orbital()) { extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0) eye = Vector3(0.0, 0.0, 2.0)
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = "x_fill = vec4(v_viewNormal*0.5+0.5, 1.0);"
} }
extend { for (point in points) drawer.isolated {
drawer.shadeStyle = shadeStyle { drawer.translate(point)
fragmentTransform = "x_fill = vec4(v_viewNormal*0.5+0.5, 1.0);" drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES)
}
for (point in points) {
drawer.isolated {
drawer.translate(point)
drawer.vertexBuffer(sphere, DrawPrimitive.TRIANGLES)
}
}
} }
} }
} }
} }

View File

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

@@ -0,0 +1,57 @@
import org.openrndr.WindowMultisample
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.objloader.loadOBJMeshData
import org.openrndr.extra.mesh.noise.uniformPoints
import org.openrndr.extra.meshgenerators.cylinderMesh
import org.openrndr.extra.meshgenerators.normals.estimateNormals
import org.openrndr.extra.meshgenerators.tangents.estimateTangents
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.buildTransform
import java.io.File
import kotlin.math.cos
import kotlin.random.Random
/**
* This demo loads a 3D model from an OBJ file, processes the mesh data to estimate normals and tangents, and generates
* a set of uniformly distributed pose points. These pose points determine the transformations applied to individual
* objects rendered in the viewport.
*
* It extends the rendering with an orbital camera for navigation and shaders for custom visual
* effects. Cylinders represent transformed objects, with their scale animations based on time-dependent
* trigonometric functions.
*/
fun main() = application {
configure {
width = 720
height = 720
multisample = WindowMultisample.SampleCount(8)
}
program {
val mesh = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData().triangulate()
.estimateNormals().estimateTangents()
val poses = mesh.uniformPoints(10000, Random(0)).map { it.pose() }
val cylinder = cylinderMesh(radius = 0.01, length = 0.2)
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
}
extend {
drawer.shadeStyle = shadeStyle {
fragmentTransform = "x_fill = vec4(v_viewNormal*0.5+0.5, 1.0);"
}
for (pose in poses) {
drawer.isolated {
drawer.model = buildTransform {
multiply(pose)
scale(1.0, 1.0, cos(pose.c3r0 * 10.0 + seconds) * 0.5 + 0.5)
}
drawer.vertexBuffer(cylinder, DrawPrimitive.TRIANGLES)
}
}
}
}
}

View File

@@ -0,0 +1,75 @@
import org.openrndr.WindowMultisample
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.IIndexedPolygon
import org.openrndr.extra.mesh.IVertexData
import org.openrndr.extra.mesh.noise.nonuniform
import org.openrndr.extra.mesh.noise.nonuniformHammersley
import org.openrndr.extra.mesh.noise.nonuniformRSeq
import org.openrndr.extra.objloader.loadOBJMeshData
import org.openrndr.extra.mesh.noise.uniform
import org.openrndr.extra.meshgenerators.normals.estimateNormals
import org.openrndr.extra.meshgenerators.sphereMesh
import org.openrndr.math.Spherical
import org.openrndr.math.Vector3
import java.io.File
import kotlin.math.absoluteValue
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import kotlin.random.Random
/**
* The program demonstrates the loading of a 3D model, estimating its normals,
* sampling points based on non-uniform distribution, and rendering points as spheres.
*
* Key functionalities include:
* - Loading a 3D model from an OBJ file.
* - Estimating per-vertex normals for the mesh.
* - Generating and rendering a sphere mesh for sampled points.
* - Using a lighting direction vector to bias the point sampling distribution.
* - Extending the program with an orbital camera for interactive navigation.
* - Applying shading to simulate lighting effects based on vertex normals.
*
* The rendering of spheres is performed by iterating over the sampled points and isolating each in the transformation matrix.
* This setup allows customization for complex rendering pipelines.
*/
fun main() = application {
configure {
width = 720
height = 720
multisample = WindowMultisample.SampleCount(8)
}
program {
val mesh = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData().estimateNormals()
val sphere = sphereMesh(radius = 0.0125)
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 8.0)
fov = 25.0
}
val v = Vector3(1.0, 1.0, 1.0).normalized
val points = mesh.nonuniformRSeq(
10000,
false,
Random((seconds * 0).toInt())
) { vertexData: IVertexData, polygon: IIndexedPolygon, vertexIndex: Int ->
vertexData.normals[polygon.normals[vertexIndex]].dot(v).coerceIn(0.1, 1.0).pow(2.0)
}
extend {
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)
}
}
}
}