package com.martianwabbit import org.openrndr.math.Vector2 import org.openrndr.shape.* import java.io.File import java.util.logging.Logger import kotlin.math.pow import kotlin.math.sqrt val logger = Logger.getLogger("") fun Composition.saveToHPGL(file: File) { HPGL().generateFromComposition(this) } class HPGL(pageSize: PageSize = PageSize.A4) { fun generateFromComposition(composition: Composition) { composition.root.traverse { shape -> if (shape == Stage.Before) { if (this is ShapeNode) { this.shape.contours.forEach { shape -> val i = shape.sampleEquidistant(200) val rr = StringBuilder() for (v in i.segments) { rr.appendln("${v.start.x}, ${v.start.y}") } calculateSpline(shape.segments) } } } } } } private fun Vector2.distanceTo(point: Vector2): Double { return sqrt((point.x - this.x).pow(2.0) + (point.y - this.y).pow(2.0)) } enum class PageSize { A4, A3 } private enum class Stage { Before, After } private fun CompositionNode.traverse(cb: CompositionNode.(stage: Stage) -> Unit) { this.cb(Stage.Before) if (this is GroupNode) { this.children.forEach { it.traverse(cb) } } } // These default values correspond to a centripetal Catmull-Rom spline private fun calculateSpline(segments: List, resolution: Int = 100, alpha: Double = .5, tension: Double = .0) { val result = mutableListOf() segments.forEach { val points = mutableListOf() points.add(it.start) points.addAll(it.control) points.add(it.end) when(points.size) { 4 -> { // Catmull-Rom val t0 = 0.0 val t1 = t0 + points[0].distanceTo(points[1]).pow(alpha) val t2 = t1 + points[1].distanceTo(points[2]).pow(alpha) val t3 = t2 + points[2].distanceTo(points[3]).pow(alpha) val p0 = points[0] val p1 = points[1] val p2 = points[2] val p3 = points[3] val m1 = ((p1 - p0) / (t1 - t0) - (p2 - p0) / (t2 - t0) + (p2 - p1) / (t2 - t1)) * (1.0 - tension) * (t2 - t1) val m2 = ((p2 - p1) / (t2 - t1) - (p3 - p1) / (t3 - t1) + (p3 - p2) / (t3 - t2)) * (1.0 - tension) * (t2 - t1) val a = (p1 - p2) + m1 + m2 * 2.0 val b = ((p1 - p2) * -3.0) - m1 - m1 - m2 for (i in 0..100 step 10) { val t = (i / 100).toDouble() val r = a * t.pow(3) + b * t.pow(2) + m1 * t.pow(1) + p1 result.add(r) } } } } val rr = StringBuilder() for (v in result) { rr.appendln("${v.x}, ${v.y}") } val a = Vector2(0.0, 10.0).distanceTo(Vector2(13.0, 12.0)) logger.info("done") }