Skip to content

Instantly share code, notes, and snippets.

@Hermann-SW
Last active December 5, 2025 10:21
Show Gist options
  • Select an option

  • Save Hermann-SW/374019946acdb2d9399f7619247ae4c3 to your computer and use it in GitHub Desktop.

Select an option

Save Hermann-SW/374019946acdb2d9399f7619247ae4c3 to your computer and use it in GitHub Desktop.
Apollonian circles playground
"use strict"
const jscad = require('@jscad/modeling')
const math = require('mathjs')
const { extrudeLinear } = jscad.extrusions
const { hullChain } = jscad.hulls
const { circle } = jscad.primitives
const { vectorText } = jscad.text
const { scale, translate } = jscad.transforms
const th=1
const sc=0.03
const seg=360
const f=10
function C(c,notext=false) {
return [
notext?[]:buildFlatText([f*(c[0]-sc*15/c[2]),f*c[1]], ""+c[2], th, f*sc/c[2]),
circle({segments: seg, center: [f*c[0],f*c[1]], radius: f/Math.abs(c[2])})
]
}
const issquare = (n) => { var i=isqrt(n); return n === i*i }
const isqrt = (n) => { return Math.floor(Math.sqrt(n)) }
function nxt(A) {
var a,b,c
[a,b,c]=[A[0][2],A[1][2],A[2][2]]
console.assert(issquare(a*b+a*c+b*c))
return a+b+c+2*isqrt(a*b+a*c+b*c)
}
function cent(A,k4) {
var a=r1+r2
var b=r1+r3
var c=r2+r3
var k1=A[0][2]
var k2=A[1][2]
var k3=A[2][2]
var r1=1/k1
var r2=1/k2
var r3=1/k3
var z1 = math.complex(A[0][0],A[0][1])
var z2 = math.complex(A[1][0],A[1][1])
var z3 = math.complex(A[2][0],A[2][1])
// z4=(k1*z1+k2*z2+k3*z3+2*sqrt(k1*k2*z1*z2+k2*k3*z2*z3+k3*k1*z3*z1))/k4
var z4 =
math.multiply(
math.add(
math.add(
math.multiply(k1,z1),
math.add(
math.multiply(k2,z2),
math.multiply(k3,z3)
)
),
math.multiply(2,
math.sqrt(
math.add(
math.add(
math.multiply(
k1*k2,
math.multiply(z1,z2)
),
math.multiply(k2*k3,
math.multiply(z2,z3)
)
),
math.multiply(k3*k1,
math.multiply(z3,z1)
)
)
)
)
),
1/k4
)
return [z4.re, z4.im]
}
function r(A) {
var n=nxt(A)
var c=cent(A,n).concat([n])
return C(c)
}
function main() {
var A=[[0,0,-1],[0,1/2,2],[0,-1/2,2],[2/3,0,3]]
return [
C(A[0], true), buildFlatText([-5,f], "-1", th, f*sc),
C(A[1]),
C(A[2]),
C(A[3]),
r([...A.slice(1,4), ...A.slice(-0)]),
// r([...A.slice(0,2), ...A.slice(-1)]),
]
}
const buildFlatText = (pos, message, characterLineWidth, s) => {
if (message === undefined || message.length === 0) return []
const lineSegments = []
vectorText({ x: 0, y: 0, input: message }).forEach((segmentPoints) => {
const corners = segmentPoints.map((point) =>
translate(point, circle({ radius: characterLineWidth / 2})))
lineSegments.push(hullChain(corners))
})
return translate(pos, scale([s,s,s],extrudeLinear({ height: 0.00001 }, lineSegments)))
}
module.exports = { main }
@Hermann-SW
Copy link
Author

Hermann-SW commented Dec 4, 2025

Initial version.

  • compute bend=1/radius of inner (to 2,2,3 bend circles) circle as 15
  • compute center of that circle so that it "kisses" the three other circles
  • lots of mathjs complex number computations

jscad.app share link:
https://jscad.app/#https://gist.githubusercontent.com/Hermann-SW/374019946acdb2d9399f7619247ae4c3/raw/fa33731936b415cebc6a25968d7ff75f1aa36b82/apollonian_circles.jscad

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment