-- tkz_elements_circles.lua -- date 2025/05/25 -- version 4.00c -- Copyright 2025 Alain Matthes -- This work may be distributed and/or modified under the -- conditions of the LaTeX Project Public License, either version 1.3 -- of this license or (at your option) any later version. -- The latest version of this license is in -- http://www.latex-project.org/lppl.txt -- and version 1.3 or later is part of all distributions of LaTeX -- version 2005/12/01 or later. -- This work has the LPPL maintenance status “maintained”. -- The Current Maintainer of this work is Alain Matthes. --------------------------------------------------------------------------- -- circles --------------------------------------------------------------------------- circle = {} circle.__index = circle function circle:new(c, t) -- c --> center t --> through local type = "circle" local ct = line:new(c, t) local opp = antipode_(c, t) local radius = point.abs(c - t) local south = c - point(0, radius) local east = c + point(radius, 0) local north = c + point(0, radius) local west = c - point(radius, 0) local perimeter = 2 * math.pi * radius local area = 4 * math.pi * radius * radius local cir = { center = c, through = t, ct = ct, opp = opp, radius = radius, south = south, east = east, north = north, west = west, type = type, perimeter = perimeter, area = area, } setmetatable(cir, self) return cir end setmetatable(circle, { __call = function(cls, ...) return cls:new(...) end }) function circle:get() return self.center, self.through end -- other definition function circle:radius(center, radius) return circle:new(center, center + point(radius, 0)) end function circle:diameter(za, zb) return circle:new(midpoint_(za, zb), zb) end ----------------------- -- boolean -- ----------------------- function circle:in_out(pt) return math.abs(point.abs(pt - self.center) - self.radius) < tkz_epsilon end function circle:in_out_disk(pt) return point.abs(pt - self.center) <= self.radius end function circle:is_tangent(l) local a, b = intersection(self, l) -- Checks whether the intersection produces valid points if not a or not b then return false end -- Checks whether the distance between the two intersection points is less than a given tolerance return (point.abs(b - a) < tkz_epsilon) end ------------------------ -- string -------------- ------------------------ function circle:circles_position(C) return circles_position_(self.center, self.radius, C.center, C.radius) end ----------------------- -- real -- ----------------------- function circle:power(pt) local d = point.abs(self.center - pt) return d * d - self.radius * self.radius end ----------------------- -- points -- ----------------------- function circle:antipode(pt) return 2 * self.center - pt end function circle:midarc(z1, z2) local phi = 0.5 * get_angle(self.center, z1, z2) return rotation_(self.center, phi, z1) end function circle:point(t) local phi = 2 * t * math.pi return rotation_(self.center, phi, self.through) end function circle:random(inside) inside = (inside == "inside") math.randomseed(os.time()) if inside then local x = self:point(math.random()) local L = line:new(self.center, x) return L:random() else return self:point(math.random()) end end function circle:internal_similitude(C) return internal_similitude_(self.center, self.radius, C.center, C.radius) end function circle:external_similitude(C) return external_similitude_(self.center, self.radius, C.center, C.radius) end function circle:radical_center(C1, C2) if C2 == nil then if self.radius > C1.radius then return radical_center_(self.center, self.through, C1.center, C1.through) else return radical_center_(C1.center, C1.through, self.center, self.through) end else return radical_center3(self, C1, C2) end end ----------------------- -- lines -- ----------------------- function circle:tangent_at(pt) return line:new(rotation_(pt, math.pi / 2, self.center), rotation_(pt, -math.pi / 2, self.center)) end function circle:tangent_from(pt) local t1, t2 = tangent_from_(self.center, self.through, pt) return line:new(pt, t1), line:new(pt, t2) end function circle:radical_axis(C) local t1, t2 if self.radius > C.radius then t1, t2 = radical_axis_(self.center, self.through, C.center, C.through) else t1, t2 = radical_axis_(C.center, C.through, self.center, self.through) end return line:new(t1, t2) end function circle:polar(p) local q = self:inversion(p) local qa = (p - q):orthogonal(1):at(q) local qb = (q - p):orthogonal(1):at(q) return line:new(qa, qb) end function circle:external_tangent(C) local i, t1, t2, k, T1, T2 -- Find the barycenter of the two circles i = barycenter_({ C.center, self.radius }, { self.center, -C.radius }) -- Calculate the tangents from the circle to the point of intersection t1, t2 = tangent_from_(self.center, self.through, i) -- Calculate the scaling factor for the homothety k = point.mod((C.center - i) / (self.center - i)) -- Apply homothety to the tangents T1 = homothety_(i, k, t1) T2 = homothety_(i, k, t2) -- Return the two tangent lines return line:new(t1, T1), line:new(t2, T2) end function circle:internal_tangent(C) local i, t1, t2, k, T1, T2 -- Find the barycenter of the two circles with opposite signs for radii i = barycenter_({ C.center, self.radius }, { self.center, C.radius }) -- Calculate the tangents from the circle to the point of intersection t1, t2 = tangent_from_(self.center, self.through, i) -- Calculate the scaling factor for the homothety k = -point.mod((C.center - i) / (self.center - i)) -- Apply homothety to the tangents T1 = homothety_(i, k, t1) T2 = homothety_(i, k, t2) -- Return the two tangent lines return line:new(t1, T1), line:new(t2, T2) end function circle:common_tangent(C) local o, s1, s2, t1, t2 -- Calculating the external similarity between the two circles o = external_similitude_(self.center, self.radius, C.center, C.radius) if self.radius < C.radius then -- Si le rayon de 'self' est plus petit que celui de 'C' t1, t2 = tangent_from_(C.center, C.through, o) -- Tangentes depuis le cercle C s1, s2 = tangent_from_(self.center, self.through, o) -- Tangentes depuis 'self' -- Retourner les tangentes dans un ordre spécifique return s1, t1, t2, s2 else -- Si le rayon de 'self' est plus grand ou égal à celui de 'C' s1, s2 = tangent_from_(C.center, C.through, o) -- Tangentes depuis le cercle C t1, t2 = tangent_from_(self.center, self.through, o) -- Tangentes depuis 'self' -- Retourner les tangentes dans un ordre spécifique return s1, t1, t2, s2 end end ----------------------- -- circles -- ----------------------- function circle:orthogonal_from(pt) -- Calculate tangents from point ‘pt’. local t1, t2 = tangent_from_(self.center, self.through, pt) -- Return a circle with the center in 'pt' and one of the tangents as the radius return circle:new(pt, t1) end function circle:orthogonal_through(pta, ptb) -- Retourne un cercle défini par l'orthogonale passant par 'pta' et 'ptb' local o = orthogonal_through_(self.center, self.through, pta, ptb) return circle:new(o, pta) end function circle:radical_circle(C1, C2) local rc = self:radical_center(C1, C2) if C2 == nil then return self:orthogonal_from(rc) else return C1:orthogonal_from(rc) end end function circle:draw() -- Récupère les coordonnées du centre et le rayon du cercle local x, y = self.center:get() local r = self.radius -- Format de la commande LaTeX pour dessiner le cercle local frmt = "\\draw (%0.3f,%0.3f) circle [radius=%0.3f];" -- Affiche la commande LaTeX en formatant les valeurs de x, y et r tex.sprint(string.format(frmt, x, y, r)) end function circle:midcircle(C) -- Retourne le cercle médian entre 'self' et 'C' return midcircle_(self, C) end -- ----------------------------------------------------------- -- Circle tangent to a circle passing through two points function circle:c_c_pp(a, b) -- test If one point is inside the disk and the other is outside, there is no solution. if (self:in_out_disk(a) and not self:in_out_disk(b)) or (self:in_out_disk(b) and not self:in_out_disk(a)) then tex.error("An error has occurred", { "Bad configuration. Only one point is in the disk" }) return end -- Find the mediator of the current line local lab = line:new(a, b) local lmed = lab:mediator() if self:is_tangent(lab) then local c = intersection(self, lab) local d = self:antipode(c) return circle:new(circum_circle_(a, b, d), a), circle:new(circum_circle_(a, b, d), a) end -- pb are (AB) tgt to circle A and B equidistant of O tgt and equidistant if lab:is_equidistant(self.center) then local t1, t2 = intersection(lmed, self) return circle:new(circum_circle_(a, b, t1), t1), circle:new(circum_circle_(a, b, t2), t2) else -- Create a circumcircle passing through a, b, and a point on C local Cc = circle:new(circum_circle_(a, b, self.center), a) -- Find the intersection points of C and Cc local c, d = intersection(self, Cc) -- Create a line passing through the two intersection points local lcd = line:new(c, d) -- Find the intersection of the current line (self) with the line lcd local i = intersection(lab, lcd) -- Create tangents from the intersection point to C local lt, ltp = self:tangent_from(i) -- Get the tangent points local t, tp = lt.pb, ltp.pb -- Return two new circles tangent to C and passing through the tangent points return circle:new(intersection(lmed, line:new(self.center, t)), t), circle:new(intersection(lmed, line:new(self.center, tp)), tp) end end -- Circle tangent to two circles passing through a point function circle:c_cc_p(C, p) -- Calcule la similitude externe entre les cercles 'self' et 'C' local i = self:external_similitude(C) -- Crée la ligne passant par les centres des cercles 'self' et 'C' local lofcenters = line:new(self.center, C.center) -- Trouve les intersections de la ligne avec 'self' et avec 'C' local u1, u2 = intersection(lofcenters, self) local v1, v2 = intersection(lofcenters, C) -- Trouve les tangentes communes entre 'self' et 'C' local u1, v1 = self:common_tangent(C) -- Calcule le cercle circonscrit passant par u1, v1 et p local o = circum_circle_(u1, v1, p) -- Trouve les intersections du cercle 'o' avec la ligne i et le point p local a, b = intersection_lc_(i, p, o, p) -- Si les deux intersections sont très proches, retourne un cercle défini par la ligne et l'intersection if point.abs(a - b) < tkz_epsilon then local li = line:new(i, p) return C:c_lc_p(li, a) else local q -- Résout le cas où p et q sont égaux if point.abs(a - p) < tkz_epsilon then q = b else q = a end -- Retourne le cercle défini par p et q return C:c_c_pp(p, q) end end -- Circle tangent to one circle, on line and passing through a point function circle:c_lc_p(l, p, inside) inside = (inside == "inside") -- Vérifie si le point p est à l'intérieur ou à l'extérieur du cercle if self:in_out(p) then -- Trouve les intersections de la ligne avec les deux bords du cercle local t1 = intersection_ll_(self.north, p, l.pa, l.pb) local t2 = intersection_ll_(self.south, p, l.pa, l.pb) -- Trouve les lignes orthogonales aux tangentes local l1 = l:ortho_from(t1) local l2 = l:ortho_from(t2) -- Trouve les intersections de ces lignes avec le cercle local o1 = intersection_ll_(self.center, p, l1.pa, l1.pb) local o2 = intersection_ll_(self.center, p, l2.pa, l2.pb) -- Return circles defined by intersection points return circle:new(o1, t1), circle:new(o2, t2) else -- If point p is outside the circle, check whether p is inside or outside the line if l:in_out(p) then -- Calculates the projection of 'self.center' onto the line local i = l:projection(self.center) -- Find the orthogonal lines passing through p local lortho = l:ortho_from(p) -- Find the ratio points for the orthogonal local u = lortho:report(self.radius, p) local v = lortho:report(-self.radius, p) -- Find the medians between the center of the circle and the ratio points local ux, uy = mediator_(self.center, u) local vx, vy = mediator_(self.center, v) -- Find mediator intersections local o1 = intersection_ll_(u, v, ux, uy) local o2 = intersection_ll_(u, v, vx, vy) -- Returns circles defined by points of intersection and p return circle:new(o1, p), circle:new(o2, p) else -- General case local u = self.north local v = self.south local h = intersection_ll_(u, v, l.pa, l.pb) if inside then -- Calculate the circumscribed circle between p, u and h local o = circum_circle_(p, u, h) -- Finds the intersection of the circle and returns the result local q = intersection_lc_(p, v, o, p) return self:c_c_pp(p, q) else -- if 'inside' is false, we calculate the other circumscribed circle local o = circum_circle_(p, v, h) -- Finds the intersection of the circle and returns the result local q = intersection_lc_(u, p, o, v) return self:c_c_pp(p, q) end end end end ----------------------------------------------------------- ---- Inversion ---- ----------------------------------------------------------- function circle:set_inversion(...) local tp = table.pack(...) local i local t = {} for i = 1, tp.n do table.insert(t, inversion_(self.center, self.through, tp[i])) end return table.unpack(t) end function circle:inversion_L(L) -- Vérifie si le centre du cercle est à l'intérieur ou à l'extérieur de la ligne L if L:in_out(self.center) then return L -- Retourne la ligne L inchangée si le centre est du bon côté else -- Calcul de la projection du centre sur la ligne L local p = L:projection(self.center) -- Inversion du point projeté par rapport au cercle défini par 'self.center' et 'self.through' local q = inversion_(self.center, self.through, p) -- Retourne un cercle avec le centre au milieu de 'self.center' et 'q' et comme rayon 'q' return circle:new(midpoint_(self.center, q), q) end end function circle:inversion_C(C) local p, q, x, y, X, Y if C:in_out(self.center) then p = C:antipode(self.center) q = inversion_(self.center, self.through, p) x = ortho_from_(q, self.center, p) y = ortho_from_(q, p, self.center) return line:new(x, y) else x, y = intersection_lc_(self.center, C.center, C.center, C.through) X = inversion_(self.center, self.through, x) Y = inversion_(self.center, self.through, y) return circle:new(midpoint_(X, Y), X) end end function circle:inversion(...) local tp = table.pack(...) local obj = tp[1] local nb = tp.n if nb == 1 then if obj.type == "point" then return inversion_(self.center, self.through, obj) elseif obj.type == "line" then return self:inversion_L(obj) else return self:inversion_C(obj) end else local t = {} for i = 1, nb do table.insert(t, inversion_(self.center, self.through, tp[i])) end return table.unpack(t) end end function circle:path(za,zb,nb) local list = {} for i = 0, nb do local t = i / nb local P = self:point_arc(za, zb, t) table.insert(list, "(" .. checknumber(P.re) .. "," .. checknumber(P.im) .. ")") end return path:new(list) end function circle:point_arc(za, zb, t) local anglezazb = get_angle(self.center,za,zb) local phi = t * anglezazb return rotation_(self.center, phi, za) end return circle