1- -- under development for v1.20 (for beta42) r7
1+ -- under development for v1.20 (for beta42) r8
22--[[
33MIT License
44Copyright (c) 2025-2026 sigma-axis
422422
423423-- measure and move the path.
424424path_s.transform(pts, n_pts, 1, math.atan2(Y, X));
425- local L, R, T, B, len = path_s.measure(pts, n_pts);
425+ local L, R, T, B = path_s.measure(pts, n_pts);
426426local th = math.ceil(line * (end_shape == 1 and 0.5 ^ 0.5 or 0.5) + antialias);
427427L, T = math.floor(L - th), math.floor(T - th);
428428R, B = math.max(math.ceil(R + th), L + 1), math.max(math.ceil(B + th), T + 1);
@@ -434,7 +434,7 @@ obj.clearbuffer("object", R - L, B - T, color);
434434-- draw the line.
435435path_s.path_mask_line(
436436 0, 1, line, antialias,
437- 0 , pts, n_pts - 1, false, 1,
437+ nil , pts, n_pts - 1, false, 1,
438438 0, 1, end_shape, dash_pat, dash_pos, false,
439439 1, 0, obj.cx, obj.cy);
440440
@@ -614,7 +614,7 @@ obj.clearbuffer("object", R - L, B - T, color);
614614-- draw the path.
615615path_s.path_mask_line(
616616 0, 1, line, antialias,
617- 0 , pts, n_pts - 1, false, 1,
617+ nil , pts, n_pts - 1, false, 1,
618618 0, 1, end_shape, dash_pat, dash_pos, false,
619619 1, 0, obj.cx, obj.cy);
620620
@@ -782,86 +782,93 @@ if rand_amplify > 0 then
782782 rand_period, rand_amplify, 0, rand_seed);
783783 end
784784end
785- local L, R, T, B, len = path_s.measure(points, num_points);
785+ local L, R, T, B = path_s.measure(points, num_points);
786786local th = line * (end_shape == 1 and 0.5 ^ 0.5 or 0.5) + antialias;
787787if head_type ~= 0 then
788788 -- take the arrow heads into account.
789789 th = math.max(th, head_size / 2 * ((math.abs(head_center) + 1) ^ 2 + head_width ^ 2) ^ 0.5);
790790end
791791L, T = math.floor(L - th), math.floor(T - th);
792792R, B = math.max(math.ceil(R + th), L + 1), math.max(math.ceil(B + th), T + 1);
793+ local W, H, cx, cy = R - L, B - T, -(L + R) / 2, -(T + B) / 2;
794+
795+ -- calculate the head position.
796+ local head_vertices = nil;
797+ if head_type ~= 0 and head_size > 0 and head_width > 0 then
798+ local function polygon(i, j, rot)
799+ local X, Y =
800+ (1 - j) * points[2 * i - 1] + j * points[2 * i + 1],
801+ (1 - j) * points[2 * i - 0] + j * points[2 * i + 2];
802+ local A = math.atan2(
803+ points[2 * i + 2] - points[2 * i - 0],
804+ points[2 * i + 1] - points[2 * i - 1]);
805+
806+ -- as to the angle, interpolate with the neighbor secant.
807+ if j < 0.5 then i = i - 1;
808+ else i, j = i + 1, 1 - j end
809+ local dA = 0;
810+ if 0 < i and i < num_points then
811+ dA = math.atan2(
812+ points[2 * i + 2] - points[2 * i - 0],
813+ points[2 * i + 1] - points[2 * i - 1]);
814+ dA = (dA - A) / (2 * math.pi);
815+ dA = ((dA + 0.5) % 1) - 0.5;
816+ dA = 2 * math.pi * (0.5 - j) * dA;
817+ end
818+ A = A + dA + rot;
819+
820+ local c, s = math.cos(A), math.sin(A);
821+ X, Y =
822+ X - head_center * head_size / 2 * s + cx,
823+ Y + head_center * head_size / 2 * c + cy;
824+ local w2x, w2y, h2x, h2y = head_size * head_width / 2, 0, 0, head_size / 2;
825+ w2x, w2y = w2x * c, w2x * s;
826+ h2x, h2y = h2y *-s, h2y * c;
827+ return
828+ { X - w2x - h2x, Y - w2y - h2y, 0; 0, 0 },
829+ { X + w2x - h2x, Y + w2y - h2y, 0; 1, 0 },
830+ { X + w2x + h2x, Y + w2y + h2y, 0; 1, 1 },
831+ { X - w2x + h2x, Y - w2y + h2y, 0; 0, 1 };
832+ end
833+
834+ -- first tip.
835+ local pos = math.min(math.max(1 + head_pos, 0), 1);
836+ pos = (1 - pos) * start_pos + pos * end_pos;
837+ local i, j, l = path_s.find_index(-pos, points, num_points);
838+ local vts = { polygon(i, j, head_rot + math.pi / 2) };
839+
840+ if head_type > 1 then
841+ -- second tip.
842+ local pos2 = math.min(math.max(head_pos, 0), 1);
843+ pos2 = (1 - pos2) * start_pos + pos2 * end_pos;
844+ if pos ~= pos2 then
845+ i, j = path_s.find_index(-pos2, l);
846+ vts[5], vts[6], vts[7], vts[8] = polygon(i, j, head_rot
847+ + math.pi / 2 * (head_type == 2 and 1 or -1));
848+ end
849+ end
850+
851+ head_vertices = vts;
852+ end
793853
794854-- draw the path.
795- local cx, cy = -(L + R) / 2, -(T + B) / 2;
796- obj.clearbuffer("object", R - L, B - T, color);
855+ obj.clearbuffer(head_vertices and "tempbuffer" or "object", W, H, color);
797856path_s.path_mask_line(
798857 0, 1, line, antialias,
799- 0 , points, num_points - 1, false, 1,
858+ nil , points, num_points - 1, false, 1,
800859 start_pos, end_pos, end_shape, dash_pat, dash_pos, false,
801- 1, 0, cx, cy);
860+ 1, 0, cx, cy,
861+ head_vertices and { name = "tempbuffer", w = W, h = H } or nil,
862+ head_vertices and "object" or nil);
802863
803864-- draw the arrow heads.
804- if head_type ~= 0 and head_size > 0 and head_width > 0 then
865+ if head_vertices then
805866 obj.setoption("drawtarget", "tempbuffer");
806- obj.copybuffer("tempbuffer", "object");
807867 obj.load("figure", head_fig, color, math.ceil(head_size * math.max(head_width, 1)));
808868 if obj.w > 0 and obj.h > 0 then
809- local function polygon(i, j, rot)
810- local X, Y =
811- (1 - j) * points[2 * i - 1] + j * points[2 * i + 1],
812- (1 - j) * points[2 * i - 0] + j * points[2 * i + 2];
813- local A = math.atan2(
814- points[2 * i + 2] - points[2 * i - 0],
815- points[2 * i + 1] - points[2 * i - 1]);
816-
817- -- as to the angle, interpolate with the neighbor secant.
818- if j < 0.5 then i = i - 1;
819- else i, j = i + 1, 1 - j end
820- local dA = 0;
821- if 0 < i and i < num_points then
822- dA = math.atan2(
823- points[2 * i + 2] - points[2 * i - 0],
824- points[2 * i + 1] - points[2 * i - 1]);
825- dA = (dA - A) / (2 * math.pi);
826- dA = ((dA + 0.5) % 1) - 0.5;
827- dA = 2 * math.pi * (0.5 - j) * dA;
828- end
829- A = A + dA + rot;
830-
831- local c, s = math.cos(A), math.sin(A);
832- X, Y =
833- X - head_center * head_size / 2 * s + cx,
834- Y + head_center * head_size / 2 * c + cy;
835- local w2x, w2y, h2x, h2y = head_size * head_width / 2, 0, 0, head_size / 2;
836- w2x, w2y = w2x * c, w2x * s;
837- h2x, h2y = h2y *-s, h2y * c;
838- return
839- { X - w2x - h2x, Y - w2y - h2y, 0; 0, 0 },
840- { X + w2x - h2x, Y + w2y - h2y, 0; 1, 0 },
841- { X + w2x + h2x, Y + w2y + h2y, 0; 1, 1 },
842- { X - w2x + h2x, Y - w2y + h2y, 0; 0, 1 };
843- end
844-
845- -- first tip.
846- local pos = math.min(math.max(1 + head_pos, 0), 1);
847- pos = (1 - pos) * start_pos + pos * end_pos;
848- local i, j, l = path_s.find_index(-pos, points, num_points);
849- local vts = { polygon(i, j, head_rot + math.pi / 2) };
850-
851- if head_type > 1 then
852- -- second tip.
853- local pos2 = math.min(math.max(head_pos, 0), 1);
854- pos2 = (1 - pos2) * start_pos + pos2 * end_pos;
855- if pos ~= pos2 then
856- i, j = path_s.find_index(-pos2, l);
857- vts[5], vts[6], vts[7], vts[8] = polygon(i, j, head_rot
858- + math.pi / 2 * (head_type == 2 and 1 or -1));
859- end
860- end
861-
862869 -- "alpha_max" does not seem to work well with obj.drawpoly().
863870 -- obj.setoption("blend", "alpha_max");
864- obj.drawpoly(vts );
871+ obj.drawpoly(head_vertices );
865872 -- obj.setoption("blend");
866873 end
867874 obj.copybuffer("object", "tempbuffer");
0 commit comments