[orx-shapes] convert to MPP
This commit is contained in:
301
orx-shapes/src/commonMain/kotlin/BezierPatch.kt
Normal file
301
orx-shapes/src/commonMain/kotlin/BezierPatch.kt
Normal 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)
|
||||
}
|
||||
43
orx-shapes/src/commonMain/kotlin/RectangleGrid.kt
Normal file
43
orx-shapes/src/commonMain/kotlin/RectangleGrid.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
99
orx-shapes/src/commonMain/kotlin/RegularPolygon.kt
Normal file
99
orx-shapes/src/commonMain/kotlin/RegularPolygon.kt
Normal 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
|
||||
}
|
||||
83
orx-shapes/src/commonMain/kotlin/RegularStar.kt
Normal file
83
orx-shapes/src/commonMain/kotlin/RegularStar.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
48
orx-shapes/src/commonMain/kotlin/RoundedRectangle.kt
Normal file
48
orx-shapes/src/commonMain/kotlin/RoundedRectangle.kt
Normal 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)
|
||||
283
orx-shapes/src/commonMain/kotlin/drawers/BezierPatchDrawer.kt
Normal file
283
orx-shapes/src/commonMain/kotlin/drawers/BezierPatchDrawer.kt
Normal 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)
|
||||
}
|
||||
41
orx-shapes/src/commonMain/kotlin/operators/BulgeContours.kt
Normal file
41
orx-shapes/src/commonMain/kotlin/operators/BulgeContours.kt
Normal 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)] }
|
||||
172
orx-shapes/src/commonMain/kotlin/operators/ChamferCorners.kt
Normal file
172
orx-shapes/src/commonMain/kotlin/operators/ChamferCorners.kt
Normal 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)
|
||||
}
|
||||
88
orx-shapes/src/commonMain/kotlin/phrases/BezierPhraseBook.kt
Normal file
88
orx-shapes/src/commonMain/kotlin/phrases/BezierPhraseBook.kt
Normal 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())
|
||||
}
|
||||
Reference in New Issue
Block a user