[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.draw.DrawPrimitive
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
* in front of the scene using the "Orbital" extension.
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val mesh = loadOBJMeshData(File("demo-data/obj-models/suzanne/Suzanne.obj")).toMeshData()
val points = mesh.uniform(1000, Random(0))
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()
val points = mesh.uniform(1000, Random(0))
val sphere = sphereMesh(radius = 0.1)
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
val sphere = sphereMesh(radius = 0.1)
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);"
}
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)
}
}
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.draw.DrawPrimitive
import org.openrndr.draw.isolated
@@ -5,44 +6,36 @@ 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
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()
val sphere = sphereMesh(radius = 0.01)
extend(Orbital()) {
eye = Vector3(0.0, 0.0, 2.0)
}
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 * 1).toInt())
drawer.shadeStyle = shadeStyle {
fragmentTransform = "x_fill = vec4(v_viewNormal*0.5+0.5, 1.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)
}
}
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)
}
}
}
}