summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/luatodonotes/path_line.lua
blob: e365a36bf48d0032879b7e90cf0f964976016a02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
-- Taken from luapower.com (Public domain)
--
--math for 2D line segments defined as (x1, y1, x2, y2).

local abs, min, max = math.abs, math.min, math.max

local distance = require'path_point'.distance
local distance2 = require'path_point'.distance2

--evaluate a line at time t using linear interpolation.
--the time between 0..1 covers the segment interval.
local function point(t, x1, y1, x2, y2)
	return x1 + t * (x2 - x1), y1 + t * (y2 - y1)
end

--length of line at time t.
local function length(t, x1, y1, x2, y2)
	return t * distance(x1, y1, x2, y2)
end

--bounding box of line in (x,y,w,h) form.
local function bounding_box(x1, y1, x2, y2)
	if x1 > x2 then x1, x2 = x2, x1 end
	if y1 > y2 then y1, y2 = y2, y1 end
	return x1, y1, x2-x1, y2-y1
end

--split line segment into two line segments at time t (t is capped between 0..1).
local function split(t, x1, y1, x3, y3)
	t = min(max(t,0),1)
	local x2, y2 = point(t, x1, y1, x3, y3)
	return
		x1, y1, x2, y2, --first segment
		x2, y2, x3, y3  --second segment
end

--intersect infinite line with its perpendicular from point (x, y); return the intersection point.
local function point_line_intersection(x, y, x1, y1, x2, y2)
	local dx = x2 - x1
	local dy = y2 - y1
	local k = dx^2 + dy^2
	if k == 0 then return x1, y1 end --line has no length
	local k = ((x - x1) * dy - (y - y1) * dx) / k
	return x - k * dy, y + k * dx
end

--return shortest distance-squared from point (x0, y0) to line, plus the touch point, and the time in the line
--where the touch point splits the line.
local function hit(x0, y0, x1, y1, x2, y2)
	local x, y = point_line_intersection(x0, y0, x1, y1, x2, y2)
	local tx = x2 == x1 and 0 or (x - x1) / (x2 - x1)
	local ty = y2 == y1 and 0 or (y - y1) / (y2 - y1)
	if tx < 0 or ty < 0 then --intersection occurs outside the segment, closer to the first endpoint
		return distance2(x0, y0, x1, y1), x1, y1, 0
	elseif tx > 1 or ty > 1 then --intersection occurs outside the segment, closer to the second endpoint
		return distance2(x0, y0, x2, y2), x2, y2, 1
	end
	return distance2(x0, y0, x, y), x, y, max(tx, ty)
end

--intersect line segment (x1, y1, x2, y2) with line segment (x3, y3, x4, y4).
--returns the time on the first line and the time on the second line where intersection occurs.
--if the intersection occurs outside the segments themselves, then t1 and t2 are outside the 0..1 range.
--if the lines are parallel or coincidental then t1 and t2 are infinite.
local function line_line_intersection(x1, y1, x2, y2, x3, y3, x4, y4)
	local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
	if d == 0 then return 1/0, 1/0 end --lines are parallel or coincidental
	return
		((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d,
		((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d
end

--transform to a quad bezier that advances linearly i.e. the point on the line at t
--best matches the point on the curve at t.
local function to_bezier2(x1, y1, x2, y2)
	return
		x1, y1,
		(x1 + x2) / 2,
		(y1 + y2) / 2,
		x2, y2
end

--transform to a cubic bezier that advances linearly i.e. the point on the line at t
--best matches the point on the curve at t.
local function to_bezier3(x1, y1, x2, y2)
	return
		x1, y1,
		(2 * x1 + x2) / 3,
		(2 * y1 + y2) / 3,
		(x1 + 2 * x2) / 3,
		(y1 + 2 * y2) / 3,
		x2, y2
end

--parallel line segment at a distance on the right side of a segment.
--use a negative distance for the left side, or reflect the returned points against their respective initial points
local function offset(d, x1, y1, x2, y2)
	local dx, dy = -(y2-y1), x2-x1 --normal vector of the same length as original segment
	local k = d / distance(x1, y1, x2, y2) --normal vector scale factor
	return --normal vector scaled and translated to (x1,y1) and (x2,y2)
		x1 + dx * k, y1 + dy * k,
		x2 + dx * k, y2 + dy * k
end

if not ... then require'path_line_demo' end

return {
	point_line_intersection = point_line_intersection,
	line_line_intersection = line_line_intersection,
	to_bezier2 = to_bezier2,
	to_bezier3 = to_bezier3,
	offset = offset,
	--path API
	bounding_box = bounding_box,
	point = point,
	length = length,
	split = split,
	hit = hit,
}