[orx-shade-styles] Add elliptical gradient support
This commit is contained in:
@@ -24,6 +24,7 @@ kotlin {
|
|||||||
implementation(project(":orx-shade-styles"))
|
implementation(project(":orx-shade-styles"))
|
||||||
implementation(project(":orx-noise"))
|
implementation(project(":orx-noise"))
|
||||||
implementation(project(":orx-shapes"))
|
implementation(project(":orx-shapes"))
|
||||||
|
implementation(project(":orx-image-fit"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package org.openrndr.extra.shadestyles.fills.gradients
|
||||||
|
|
||||||
|
import org.openrndr.color.AlgebraicColor
|
||||||
|
import org.openrndr.color.ConvertibleToColorRGBa
|
||||||
|
import org.openrndr.extra.shaderphrases.sdf.sdEllipsePhrase
|
||||||
|
import org.openrndr.math.CastableToVector4
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class EllipticalGradient<C>(
|
||||||
|
colorType: KClass<C>,
|
||||||
|
center: Vector2 = Vector2(0.5, 0.5),
|
||||||
|
radiusX: Double = 1.0,
|
||||||
|
radiusY: Double = 1.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 radiusX: Double by Parameter()
|
||||||
|
var radiusY: Double by Parameter()
|
||||||
|
var center: Vector2 by Parameter()
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.radiusX = radiusX
|
||||||
|
this.radiusY = radiusY
|
||||||
|
this.center = center
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val gradientFunction = """$sdEllipsePhrase
|
||||||
|
float gradientFunction(vec2 coord) {
|
||||||
|
if (abs(p_radiusX - p_radiusY) < 1E-4) {
|
||||||
|
vec2 d0 = coord - p_center;
|
||||||
|
float d0l = length(d0);
|
||||||
|
float f = d0l / p_radiusX;
|
||||||
|
return f;
|
||||||
|
} else {
|
||||||
|
float maxRadius = min(p_radiusX, p_radiusY);
|
||||||
|
vec2 d0 = (coord - p_center) / maxRadius;
|
||||||
|
float f = sdEllipse(d0, vec2(p_radiusX, p_radiusY)/maxRadius );
|
||||||
|
float f0 = sdEllipse(vec2(0.0), vec2(p_radiusX, p_radiusY)/maxRadius );
|
||||||
|
f -= f0;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EllipticalGradientBuilder<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 radiusX = 0.5
|
||||||
|
var radiusY = 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 EllipticalGradient(
|
||||||
|
gradientBuilder.colorType,
|
||||||
|
center,
|
||||||
|
radiusX,
|
||||||
|
radiusY,
|
||||||
|
colors,
|
||||||
|
stops,
|
||||||
|
gradientBuilder.structure()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,26 +93,40 @@ open class GradientBase<C>(
|
|||||||
f = levelWarp(coord, f);
|
f = levelWarp(coord, f);
|
||||||
|
|
||||||
if (p_quantization != 0) {
|
if (p_quantization != 0) {
|
||||||
f = floor(f * float(p_quantization)) / (float(p_quantization) - 1.0);
|
f *= float(p_quantization);
|
||||||
|
float seam = ceil(f + 0.5);
|
||||||
|
vec2 d = vec2(dFdx(f), dFdy(f));
|
||||||
|
f = (f - seam) / length(d*1.0) + seam;
|
||||||
|
f = clamp(f, seam-.5, seam+.5);
|
||||||
|
f /= float(p_quantization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float sf;
|
||||||
|
float mf = 0.0;
|
||||||
if (p_spreadMethod == 0) { // PAD
|
if (p_spreadMethod == 0) { // PAD
|
||||||
f = clamp(f, 0.0, 1.0);
|
sf = clamp(f, 0.0, 1.0);
|
||||||
} else if (p_spreadMethod == 1) { // REFLECT
|
} else if (p_spreadMethod == 1) { // REFLECT
|
||||||
f = 2.0 * abs(f / 2.0 - floor(f / 2.0 + 0.5));
|
sf = 2.0 * abs(f / 2.0 - floor(f / 2.0 + 0.5));
|
||||||
} else if (p_spreadMethod == 2) { // REPEAT
|
} else if (p_spreadMethod == 2) { // REPEAT
|
||||||
f = mod(f, 1.0);
|
sf = mod(f, 1.0);
|
||||||
|
float seam = ceil(f);
|
||||||
|
vec2 d = vec2(dFdx(f), dFdy(f));
|
||||||
|
mf = (f - seam) / length(d) + seam;
|
||||||
}
|
}
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < p_points_SIZE - 1 && f >= p_points[i+1]) { i++; }
|
while (i < p_points_SIZE - 1 && sf >= p_points[i+1]) { i++; }
|
||||||
|
|
||||||
vec4 color0 = p_colors[i];
|
vec4 color0 = p_colors[i];
|
||||||
vec4 color1 = p_colors[i+1];
|
vec4 color1 = p_colors[(i+1) % p_colors_SIZE];
|
||||||
|
|
||||||
float g = (f - p_points[i]) / (p_points[i+1] - p_points[i]);
|
float g = (sf - p_points[i]) / (p_points[i+1] - p_points[i]);
|
||||||
vec4 gradient = mix(color0, color1, clamp(g, 0.0, 1.0));
|
vec4 gradient = mix(color0, color1, clamp(g, 0.0, 1.0));
|
||||||
|
|
||||||
|
if (mf > 0.0 && p_quantization == 0) {
|
||||||
|
gradient = p_colors[p_colors_SIZE - 1] * (1.0 - mf) + p_colors[0] * mf;
|
||||||
|
}
|
||||||
|
|
||||||
${generateColorTransform(colorType)}
|
${generateColorTransform(colorType)}
|
||||||
x_fill *= gradient;
|
x_fill *= gradient;
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class GradientBuilder<C>(val colorType: KClass<C>)
|
|||||||
gradientFunction = RadialGradient.gradientFunction
|
gradientFunction = RadialGradient.gradientFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun elliptic(builder: EllipticalGradientBuilder<C>.() -> Unit) {
|
||||||
|
shadeStyleBuilder = EllipticalGradientBuilder(this).apply { builder() }
|
||||||
|
gradientFunction = EllipticalGradient.gradientFunction
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures a conic gradient by applying the provided builder block.
|
* Configures a conic gradient by applying the provided builder block.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.openrndr.extra.shadestyles.fills.gradients
|
|||||||
|
|
||||||
import org.openrndr.color.AlgebraicColor
|
import org.openrndr.color.AlgebraicColor
|
||||||
import org.openrndr.color.ConvertibleToColorRGBa
|
import org.openrndr.color.ConvertibleToColorRGBa
|
||||||
|
import org.openrndr.extra.shaderphrases.sdf.sdStarPhrase
|
||||||
import org.openrndr.math.CastableToVector4
|
import org.openrndr.math.CastableToVector4
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.math.Vector4
|
import org.openrndr.math.Vector4
|
||||||
@@ -42,48 +43,17 @@ open class StellarGradient<C>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val gradientFunction = """
|
val gradientFunction = """$sdStarPhrase
|
||||||
const float pi = $PI;
|
float gradientFunction(vec2 coord) {
|
||||||
const vec3 c = vec3(1,0,-1);
|
vec2 d0 = coord - p_center;
|
||||||
/*
|
d0 = rotate2D(d0, p_rotation);
|
||||||
taken from https://www.shadertoy.com/view/csXcD8# by ShaderToy user 'nr4'
|
float f = sdStar(d0 / p_radius, 1.0, p_sides, p_sharpness);
|
||||||
*/
|
float f0 = sdStar(vec2(0.0), 1.0, p_sides, p_sharpness);
|
||||||
float dstar(in vec2 x, in float r1, in float r2, in float N) {
|
f -= f0;
|
||||||
N *= 2.;
|
f /= 0.5 * 1.0 * (1.0 + cos($PI / float(p_sides)));
|
||||||
float p = atan(x.y,x.x),
|
return f;
|
||||||
k = pi/N,
|
}
|
||||||
dp = mod(p, 2.*k),
|
""".trimIndent()
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package gradients
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.draw.loadFont
|
||||||
|
import org.openrndr.extra.color.presets.BLUE_STEEL
|
||||||
|
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||||
|
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||||
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
application {
|
||||||
|
configure {
|
||||||
|
width = 720
|
||||||
|
height = 720
|
||||||
|
}
|
||||||
|
program {
|
||||||
|
extend {
|
||||||
|
drawer.shadeStyle = gradient<ColorRGBa> {
|
||||||
|
stops[0.0] = ColorRGBa.BLACK
|
||||||
|
stops[1.0] = ColorRGBa.WHITE
|
||||||
|
|
||||||
|
fillUnits = FillUnits.WORLD
|
||||||
|
spreadMethod = SpreadMethod.REPEAT
|
||||||
|
//levelWarpFunction = """float levelWarp(vec2 p, float level) { return level + cos(p.x*0.01 + level)*0.1; } """
|
||||||
|
|
||||||
|
//quantization = 3
|
||||||
|
// stellar {
|
||||||
|
// radius = drawer.bounds.width/8.0
|
||||||
|
// center = drawer.bounds.position(0.5, 0.0)
|
||||||
|
// sides = 6
|
||||||
|
// sharpness = 0.5
|
||||||
|
// rotation = seconds * 36.0
|
||||||
|
// }
|
||||||
|
conic {
|
||||||
|
angle = 360.0 * 8.0
|
||||||
|
center = drawer.bounds.position(0.5, 0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawer.rectangle(drawer.bounds)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package gradients
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.draw.loadFont
|
||||||
|
import org.openrndr.extra.color.presets.BLUE_STEEL
|
||||||
|
import org.openrndr.extra.shadestyles.fills.FillUnits
|
||||||
|
import org.openrndr.extra.shadestyles.fills.SpreadMethod
|
||||||
|
import org.openrndr.extra.shadestyles.fills.gradients.gradient
|
||||||
|
import org.openrndr.extra.shapes.primitives.grid
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
configure {
|
||||||
|
width = 720
|
||||||
|
height = 720
|
||||||
|
}
|
||||||
|
program {
|
||||||
|
extend {
|
||||||
|
val grid = drawer.bounds.grid(2, 2)
|
||||||
|
drawer.stroke = null
|
||||||
|
|
||||||
|
for ((index, cell) in grid.flatten().withIndex()) {
|
||||||
|
drawer.shadeStyle = gradient<ColorRGBa> {
|
||||||
|
stops[0.0] = ColorRGBa.RED
|
||||||
|
stops[0.5] = ColorRGBa.PINK
|
||||||
|
stops[1.0] = ColorRGBa.WHITE
|
||||||
|
|
||||||
|
fillUnits = FillUnits.BOUNDS
|
||||||
|
spreadMethod = SpreadMethod.REPEAT
|
||||||
|
quantization = 8
|
||||||
|
elliptic {
|
||||||
|
radiusX = cos(index / 2.0 * PI + seconds) * 0.45 + 0.5
|
||||||
|
radiusY = sin(index / 2.0 * PI + seconds) * 0.45 + 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawer.rectangle(cell)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user