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:
13
orx-jvm/orx-processing/README.md
Normal file
13
orx-jvm/orx-processing/README.md
Normal 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.
|
||||||
18
orx-jvm/orx-processing/build.gradle.kts
Normal file
18
orx-jvm/orx-processing/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
14
orx-jvm/orx-processing/src/demo/kotlin/DemoPShape01.kt
Normal file
14
orx-jvm/orx-processing/src/demo/kotlin/DemoPShape01.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
219
orx-jvm/orx-processing/src/main/kotlin/PShapeExtensions.kt
Normal file
219
orx-jvm/orx-processing/src/main/kotlin/PShapeExtensions.kt
Normal 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())
|
||||||
|
}
|
||||||
66
orx-jvm/orx-processing/src/main/kotlin/PVectorExtensions.kt
Normal file
66
orx-jvm/orx-processing/src/main/kotlin/PVectorExtensions.kt
Normal 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())
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ include(
|
|||||||
"orx-property-watchers",
|
"orx-property-watchers",
|
||||||
"orx-jvm:orx-panel",
|
"orx-jvm:orx-panel",
|
||||||
"orx-jvm:orx-poisson-fill",
|
"orx-jvm:orx-poisson-fill",
|
||||||
|
"orx-jvm:orx-processing",
|
||||||
"orx-quadtree",
|
"orx-quadtree",
|
||||||
"orx-jvm:orx-rabbit-control",
|
"orx-jvm:orx-rabbit-control",
|
||||||
"orx-jvm:orx-realsense2",
|
"orx-jvm:orx-realsense2",
|
||||||
@@ -71,6 +72,7 @@ include(
|
|||||||
"orx-shade-styles",
|
"orx-shade-styles",
|
||||||
"orx-shapes",
|
"orx-shapes",
|
||||||
"orx-svg",
|
"orx-svg",
|
||||||
|
|
||||||
"orx-jvm:orx-syphon",
|
"orx-jvm:orx-syphon",
|
||||||
"orx-temporal-blur",
|
"orx-temporal-blur",
|
||||||
"orx-timer",
|
"orx-timer",
|
||||||
|
|||||||
Reference in New Issue
Block a user