[orx-turtle] Add orx-turtle module
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user