summaryrefslogtreecommitdiff
path: root/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua')
-rw-r--r--graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua316
1 files changed, 316 insertions, 0 deletions
diff --git a/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua
new file mode 100644
index 0000000000..1875b8d1a5
--- /dev/null
+++ b/graphics/pgf/base/tex/generic/graphdrawing/lua/pgf/gd/model/Path_arced.lua
@@ -0,0 +1,316 @@
+-- Copyright 2014 by Till Tantau
+--
+-- This file may be distributed an/or modified
+--
+-- 1. under the LaTeX Project Public License and/or
+-- 2. under the GNU Public License
+--
+-- See the file doc/generic/pgf/licenses/LICENSE for more information
+
+-- @release $Header$
+
+
+local Path = require 'pgf.gd.model.Path'
+
+-- Imports
+
+local Coordinate = require "pgf.gd.model.Coordinate"
+local Transform = require "pgf.gd.lib.Transform"
+
+
+
+-- Locals
+
+local rigid = Path.rigid
+
+local tan = math.tan
+local sin = math.sin
+local cos = math.cos
+local sqrt = math.sqrt
+local atan2 = math.atan2
+local abs = math.abs
+
+local to_rad = math.pi/180
+local to_deg = 180/math.pi
+local pi_half = math.pi/2
+
+local function sin_quarter(x)
+ x = x % 360
+ if x == 0 then
+ return 0
+ elseif x == 90 then
+ return 1
+ elseif x == 180 then
+ return 0
+ else
+ return -1
+ end
+end
+
+local function cos_quarter(x)
+ x = x % 360
+ if x == 0 then
+ return 1
+ elseif x == 90 then
+ return 0
+ elseif x == 180 then
+ return -1
+ else
+ return 0
+ end
+end
+
+local function atan2deg(y,x)
+
+ -- Works like atan2, but returns the angle in degrees and, returns
+ -- exactly a multiple of 90 if x or y are zero
+
+ if x == 0 then
+ if y < 0 then
+ return -90
+ else
+ return 90
+ end
+ elseif y == 0 then
+ if x < 0 then
+ return 180
+ else
+ return 0
+ end
+ else
+ return atan2(y,x) * to_deg
+ end
+
+end
+
+local function subarc (path, startx, starty, start_angle, delta, radius, trans, center_x, center_y)
+
+ local end_angle = start_angle + delta
+ local factor = tan (delta*to_rad/4) * 1.333333333333333333333 * radius
+
+ local s1, c1, s190, c190, s2, c2, s290, c290
+
+ if start_angle % 90 == 0 then
+ s1, c1, s190, c190 = sin_quarter(start_angle), cos_quarter(start_angle), sin_quarter(start_angle+90), cos_quarter(start_angle+90)
+ else
+ local a1 = start_angle*to_rad
+ s1, c1, s190, c190 = sin(a1), cos(a1), sin(a1+pi_half), cos(a1+pi_half)
+ end
+
+ if end_angle % 90 == 0 then
+ s2, c2, s290, c290 = sin_quarter(end_angle), cos_quarter(end_angle), sin_quarter(end_angle-90), cos_quarter(end_angle-90)
+ else
+ local a2 = end_angle * to_rad
+ s2, c2, s290, c290 = sin(a2), cos(a2), sin(a2-pi_half), cos(a2-pi_half)
+ end
+
+ local lastx, lasty = center_x + c2*radius, center_y + s2*radius
+
+ path[#path + 1] = "curveto"
+ path[#path + 1] = Coordinate.new (startx + c190*factor, starty + s190*factor)
+ path[#path + 1] = Coordinate.new (lastx + c290*factor, lasty + s290*factor)
+ path[#path + 1] = Coordinate.new (lastx, lasty)
+
+ if trans then
+ path[#path-2]:apply(trans)
+ path[#path-1]:apply(trans)
+ path[#path ]:apply(trans)
+ end
+
+ return lastx, lasty, end_angle
+end
+
+
+
+local function arc (path, start, start_angle, end_angle, radius, trans, centerx, centery)
+
+ -- @param path is the path object
+ -- @param start is the start coordinate
+ -- @param start_angle is given in degrees
+ -- @param end_angle is given in degrees
+ -- @param radius is the radius
+ -- @param trans is an optional transformation matrix that gets applied to all computed points
+ -- @param centerx optionally: x-part of the center of the circle
+ -- @param centery optionally: y-part of the center of the circle
+
+ local startx, starty = start.x, start.y
+
+ -- Compute center:
+ centerx = centerx or startx - cos(start_angle*to_rad)*radius
+ centery = centery or starty - sin(start_angle*to_rad)*radius
+
+ if start_angle < end_angle then
+ -- First, ensure that the angles are in a reasonable range:
+ start_angle = start_angle % 360
+ end_angle = end_angle % 360
+
+ if end_angle <= start_angle then
+ -- In case the modulo has inadvertently moved the end angle
+ -- before the start angle:
+ end_angle = end_angle + 360
+ end
+
+ -- Ok, now create a series of arcs that are at most quarter-cycles:
+ while start_angle < end_angle do
+ if start_angle + 179 < end_angle then
+ -- Add a quarter cycle:
+ startx, starty, start_angle = subarc(path, startx, starty, start_angle, 90, radius, trans, centerx, centery)
+ elseif start_angle + 90 < end_angle then
+ -- Add 60 degrees to ensure that there are no small segments
+ -- at the end
+ startx, starty, start_angle = subarc(path, startx, starty, start_angle, (end_angle-start_angle)/2, radius, trans, centerx, centery)
+ else
+ subarc(path, startx, starty, start_angle, end_angle - start_angle, radius, trans, centerx, centery)
+ break
+ end
+ end
+
+ elseif start_angle > end_angle then
+ -- First, ensure that the angles are in a reasonable range:
+ start_angle = start_angle % 360
+ end_angle = end_angle % 360
+
+ if end_angle >= start_angle then
+ -- In case the modulo has inadvertedly moved the end angle
+ -- before the start angle:
+ end_angle = end_angle - 360
+ end
+
+ -- Ok, now create a series of arcs that are at most quarter-cycles:
+ while start_angle > end_angle do
+ if start_angle - 179 > end_angle then
+ -- Add a quarter cycle:
+ startx, starty, start_angle = subarc(path, startx, starty, start_angle, -90, radius, trans, centerx, centery)
+ elseif start_angle - 90 > end_angle then
+ -- Add 60 degrees to ensure that there are no small segments
+ -- at the end
+ startx, starty, start_angle = subarc(path, startx, starty, start_angle, (end_angle-start_angle)/2, radius, trans, centerx, centery)
+ else
+ subarc(path, startx, starty, start_angle, end_angle - start_angle, radius, trans, centerx, centery)
+ break
+ end
+ end
+
+ -- else, do nothing
+ end
+end
+
+
+-- Doc see Path.lua
+
+function Path:appendArc(start_angle,end_angle,radius, trans)
+
+ local start = rigid(self[#self])
+ assert(type(start) == "table", "trying to append an arc to a path that does not end with a coordinate")
+
+ if trans then
+ start = start:clone()
+ start:apply(Transform.invert(trans))
+ end
+
+ arc (self, start, start_angle, end_angle, radius, trans)
+end
+
+
+
+
+-- Doc see Path.lua
+
+function Path:appendArcTo (target, radius_or_center, clockwise, trans)
+
+ local start = rigid(self[#self])
+ assert(type(start) == "table", "trying to append an arc to a path that does not end with a coordinate")
+
+ local trans_target = target
+ local centerx, centery, radius
+
+ if type(radius_or_center) == "number" then
+ radius = radius_or_center
+ else
+ centerx, centery = radius_or_center.x, radius_or_center.y
+ end
+
+ if trans then
+ start = start:clone()
+ trans_target = target:clone()
+ local itrans = Transform.invert(trans)
+ start:apply(itrans)
+ trans_target:apply(itrans)
+ if centerx then
+ local t = radius_or_center:clone()
+ t:apply(itrans)
+ centerx, centery = t.x, t.y
+ end
+ end
+
+ if not centerx then
+ -- Compute center
+ local dx, dy = target.x - start.x, target.y - start.y
+
+ if abs(dx) == abs(dy) and abs(dx) == radius then
+ if (dx < 0 and dy < 0) or (dx > 0 and dy > 0) then
+ centerx = start.x
+ centery = trans_target.y
+ else
+ centerx = trans_target.x
+ centery = start.y
+ end
+ else
+ local l_sq = dx*dx + dy*dy
+ if l_sq >= radius*radius*4*0.999999 then
+ centerx = (start.x+trans_target.x) / 2
+ centery = (start.y+trans_target.y) / 2
+ assert(l_sq <= radius*radius*4/0.999999, "radius too small for arc")
+ else
+ -- Normalize
+ local l = sqrt(l_sq)
+ local nx = dx / l
+ local ny = dy / l
+
+ local e = sqrt(radius*radius - 0.25*l_sq)
+
+ centerx = start.x + 0.5*dx - ny*e
+ centery = start.y + 0.5*dy + nx*e
+ end
+ end
+ end
+
+ local start_dx, start_dy, target_dx, target_dy =
+ start.x - centerx, start.y - centery,
+ trans_target.x - centerx, trans_target.y - centery
+
+ if not radius then
+ -- Center is given, compute radius:
+ radius_sq = start_dx^2 + start_dy^2
+
+ -- Ensure that the circle is, indeed, centered:
+ assert (abs(target_dx^2 + target_dy^2 - radius_sq)/radius_sq < 1e-5, "attempting to add an arc with incorrect center")
+
+ radius = sqrt(radius_sq)
+ end
+
+ -- Compute start and end angle:
+ local start_angle = atan2deg(start_dy, start_dx)
+ local end_angle = atan2deg(target_dy, target_dx)
+
+ if clockwise then
+ if end_angle > start_angle then
+ end_angle = end_angle - 360
+ end
+ else
+ if end_angle < start_angle then
+ end_angle = end_angle + 360
+ end
+ end
+
+ arc (self, start, start_angle, end_angle, radius, trans, centerx, centery)
+
+ -- Patch last point to avoid rounding problems:
+ self[#self] = target
+end
+
+
+
+-- Done
+
+return true