[orx-shapes] convert to MPP

This commit is contained in:
Edwin Jakobs
2021-07-06 10:17:45 +02:00
parent 23780a3102
commit fa2ae01173
21 changed files with 143 additions and 43 deletions

View File

@@ -0,0 +1,301 @@
package org.openrndr.extra.shapes
import org.openrndr.color.AlgebraicColor
import org.openrndr.color.ColorRGBa
import org.openrndr.color.ConvertibleToColorRGBa
import org.openrndr.math.Matrix44
import org.openrndr.math.Vector2
import org.openrndr.shape.Rectangle
import org.openrndr.shape.Segment
import org.openrndr.shape.ShapeContour
import kotlin.random.Random
open class BezierPatchBase<C>(
val points: List<List<Vector2>>,
val colors: List<List<C>> = emptyList()
)
where C : AlgebraicColor<C>, C : ConvertibleToColorRGBa {
init {
require(points.size == 4 && points.all { it.size == 4 })
require(colors.isEmpty() || colors.size == 4 && colors.all { it.size == 4 })
}
/**
* Return a transposed version of the bezier path by transposing the [points] matrix
*/
val transposed
get() = BezierPatchBase(
listOf(
listOf(points[0][0], points[1][0], points[2][0], points[3][0]),
listOf(points[0][1], points[1][1], points[2][1], points[3][1]),
listOf(points[0][2], points[1][2], points[2][2], points[3][2]),
listOf(points[0][3], points[1][3], points[2][3], points[3][3]),
),
if (colors.isEmpty()) emptyList() else {
listOf(
listOf(colors[0][0], colors[1][0], colors[2][0], colors[3][0]),
listOf(colors[0][1], colors[1][1], colors[2][1], colors[3][1]),
listOf(colors[0][2], colors[1][2], colors[2][2], colors[3][2]),
listOf(colors[0][3], colors[1][3], colors[2][3], colors[3][3]),
)
}
)
fun transform(transform: Matrix44) = BezierPatchBase(points.map { r ->
r.map { (transform * it.xy01).div.xy }
}, colors)
private fun coeffs2(t: Double): DoubleArray {
val it = 1.0 - t
val it2 = it * it
val t2 = t * t
return doubleArrayOf(it2, 2 * it * t, t2)
}
private fun coeffs3(t: Double): DoubleArray {
val it = 1.0 - t
val it2 = it * it
val it3 = it2 * it
val t2 = t * t
val t3 = t2 * t
return doubleArrayOf(it3, 3 * it2 * t, 3 * it * t2, t3)
}
/**
* Return a point on the patch by using its u,v parameterization
* @param u a value between 0 and 1
* @param v a value between 0 and 1
*/
fun position(u: Double, v: Double): Vector2 {
val csu = coeffs3(u)
val csv = coeffs3(v)
var result = Vector2.ZERO
for (j in 0 until 4) {
for (i in 0 until 4) {
result += points[j][i] * csu[i] * csv[j]
}
}
return result
}
/**
* Return a gradient vector on the patch by using its u,v parameterization
* @param u a value between 0 and 1
* @param v a value between 0 and 1
*/
fun gradient(u: Double, v: Double): Vector2 {
val f0 = List(4) { MutableList(3) { Vector2.ZERO } }
for (j in 0 until 4) {
for (i in 0 until 3) {
f0[j][i] = points[j][i + 1] - points[j][i]
}
}
val f1 = List(3) { MutableList(3) { Vector2.ZERO } }
for (j in 0 until 3) {
for (i in 0 until 3) {
f1[j][i] = f0[j + 1][i] - f0[j][i]
}
}
val csu = coeffs2(u)
val csv = coeffs2(v)
var result = Vector2.ZERO
for (j in 0 until 3) {
for (i in 0 until 3) {
result += f1[j][i] * csu[i] * csv[j]
}
}
return result
}
/**
* Generate a random point on the path
* @return a point that is uniformly distributed in uv space
*/
fun randomPoint(random: Random = Random.Default) = position(random.nextDouble(), random.nextDouble())
fun horizontal(v: Double): ShapeContour {
val cs = coeffs3(v)
val cps = Array(4) { Vector2.ZERO }
for (j in 0 until 4) {
for (i in 0 until 4) {
cps[j] += points[i][j] * cs[i]
}
}
return ShapeContour(listOf(Segment(cps[0], cps[1], cps[2], cps[3])), false)
}
fun vertical(u: Double): ShapeContour {
val cs = coeffs3(u)
val cps = Array(4) { Vector2.ZERO }
for (j in 0 until 4) {
for (i in 0 until 4) {
cps[j] += points[j][i] * cs[i]
}
}
return ShapeContour(listOf(Segment(cps[0], cps[1], cps[2], cps[3])), false)
}
/**
* Extract a sub-patch based on uv parameterization
*/
fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatchBase<C> {
val c0 = Segment(points[0][0], points[0][1], points[0][2], points[0][3]).sub(u0, u1)
val c1 = Segment(points[1][0], points[1][1], points[1][2], points[1][3]).sub(u0, u1)
val c2 = Segment(points[2][0], points[2][1], points[2][2], points[2][3]).sub(u0, u1)
val c3 = Segment(points[3][0], points[3][1], points[3][2], points[3][3]).sub(u0, u1)
val sub0 = bezierPatch(c0, c1, c2, c3)
val d0 = Segment(sub0.points[0][0], sub0.points[1][0], sub0.points[2][0], sub0.points[3][0]).sub(v0, v1)
val d1 = Segment(sub0.points[0][1], sub0.points[1][1], sub0.points[2][1], sub0.points[3][1]).sub(v0, v1)
val d2 = Segment(sub0.points[0][2], sub0.points[1][2], sub0.points[2][2], sub0.points[3][2]).sub(v0, v1)
val d3 = Segment(sub0.points[0][3], sub0.points[1][3], sub0.points[2][3], sub0.points[3][3]).sub(v0, v1)
return fromSegments<C>(d0, d1, d2, d3).transposed
}
val contour: ShapeContour = ShapeContour(
listOf(
Segment(points[0][0], points[0][1], points[0][2], points[0][3]),
Segment(points[0][3], points[1][3], points[2][3], points[3][3]),
Segment(points[3][3], points[3][2], points[3][1], points[3][0]),
Segment(points[3][0], points[2][0], points[1][0], points[0][0]),
), true
)
operator fun times(scale: Double) =
BezierPatchBase(
points.map { j -> j.map { i -> i * scale } },
if (colors.isEmpty()) colors else colors.map { j -> j.map { i -> i * scale } }
)
operator fun div(scale: Double) =
BezierPatchBase(points.map { j -> j.map { i -> i / scale } },
if (colors.isEmpty()) colors else colors.map { j -> j.map { i -> i / scale } }
)
operator fun plus(right: BezierPatchBase<C>) =
BezierPatchBase(List(4) { j -> List(4) { i -> points[j][i] + right.points[j][i] } },
if (colors.isEmpty() && right.colors.isEmpty()) { colors }
else if (colors.isEmpty() && right.colors.isNotEmpty()) { right.colors }
else if (colors.isNotEmpty() && right.colors.isEmpty()) { colors }
else { List(4) { j -> List(4) { i -> colors[j][i] + right.colors[j][i] } } }
)
operator fun minus(right: BezierPatchBase<C>) =
BezierPatchBase(List(4) { j -> List(4) { i -> points[j][i] - right.points[j][i] } },
if (colors.isEmpty() && right.colors.isEmpty()) { colors }
else if (colors.isEmpty() && right.colors.isNotEmpty()) { right.colors }
else if (colors.isNotEmpty() && right.colors.isEmpty()) { colors }
else { List(4) { j -> List(4) { i -> colors[j][i] - right.colors[j][i] } } }
)
fun <K> withColors(colors: List<List<K>>): BezierPatchBase<K>
where K : AlgebraicColor<K>, K : ConvertibleToColorRGBa {
return BezierPatchBase(points, colors)
}
companion object {
fun <C> fromSegments(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatchBase<C>
where C : AlgebraicColor<C>, C : ConvertibleToColorRGBa {
val c0c = c0.cubic
val c1c = c1.cubic
val c2c = c2.cubic
val c3c = c3.cubic
val c0l = listOf(c0c.start, c0c.control[0], c0c.control[1], c0c.end)
val c1l = listOf(c1c.start, c1c.control[0], c1c.control[1], c1c.end)
val c2l = listOf(c2c.start, c2c.control[0], c2c.control[1], c2c.end)
val c3l = listOf(c3c.start, c3c.control[0], c3c.control[1], c3c.end)
return BezierPatchBase(listOf(c0l, c1l, c2l, c3l))
}
}
}
class BezierPatch(points: List<List<Vector2>>, colors: List<List<ColorRGBa>> = emptyList()) :
BezierPatchBase<ColorRGBa>(points, colors)
/**
* Create a cubic bezier patch from 4 segments. The control points of the segments are used in row-wise fashion
*/
fun bezierPatch(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatch {
val c0c = c0.cubic
val c1c = c1.cubic
val c2c = c2.cubic
val c3c = c3.cubic
val c0l = listOf(c0c.start, c0c.control[0], c0c.control[1], c0c.end)
val c1l = listOf(c1c.start, c1c.control[0], c1c.control[1], c1c.end)
val c2l = listOf(c2c.start, c2c.control[0], c2c.control[1], c2c.end)
val c3l = listOf(c3c.start, c3c.control[0], c3c.control[1], c3c.end)
return BezierPatch(listOf(c0l, c1l, c2l, c3l))
}
/**
* Create a bezier patch from a closed shape contour (with 4 segments).
* @param alpha control for linearity, default is `1.0/3.0`
*/
fun bezierPatch(shapeContour: ShapeContour, alpha: Double = 1.0 / 3.0): BezierPatch {
require(shapeContour.segments.size == 4) {
"""contour needs exactly 4 segments (has ${shapeContour.segments.size})"""
}
val c0 = shapeContour.segments[0].cubic
val c1 = shapeContour.segments[1].cubic
val c2 = shapeContour.segments[2].cubic
val c3 = shapeContour.segments[3].cubic
val fa = 1.0 - alpha
val fb = alpha
val x00 = (c0.control[0] * fa + c2.control[1] * fb + c3.control[1] * fa + c1.control[0] * fb) / 2.0
val x01 = (c0.control[1] * fa + c2.control[0] * fb + c3.control[1] * fb + c1.control[0] * fa) / 2.0
val x10 = (c0.control[0] * fb + c2.control[1] * fa + c3.control[0] * fa + c1.control[1] * fb) / 2.0
val x11 = (c0.control[1] * fb + c2.control[0] * fa + c3.control[0] * fb + c1.control[1] * fa) / 2.0
val cps = listOf(
listOf(c0.start, c0.control[0], c0.control[1], c0.end),
listOf(c3.control[1], x00, x01, c1.control[0]),
listOf(c3.control[0], x10, x11, c1.control[1]),
listOf(c2.end, c2.control[1], c2.control[0], c2.start),
)
return BezierPatch(cps)
}
/**
* Create a bezier patch from 4 corners
* @param corners a list of corners from which to create the patch
* @param alpha control for linearity, default is `1.0/3.0`
*/
fun bezierPatch(corners: List<Vector2>, alpha: Double = 1.0 / 3.0): BezierPatch {
require(corners.size == 4) {
"""need exactly 4 corners (got ${corners.size}"""
}
return bezierPatch(ShapeContour.fromPoints(corners, true), alpha)
}
/**
* Distort a shape contour
*/
fun BezierPatch.distort(shapeContour: ShapeContour, referenceRectangle: Rectangle = shapeContour.bounds): ShapeContour {
val distortedSegments = shapeContour.segments.map {
val c = it.cubic
val e = c.end.map(referenceRectangle)
val c0 = c.control[0].map(referenceRectangle)
val c1 = c.control[1].map(referenceRectangle)
val s = c.start.map(referenceRectangle)
val ne = position(e.x, e.y)
val ns = position(s.x, s.y)
val nc0 = position(c0.x, c0.y)
val nc1 = position(c1.x, c1.y)
Segment(ns, nc0, nc1, ne)
}
return ShapeContour(distortedSegments, shapeContour.closed, shapeContour.polarity)
}
private fun Vector2.map(rect: Rectangle): Vector2 {
val nx = (x - rect.x) / rect.width
val ny = (y - rect.y) / rect.height
return Vector2(nx, ny)
}

View File

@@ -0,0 +1,43 @@
package org.openrndr.extra.shapes
import org.openrndr.shape.Rectangle
/**
* Splits [Rectangle] into a grid of [Rectangle]s
* @param columns the number of columns in the resulting grid
* @param rows the number of rows in the resulting grid
* @param marginX the unitless margin width
* @param marginY the unitless margin height
* @param gutterX the unitless gutter width, the horizontal space between grid cells
* @param gutterY the unitless gutter height, the vertical space between grid cells
*/
fun Rectangle.grid(
columns: Int,
rows: Int,
marginX: Double = 0.0,
marginY: Double = 0.0,
gutterX: Double = 0.0,
gutterY: Double = 0.0
): List<List<Rectangle>> {
val totalWidth = width - marginX * 2.0
val totalHeight = height - marginY * 2.0
val totalGutterWidth = gutterX * (columns - 1).coerceAtLeast(0)
val totalGutterHeight = gutterY * (rows - 1).coerceAtLeast(0)
val cellWidth = ((totalWidth - totalGutterWidth) / columns).coerceAtLeast(0.0)
val cellHeight = ((totalHeight - totalGutterHeight) / rows).coerceAtLeast(0.0)
val cellSpaceX = cellWidth + gutterX
val cellSpaceY = cellHeight + gutterY
val x0 = x + marginX
val y0 = y + marginY
return (0 until rows).map { row ->
(0 until columns).map { column ->
Rectangle(x0 + column * cellSpaceX, y0 + row * cellSpaceY, cellWidth, cellHeight)
}
}
}

View File

@@ -0,0 +1,99 @@
package org.openrndr.extra.shapes
import org.openrndr.math.Vector2
import org.openrndr.math.asRadians
import org.openrndr.shape.ShapeContour
import org.openrndr.shape.contour
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
fun regularPolygon(sides: Int, center: Vector2 = Vector2.ZERO, radius: Double = 100.0, phase: Double = 0.0): ShapeContour {
val c = contour {
val phi = phase.asRadians
for (i in 0 until sides) {
val x = center.x + radius * cos(i.toDouble() / sides * PI * 2 + phi)
val y = center.y + radius * sin(i.toDouble() / sides * PI * 2 + phi)
moveOrLineTo(x, y)
}
close()
}
return c
}
fun regularPolygonRounded(sides: Int, roundFactor: Double = 0.5, center: Vector2 = Vector2.ZERO, radius: Double = 100.0, phase: Double = 0.0): ShapeContour {
val c = contour {
val phi = phase.asRadians
for (i in 0 until sides) {
val x0 = center.x + radius * cos(i.toDouble() / sides * PI * 2 + phi)
val y0 = center.y + radius * sin(i.toDouble() / sides * PI * 2 + phi)
val x1 = center.x + radius * cos((i + 1.0) / sides * PI * 2 + phi)
val y1 = center.y + radius * sin((i + 1.0) / sides * PI * 2 + phi)
val x2 = center.x + radius * cos((i + 2.0) / sides * PI * 2 + phi)
val y2 = center.y + radius * sin((i + 2.0) / sides * PI * 2 + phi)
val f = roundFactor / 2.0
val dx10 = x1 - x0
val dy10 = y1 - y0
val dx21 = x2 - x1
val dy21 = y2 - y1
val x3 = x0 + dx10 * f
val y3 = y0 + dy10 * f
val x4 = x1 - dx10 * f
val y4 = y1 - dy10 * f
val x5 = x1 + dx21 * f
val y5 = y1 + dy21 * f
moveOrLineTo(x3, y3)
lineTo(x4, y4)
curveTo(x1, y1, x5, y5)
}
close()
}
return c
}
fun regularPolygonBeveled(sides: Int, bevelFactor: Double = 0.5, center: Vector2 = Vector2.ZERO, radius: Double = 100.0, phase: Double = 0.0): ShapeContour {
val c = contour {
val phi = phase.asRadians
for (i in 0 until sides) {
val x0 = center.x + radius * cos(i.toDouble() / sides * PI * 2 + phi)
val y0 = center.y + radius * sin(i.toDouble() / sides * PI * 2 + phi)
val x1 = center.x + radius * cos((i + 1.0) / sides * PI * 2 + phi)
val y1 = center.y + radius * sin((i + 1.0) / sides * PI * 2 + phi)
val x2 = center.x + radius * cos((i + 2.0) / sides * PI * 2 + phi)
val y2 = center.y + radius * sin((i + 2.0) / sides * PI * 2 + phi)
val f = bevelFactor / 2.0
val dx10 = x1 - x0
val dy10 = y1 - y0
val dx21 = x2 - x1
val dy21 = y2 - y1
val x3 = x0 + dx10 * f
val y3 = y0 + dy10 * f
val x4 = x1 - dx10 * f
val y4 = y1 - dy10 * f
val x5 = x1 + dx21 * f
val y5 = y1 + dy21 * f
moveOrLineTo(x3, y3)
lineTo(x4, y4)
lineTo(x5, y5)
}
close()
}
return c
}

View File

@@ -0,0 +1,83 @@
package org.openrndr.extra.shapes
import org.openrndr.math.Vector2
import org.openrndr.math.asRadians
import org.openrndr.shape.ShapeContour
import org.openrndr.shape.contour
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
fun regularStar(points: Int, innerRadius: Double, outerRadius: Double, center: Vector2 = Vector2.ZERO, phase: Double = 0.0): ShapeContour {
return contour {
val theta = phase.asRadians
val phi = PI * 2.0 / (points * 2)
for (i in 0 until points * 2 step 2) {
val outerPoint = Vector2(cos(i * phi + theta), sin(i * phi + theta)) * outerRadius + center
val innerPoint = Vector2(cos((i + 1) * phi + theta), sin((i + 1) * phi + theta)) * innerRadius + center
moveOrLineTo(outerPoint)
lineTo(innerPoint)
}
close()
}
}
fun regularStarRounded(points: Int, innerRadius: Double, outerRadius: Double,
innerFactor: Double, outerFactor: Double,
center: Vector2 = Vector2.ZERO,
phase: Double = 0.0): ShapeContour {
return contour {
val theta = phase.asRadians
val phi = PI * 2.0 / (points * 2)
for (i in 0 until points * 2 step 2) {
val outerPoint0 = Vector2(cos(i * phi + theta), sin(i * phi + theta)) * outerRadius + center
val innerPoint = Vector2(cos((i + 1) * phi + theta), sin((i + 1) * phi + theta)) * innerRadius + center
val outerPoint1 = Vector2(cos((i + 2) * phi + theta), sin((i + 2) * phi + theta)) * outerRadius + center
val innerPoint1 = Vector2(cos((i + 3) * phi + theta), sin((i + 3) * phi + theta)) * innerRadius + center
val fo = (outerFactor * 0.5)
val fi = (innerFactor * 0.5)
val p0 = innerPoint - (innerPoint - outerPoint0) * fi
val p1 = innerPoint + (outerPoint1 - innerPoint) * fi
val p2 = outerPoint1 - (outerPoint1 - innerPoint) * fo
val p3 = outerPoint1 + (innerPoint1 - outerPoint1) * fo
moveOrLineTo(p0)
curveTo(innerPoint, p1)
lineTo(p2)
curveTo(outerPoint1, p3)
}
close()
}
}
fun regularStarBeveled(points: Int, innerRadius: Double, outerRadius: Double,
innerFactor: Double, outerFactor: Double,
center: Vector2 = Vector2.ZERO,
phase: Double = 0.0): ShapeContour {
return contour {
val theta = phase.asRadians
val phi = PI * 2.0 / (points * 2)
for (i in 0 until points * 2 step 2) {
val outerPoint0 = Vector2(cos(i * phi + theta), sin(i * phi + theta)) * outerRadius + center
val innerPoint = Vector2(cos((i + 1) * phi + theta), sin((i + 1) * phi + theta)) * innerRadius + center
val outerPoint1 = Vector2(cos((i + 2) * phi + theta), sin((i + 2) * phi + theta)) * outerRadius + center
val innerPoint1 = Vector2(cos((i + 3) * phi + theta), sin((i + 3) * phi + theta)) * innerRadius + center
val fo = (outerFactor * 0.5)
val fi = (innerFactor * 0.5)
val p0 = innerPoint - (innerPoint - outerPoint0) * fi
val p1 = innerPoint + (outerPoint1 - innerPoint) * fi
val p2 = outerPoint1 - (outerPoint1 - innerPoint) * fo
val p3 = outerPoint1 + (innerPoint1 - outerPoint1) * fo
moveOrLineTo(p0)
lineTo(p1)
lineTo(p2)
lineTo(p3)
}
close()
}
}

View File

@@ -0,0 +1,48 @@
package org.openrndr.extra.shapes
import org.openrndr.draw.Drawer
import org.openrndr.math.Vector2
import org.openrndr.shape.contour
import kotlin.math.min
class RoundedRectangle(val corner: Vector2, val width: Double, val height: Double, val radius: Double) {
constructor(x: Double, y: Double, width: Double, height: Double, radius: Double) : this(Vector2(x, y), width, height, radius)
/** the center of the rounded rectangle */
val center: Vector2
get() = corner + Vector2(width / 2, height / 2)
val x: Double get() = corner.x
val y: Double get() = corner.y
/** [ShapeContour] representation of the rounded rectangle */
val contour
get() = contour {
// A higher radius than half the width/height makes it go weird
val r = min(min(radius, width / 2), height / 2)
moveTo(x + r, y)
lineTo(x + width - r, y)
curveTo(Vector2(x + width, y), Vector2(x + width, y + r))
lineTo(x + width, y + height - r)
curveTo(Vector2(x + width, y + height), Vector2(x + width - r, y + height))
lineTo(x + r, y + height)
curveTo(Vector2(x, y + height), Vector2(x, y + height - r))
lineTo(x, y + r)
curveTo(Vector2(x, y), Vector2(x + r, y))
close()
}
}
fun Drawer.roundedRectangle(x: Double, y: Double, width: Double, height: Double, radius: Double) =
contour(RoundedRectangle(x, y, width, height, radius).contour)
fun Drawer.roundedRectangle(position: Vector2, width: Double, height: Double, radius: Double) =
contour(RoundedRectangle(position, width, height, radius).contour)
fun Drawer.roundedRectangle(roundedRectangle: RoundedRectangle) =
contour(roundedRectangle.contour)

View File

@@ -0,0 +1,283 @@
package org.openrndr.extra.shapes.drawers
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.*
import org.openrndr.extra.shapes.BezierPatchBase
import org.openrndr.internal.Driver
import org.openrndr.math.Vector2
import org.openrndr.draw.ShadeStyleGLSL.Companion.drawerUniforms
import org.openrndr.draw.ShadeStyleGLSL.Companion.fragmentMainConstants
import org.openrndr.draw.ShadeStyleGLSL.Companion.vertexMainConstants
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.extra.shapes.phrases.BezierPhraseBook
import org.openrndr.extras.color.phrases.ColorPhraseBook
import org.openrndr.extras.color.spaces.ColorOKLABa
import org.openrndr.math.Vector4
import kotlin.jvm.JvmName
private val glslVersion = "410 core"
class BezierPatchDrawer {
private fun vsGenerator(structure: ShadeStructure): String {
return """
|// BezierPatchDrawer.kt / vsGenerator
|#version $glslVersion
|${drawerUniforms()}
|${structure.attributes.orEmpty()}
|${structure.varyingOut.orEmpty()}
|void main() {
| ${vertexMainConstants()}
| vec3 x_normal = vec3(0.0, 0.0, 1.0);
| vec3 x_position = a_position;
| ${structure.varyingBridge}
|}""".trimMargin()
}
private fun fsGenerator(structure: ShadeStructure): String {
return ("""
|// BezierPatchDrawer.kt / fsGenerator
|#version $glslVersion
|${drawerUniforms()}
|${structure.varyingIn.orEmpty()}
|out vec4 o_color;
|void main() {
| vec4 x_fill = u_fill * va_color;
| vec4 x_stroke = u_stroke;
| {
| ${structure.fragmentTransform.orEmpty()}
| }
| o_color = x_fill;
| o_color.rgb *= o_color.a;
}""".trimMargin())
}
private fun fsGeneratorOKLab(structure: ShadeStructure): String {
return ("""
|// BezierPatchDrawer.kt / fsGeneratorOKLab
|#version $glslVersion
|${drawerUniforms()}
|${ColorPhraseBook.oklabToLinearRgb.phrase}
|${ColorPhraseBook.linearRgbToSRgb.phrase}
|${structure.varyingIn.orEmpty()}
|out vec4 o_color;
|void main() {
| ${fragmentMainConstants(instance = "0")}
| vec4 x_fill = u_fill * va_color;
| vec4 x_stroke = u_stroke;
| {
| ${structure.fragmentTransform.orEmpty()}
| }
| o_color = linear_rgb_to_srgb(oklab_to_linear_rgb(x_fill));
| o_color.rgb *= o_color.a;
|}""".trimMargin())
}
private fun tseGenerator(structure: ShadeStructure): String {
BezierPhraseBook.register()
return """
|#version $glslVersion
|
|#pragma import beziers.bezier_patch42
|#pragma import beziers.bezier_patch43
|#pragma import beziers.bezier_patch44
|
|${drawerUniforms()}
|layout(quads, equal_spacing, ccw) in;
|
|in vec3 cva_position[gl_MaxPatchVertices];
|in vec4 cva_color[gl_MaxPatchVertices];
|in vec2 cva_texCoord0[gl_MaxPatchVertices];
|
|${structure.varyingOut.orEmpty()}
|
|void main() {
| va_position = bezier_patch43(cva_position, gl_TessCoord.xy);
| va_color = bezier_patch44(cva_color, gl_TessCoord.xy);
| va_texCoord0 = bezier_patch42(cva_texCoord0, gl_TessCoord.xy);
| gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * vec4(va_position,1.0);
}""".trimMargin().preprocess()
}
private fun tscGenerator(structure: ShadeStructure): String {
return """
|#version $glslVersion
|uniform int u_subdivisions;
|${drawerUniforms()}
|layout(vertices = 16) out; // 16 points per patch
|
|in vec3 va_position[];
|in vec4 va_color[];
|in vec2 va_texCoord0[];
|
|out vec3 cva_position[];
|out vec4 cva_color[];
|out vec2 cva_texCoord0[];
|
|void main() {
| cva_position[gl_InvocationID] = va_position[gl_InvocationID];
| cva_color[gl_InvocationID] = va_color[gl_InvocationID];
| cva_texCoord0[gl_InvocationID] = va_texCoord0[gl_InvocationID];
|
| if (gl_InvocationID == 0) {
| gl_TessLevelOuter[0] = u_subdivisions;
| gl_TessLevelOuter[1] = u_subdivisions;
| gl_TessLevelOuter[2] = u_subdivisions;
| gl_TessLevelOuter[3] = u_subdivisions;
| gl_TessLevelInner[0] = u_subdivisions;
| gl_TessLevelInner[1] = u_subdivisions;
| }
|}""".trimMargin()
}
val shadeStyleManager by lazy {
ShadeStyleManager.fromGenerators(
name = "bezier-patches",
vsGenerator = ::vsGenerator,
tscGenerator = ::tscGenerator,
tseGenerator = ::tseGenerator,
fsGenerator = ::fsGenerator
)
}
val shadeStyleManagerOKLab by lazy {
ShadeStyleManager.fromGenerators(
name = "bezier-patches-oklab",
vsGenerator = ::vsGenerator,
tscGenerator = ::tscGenerator,
tseGenerator = ::tseGenerator,
fsGenerator = ::fsGeneratorOKLab
)
}
var vertices =
vertexBuffer(
vertexFormat {
position(3)
color(4)
textureCoordinate(2)
}, 16, session = Session.root)
internal fun ensureVertexCount(count: Int) {
if (vertices.vertexCount < count) {
vertices.destroy()
vertexBuffer(
vertexFormat {
position(3)
color(4)
textureCoordinate(2)
}, count, session = Session.root)
}
}
fun drawBezierPatches(
context: DrawContext,
drawStyle: DrawStyle,
bezierPatches: List<BezierPatchBase<ColorRGBa>>,
subdivisions: Int = 32
) {
ensureVertexCount(bezierPatches.size * 16)
val shader = shadeStyleManager.shader(
drawStyle.shadeStyle,
listOf(vertices.vertexFormat),
emptyList()
)
vertices.put {
for (bezierPatch in bezierPatches) {
for (j in 0 until 4) {
for (i in 0 until 4) {
write(bezierPatch.points[j][i].xy0)
if (bezierPatch.colors.isEmpty()) {
write(ColorRGBa.WHITE)
} else {
write(bezierPatch.colors[j][i])
}
write(Vector2(i / 3.0, j / 3.0))
}
}
}
}
shader.begin()
shader.uniform("u_subdivisions", subdivisions)
context.applyToShader(shader)
drawStyle.applyToShader(shader)
Driver.instance.setState(drawStyle)
Driver.instance.drawVertexBuffer(
shader,
listOf(vertices),
DrawPrimitive.PATCHES,
0,
16 * bezierPatches.size,
16
)
shader.end()
}
@JvmName("drawBezierPatchOKLab")
fun drawBezierPatches(
context: DrawContext,
drawStyle: DrawStyle,
bezierPatches: List<BezierPatchBase<ColorOKLABa>>,
subdivisions: Int = 32
) {
ensureVertexCount(bezierPatches.size * 16)
val shader = shadeStyleManagerOKLab.shader(
drawStyle.shadeStyle,
listOf(vertices.vertexFormat),
emptyList()
)
vertices.put {
for(bezierPatch in bezierPatches) {
for (j in 0 until 4) {
for (i in 0 until 4) {
write(bezierPatch.points[j][i].xy0)
if (bezierPatch.colors.isEmpty()) {
write(ColorRGBa.WHITE)
} else {
write(bezierPatch.colors[j][i].let {
Vector4(it.l, it.a, it.b, it.alpha)
})
}
write(Vector2(i / 3.0, j / 3.0))
}
}
}
}
shader.begin()
shader.uniform("u_subdivisions", subdivisions)
context.applyToShader(shader)
drawStyle.applyToShader(shader)
Driver.instance.setState(drawStyle)
Driver.instance.drawVertexBuffer(
shader,
listOf(vertices),
DrawPrimitive.PATCHES,
0,
16 * bezierPatches.size,
16
)
shader.end()
}
}
private val Drawer.bezierPatchDrawer: BezierPatchDrawer by lazy { BezierPatchDrawer() }
@JvmName("bezierPatchRGBa")
fun Drawer.bezierPatch(bezierPatch: BezierPatchBase<ColorRGBa>, subdivisions: Int = 32) {
bezierPatchDrawer.drawBezierPatches(context, drawStyle, listOf(bezierPatch), subdivisions)
}
@JvmName("bezierPatchesRGBa")
fun Drawer.bezierPatches(bezierPatch: List<BezierPatchBase<ColorRGBa>>, subdivisions: Int = 32) {
bezierPatchDrawer.drawBezierPatches(context, drawStyle, bezierPatch, subdivisions)
}
@JvmName("bezierPatchOKLAB")
fun Drawer.bezierPatch(bezierPatch: BezierPatchBase<ColorOKLABa>, subdivisions: Int = 32) {
bezierPatchDrawer.drawBezierPatches(context, drawStyle, listOf(bezierPatch), subdivisions)
}
@JvmName("bezierPatchesOKLAB")
fun Drawer.bezierPatches(bezierPatch: List<BezierPatchBase<ColorOKLABa>>, subdivisions: Int = 32) {
bezierPatchDrawer.drawBezierPatches(context, drawStyle, bezierPatch, subdivisions)
}

View File

@@ -0,0 +1,41 @@
package org.openrndr.extra.shapes.operators
import org.openrndr.math.mod_
import org.openrndr.shape.Segment
import org.openrndr.shape.ShapeContour
import org.openrndr.shape.contour
fun ShapeContour.bulgeSegments(distortion: (index: Int, segment: Segment) -> Double): ShapeContour {
val c = contour {
moveTo(position(0.0))
var index = 0
for (segment in this@bulgeSegments.segments) {
when {
segment.linear -> {
val q = segment.quadratic
val d = distortion(index, segment)
curveTo(q.control[0] + segment.normal(0.5) * d, q.end)
index++
}
segment.control.size == 2 -> {
curveTo(segment.control[0], segment.control[1], segment.end)
}
segment.control.size == 1 -> {
curveTo(segment.control[0], segment.end)
}
}
}
if (this@bulgeSegments.closed) {
close()
}
}
return c
}
fun ShapeContour.bulgeSegments(distortion: Double) =
bulgeSegments { _, _ -> distortion }
fun ShapeContour.bulgeSegments(distortion: List<Double>) =
bulgeSegments { index, _ -> distortion[index.mod_(distortion.size)] }

View File

@@ -0,0 +1,172 @@
package org.openrndr.extra.shapes.operators
import org.openrndr.math.Vector2
import org.openrndr.math.mod_
import org.openrndr.shape.*
import kotlin.math.abs
import kotlin.math.sign
import kotlin.math.sqrt
private fun Segment.linearSub(l0: Double, l1: Double): Segment {
return sub(l0 / length, l1 / length)
}
private fun Segment.linearPosition(l: Double): Vector2 {
return position((l / length).coerceIn(0.0, 1.0))
}
private fun pickLength(leftLength: Double, rightLength: Double, s0: Segment, s1: Segment): Double {
val p3 = s1.end
val p2 = s0.end
val p1 = s0.start
val det = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)
return if (det < 0.0) {
leftLength
} else {
rightLength
}
}
/**
* Chamfers corners between linear segments
* @param length the length of the chamfer
* @param angleThreshold the maximum (smallest) angle between between linear segments
* @param chamfer the chamfer function to apply
*/
fun ShapeContour.chamferCorners(
lengths: (index: Int, left: Segment, right: Segment) -> Double,
expands: (index: Int, left: Segment, right: Segment) -> Double = { _, _, _ -> 0.0 },
clip: Boolean = true,
angleThreshold: Double = 180.0,
chamfer: ContourBuilder.(p1: Vector2, p2: Vector2, p3: Vector2) -> Unit
): ShapeContour {
if (segments.size <= 1) {
return this
}
return contour {
val sourceSegments = if (closed) {
(this@chamferCorners.segments + this@chamferCorners.segments.first())
} else {
this@chamferCorners.segments
}
var lengthIndex = sourceSegments.size - 1
sourceSegments.first().let {
if (it.control.size == 1) {
moveTo(position(0.0))
}
if (it.control.size == 2) {
moveTo(position(0.0))
}
if (it.linear) {
if (!this@chamferCorners.closed)
moveTo(position(0.0))
}
}
lengthIndex = 0
for ((s0, s1) in sourceSegments.zipWithNext()) {
lengthIndex++
if (s0.control.size == 1) {
moveOrCurveTo(s0.control[0], s0.end)
} else if (s0.control.size == 2) {
moveOrCurveTo(s0.control[0], s0.control[1], s0.end)
} else if (s0.linear) {
val length = lengths(lengthIndex, s0, s1)
if (s0.linear && s1.linear && (clip || (length <= s0.length / 2 && length <= s1.length / 2))) {
val expand = expands(lengthIndex, s0, s1)
val p0 = s0.linearPosition(s0.length - length)
val p1 = s1.linearPosition(length)
val d = p1 - p0
val q0 = p0 - d * expand
val q1 = p1 + d * expand
moveOrLineTo(q0)
chamfer(q0, s0.end, q1)
} else {
lineTo(s0.end)
}
}
}
// Postlude
if (closed) {
close()
} else {
val last = sourceSegments.last()
when {
last.linear -> {
lineTo(last.end)
}
last.control.size == 1 -> {
curveTo(last.control[0], last.end)
}
last.control.size == 2 -> {
curveTo(last.control[0], last.control[1], last.end)
}
}
}
}
}
fun ShapeContour.bevelCorners(length: Double, angleThreshold: Double = 180.0): ShapeContour =
chamferCorners({ _, _, _ -> length }, angleThreshold = angleThreshold) { _, _, p3 ->
lineTo(p3)
}
fun ShapeContour.roundCorners(length: Double, angleThreshold: Double = 180.0): ShapeContour =
chamferCorners({ _, _, _ -> length }, angleThreshold = angleThreshold) { _, p2, p3 ->
curveTo(p2, p3)
}
fun ShapeContour.arcCorners(lengths: List<Double>,
expands: List<Double> = listOf(0.0),
scales: List<Double> = listOf(1.0),
largeArcs: List<Boolean> = mutableListOf(false),
angleThreshold: Double = 180.0): ShapeContour {
val scaleRing = scales.ring()
val lengthRing = lengths.ring()
val expandRing = expands.ring()
val largeArcRing = largeArcs.ring()
var segmentIndex = 0
return chamferCorners({ index, _, _ -> lengthRing[index] },
{ index, _, _ -> expandRing[index] },
angleThreshold = angleThreshold) { p1, p2, p3 ->
val dx = abs(p3.x - p2.x)
val dy = abs(p3.y - p2.y)
val radius = sqrt(dx * dx + dy * dy)
val det = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)
val scale = scaleRing[segmentIndex]
val sweep = scale * sign(det)
val largeArc = largeArcRing[segmentIndex]
arcTo(radius * abs(scale), radius * abs(scale), 90.0, largeArc, sweep > 0.0, p3)
segmentIndex++
}
}
private class Ring<T>(private val x: List<T>) : List<T> by x {
override operator fun get(index: Int): T {
return x[index.mod_(x.size)]
}
}
private fun <T> List<T>.ring(): List<T> {
return Ring(this)
}

