diff --git a/orx-shapes/build.gradle b/orx-shapes/build.gradle index dec3965b..c9088ade 100644 --- a/orx-shapes/build.gradle +++ b/orx-shapes/build.gradle @@ -9,6 +9,8 @@ sourceSets { } dependencies { + implementation(project(":orx-color")) + implementation(project(":orx-shader-phrases")) demoImplementation("org.openrndr:openrndr-core:$openrndrVersion") demoImplementation("org.openrndr:openrndr-extensions:$openrndrVersion") demoRuntimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion") diff --git a/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer01.kt b/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer01.kt new file mode 100644 index 00000000..96a7ad3e --- /dev/null +++ b/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer01.kt @@ -0,0 +1,42 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.extra.shapes.bezierPatch +import org.openrndr.extra.shapes.drawers.bezierPatch +import org.openrndr.shape.Circle + +fun main() { + application { + program { + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + extend { + drawer.clear(ColorRGBa.PINK) + val bp = bezierPatch( + Circle(width/2.0, height/2.0, 200.0).contour + ).withColors( + listOf( + listOf(ColorRGBa.PINK, ColorRGBa.RED, ColorRGBa.BLACK, ColorRGBa.BLUE), + listOf(ColorRGBa.RED, ColorRGBa.BLACK, ColorRGBa.BLUE, ColorRGBa.GREEN), + listOf(ColorRGBa.PINK, ColorRGBa.RED, ColorRGBa.WHITE, ColorRGBa.GREEN), + listOf(ColorRGBa.BLACK, ColorRGBa.WHITE, ColorRGBa.BLACK, ColorRGBa.BLUE), + ) + ) + + drawer.bezierPatch(bp) + + drawer.fill = null + drawer.contour(bp.contour) + for (i in 0 until 10) { + drawer.contour(bp.horizontal(i/9.0)) + } + for (i in 0 until 10) { + drawer.contour(bp.vertical(i/9.0)) + } + } + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer02.kt b/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer02.kt new file mode 100644 index 00000000..56ef2fa3 --- /dev/null +++ b/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer02.kt @@ -0,0 +1,54 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.loadFont +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.extra.shapes.bezierPatch +import org.openrndr.extra.shapes.drawers.bezierPatch +import org.openrndr.extras.color.spaces.toOKLABa +import org.openrndr.shape.Circle + +fun main() { + application { + configure { + width = 720 + height = 720 + } + program { + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + extend { + drawer.clear(ColorRGBa.BLACK) + val bp2 = bezierPatch( + Circle(width/2.0 - 180.0, height/2.0, 170.0).contour + ).withColors( + listOf( + listOf(ColorRGBa.PINK, ColorRGBa.PINK, ColorRGBa.PINK, ColorRGBa.PINK), + listOf(ColorRGBa.RED, ColorRGBa.RED, ColorRGBa.RED, ColorRGBa.RED), + listOf(ColorRGBa.BLUE, ColorRGBa.BLUE, ColorRGBa.BLUE, ColorRGBa.BLUE), + listOf(ColorRGBa.WHITE, ColorRGBa.WHITE, ColorRGBa.WHITE, ColorRGBa.WHITE), + ) + ) + drawer.bezierPatch(bp2) + val bp3 = bezierPatch( + Circle(width/2.0 + 180.0, height/2.0, 170.0).contour + ).withColors( + listOf( + listOf(ColorRGBa.PINK.toOKLABa(), ColorRGBa.PINK.toOKLABa(), ColorRGBa.PINK.toOKLABa(), ColorRGBa.PINK.toOKLABa()), + listOf(ColorRGBa.RED.toOKLABa(), ColorRGBa.RED.toOKLABa(), ColorRGBa.RED.toOKLABa(), ColorRGBa.RED.toOKLABa()), + listOf(ColorRGBa.BLUE.toOKLABa(), ColorRGBa.BLUE.toOKLABa(), ColorRGBa.BLUE.toOKLABa(), ColorRGBa.BLUE.toOKLABa()), + listOf(ColorRGBa.WHITE.toOKLABa(), ColorRGBa.WHITE.toOKLABa(), ColorRGBa.WHITE.toOKLABa(), ColorRGBa.WHITE.toOKLABa()), + ) + ) + drawer.bezierPatch(bp3) + + drawer.fill = ColorRGBa.WHITE + drawer.fontMap = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 16.0) + drawer.text("RGB", width/2.0 - 180.0, height/2.0 + 200.0) + drawer.text("OKLab", width/2.0 + 180.0, height/2.0 + 200.0) + } + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer03.kt b/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer03.kt new file mode 100644 index 00000000..abb2c752 --- /dev/null +++ b/orx-shapes/src/demo/kotlin/DemoBezierPatchDrawer03.kt @@ -0,0 +1,68 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.isolated +import org.openrndr.draw.loadFont +import org.openrndr.extensions.SingleScreenshot +import org.openrndr.extra.shapes.bezierPatch +import org.openrndr.extra.shapes.drawers.bezierPatch +import org.openrndr.extra.shapes.grid +import org.openrndr.extras.color.spaces.toOKLABa +import org.openrndr.math.Vector2 +import org.openrndr.math.Vector3 +import org.openrndr.math.min +import org.openrndr.math.transforms.buildTransform +import org.openrndr.shape.Circle +import org.openrndr.shape.Rectangle +import kotlin.math.min + +fun main() { + application { + configure { + width = 720 + height = 720 + } + program { + if (System.getProperty("takeScreenshot") == "true") { + extend(SingleScreenshot()) { + this.outputFile = System.getProperty("screenshotPath") + } + } + extend { + drawer.clear(ColorRGBa.BLACK) + val colors = listOf( + listOf(ColorRGBa.PINK.toOKLABa(), ColorRGBa.PINK.toOKLABa(), ColorRGBa.PINK.toOKLABa(), ColorRGBa.PINK.toOKLABa()), + listOf(ColorRGBa.RED.toOKLABa(), ColorRGBa.RED.toOKLABa(), ColorRGBa.RED.toOKLABa(), ColorRGBa.RED.toOKLABa()), + listOf(ColorRGBa.BLUE.toOKLABa(), ColorRGBa.BLUE.toOKLABa(), ColorRGBa.BLUE.toOKLABa(), ColorRGBa.BLUE.toOKLABa()), + listOf(ColorRGBa.WHITE.toOKLABa(), ColorRGBa.WHITE.toOKLABa(), ColorRGBa.WHITE.toOKLABa(), ColorRGBa.WHITE.toOKLABa()), + ) + + val grid = drawer.bounds.grid(4,4, marginX = 20.0, marginY = 20.0, gutterX = 10.0, gutterY = 10.0) + + val cellWidth = grid[0][0].width + val cellHeight = grid[0][0].height + + val a = bezierPatch(Rectangle.fromCenter(Vector2(0.0, 0.0), cellWidth, cellHeight).contour) + .withColors(colors) + + val b = bezierPatch( + Circle(0.0, 0.0, min(cellWidth, cellHeight) / 2.0).contour.transform( + buildTransform { + rotate(Vector3.UNIT_Z, 45.0) + } + ) + ).withColors(colors) + + for (y in grid.indices) { + for (x in grid[y].indices) { + val f = (y * grid[y].size + x).toDouble() / (grid.size * grid[y].size - 1.0) + val blend = a * (1.0 - f) + b * f + drawer.isolated { + drawer.translate(grid[y][x].center) + drawer.bezierPatch(blend) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/orx-shapes/src/main/kotlin/BezierPatch.kt b/orx-shapes/src/main/kotlin/BezierPatch.kt index 6b4f79f0..c133f237 100644 --- a/orx-shapes/src/main/kotlin/BezierPatch.kt +++ b/orx-shapes/src/main/kotlin/BezierPatch.kt @@ -1,5 +1,8 @@ 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 @@ -7,27 +10,40 @@ import org.openrndr.shape.Segment import org.openrndr.shape.ShapeContour import kotlin.random.Random -class BezierPatch(val points: List>) { +open class BezierPatchBase( + val points: List>, + val colors: List> = emptyList() +) + where C : AlgebraicColor, 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() = BezierPatch( + 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(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]), + 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) = BezierPatch(points.map { r -> + 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 @@ -124,7 +140,7 @@ class BezierPatch(val points: List>) { /** * Extract a sub-patch based on uv parameterization */ - fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatch { + fun sub(u0: Double, v0: Double, u1: Double, v1: Double): BezierPatchBase { 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) @@ -136,27 +152,69 @@ class BezierPatch(val points: List>) { 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 bezierPatch(d0, d1, d2, d3).transposed + return fromSegments(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) + 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) = BezierPatch(points.map { j -> j.map { i -> i * scale } }) - operator fun div(scale: Double) = BezierPatch(points.map { j -> j.map { i -> i / scale } }) - operator fun plus(right: BezierPatch) = - BezierPatch(List(4) { j -> List(4) { i -> points[j][i] + right.points[j][i] } }) + 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 minus(right: BezierPatch) = - BezierPatch(List(4) { j -> List(4) { i -> points[j][i] - right.points[j][i] } }) + 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) = + 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) = + 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 withColors(colors: List>): BezierPatchBase + where K : AlgebraicColor, K : ConvertibleToColorRGBa { + return BezierPatchBase(points, colors) + } + + companion object { + fun fromSegments(c0: Segment, c1: Segment, c2: Segment, c3: Segment): BezierPatchBase + where C : AlgebraicColor, 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>, colors: List> = emptyList()) : + BezierPatchBase(points, colors) /** * Create a cubic bezier patch from 4 segments. The control points of the segments are used in row-wise fashion @@ -196,10 +254,10 @@ fun bezierPatch(shapeContour: ShapeContour, alpha: Double = 1.0 / 3.0): BezierPa 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), + 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) } diff --git a/orx-shapes/src/main/kotlin/drawers/BezierPatchDrawer.kt b/orx-shapes/src/main/kotlin/drawers/BezierPatchDrawer.kt new file mode 100644 index 00000000..3d3d6f04 --- /dev/null +++ b/orx-shapes/src/main/kotlin/drawers/BezierPatchDrawer.kt @@ -0,0 +1,282 @@ +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 + +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>, + 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>, + 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, subdivisions: Int = 32) { + bezierPatchDrawer.drawBezierPatches(context, drawStyle, listOf(bezierPatch), subdivisions) +} + +@JvmName("bezierPatchesRGBa") +fun Drawer.bezierPatches(bezierPatch: List>, subdivisions: Int = 32) { + bezierPatchDrawer.drawBezierPatches(context, drawStyle, bezierPatch, subdivisions) +} + +@JvmName("bezierPatchOKLAB") +fun Drawer.bezierPatch(bezierPatch: BezierPatchBase, subdivisions: Int = 32) { + bezierPatchDrawer.drawBezierPatches(context, drawStyle, listOf(bezierPatch), subdivisions) +} + +@JvmName("bezierPatchesOKLAB") +fun Drawer.bezierPatches(bezierPatch: List>, subdivisions: Int = 32) { + bezierPatchDrawer.drawBezierPatches(context, drawStyle, bezierPatch, subdivisions) +} \ No newline at end of file diff --git a/orx-shapes/src/main/kotlin/phrases/BezierPhraseBook.kt b/orx-shapes/src/main/kotlin/phrases/BezierPhraseBook.kt new file mode 100644 index 00000000..a2f40421 --- /dev/null +++ b/orx-shapes/src/main/kotlin/phrases/BezierPhraseBook.kt @@ -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()) +} \ No newline at end of file