Add orx-processing module for OPENRNDR and Processing interop

This commit introduces the `orx-processing` module, enabling seamless type conversions between OPENRNDR and Processing. It includes extensions for converting vectors and shapes, supporting interoperability through utilities like `PVector`, `PShape`, and `ShapeContour`.
This commit is contained in:
Edwin Jakobs
2025-04-15 22:41:57 +02:00
parent 3723623cad
commit 498f776493
6 changed files with 332 additions and 0 deletions

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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)
}
}
}

View File

@@ -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<Shape>): 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<Vector2>()
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<Segment2D>()
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<ShapeContour> {
return when (this.family) {
PShape.GROUP -> {
children.flatMap { it.toShapeContours() }
}
PShape.PATH -> {
listOf(toShapeContour())
}
PShape.GEOMETRY -> {
val contourPoints = mutableListOf<MutableList<Vector2>>()
//https://github.com/processing/processing4/blob/d35f4de58936d41946d253f37986127fd100654c/core/src/processing/core/PShape.java#L1772
var codeIndex = 0
var activeContour = mutableListOf<Vector2>()
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())
}

View File

@@ -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())
}

View File

@@ -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",