[orx-turtle] Add orx-turtle module
This commit is contained in:
@@ -29,7 +29,8 @@ def multiplatformModules = [
|
|||||||
"orx-hash-grid",
|
"orx-hash-grid",
|
||||||
"orx-depth-camera",
|
"orx-depth-camera",
|
||||||
"orx-triangulation",
|
"orx-triangulation",
|
||||||
"orx-view-box"
|
"orx-view-box",
|
||||||
|
"orx-turtle"
|
||||||
]
|
]
|
||||||
|
|
||||||
def doNotPublish = ["openrndr-demos"]
|
def doNotPublish = ["openrndr-demos"]
|
||||||
|
|||||||
33
orx-turtle/README.md
Normal file
33
orx-turtle/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# orx-turtle
|
||||||
|
|
||||||
|
Bezier (`ShapeContour`) backed turtle graphics.
|
||||||
|
|
||||||
|
## The turtle language
|
||||||
|
|
||||||
|
The basic turtle language consists of:
|
||||||
|
|
||||||
|
* `rotate(degrees: Double)` to rotate
|
||||||
|
* `forward(distance: Double` to walk forward
|
||||||
|
* `penUp()` to raise the pen
|
||||||
|
* `penDown()` to lower the pen, this will start a new contour
|
||||||
|
|
||||||
|
Orientation/direction and position can be set directly
|
||||||
|
* `position: Vector2` get/set position of the turtle, teleporting the turtle will start a new contour
|
||||||
|
* `direction: Vector2` get/set direction of the turtle, setting direction will compute `orientation`
|
||||||
|
* `orientation: Matrix44` the orientation matrix from which `direction` is evaluated
|
||||||
|
* `isPenDown: Boolean`
|
||||||
|
|
||||||
|
The language also holds some tools to manage the position and orientation of the turtle.
|
||||||
|
|
||||||
|
* `resetOrientation()` will reset the orientation to the default orientation
|
||||||
|
* `push()` push the position and orientation on the stack
|
||||||
|
* `pop()` pop the position and orientation from the stack
|
||||||
|
* `pushOrientation()` push the orientation on the stack
|
||||||
|
* `popOrientation()` pop the orientation on the stack
|
||||||
|
* `pushPosition()` push the position on the stack
|
||||||
|
* `popPosition()` pop the position from the stack
|
||||||
|
|
||||||
|
## The extended turtle language
|
||||||
|
|
||||||
|
* `segment(s: Segment)` to draw a segment with its entrance tangent aligned to the turtle's orientation
|
||||||
|
* `contour(c: ShapeContour)` to draw a contour with its entrance tangent aligned to the turtle's orientation
|
||||||
23
orx-turtle/build.gradle.kts
Normal file
23
orx-turtle/build.gradle.kts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
plugins {
|
||||||
|
org.openrndr.extra.convention.`kotlin-multiplatform`
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
api(libs.openrndr.math)
|
||||||
|
api(libs.openrndr.shape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val jvmDemo by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-shapes"))
|
||||||
|
implementation(project(":orx-noise"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
orx-turtle/src/commonMain/kotlin/NinjaTurtle.kt
Normal file
51
orx-turtle/src/commonMain/kotlin/NinjaTurtle.kt
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package org.openrndr.extra.turtle
|
||||||
|
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.transforms.buildTransform
|
||||||
|
import org.openrndr.shape.Segment
|
||||||
|
import org.openrndr.shape.ShapeContour
|
||||||
|
|
||||||
|
fun Turtle.contour(contour: ShapeContour, alignTangent: Boolean = true) {
|
||||||
|
if (!contour.empty) {
|
||||||
|
val align = segment(contour.segments.first(), alignTangent)
|
||||||
|
for (segment in contour.segments.drop(1)) {
|
||||||
|
segment(segment, alignTangent = false, externalAlignTransform = align)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Turtle.segment(
|
||||||
|
segment: Segment,
|
||||||
|
alignTangent: Boolean = true,
|
||||||
|
externalAlignTransform: Matrix44 = Matrix44.IDENTITY
|
||||||
|
): Matrix44 {
|
||||||
|
var segment0 = segment.transform(buildTransform {
|
||||||
|
translate(-segment.start)
|
||||||
|
})
|
||||||
|
|
||||||
|
var alignTransform = externalAlignTransform
|
||||||
|
|
||||||
|
if (alignTangent) {
|
||||||
|
val n = -segment0.normal(0.0)
|
||||||
|
val t = n.perpendicular()
|
||||||
|
val m = Matrix44.fromColumnVectors(t.xy00, n.xy00, Vector4.UNIT_Z, Vector4.UNIT_W)
|
||||||
|
alignTransform = orientation * m.inversed
|
||||||
|
}
|
||||||
|
|
||||||
|
segment0 = segment0.transform(buildTransform {
|
||||||
|
translate(position)
|
||||||
|
multiply(alignTransform)
|
||||||
|
})
|
||||||
|
|
||||||
|
require(position.distanceTo(segment0.start) < 1E-5) {
|
||||||
|
"""${position}, ${segment0.start}"""
|
||||||
|
}
|
||||||
|
cb.segment(segment0)
|
||||||
|
orientation = cb.segments.last().pose(1.0).matrix33.matrix44
|
||||||
|
|
||||||
|
if (!isPenDown) {
|
||||||
|
cb.segments.removeLastOrNull()
|
||||||
|
}
|
||||||
|
return alignTransform
|
||||||
|
}
|
||||||
96
orx-turtle/src/commonMain/kotlin/Turtle.kt
Normal file
96
orx-turtle/src/commonMain/kotlin/Turtle.kt
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package org.openrndr.extra.turtle
|
||||||
|
|
||||||
|
import org.openrndr.math.*
|
||||||
|
import org.openrndr.math.transforms.rotateZ
|
||||||
|
import org.openrndr.shape.*
|
||||||
|
|
||||||
|
class Turtle(initialPosition: Vector2) {
|
||||||
|
val cb = ContourBuilder(multipleContours = true).apply {
|
||||||
|
moveTo(initialPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
var orientation = Matrix44.fromColumnVectors(Vector4.UNIT_X, -Vector4.UNIT_Y, Vector4.UNIT_Z, Vector4.UNIT_W)
|
||||||
|
private val orientationStack = ArrayDeque<Matrix44>()
|
||||||
|
fun pushOrientation() {
|
||||||
|
orientationStack.addLast(orientation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popOrientation() {
|
||||||
|
orientation = orientationStack.removeLastOrNull() ?: error("orientation stack underflow")
|
||||||
|
}
|
||||||
|
|
||||||
|
var position: Vector2
|
||||||
|
get() {
|
||||||
|
return cb.cursor
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
cb.moveTo(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val positionStack = ArrayDeque<Vector2>()
|
||||||
|
fun pushPosition() {
|
||||||
|
positionStack.addLast(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popPosition() {
|
||||||
|
position = positionStack.removeLastOrNull() ?: error("position stack underflow")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
cb.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetOrientation() {
|
||||||
|
orientation = Matrix44.fromColumnVectors(Vector4.UNIT_X, -Vector4.UNIT_Y, Vector4.UNIT_Z, Vector4.UNIT_W)
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction: Vector2
|
||||||
|
get() = (orientation * Vector4.UNIT_X).xy
|
||||||
|
set(value) {
|
||||||
|
val directionNormalized = value.normalized
|
||||||
|
orientation = Matrix44.fromColumnVectors(
|
||||||
|
directionNormalized.xy00,
|
||||||
|
directionNormalized.perpendicular().xy00,
|
||||||
|
Vector4.UNIT_Z,
|
||||||
|
Vector4.UNIT_W
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPenDown = true
|
||||||
|
|
||||||
|
fun penUp() {
|
||||||
|
isPenDown = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun penDown() {
|
||||||
|
isPenDown = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun push() {
|
||||||
|
pushOrientation()
|
||||||
|
pushPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pop() {
|
||||||
|
popPosition()
|
||||||
|
popOrientation()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rotate(degrees: Double) {
|
||||||
|
orientation *= Matrix44.rotateZ(degrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forward(distance: Double) {
|
||||||
|
if (distance >= 1E-6) {
|
||||||
|
if (isPenDown) {
|
||||||
|
cb.lineTo(position + direction * distance)
|
||||||
|
} else {
|
||||||
|
cb.moveTo(position + direction * distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun turtle(initalPosition: Vector2, program: Turtle.() -> Unit): List<ShapeContour> {
|
||||||
|
return Turtle(initalPosition).apply(program).cb.result
|
||||||
|
}
|
||||||
40
orx-turtle/src/jvmDemo/kotlin/DemoTurtle01.kt
Normal file
40
orx-turtle/src/jvmDemo/kotlin/DemoTurtle01.kt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Drawing a square using the turtle interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.turtle.turtle
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
application {
|
||||||
|
program {
|
||||||
|
// turtle returns List<ShapeContour>
|
||||||
|
val contours = turtle(drawer.bounds.center) {
|
||||||
|
penUp()
|
||||||
|
forward(50.0)
|
||||||
|
rotate(90.0)
|
||||||
|
forward(50.0)
|
||||||
|
penDown()
|
||||||
|
|
||||||
|
rotate(90.0)
|
||||||
|
forward(100.0)
|
||||||
|
|
||||||
|
rotate(90.0)
|
||||||
|
forward(100.0)
|
||||||
|
|
||||||
|
rotate(90.0)
|
||||||
|
forward(100.0)
|
||||||
|
|
||||||
|
rotate(90.0)
|
||||||
|
forward(100.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
extend {
|
||||||
|
// draw the contours
|
||||||
|
drawer.stroke = ColorRGBa.PINK
|
||||||
|
drawer.contours(contours)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
orx-turtle/src/jvmDemo/kotlin/DemoTurtle02.kt
Normal file
28
orx-turtle/src/jvmDemo/kotlin/DemoTurtle02.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
A simple random walk made using the turtle interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.noise.uniform
|
||||||
|
import org.openrndr.extra.turtle.turtle
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
application {
|
||||||
|
program {
|
||||||
|
val r = Random(40)
|
||||||
|
val contours = turtle(drawer.bounds.center + Vector2(-50.0, 50.0)) {
|
||||||
|
for (i in 0 until 500) {
|
||||||
|
rotate(Double.uniform(-90.0, 90.0, r))
|
||||||
|
forward(Double.uniform(10.0, 40.0, r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extend {
|
||||||
|
drawer.stroke = ColorRGBa.PINK
|
||||||
|
drawer.contours(contours)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
orx-turtle/src/jvmDemo/kotlin/DemoTurtle03.kt
Normal file
37
orx-turtle/src/jvmDemo/kotlin/DemoTurtle03.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Drawing shape contours aligned to the turtle's orientation.
|
||||||
|
*/
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.turtle.contour
|
||||||
|
import org.openrndr.extra.turtle.turtle
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.shape.Circle
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
application {
|
||||||
|
program {
|
||||||
|
// turtle returns List<ShapeContour>
|
||||||
|
val contours = turtle(drawer.bounds.center + Vector2(-100.0, 100.0)) {
|
||||||
|
forward(100.0)
|
||||||
|
|
||||||
|
// let the turtle draw a full a circle
|
||||||
|
val circle0 = Circle(Vector2.ZERO, 100.0)
|
||||||
|
contour(circle0.contour)
|
||||||
|
|
||||||
|
// let the turtle draw a half circle
|
||||||
|
val circle1 = Circle(Vector2.ZERO, 50.0)
|
||||||
|
contour(circle1.contour.sub(0.0, 0.5))
|
||||||
|
|
||||||
|
// let the turtle draw another half circle
|
||||||
|
val circle2 = Circle(Vector2.ZERO, 25.0)
|
||||||
|
contour(circle2.contour.sub(0.0, 0.5))
|
||||||
|
}
|
||||||
|
extend {
|
||||||
|
// draw the contours
|
||||||
|
drawer.stroke = ColorRGBa.PINK
|
||||||
|
drawer.contours(contours)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,6 +83,7 @@ include(
|
|||||||
"orx-jvm:orx-video-profiles",
|
"orx-jvm:orx-video-profiles",
|
||||||
"orx-depth-camera",
|
"orx-depth-camera",
|
||||||
"orx-jvm:orx-depth-camera-calibrator",
|
"orx-jvm:orx-depth-camera-calibrator",
|
||||||
"orx-view-box"
|
"orx-view-box",
|
||||||
|
"orx-turtle"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user