-- tkz_elements-circles.lua -- date 2025/01/06 -- version 3.10 -- Copyright 2024 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 = {} 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 o = { 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(o, self) self.__index = self return o end -- other definition function circle: radius (center, radius) -- c --> center r --> radius return circle : new (center, center + point( radius, 0 ) ) end function circle: diameter (za, zb) local c = midpoint_(za,zb) return circle : new (c, 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 -- new version 1.80 added function circle : circles_position (C) return circles_position_ (self.center,self.radius,C.center,C.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 ----------------------- -- real -- ----------------------- function circle: power (pt) local d 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: 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: 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_pt (lower, upper) local t math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) phi = lower + math.random() * (upper - lower) return point (self.center.re+self.radius*math.cos(phi),self.center.im+self.radius*math.sin(phi) ) 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 ----------------------- -- 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: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 function circle : radical_circle (C1,C2) local rc if C2 == nil then rc = self : radical_center (C1) return self : orthogonal_from (rc) else rc = self : radical_center (C1,C2) return C1 : orthogonal_from (rc) end end function circle:external_tangent(C) local i, t1, t2, k, T1, T2 i = barycenter_({C.center, self.radius}, {self.center, -C.radius}) t1, t2 = tangent_from_(self.center, self.through, i) k = point.mod((C.center - i) / (self.center - i)) T1 = homothety_(i, k, t1) T2 = homothety_(i, k, t2) return line:new(t1, T1), line:new(t2, T2) end function circle : internal_tangent(C) local i,t1,t2,k,T1,T2 i = barycenter_ ({C.center,self.radius},{self.center,C.radius}) t1,t2 = tangent_from_ (self.center,self.through,i) k = -point.mod((C.center-i)/(self.center-i)) T1 = homothety_(i,k,t1) T2 = homothety_(i,k,t2) return line : new (t1,T1),line : new (t2,T2) end function circle : common_tangent(C) local o,s1,s2,t1,t2 o = external_similitude_ (self.center,self.radius,C.center,C.radius) if self.radius < C.radius then t1,t2 = tangent_from_ (C.center,C.through,o) s1,s2 = tangent_from_ (self.center,self.through,o) return s1,t1,t2,s2 else s1,s2 = tangent_from_ (C.center,C.through,o) t1,t2 = tangent_from_ (self.center,self.through,o) return s1,t1,t2,s2 end end ----------------------- -- circles -- ----------------------- function circle: orthogonal_from (pt) local t1,t2 t1,t2 = tangent_from_ (self.center,self.through,pt) return circle : new (pt,t1) end function circle: orthogonal_through (pta,ptb) return circle : new (orthogonal_through_ (self.center,self.through,pta,ptb),pta) end function circle:inversion_L(L) if L:in_out(self.center) then return L else local p = L:projection(self.center) local q = inversion_(self.center, self.through, p) 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: draw () local x,y x, y = self.center: get () local r = self.radius local frmt = '\\draw (%0.3f,%0.3f) circle [radius=%0.3f];' tex.sprint(string.format(frmt, x,y,r)) end function circle: midcircle(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) local i = self: external_similitude (C) local lofcenters = line : new (self.center,C.center) local u1,u2 = intersection (lofcenters,self) local v1,v2 = intersection (lofcenters,C) local u1,v1 = self : common_tangent (C) local o = circum_circle_(u1,v1,p) local a,b = intersection_lc_(i,p,o,p) if (point.abs(a - b) < tkz_epsilon) then local li = line:new (i,p) return C : c_lc_p (li,a) else local q -- problem if p == q ? if (point.abs(a - p) < tkz_epsilon) then q = b else q=a end 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 or false if self : in_out (p) then local t1 = intersection_ll_( self.north,p,l.pa,l.pb) local t2 = intersection_ll_( self.south,p,l.pa,l.pb) local l1 = l : ortho_from (t1) local l2 = l : ortho_from (t2) local o1 = intersection_ll_( self.center,p,l1.pa,l1.pb) local o2 = intersection_ll_( self.center,p,l2.pa,l2.pb) return circle:new(o1,t1),circle:new(o2,t2) else if l : in_out (p) then local i = l : projection (self.center) local lortho = l : ortho_from (p) local u = lortho : report (self.radius,p) local v = lortho : report (-self.radius,p) local ux,uy = mediator_ (self.center,u) local vx,vy = mediator_ (self.center,v) -- pb if c tgt l local o1= intersection_ll_(u,v,ux,uy) local o2 = intersection_ll_(u,v,vx,vy) return circle:new(o1,p),circle:new(o2,p) else -- genral case local u = self.north local v = self.south local h = intersection_ll_(u,v,l.pa,l.pb) if inside then local o = circum_circle_(p,u,h) local q = intersection_lc_(p,v,o,p) return self : c_c_pp (p,q) else local o = circum_circle_(p,v,h) local q = intersection_lc_(u,p,o,v) return self : c_c_pp (p,q) end end end end return circle