[orx-shapes] Add circle inversion primitives and demo examples

This commit is contained in:
Edwin Jakobs
2025-08-15 22:01:32 +02:00
parent 25d0302a68
commit c44175c1c9
5 changed files with 374 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
import org.openrndr.math.Vector2
import org.openrndr.shape.Circle
import org.openrndr.extra.shapes.primitives.invert
import kotlin.math.abs
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class TestCircleInvert {
@Test
fun testInvertPointOutsideCircle() {
val circle = Circle(100.0, 100.0, 50.0)
val point = Vector2(200.0, 100.0) // Point outside the circle
val inverted = circle.invert(point)
// The inverted point should be at (125.0, 100.0)
// This is because:
// - The point is 100 units away from the center
// - The radius is 50
// - The inverted distance is 50²/100 = 25
// - So the inverted point is 25 units from the center in the same direction
assertEquals(125.0, inverted.x, 1e-10)
assertEquals(100.0, inverted.y, 1e-10)
// Verify the inversion property: |OPʹ| × |OP| = r²
val distanceToPoint = (point - circle.center).length
val distanceToInverted = (inverted - circle.center).length
assertTrue(abs(distanceToPoint * distanceToInverted - circle.radius * circle.radius) < 1e-10)
}
@Test
fun testInvertPointInsideCircle() {
val circle = Circle(100.0, 100.0, 50.0)
val point = Vector2(125.0, 100.0) // Point inside the circle
val inverted = circle.invert(point)
// The inverted point should be at (200.0, 100.0)
// This is because:
// - The point is 25 units away from the center
// - The radius is 50
// - The inverted distance is 50²/25 = 100
// - So the inverted point is 100 units from the center in the same direction
assertEquals(200.0, inverted.x, 1e-10)
assertEquals(100.0, inverted.y, 1e-10)
// Verify the inversion property: |OPʹ| × |OP| = r²
val distanceToPoint = (point - circle.center).length
val distanceToInverted = (inverted - circle.center).length
assertTrue(abs(distanceToPoint * distanceToInverted - circle.radius * circle.radius) < 1e-10)
}
@Test
fun testInvertPointOnCircle() {
val circle = Circle(100.0, 100.0, 50.0)
val point = Vector2(150.0, 100.0) // Point on the circle
val inverted = circle.invert(point)
// The inverted point should be the same as the original point
// This is because points on the circle invert to themselves
assertEquals(150.0, inverted.x, 1e-10)
assertEquals(100.0, inverted.y, 1e-10)
// Verify the inversion property: |OPʹ| × |OP| = r²
val distanceToPoint = (point - circle.center).length
val distanceToInverted = (inverted - circle.center).length
assertTrue(abs(distanceToPoint * distanceToInverted - circle.radius * circle.radius) < 1e-10)
}
@Test
fun testInvertPointAtCenter() {
val circle = Circle(100.0, 100.0, 50.0)
val point = Vector2(100.0, 100.0) // Point at the center
// Inverting a point at the center should throw an exception
assertFailsWith<IllegalArgumentException> {
circle.invert(point)
}
}
}

View File

@@ -0,0 +1,83 @@
import org.openrndr.math.Vector2
import org.openrndr.shape.Circle
import org.openrndr.extra.shapes.primitives.invertConformal
import kotlin.math.abs
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class TestCircleInvertConformal {
/**
* Helper function to check if two circles are tangent
*/
private fun areTangent(circle1: Circle, circle2: Circle): Boolean {
val centerDistance = (circle1.center - circle2.center).length
val radiusSum = circle1.radius + circle2.radius
val radiusDiff = abs(circle1.radius - circle2.radius)
// Circles are externally tangent if the distance between centers equals the sum of radii
val externallyTangent = abs(centerDistance - radiusSum) < 1e-10
// Circles are internally tangent if the distance between centers equals the difference of radii
val internallyTangent = abs(centerDistance - radiusDiff) < 1e-10
return externallyTangent || internallyTangent
}
@Test
fun testInvertConformalPreservesTangency() {
// Create an inverting circle
val invertingCircle = Circle(100.0, 100.0, 50.0)
// Create two externally tangent circles
val circle1 = Circle(200.0, 100.0, 30.0)
val circle2 = Circle(260.0, 100.0, 30.0)
// Verify that the circles are indeed tangent
assertTrue(areTangent(circle1, circle2), "The test circles should be tangent")
// Perform conformal inversion
val inverted1 = invertingCircle.invertConformal(circle1)
val inverted2 = invertingCircle.invertConformal(circle2)
// Verify that the inverted circles are also tangent
assertTrue(areTangent(inverted1, inverted2), "The inverted circles should remain tangent")
}
@Test
fun testInvertConformalPreservesInternalTangency() {
// Create an inverting circle
val invertingCircle = Circle(100.0, 100.0, 50.0)
// Create two internally tangent circles
// For internal tangency, one circle must be inside the other with their boundaries touching at exactly one point
val circle1 = Circle(200.0, 100.0, 50.0)
val circle2 = Circle(230.0, 100.0, 20.0) // Center is at distance (radius1 - radius2) from circle1's center
// Verify that the circles are indeed tangent
assertTrue(areTangent(circle1, circle2), "The test circles should be internally tangent")
// Perform conformal inversion
val inverted1 = invertingCircle.invertConformal(circle1)
val inverted2 = invertingCircle.invertConformal(circle2)
// Verify that the inverted circles are also tangent
assertTrue(areTangent(inverted1, inverted2), "The inverted circles should remain tangent")
}
@Test
fun testInvertConformalWithCircleAtCenter() {
// Create an inverting circle
val invertingCircle = Circle(100.0, 100.0, 50.0)
// Create a circle centered at the center of the inverting circle
val circle = Circle(100.0, 100.0, 20.0)
// Inverting a circle centered at the center of the inverting circle should throw an exception
assertFailsWith<IllegalArgumentException> {
invertingCircle.invertConformal(circle)
}
}
}