diff --git a/orx-jvm/orx-processing/README.md b/orx-jvm/orx-processing/README.md new file mode 100644 index 00000000..648c2de0 --- /dev/null +++ b/orx-jvm/orx-processing/README.md @@ -0,0 +1,13 @@ +# orx-processing + +orx-processing is a module designed to facilitate seamless type conversions +between Processing's types and OPENRNDR's types. It provides utilities and +methods that allow developers to integrate the two graphics frameworks +effectively by bridging the gap between their respective data structures. + +For example, orx-processing enables you to: + - Convert Processing's PVector to OPENRNDR's Vector2 or Vector3. + - Transform OPENRNDR Shape and ShapeContour into their Processing equivalents. + +This module is particularly useful in projects that require the features or +APIs of both Processing and OPENRNDR, simplifying interoperability and reducing boilerplate code for type translation. \ No newline at end of file diff --git a/orx-jvm/orx-processing/build.gradle.kts b/orx-jvm/orx-processing/build.gradle.kts new file mode 100644 index 00000000..6d1a0a65 --- /dev/null +++ b/orx-jvm/orx-processing/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + org.openrndr.extra.convention.`kotlin-jvm` +} + +dependencies { +// api(project(":orx-parameters")) +// api(project(":orx-jvm:orx-panel")) +// api(libs.minim) { +// exclude(group = "org.apache.maven.plugins", module = "maven-javadoc-plugin") +// } + api(libs.processing.core) { + exclude(group = "com.jogamp") + } + implementation(libs.openrndr.application) + implementation(libs.openrndr.math) + implementation(libs.kotlin.reflect) + demoRuntimeOnly(libs.slf4j.simple) +} \ No newline at end of file diff --git a/orx-jvm/orx-processing/src/demo/kotlin/DemoPShape01.kt b/orx-jvm/orx-processing/src/demo/kotlin/DemoPShape01.kt new file mode 100644 index 00000000..51ba5ffa --- /dev/null +++ b/orx-jvm/orx-processing/src/demo/kotlin/DemoPShape01.kt @@ -0,0 +1,14 @@ +import org.openrndr.application +import org.openrndr.extra.processing.PShape +import org.openrndr.extra.processing.toShape + +fun main() = application { + program { + val c = drawer.bounds.offsetEdges(-100.0).shape + val ps = PShape(c) + val rc = ps.toShape() + extend { + drawer.shape(rc) + } + } +} \ No newline at end of file diff --git a/orx-jvm/orx-processing/src/main/kotlin/PShapeExtensions.kt b/orx-jvm/orx-processing/src/main/kotlin/PShapeExtensions.kt new file mode 100644 index 00000000..507f28f6 --- /dev/null +++ b/orx-jvm/orx-processing/src/main/kotlin/PShapeExtensions.kt @@ -0,0 +1,219 @@ +package org.openrndr.extra.processing + +import org.openrndr.color.ColorRGBa +import org.openrndr.math.Vector2 +import org.openrndr.shape.Segment2D +import org.openrndr.shape.SegmentType +import org.openrndr.shape.Shape +import org.openrndr.shape.ShapeContour +import processing.core.PShape + +/** + * Appends a vertex to the current shape using a 2D vector. + * + * @param v the [Vector2] instance containing the x and y coordinates of the vertex to be added. + * The coordinates are converted to Float for use in the shape. + */ +fun PShape.vertex(v: Vector2) { + vertex(v.x.toFloat(), v.y.toFloat()) +} + +/** + * Adds a quadratic Bezier vertex to the shape. The method specifies control and anchor points + * for the curve using [Vector2] instances. + * + * @param v2 The control point of the quadratic Bezier curve, used to define its curvature. + * @param v3 The anchor point of the quadratic Bezier curve, which is the endpoint of the curve. + */ +fun PShape.quadraticVertex(v2: Vector2, v3: Vector2) { + quadraticVertex( + v2.x.toFloat(), v2.y.toFloat(), + v3.x.toFloat(), v3.y.toFloat() + ) +} + +fun PShape.fill(c: ColorRGBa) { + fill(c.r.toFloat() * 255.0f, c.g.toFloat() * 255.0f, c.b.toFloat() * 255.0f, c.alpha.toFloat() * 255.0f) +} + +fun PShape.stroke(c: ColorRGBa) { + stroke(c.r.toFloat() * 255.0f, c.g.toFloat() * 255.0f, c.b.toFloat() * 255.0f, c.alpha.toFloat() * 255.0f) +} + +fun PShape.bezierVertex(v2: Vector2, v3: Vector2, v4: Vector2) { + bezierVertex( + v2.x.toFloat(), v2.y.toFloat(), + v3.x.toFloat(), v3.y.toFloat(), + v4.x.toFloat(), v4.y.toFloat() + ) +} + +fun PShape(shapes: List): PShape { + val ps = PShape(PShape.GROUP) + for (shape in shapes) { + ps.addChild(PShape(shape)) + } + return ps +} + +fun PShape(shape: Shape): PShape { + if (shape.contours.size == 1) { + return PShape(shape.contours[0]) + } else { + val ps = PShape(PShape.PATH) + ps.beginShape() + for (contour in shape.contours) { + ps.beginContour() + ps.vertex(contour.segments[0].start) + for (segment in contour.segments) { + when (segment.type) { + SegmentType.LINEAR -> ps.vertex(segment.end) + SegmentType.QUADRATIC -> ps.quadraticVertex(segment.control[0], segment.end) + SegmentType.CUBIC -> ps.bezierVertex(segment.control[0], segment.control[1], segment.end) + } + } + ps.endContour() + } + ps.endShape(PShape.CLOSE) + return ps + } +} + +fun PShape(contour: ShapeContour): PShape { + val ps = PShape(PShape.PATH) + if (!contour.empty) { + ps.beginShape() + ps.vertex(contour.segments[0].start) + for (segment in contour.segments) { + when (segment.type) { + SegmentType.LINEAR -> ps.vertex(segment.end) + SegmentType.QUADRATIC -> ps.quadraticVertex(segment.control[0], segment.end) + SegmentType.CUBIC -> { + ps.bezierVertex(segment.control[0], segment.control[1], segment.end) + } + } + } + ps.endShape(if (contour.closed) PShape.CLOSE else PShape.OPEN) + } + return ps +} + + +/** + * Converts a [PShape] of type `PATH` into a [ShapeContour]. + * + * This function processes the vertices and vertex codes of the `PShape` to construct a + * corresponding [ShapeContour]. The function supports vertex types including `VERTEX`, + * `BEZIER_VERTEX`, and `QUADRATIC_VERTEX`. Other vertex codes will result in an error. + * The contour will reflect whether the `PShape` is closed or open. + * + * @return A [ShapeContour] that represents the geometry of the given `PShape.PATH`. + * @throws IllegalArgumentException if the `PShape` is not of type `PATH`. + * @throws IllegalStateException for unsupported vertex codes. + */ +fun PShape.toShapeContour(): ShapeContour { + require(family == PShape.PATH) { + "can only convert PShape.PATH to ShapeContour" + } + if (vertexCodeCount == 0) { + val vertices = mutableListOf() + for (i in 0 until vertexCount) { + val pv = getVertex(i) + vertices.add(pv.toVector2()) + } + val contour = ShapeContour.fromPoints(vertices, isClosed) + return contour + } else { + val segments = mutableListOf() + var vertexIndex = 0 + var vertex = getVertex(vertexIndex).toVector2() + vertexIndex++ + for (i in 1 until vertexCodeCount) { + val code = vertexCodes[i] + when (code) { + PShape.VERTEX -> { + val pv = getVertex(vertexIndex).toVector2() + vertexIndex++ + segments.add(Segment2D(vertex, pv)) + vertex = pv + } + PShape.BEZIER_VERTEX -> { + val c0 = getVertex(vertexIndex).toVector2(); vertexIndex++ + val c1 = getVertex(vertexIndex).toVector2(); vertexIndex++ + val pv = getVertex(vertexIndex).toVector2(); vertexIndex++ + segments.add(Segment2D(vertex, c0, c1, pv)) + vertex = pv + } + PShape.QUADRATIC_VERTEX -> { + val c0 = getVertex(vertexIndex).toVector2(); vertexIndex++ + val pv = getVertex(vertexIndex).toVector2(); vertexIndex++ + segments.add(Segment2D(vertex, c0, pv)) + vertex = pv + } + else -> error("unsupported code $code") + } + } + val contour = ShapeContour(segments, closed = isClosed) + return contour + } +} + +/** + * Converts this [PShape] instance into a list of [ShapeContour] objects. + * + * This function processes the shape based on its family type: + * - If the shape is a `GROUP`, it recursively converts its children into contours. + * - If the shape is a `PATH`, it converts it directly to a single [ShapeContour]. + * - If the shape is `GEOMETRY`, it constructs contours based on vertex information. + * + * Unsupported shape families will throw an error. + * + * @return A list of [ShapeContour] objects representing the contours of this [PShape]. + */ +fun PShape.toShapeContours(): List { + return when (this.family) { + PShape.GROUP -> { + children.flatMap { it.toShapeContours() } + } + + PShape.PATH -> { + listOf(toShapeContour()) + } + + PShape.GEOMETRY -> { + val contourPoints = mutableListOf>() + //https://github.com/processing/processing4/blob/d35f4de58936d41946d253f37986127fd100654c/core/src/processing/core/PShape.java#L1772 + + var codeIndex = 0 + var activeContour = mutableListOf() + for (i in 0 until vertexCount) { + if (vertexCodes[codeIndex++] == PShape.BREAK) { + contourPoints.add(activeContour) + activeContour = mutableListOf() + codeIndex++ + } + + // activeContour.add(getVertex(i).toVector2()) + } + if (activeContour.isNotEmpty()) { + contourPoints.add(activeContour) + } + //Shape(contourPoints.map { ShapeContour.fromPoints(it, false) }) + contourPoints.map { ShapeContour.fromPoints(it, false) } + } + + else -> error("unsupported shape family: ${this.family}") + } +} + +/** + * Converts this [PShape] instance into a [Shape] instance. + * + * This function processes the contours of the [PShape] and transforms them into + * the corresponding contours of a [Shape] object. + * + * @return A [Shape] object representing the converted [PShape]. + */ +fun PShape.toShape(): Shape { + return Shape(toShapeContours()) +} \ No newline at end of file diff --git a/orx-jvm/orx-processing/src/main/kotlin/PVectorExtensions.kt b/orx-jvm/orx-processing/src/main/kotlin/PVectorExtensions.kt new file mode 100644 index 00000000..5492fa67 --- /dev/null +++ b/orx-jvm/orx-processing/src/main/kotlin/PVectorExtensions.kt @@ -0,0 +1,66 @@ +package org.openrndr.extra.processing + +import org.openrndr.math.Vector2 +import org.openrndr.math.Vector3 +import processing.core.PVector + +/** + * Converts a given `Vector2` instance into a `PVector` instance. + * + * @param v the source Vector2 whose x and y coordinates will be used to create the PVector. + * @return a new PVector instance initialized with the x and y components of the given Vector2, + * cast to Float. + */ +fun PVector(v: Vector2): PVector = PVector(v.x.toFloat(), v.y.toFloat()) + +/** + * Converts a `Vector3` object into a `PVector` instance by converting its components to `Float`. + * + * @param v the `Vector3` instance to convert + * @return a `PVector` instance with corresponding x, y, and z components in `Float` + */ +fun PVector(v: Vector3): PVector = PVector(v.x.toFloat(), v.y.toFloat(), v.z.toFloat()) + +/** + * Converts an instance of [Vector2] to a [PVector] by transforming its x and y values + * into floating-point numbers. + * + * @receiver The [Vector2] instance to be converted. + * @return A new [PVector] containing the x and y components of the receiver as floats. + */ +fun Vector2.toPVector() = PVector(this.x.toFloat(), this.y.toFloat()) + +/** + * Converts a [Vector3] instance to a [PVector] instance. + * + * Each component of the [Vector3] (x, y, z) is cast to a float and used to + * construct a new [PVector]. + * + * @receiver The [Vector3] to be converted. + * @return A [PVector] with the corresponding float components. + */ +fun Vector3.toPVector() = PVector(this.x.toFloat(), this.y.toFloat(), this.z.toFloat()) + + +/** + * Converts this [PVector] instance into a [Vector2] instance. + * + * The `x` and `y` components of the [PVector] are converted to `Double` and used + * to create a new [Vector2]. + * + * @return A [Vector2] instance with the `x` and `y` components of this [PVector] + * converted to `Double`. + */ +fun PVector.toVector2(): Vector2 { + return Vector2(x.toDouble(), y.toDouble()) +} + +/** + * Converts a [PVector] into an [Vector3] instance. + * + * @return a [Vector3] object with the same x, y, and z values as the original [PVector], + * converted to Double. + */ +fun PVector.toVector3(): Vector3 { + return Vector3(x.toDouble(), y.toDouble(), z.toDouble()) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9130ef44..5a2bc79f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,6 +61,7 @@ include( "orx-property-watchers", "orx-jvm:orx-panel", "orx-jvm:orx-poisson-fill", + "orx-jvm:orx-processing", "orx-quadtree", "orx-jvm:orx-rabbit-control", "orx-jvm:orx-realsense2", @@ -71,6 +72,7 @@ include( "orx-shade-styles", "orx-shapes", "orx-svg", + "orx-jvm:orx-syphon", "orx-temporal-blur", "orx-timer",