View File

@@ -0,0 +1,88 @@
package org.openrndr.extra.shapes.phrases
import org.openrndr.extra.shaderphrases.ShaderPhrase
import org.openrndr.extra.shaderphrases.ShaderPhraseBook
object BezierPhraseBook: ShaderPhraseBook("beziers") {
val bezier22 = ShaderPhrase("""
|vec2 bezier22(vec2 a, vec2 b, float t) {
| return mix(a, b, t);
|}""".trimMargin())
val bezier32 = ShaderPhrase("""
|#pragma import $bookId.bezier22
|vec2 bezier32(vec2 a, vec2 b, vec2 c, float t) {
| return mix(bezier22(a, b, t), bezier22(b, c, t), t);
|}""".trimMargin())
val bezier42 = ShaderPhrase("""
|#pragma import $bookId.bezier32
|vec2 bezier42(vec2 a, vec2 b, vec2 c, vec2 d, float t) {
| return mix(bezier32(a, b, c, t), bezier32(b, c, d, t), t);
|}""".trimMargin())
val bezier23 = ShaderPhrase("""
|vec3 bezier23(vec3 a, vec3 b, float t) {
| return mix(a, b, t);
|}""".trimMargin())
val bezier33 = ShaderPhrase("""
|#pragma import $bookId.bezier23
|vec3 bezier33(vec3 a, vec3 b, vec3 c, float t) {
| return mix(bezier23(a, b, t), bezier23(b, c, t), t);
|}""".trimMargin())
val bezier43 = ShaderPhrase("""
|#pragma import $bookId.bezier33
|vec3 bezier43(vec3 a, vec3 b, vec3 c, vec3 d, float t) {
| return mix(bezier33(a, b, c, t), bezier33(b, c, d, t), t);
|}""".trimMargin())
val bezier24 = ShaderPhrase("""
|vec4 bezier24(vec4 a, vec4 b, float t) {
| return mix(a, b, t);
|}""".trimMargin())
val bezier34 = ShaderPhrase("""
|#pragma import $bookId.bezier24
|vec4 bezier34(vec4 a, vec4 b, vec4 c, float t) {
| return mix(bezier24(a, b, t), bezier24(b, c, t), t);
|}""".trimMargin())
val bezier44 = ShaderPhrase("""
|#pragma import $bookId.bezier34
|vec4 bezier44(vec4 a, vec4 b, vec4 c, vec4 d, float t) {
| return mix(bezier34(a, b, c, t), bezier34(b, c, d, t), t);
|}""".trimMargin())
val bezierPatch42 = ShaderPhrase("""
|#pragma import $bookId.bezier42
|vec2 bezier_patch42(in vec2[gl_MaxPatchVertices] cps, vec2 uv) {
| vec2 p0 = bezier42(cps[0], cps[1], cps[2], cps[3], uv.x);
| vec2 p1 = bezier42(cps[4], cps[5], cps[6], cps[7], uv.x);
| vec2 p2 = bezier42(cps[8], cps[9], cps[10], cps[11], uv.x);
| vec2 p3 = bezier42(cps[12], cps[13], cps[14], cps[15], uv.x);
| return bezier42(p0, p1, p2, p3, uv.y);
|}""".trimMargin())
val bezierPatch43 = ShaderPhrase("""
|#pragma import $bookId.bezier43
|vec3 bezier_patch43(in vec3[gl_MaxPatchVertices] cps, vec2 uv) {
| vec3 p0 = bezier43(cps[0], cps[1], cps[2], cps[3], uv.x);
| vec3 p1 = bezier43(cps[4], cps[5], cps[6], cps[7], uv.x);
| vec3 p2 = bezier43(cps[8], cps[9], cps[10], cps[11], uv.x);
| vec3 p3 = bezier43(cps[12], cps[13], cps[14], cps[15], uv.x);
| return bezier43(p0, p1, p2, p3, uv.y);
|}""".trimMargin())
val bezierPatch44 = ShaderPhrase("""
|#pragma import $bookId.bezier44
|vec4 bezier_patch44(in vec4[gl_MaxPatchVertices] cps, vec2 uv) {
| vec4 p0 = bezier44(cps[0], cps[1], cps[2], cps[3], uv.x);
| vec4 p1 = bezier44(cps[4], cps[5], cps[6], cps[7], uv.x);
| vec4 p2 = bezier44(cps[8], cps[9], cps[10], cps[11], uv.x);
| vec4 p3 = bezier44(cps[12], cps[13], cps[14], cps[15], uv.x);
| return bezier44(p0, p1, p2, p3, uv.y);
|}""".trimMargin())
}