[orx-turtle] Add orx-turtle module

This commit is contained in:
Edwin Jakobs
2023-02-02 09:33:06 +01:00
parent 443b7f4155
commit c850ed9c84
9 changed files with 312 additions and 2 deletions

View 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
}

View 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
}