[orx-shapes] Comment the ContourAdjuster and its demos

This commit is contained in:
Abe Pazos
2025-08-29 13:00:27 +02:00
parent c531c45608
commit 2e6c637b49
9 changed files with 248 additions and 33 deletions

View File

@@ -79,41 +79,113 @@ data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segment
contourAdjuster.updateSelection(newEdge.adjustments)
}
/**
* Convert the edge to a linear edge, truncating control points if those exist
*/
fun toLinear() = wrap { toLinear() }
/**
* Convert the edge to a cubic edge
*/
fun toCubic() = wrap { toCubic() }
/**
* Split the edge at [t]
* @param t an edge t value between 0 and 1. No splitting happens when t == 0 or t == 1.
*/
fun splitAt(t: Double) = wrap { splitAt(t) }
/**
* split edge in [numberOfParts] parts of equal length
* Split the edge in [numberOfParts] parts of equal length
*/
fun splitIn(numberOfParts: Int) = wrap { splitIn(numberOfParts) }
/**
* Creates a new contour edge by applying a translation to the current edge.
*
* @param translation the translation vector to apply to the contour edge.
* @param updateTangents whether the tangents of adjacent segments should be updated after the transformation.
* @return a new instance of the contour edge, transformed by the given translation.
*/
fun moveBy(translation: Vector2, updateTangents: Boolean = true) = wrap { movedBy(translation, updateTangents) }
/**
* Rotates the current edge by a specified angle around an anchor point relative to the edge.
* Optionally updates the tangents of adjacent segments after the rotation.
*
* @param rotationInDegrees the rotation angle in degrees to be applied.
* @param anchorT the relative position along the edge (range 0.0 to 1.0) defining the anchor point of rotation. Defaults to 0.5 (the center of the edge).
* @param updateTangents whether the tangents of adjacent segments should be updated after the rotation. Defaults to true.
*/
fun rotate(rotationInDegrees: Double, anchorT: Double = 0.5, updateTangents: Boolean = true) =
wrap { rotatedBy(rotationInDegrees, anchorT, updateTangents) }
fun scale(scaleFactor: Double, anchorT: Double = 0.5, updateTangents: Boolean = true) =
/**
* Scales the current edge by a specified factor, with an optional anchor point determining the
* scaling center along the edge. The scaling operation updates the tangents of the edge.
*/
fun scale(scaleFactor: Double, anchorT: Double = 0.5) =
wrap { scaledBy(scaleFactor, anchorT, updateTangents = true) }
fun replaceWith(t: Double, updateTangents: Boolean = true) = wrap { replacedWith(t, updateTangents) }
/**
* Replace this edge with a point at [t]
* @param t an edge t value between 0 and 1
*/
fun replaceWith(t: Double) = wrap { replacedWith(t) }
/**
* Replaces the current edge with the segments of an open shape contour.
*
* @param openContour the open shape contour whose segments replace the current edge. The provided
* contour must not be closed.
* @return a new ContourEdge instance with the updated segments from the `openContour`.
*/
fun replaceWith(openContour: ShapeContour) = wrap { replacedWith(openContour) }
fun sub(t0: Double, t1: Double, updateTangents: Boolean = true) {
/**
* Returns part of the edge between [t0] to [t1].
* Preserves topology unless t0 = t1.
* @param t0 the edge's start t-value, between 0 and 1
* @param t1 the edge's end t-value, between 0 and 1
*/
fun sub(t0: Double, t1: Double) {
contourAdjuster.contour =
ContourEdge(contourAdjuster.contour, segmentIndex())
.subbed(t0, t1)
.contour
}
/**
* Moves the starting point of the contour edge by the given translation vector.
*
* @param translation the translation vector to apply to the starting point of the contour edge.
* @param updateTangents whether the tangents of adjacent segments should be updated after the transformation. Defaults to true.
* @return a new instance of the contour edge with the starting point moved by the given translation.
*/
fun moveStartBy(translation: Vector2, updateTangents: Boolean = true) = wrap { startMovedBy(translation, updateTangents) }
/**
* Moves the first control point of a contour edge by a specified translation vector.
*
* @param translation the translation vector to apply to the first control point of the contour edge.
* @return a new instance of the contour edge with the first control point moved by the given translation.
*/
fun moveControl0By(translation: Vector2) = wrap { control0MovedBy(translation) }
/**
* Moves the second control point (Control1) of a contour edge by a specified translation vector.
*
* @param translation the translation vector to apply to the second control point of the contour edge.
* @return a new instance of the contour edge with the second control point moved by the given translation.
*/
fun moveControl1By(translation: Vector2) = wrap { control1MovedBy(translation) }
/**
* Moves the end point of the contour edge by the specified translation vector.
*
* @param translation the translation vector to apply to the end point of the contour edge.
* @param updateTangents whether the tangents of adjacent segments should be updated after the transformation. Defaults to true.
* @return a new instance of the contour edge with the end point moved by the given translation.
*/
fun moveEndBy(translation: Vector2, updateTangents: Boolean = true) = wrap { startMovedBy(translation, updateTangents) }
}

View File

