[orx-shadestyles] Improve gradient and imageFill shadestyles
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package org.openrndr.extra.shadestyles.fills.gradients
|
||||
|
||||
import org.openrndr.color.AlgebraicColor
|
||||
import org.openrndr.color.ConvertibleToColorRGBa
|
||||
import org.openrndr.math.CastableToVector4
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector4
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class ConicGradient<C>(
|
||||
colorType: KClass<C>,
|
||||
center: Vector2 = Vector2(0.5, 0.5),
|
||||
rotation: Double = 0.0,
|
||||
angle: Double = 0.0,
|
||||
startAngle: Double = 0.0,
|
||||
colors: Array<Vector4>,
|
||||
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
|
||||
structure: GradientBaseStructure
|
||||
) : GradientBase<C>(
|
||||
colorType,
|
||||
colors,
|
||||
points,
|
||||
structure
|
||||
)
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
var angle: Double by Parameter()
|
||||
var startAngle: Double by Parameter()
|
||||
var center: Vector2 by Parameter()
|
||||
var rotation: Double by Parameter()
|
||||
|
||||
init {
|
||||
this.center = center
|
||||
this.startAngle = startAngle
|
||||
this.angle = angle
|
||||
this.rotation = rotation
|
||||
}
|
||||
|
||||
companion object {
|
||||
val gradientFunction = """
|
||||
float gradientFunction(vec2 coord) {
|
||||
vec2 d0 = coord - p_center;
|
||||
float angle = atan(d0.y, d0.x);
|
||||
angle += ${PI};
|
||||
angle /= ${2.0 * PI};
|
||||
angle += p_rotation / 360.0;
|
||||
angle = mod(angle, 1.0);
|
||||
angle *= p_angle / 360.0;
|
||||
angle += $PI * p_startAngle / 180.0;
|
||||
return angle;
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
class ConicGradientBuilder<C>(private val gradientBuilder: GradientBuilder<C>) : GradientShadeStyleBuilder<C>
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
/**
|
||||
* Specifies the center point for the gradient.
|
||||
* When using BOUNDS coordinates, the coordinates are normalized, where (0,0) represents the top-left corner and (1,1)
|
||||
* represents the bottom-right corner. The default value is set to `Vector2(0.5, 0.5)`, which corresponds to the center
|
||||
* of the gradient's bounding box.
|
||||
*/
|
||||
var center = Vector2(0.5, 0.5)
|
||||
|
||||
/**
|
||||
* Defines the angular extent of the conic gradient in degrees.
|
||||
* By default, it is set to 360.0 degrees, representing a full circular gradient.
|
||||
* Adjusting this value can control the gradient's angular sweep, with values ranging between 0 and 360.
|
||||
* Negative values or values exceeding 360 might have no effect or be clamped depending on implementation.
|
||||
*/
|
||||
var angle: Double = 360.0
|
||||
|
||||
/**
|
||||
* Specifies the starting angle of the conic gradient in degrees.
|
||||
* This value determines the initial orientation of the gradient's angular sweep.
|
||||
* By default, it is set to `0.0` degrees, which aligns with a standard reference point.
|
||||
* You can adjust this value to rotate the gradient's starting position around the center.
|
||||
*/
|
||||
var startAngle: Double = 0.0
|
||||
|
||||
/**
|
||||
* Defines the rotation angle of the conic gradient in degrees.
|
||||
* This value applies a global rotation to the gradient, rotating it around its center point.
|
||||
* By default, it is set to `0.0` degrees, meaning no rotation is applied.
|
||||
* Modifying this value allows for tilting the gradient's angular orientation to achieve
|
||||
* specific visual effects or alignments.
|
||||
*/
|
||||
var rotation: Double = 0.0
|
||||
|
||||
override fun build(): GradientBase<C> {
|
||||
val (stops, colors) = gradientBuilder.extractStepsUnzip()
|
||||
return ConicGradient<C>(
|
||||
gradientBuilder.colorType,
|
||||
center,
|
||||
rotation,
|
||||
angle,
|
||||
startAngle,
|
||||
colors,
|
||||
stops,
|
||||
gradientBuilder.structure()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package org.openrndr.extra.shadestyles.fills.gradients
|
||||
|
||||
import org.openrndr.color.AlgebraicColor
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.color.ConvertibleToColorRGBa
|
||||
import org.openrndr.draw.ShadeStyle
|
||||
import org.openrndr.extra.shadestyles.fills.FillFit
|
||||
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||
import org.openrndr.math.CastableToVector4
|
||||
import org.openrndr.math.Vector4
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class GradientBuilder<C>(val colorType: KClass<C>)
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
var stops = mutableMapOf<Double, C>()
|
||||
var fillUnits = FillUnits.BOUNDS
|
||||
var fillFit = FillFit.STRETCH
|
||||
var spreadMethod = SpreadMethod.PAD
|
||||
var levelWarpFunction = """float levelWarp(vec2 coord, float level) { return level; }"""
|
||||
var domainWarpFunction = """vec2 domainWarp(vec2 coord) { return coord; }"""
|
||||
var gradientFunction = """float gradientFunction(vec2 coord) { return 0.0; }"""
|
||||
var quantization = 0
|
||||
|
||||
private fun setBaseParameters(style: GradientBase<C>) {
|
||||
style.quantization = quantization
|
||||
style.spreadMethod = spreadMethod.ordinal
|
||||
style.fillUnits = fillUnits.ordinal
|
||||
style.fillFit = fillFit.ordinal
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal var shadeStyleBuilder: GradientShadeStyleBuilder<C> = LinearGradientBuilder(this)
|
||||
|
||||
/**
|
||||
* Configures a linear gradient by applying the provided builder block.
|
||||
*
|
||||
* @param builder A lambda function used to define the properties of the linear gradient.
|
||||
* The builder block allows customization of attributes such as
|
||||
* start and end positions.
|
||||
*/
|
||||
fun linear(builder: LinearGradientBuilder<C>.() -> Unit) {
|
||||
shadeStyleBuilder = LinearGradientBuilder(this).apply { builder() }
|
||||
gradientFunction = LinearGradient.gradientFunction
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a radial gradient by applying the provided builder block.
|
||||
*
|
||||
* @param builder A lambda function used to define the properties of the radial gradient.
|
||||
* The builder block allows customization of attributes such as the center,
|
||||
* radius, focal center, and focal radius.
|
||||
*/
|
||||
fun radial(builder: RadialGradientBuilder<C>.() -> Unit) {
|
||||
shadeStyleBuilder = RadialGradientBuilder(this).apply { builder() }
|
||||
gradientFunction = RadialGradient.gradientFunction
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a conic gradient by applying the provided builder block.
|
||||
*
|
||||
* @param builder A lambda function used to define the properties of the conic gradient.
|
||||
* The builder block allows customization of attributes such as the center,
|
||||
* angle, start angle, and rotation.
|
||||
*/
|
||||
fun conic(builder: ConicGradientBuilder<C>.() -> Unit) {
|
||||
shadeStyleBuilder = ConicGradientBuilder(this).apply { builder() }
|
||||
gradientFunction = ConicGradient.gradientFunction
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a stellar gradient by applying the provided builder block.
|
||||
*
|
||||
* @param builder A lambda function used to define the properties of the stellar gradient.
|
||||
* The builder block allows customization of attributes such as center, radius,
|
||||
* sharpness, rotation, and the number of sides.
|
||||
*/
|
||||
fun stellar(builder: StellarGradientBuilder<C>.() -> Unit) {
|
||||
shadeStyleBuilder = StellarGradientBuilder(this).apply { builder() }
|
||||
gradientFunction = StellarGradient.gradientFunction
|
||||
}
|
||||
|
||||
internal fun extractSteps(): List<Pair<Double, C>> {
|
||||
return stops.entries.sortedBy { it.key }.map {
|
||||
Pair(it.key, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun extractStepsUnzip(): Pair<Array<Double>, Array<Vector4>> {
|
||||
val steps = extractSteps()
|
||||
val stopsArray = Array(steps.size) { steps[it].first }
|
||||
val colorsArray = Array(steps.size) {
|
||||
(steps[it].second.let { c ->
|
||||
if (c is ColorRGBa) {
|
||||
c.toLinear()
|
||||
} else {
|
||||
c
|
||||
}
|
||||
}).toVector4()
|
||||
|
||||
}
|
||||
return Pair(stopsArray, colorsArray)
|
||||
}
|
||||
|
||||
internal fun structure(): GradientBaseStructure =
|
||||
GradientBaseStructure(gradientFunction, domainWarpFunction, levelWarpFunction)
|
||||
|
||||
@PublishedApi
|
||||
internal fun build(): GradientBase<C> {
|
||||
return this.shadeStyleBuilder.build().apply {
|
||||
setBaseParameters(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface GradientShadeStyleBuilder<C>
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
/**
|
||||
* Constructs and returns a `GradientBase` object representing a gradient with the
|
||||
* desired configuration defined in the implementing class.
|
||||
*
|
||||
* @return An instance of `GradientBase` configured with the specified gradient parameters.
|
||||
*/
|
||||
fun build(): GradientBase<C>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a gradient shade style using the specified configuration.
|
||||
*
|
||||
* The method allows for building a gradient using a DSL-like approach,
|
||||
* where different properties such as gradient stops, gradient type, and
|
||||
* other configurations can be set.
|
||||
*
|
||||
* @param builder A lambda function used to configure the gradient properties
|
||||
* through an instance of [GradientBuilder].
|
||||
* @return A [ShadeStyle] instance representing the configured gradient.
|
||||
*/
|
||||
inline fun <reified C> gradient(builder: GradientBuilder<C>.() -> Unit): ShadeStyle
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
val gb = GradientBuilder(C::class)
|
||||
gb.builder()
|
||||
return gb.build()
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.openrndr.extra.shadestyles.fills.gradients
|
||||
|
||||
import org.openrndr.color.AlgebraicColor
|
||||
import org.openrndr.color.ConvertibleToColorRGBa
|
||||
import org.openrndr.draw.ShadeStyle
|
||||
import org.openrndr.extra.color.phrases.linearRgbToOklabPhrase
|
||||
import org.openrndr.extra.color.phrases.oklabToLinearRgbPhrase
|
||||
import org.openrndr.extra.shadestyles.fills.FillFit
|
||||
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||
import org.openrndr.extra.shadestyles.generateColorTransform
|
||||
import org.openrndr.math.CastableToVector4
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector4
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
class GradientBaseStructure(
|
||||
val gradientFunction: String,
|
||||
val domainWarpFunction: String,
|
||||
val levelWarpFunction: String
|
||||
)
|
||||
|
||||
open class GradientBase<C>(
|
||||
colorType: KClass<C>,
|
||||
colors: Array<Vector4>,
|
||||
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
|
||||
structure: GradientBaseStructure
|
||||
) : ShadeStyle()
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
var quantization: Int by Parameter()
|
||||
var colors: Array<Vector4> by Parameter()
|
||||
var points: Array<Double> by Parameter()
|
||||
var spreadMethod: Int by Parameter()
|
||||
var fillUnits: Int by Parameter()
|
||||
var fillFit: Int by Parameter()
|
||||
|
||||
init {
|
||||
this.quantization = 0
|
||||
this.colors = colors
|
||||
this.points = points
|
||||
this.fillUnits = FillUnits.BOUNDS.ordinal
|
||||
this.spreadMethod = SpreadMethod.PAD.ordinal
|
||||
this.fillFit = FillFit.STRETCH.ordinal
|
||||
fragmentPreamble = """
|
||||
|vec2 rotate2D(vec2 x, float angle){
|
||||
| float rad = angle / 180.0 * $PI;
|
||||
| mat2 m = mat2(cos(rad),-sin(rad), sin(rad),cos(rad));
|
||||
| return m * x;
|
||||
|}
|
||||
|$oklabToLinearRgbPhrase
|
||||
|$linearRgbToOklabPhrase
|
||||
|${structure.gradientFunction}
|
||||
|${structure.domainWarpFunction}
|
||||
|${structure.levelWarpFunction}
|
||||
|""".trimMargin()
|
||||
|
||||
fragmentTransform = """
|
||||
vec2 coord = vec2(0.0);
|
||||
|
||||
if (p_fillUnits == 0) { // BOUNDS
|
||||
coord = c_boundsPosition.xy;
|
||||
if (p_fillFit == 1) { // COVER
|
||||
float mx = max(c_boundsSize.x, c_boundsSize.y);
|
||||
float ar = mx / min(c_boundsSize.x, c_boundsSize.y);
|
||||
if (c_boundsSize.x == mx) {
|
||||
coord.y = (coord.y - 0.5) * ar + 0.5;
|
||||
} else {
|
||||
coord.x = (coord.x - 0.5) * ar + 0.5;
|
||||
}
|
||||
} else if (p_fillFit == 2) { // CONTAIN
|
||||
float mx = max(c_boundsSize.x, c_boundsSize.y);
|
||||
float ar = min(c_boundsSize.x, c_boundsSize.y) / mx;
|
||||
if (c_boundsSize.y == mx) {
|
||||
coord.y = (coord.y - 0.5) * ar + 0.5;
|
||||
} else {
|
||||
coord.x = (coord.x - 0.5) * ar + 0.5;
|
||||
}
|
||||
}
|
||||
} else if (p_fillUnits == 1) { // WORLD
|
||||
coord = v_worldPosition.xy;
|
||||
} else if (p_fillUnits == 2) { // VIEW
|
||||
coord = v_viewPosition.xy;
|
||||
} else if (p_fillUnits == 3) { // SCREEN
|
||||
coord = c_screenPosition.xy;
|
||||
coord.y = u_viewDimensions.y - coord.y;
|
||||
}
|
||||
|
||||
coord = domainWarp(coord);
|
||||
|
||||
float f = gradientFunction(coord);
|
||||
f = levelWarp(coord, f);
|
||||
|
||||
if (p_quantization != 0) {
|
||||
f = floor(f * float(p_quantization)) / (float(p_quantization) - 1.0);
|
||||
}
|
||||
|
||||
if (p_spreadMethod == 0) { // PAD
|
||||
f = clamp(f, 0.0, 1.0);
|
||||
} else if (p_spreadMethod == 1) { // REFLECT
|
||||
f = 2.0 * abs(f / 2.0 - floor(f / 2.0 + 0.5));
|
||||
} else if (p_spreadMethod == 2) { // REPEAT
|
||||
f = mod(f, 1.0);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < p_points_SIZE - 1 && f >= p_points[i+1]) { i++; }
|
||||
|
||||
vec4 color0 = p_colors[i];
|
||||
vec4 color1 = p_colors[i+1];
|
||||
|
||||
float g = (f - p_points[i]) / (p_points[i+1] - p_points[i]);
|
||||
vec4 gradient = mix(color0, color1, clamp(g, 0.0, 1.0));
|
||||
|
||||
${generateColorTransform(colorType)}
|
||||
x_fill *= gradient;
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
open class LinearGradient<C>(
|
||||
colorType: KClass<C>,
|
||||
start: Vector2 = Vector2.ZERO,
|
||||
end: Vector2 = Vector2.ONE,
|
||||
colors: Array<Vector4>,
|
||||
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
|
||||
structure: GradientBaseStructure,
|
||||
) : GradientBase<C>(
|
||||
colorType,
|
||||
colors,
|
||||
points,
|
||||
structure
|
||||
)
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
var start: Vector2 by Parameter()
|
||||
var end: Vector2 by Parameter()
|
||||
|
||||
init {
|
||||
this.start = start
|
||||
this.end = end
|
||||
}
|
||||
|
||||
companion object {
|
||||
val gradientFunction = """
|
||||
float gradientFunction(vec2 coord) {
|
||||
|
||||
vec2 d0 = coord - p_start;
|
||||
vec2 dl = p_end - p_start;
|
||||
|
||||
float d0l = length(d0);
|
||||
float dll = length(dl);
|
||||
float f = dot(d0, dl) / (dll * dll);
|
||||
|
||||
return f;
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
class LinearGradientBuilder<C>(private val gradientBuilder: GradientBuilder<C>) : GradientShadeStyleBuilder<C>
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
/**
|
||||
* Specifies the start point of the linear gradient.
|
||||
* The coordinate values are normalized when using BOUNDS coordinates,
|
||||
* where (0,0) represents the top-left corner and (1,1) represents the bottom-right corner of the gradient's bounding box.
|
||||
* The default value is set to `Vector2(0.0, 0.5)`, which places the start point at the left middle edge of the bounding box.
|
||||
*/
|
||||
var start = Vector2(0.0, 0.5)
|
||||
|
||||
/**
|
||||
* Specifies the end point of the linear gradient.
|
||||
* The coordinate values are normalized when using BOUNDS coordinates, where (0,0) represents
|
||||
* the top-left corner and (1,1) represents the bottom-right corner of the gradient's bounding box.
|
||||
* The default value is set to `Vector2(1.0, 0.5)`, which places the end point at the right middle edge
|
||||
* of the bounding box.
|
||||
*/
|
||||
var end = Vector2(1.0, 0.5)
|
||||
|
||||
override fun build(): GradientBase<C> {
|
||||
val (stops, colors) = gradientBuilder.extractStepsUnzip()
|
||||
return LinearGradient<C>(gradientBuilder.colorType, start, end, colors, stops, gradientBuilder.structure())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.openrndr.extra.shadestyles.fills.gradients
|
||||
|
||||
import org.openrndr.color.AlgebraicColor
|
||||
import org.openrndr.color.ConvertibleToColorRGBa
|
||||
import org.openrndr.math.CastableToVector4
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector4
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class RadialGradient<C>(
|
||||
colorType: KClass<C>,
|
||||
center: Vector2 = Vector2(0.5, 0.5),
|
||||
focalCenter: Vector2 = center,
|
||||
radius: Double = 1.0,
|
||||
focalRadius: Double = radius,
|
||||
colors: Array<Vector4>,
|
||||
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
|
||||
structure: GradientBaseStructure
|
||||
) : GradientBase<C>(
|
||||
colorType,
|
||||
colors,
|
||||
points,
|
||||
structure
|
||||
)
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
var radius: Double by Parameter()
|
||||
var focalRadius: Double by Parameter()
|
||||
var focalCenter: Vector2 by Parameter()
|
||||
var center: Vector2 by Parameter()
|
||||
|
||||
init {
|
||||
this.focalRadius = focalRadius
|
||||
this.focalCenter = focalCenter
|
||||
this.center = center
|
||||
this.radius = radius
|
||||
}
|
||||
|
||||
companion object {
|
||||
val gradientFunction = """
|
||||
float gradientFunction(vec2 coord) {
|
||||
vec2 d0 = coord - p_center;
|
||||
float d0l = length(d0);
|
||||
float f = d0l / p_radius;
|
||||
return f;
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
class RadialGradientBuilder<C>(private val gradientBuilder: GradientBuilder<C>) : GradientShadeStyleBuilder<C>
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the center point for the radial gradient.
|
||||
*
|
||||
* The `center` represents the normalized coordinates within the bounds of the gradient's area.
|
||||
* When using BOUNDS coordinates a value of `Vector2(0.5, 0.5)` corresponds to the geometric center of the gradient's
|
||||
* bounds. The coordinates are normalized, where (0,0) is the top-left corner and (1,1) is the bottom-right corner.
|
||||
* This value determines the starting position for the radial gradient effect.
|
||||
*/
|
||||
var center = Vector2(0.5, 0.5)
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the radius of the radial gradient.
|
||||
*
|
||||
* The `radius` determines the extent of the gradient from the center point outward.
|
||||
*
|
||||
* When using BOUNDS coordinates it is expressed as a normalized value where `0.0` represents no radius
|
||||
* (a single point at the center) and `1.0` corresponds to the full extent to the edge of the gradient's bounding area.
|
||||
* Adjusting this value alters the size and spread of the gradient.
|
||||
*/
|
||||
var radius = 0.5
|
||||
|
||||
/**
|
||||
* Specifies the focal center point for the radial gradient.
|
||||
*
|
||||
* The `focalCenter` defines an additional center point for the radial gradient,
|
||||
* allowing for more complex and visually distinct gradient effects compared to the default center.
|
||||
* If not explicitly set, it defaults to the same value as the `center`.
|
||||
*
|
||||
* This property can be used to create focused or offset gradient patterns by positioning
|
||||
* the focal center differently relative to the main center point. The coordinates can
|
||||
* be normalized within the bounds, where (0,0) represents the top-left corner and (1,1)
|
||||
* represents the bottom-right corner.
|
||||
*/
|
||||
var focalCenter: Vector2? = null
|
||||
var focalRadius: Double? = null
|
||||
|
||||
override fun build(): GradientBase<C> {
|
||||
val (stops, colors) = gradientBuilder.extractStepsUnzip()
|
||||
return RadialGradient(
|
||||
gradientBuilder.colorType,
|
||||
center,
|
||||
focalCenter ?: center,
|
||||
radius,
|
||||
focalRadius ?: radius,
|
||||
colors,
|
||||
stops,
|
||||
gradientBuilder.structure()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package org.openrndr.extra.shadestyles.fills.gradients
|
||||
|
||||
import org.openrndr.color.AlgebraicColor
|
||||
import org.openrndr.color.ConvertibleToColorRGBa
|
||||
import org.openrndr.math.CastableToVector4
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector4
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class StellarGradient<C>(
|
||||
colorType: KClass<C>,
|
||||
center: Vector2 = Vector2(0.5, 0.5),
|
||||
radius: Double = 1.0,
|
||||
sharpness: Double = 0.5,
|
||||
rotation: Double = 0.0,
|
||||
sides: Int = 3,
|
||||
colors: Array<Vector4>,
|
||||
points: Array<Double> = Array(colors.size) { it / (colors.size - 1.0) },
|
||||
structure: GradientBaseStructure
|
||||
|
||||
) : GradientBase<C>(
|
||||
colorType,
|
||||
colors,
|
||||
points,
|
||||
structure
|
||||
)
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
var sides: Int by Parameter()
|
||||
var radius: Double by Parameter()
|
||||
var center: Vector2 by Parameter()
|
||||
var sharpness: Double by Parameter()
|
||||
var rotation: Double by Parameter()
|
||||
|
||||
init {
|
||||
this.sides = sides
|
||||
this.center = center
|
||||
this.radius = radius
|
||||
this.sharpness = sharpness
|
||||
this.rotation = rotation
|
||||
}
|
||||
|
||||
companion object {
|
||||
val gradientFunction = """
|
||||
const float pi = $PI;
|
||||
const vec3 c = vec3(1,0,-1);
|
||||
/*
|
||||
taken from https://www.shadertoy.com/view/csXcD8# by ShaderToy user 'nr4'
|
||||
*/
|
||||
float dstar(in vec2 x, in float r1, in float r2, in float N) {
|
||||
N *= 2.;
|
||||
float p = atan(x.y,x.x),
|
||||
k = pi/N,
|
||||
dp = mod(p, 2.*k),
|
||||
parity = mod(round((p-dp)*.5/k), 2.),
|
||||
dkp = mix(k,-k,parity),
|
||||
kp = k+dkp,
|
||||
km = k-dkp;
|
||||
vec2 p1 = r1*vec2(cos(km),sin(km)),
|
||||
p2 = r2*vec2(cos(kp),sin(kp)),
|
||||
dpp = p2-p1,
|
||||
n = normalize(dpp).yx*c.xz,
|
||||
xp = length(x)*vec2(cos(dp), sin(dp)),
|
||||
xp1 = xp-p1;
|
||||
float t = dot(xp1,dpp)/dot(dpp,dpp)-.5,
|
||||
r = (1.-2.*parity)*dot(xp1,n);
|
||||
return t < -.5
|
||||
? sign(r)*length(xp1)
|
||||
: t < .5
|
||||
? r
|
||||
: sign(r)*length(xp-p2);
|
||||
}
|
||||
|
||||
float gradientFunction(vec2 coord) {
|
||||
vec2 d0 = coord - p_center;
|
||||
d0 = rotate2D(d0, p_rotation);
|
||||
float innerRadius = 1.0 - p_sharpness;
|
||||
float f = dstar(d0 / p_radius, innerRadius, 1.0, float(p_sides));
|
||||
// dstar is broken at vec2(0.0, 0.0), let's nudge it a bit
|
||||
float f0 = dstar(vec2(1E-6, 1E-6), innerRadius, 1.0, float(p_sides));
|
||||
f -= f0;
|
||||
f /= 0.5 * 1.0 * (1.0 + cos(pi / float(p_sides)));
|
||||
return f;
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
class StellarGradientBuilder<C>(private val gradientBuilder: GradientBuilder<C>) : GradientShadeStyleBuilder<C>
|
||||
where C : ConvertibleToColorRGBa, C : AlgebraicColor<C>, C : CastableToVector4 {
|
||||
|
||||
/**
|
||||
* Specifies the center point of the gradient.
|
||||
* The center is defined in normalized coordinates where (0, 0) represents the top-left corner
|
||||
* and (1, 1) represents the bottom-right corner of the gradient's bounding box.
|
||||
* The default value is `Vector2(0.5, 0.5)`, which corresponds to the center of the gradient.
|
||||
*/
|
||||
var center = Vector2(0.5, 0.5)
|
||||
var radius = 0.5
|
||||
|
||||
/**
|
||||
* Specifies the number of sides for the star pattern used in the gradient.
|
||||
* This property controls the symmetry and appearance of the resulting gradient.
|
||||
* Higher values produce shapes with more sides, contributing to more intricate patterns,
|
||||
* while lower values result in simpler, less detailed designs.
|
||||
* The default value is set to `6`.
|
||||
*/
|
||||
var sides = 6
|
||||
|
||||
/**
|
||||
* Determines the sharpness of the star shape. Maximum value is `1.0` which will produce pointy stars.
|
||||
* Values closer to `0.0` result in smoother, star shapes. A value of `0.0` will result in a regular polygon shape.
|
||||
* The default value is `0.5`.
|
||||
*/
|
||||
var sharpness = 0.5
|
||||
|
||||
/**
|
||||
* Specifies the rotation angle of the gradient in degrees.
|
||||
* This property adjusts the overall rotation of the gradient around its center point.
|
||||
* By default, the value is set to `0.0` degrees, indicating no rotation.
|
||||
* Modifying this value allows the gradient's orientation to be tilted, enabling various aesthetic effects.
|
||||
*/
|
||||
var rotation = 0.0
|
||||
|
||||
override fun build(): GradientBase<C> {
|
||||
val (stops, colors) = gradientBuilder.extractStepsUnzip()
|
||||
return StellarGradient(
|
||||
gradientBuilder.colorType,
|
||||
center,
|
||||
radius,
|
||||
sharpness,
|
||||
rotation,
|
||||
sides,
|
||||
colors,
|
||||
stops,
|
||||
gradientBuilder.structure()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user