1function [vx, vy] = make_region(convex)
2%MAKE_REGION Random 2D sampling region, returned as CCW vertices (VX, VY).
3% MAKE_REGION(true) — a convex polygon (convex hull of random disk points).
4% MAKE_REGION(false) — a non-convex star polygon (star-shaped about the
5% origin, so the origin is a valid interior start point).
6if nargin < 1 || isempty(convex)
7 convex = true;
8end
10if convex
11 [vx, vy] = convex_region();
12else
13 [vx, vy] = star_region();
14end
16% CCW so the interior is to the left of each edge.
17if signed_area(vx, vy) < 0
18 vx = vx(end:-1:1);
19 vy = vy(end:-1:1);
20end
21end
23function [vx, vy] = convex_region()
24% Convex hull of random points in a disk; anisotropic scaling for variety.
25m = 12;
26ang = 2 * pi * rand(m, 1);
27r = sqrt(rand(m, 1));
28px = 1.4 * r .* cos(ang);
29py = 1.0 * r .* sin(ang);
30k = convhull(px, py);
31k = k(1:end-1);
32vx = px(k);
33vy = py(k);
34end
36function [vx, vy] = star_region()
37% Spikes at sorted angles, alternating outer/inner radius. Small angle jitter
38% keeps the angles ordered, so the polygon stays simple and star-shaped about
39% the origin (lines through interior points can still leave and re-enter it).
40spikes = 4 + randi(4); % 5..8 spikes
41k = 2 * spikes;
42step = 2 * pi / k;
43ang = (0:k - 1) * step + (rand(1, k) - 0.5) * step * 0.6;
44r = zeros(1, k);
45r(1:2:k) = 0.9 + 0.4 * rand(1, numel(1:2:k)); % outer
46r(2:2:k) = 0.35 + 0.2 * rand(1, numel(2:2:k)); % inner
47vx = (1.3 * r .* cos(ang)).';
48vy = (1.0 * r .* sin(ang)).';
49end
51function A = signed_area(vx, vy)
52%SIGNED_AREA Shoelace area; positive when the vertices run counterclockwise.
53n = numel(vx);
54A = 0;
55for i = 1:n
56 j = mod(i, n) + 1;
57 A = A + (vx(i) * vy(j) - vx(j) * vy(i));
58end
59A = A / 2;
60end