@@ -57,7 +57,7 @@ data class ContourEdge(
}
/**
* convert the edge to a linear edge, truncating control points if those exist
* Convert the edge to a linear edge, truncating control points if those exist
*/
fun toLinear(): ContourEdge {
return if (contour.segments[segmentIndex].type != SegmentType.LINEAR) {
@@ -75,7 +75,7 @@ data class ContourEdge(
}
/**
* convert the edge to a cubic edge
* Convert the edge to a cubic edge
*/
fun toCubic(): ContourEdge {
return if (contour.segments[segmentIndex].type != SegmentType.CUBIC) {
@@ -99,10 +99,10 @@ data class ContourEdge(
/**
* replace this edge with a point at [t]
* Replace this edge with a point at [t]
* @param t an edge t value between 0 and 1
*/
fun replacedWith(t: Double, updateTangents: Boolean): ContourEdge {
fun replacedWith(t: Double): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
@@ -125,6 +125,9 @@ data class ContourEdge(
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex, adjustments)
}
/**
* Split the edge in [numberOfParts] parts of equal length
*/
fun splitIn(parts: Int): ContourEdge {
if (contour.empty || parts < 2) {
return withoutAdjustments()
@@ -140,13 +143,20 @@ data class ContourEdge(
return replacedWith(ShapeContour.fromContours(newSegments, false, 1.0))
}
/**
* Replaces the current edge with the segments of an open shape contour.
*
* @param openContour the open shape contour whose segments replace the current edge. The provided
* contour must not be closed.
* @return a new ContourEdge instance with the updated segments from the `openContour`.
*/
fun replacedWith(openContour: ShapeContour): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
require(!openContour.closed) { "openContour should be open" }
val segment = contour.segments[segmentIndex]
var newSegments = contour.segments.toMutableList()
val newSegments = contour.segments.toMutableList()
var insertIndex = segmentIndex
val adjustments = newSegments.adjust {
@@ -169,11 +179,12 @@ data class ContourEdge(
/**
* subs the edge from [t0] to [t1], preserves topology unless t0 = t1
* @param t0 the start edge t-value, between 0 and 1
* @param t1 the end edge t-value, between 0 and 1
* Returns part of the edge between [t0] to [t1].
* Preserves topology unless t0 = t1.
* @param t0 the edge's start t-value, between 0 and 1
* @param t1 the edge's end t-value, between 0 and 1
*/
fun subbed(t0: Double, t1: Double, updateTangents: Boolean = true): ContourEdge {
fun subbed(t0: Double, t1: Double): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
@@ -195,23 +206,23 @@ data class ContourEdge(
newSegments[segmentIndex] = sub
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex)
} else {
return replacedWith(t0, updateTangents)
return replacedWith(t0)
}
}
/**
* split the edge at [t]
* @param t an edge t value between 0 and 1, will not split when t == 0 or t == 1
* Split the edge at [t]
* @param t An edge t value between 0 and 1. No splitting happens when t == 0 or t == 1.
*/
fun splitAt(t: Double): ContourEdge {
if (contour.empty) {
return withoutAdjustments()
}
val newContour = contour.insertPointAt(segmentIndex, t)
if (newContour.segments.size == contour.segments.size + 1) {
return ContourEdge(newContour, segmentIndex, listOf(SegmentOperation.Insert(segmentIndex + 1, 1)))
return if (newContour.segments.size == contour.segments.size + 1) {
ContourEdge(newContour, segmentIndex, listOf(SegmentOperation.Insert(segmentIndex + 1, 1)))
} else {
return this.copy(adjustments = emptyList())
this.copy(adjustments = emptyList())
}
}
@@ -282,25 +293,58 @@ data class ContourEdge(
return ContourEdge(ShapeContour.fromSegments(newSegments, contour.closed), segmentIndex)
}
/**
* Moves the starting point of the contour edge by the given translation vector.
*
* @param translation the translation vector to apply to the starting point of the contour edge.
* @param updateTangents whether the tangents of adjacent segments should be updated after the transformation. Defaults to true.
* @return a new instance of the contour edge with the starting point moved by the given translation.
*/
fun startMovedBy(translation: Vector2, updateTangents: Boolean = true): ContourEdge =
transformedBy(buildTransform {
translate(translation)
}, updateTangents = updateTangents, mask = maskOf(ControlMask.START))
/**
* Moves the first control point of a contour edge by a specified translation vector.
*
* @param translation the translation vector to apply to the first control point of the contour edge.
* @return a new instance of the contour edge with the first control point moved by the given translation.
*/
fun control0MovedBy(translation: Vector2): ContourEdge = transformedBy(buildTransform {
translate(translation)
}, updateTangents = false, mask = maskOf(ControlMask.CONTROL0), promoteToCubic = true)
/**
* Moves the second control point (Control1) of a contour edge by a specified translation vector.
*
* @param translation the translation vector to apply to the second control point of the contour edge.
* @return a new instance of the contour edge with the second control point moved by the given translation.
*/
fun control1MovedBy(translation: Vector2): ContourEdge = transformedBy(buildTransform {
translate(translation)
}, updateTangents = false, mask = maskOf(ControlMask.CONTROL1), promoteToCubic = true)
/**
* Moves the end point of the contour edge by the specified translation vector.
*
* @param translation the translation vector to apply to the end point of the contour edge.
* @param updateTangents whether the tangents of adjacent segments should be updated after the transformation. Defaults to true.
* @return a new instance of the contour edge with the end point moved by the given translation.
*/
fun endMovedBy(translation: Vector2, updateTangents: Boolean = true): ContourEdge {
return transformedBy(buildTransform {
translate(translation)
}, updateTangents = updateTangents, mask = maskOf(ControlMask.END))
}
/**
* Creates a new contour edge by applying a translation to the current edge.
*
* @param translation the translation vector to apply to the contour edge.
* @param updateTangents whether the tangents of adjacent segments should be updated after the transformation.
* @return a new instance of the contour edge, transformed by the given translation.
*/
fun movedBy(translation: Vector2, updateTangents: Boolean = true): ContourEdge {
return transformedBy(buildTransform {
translate(translation)