Files
XRLib/Assets/Shapes2D/Shaders/Polygon.cginc
2025-08-12 12:54:51 +09:00

124 lines
5.2 KiB
HLSL

#if POLYGON_8
#define MAX_VERTS 8
#elif POLYGON_16
#define MAX_VERTS 16
#elif POLYGON_24
#define MAX_VERTS 24
#elif POLYGON_32
#define MAX_VERTS 32
#elif POLYGON_40
#define MAX_VERTS 40
#elif POLYGON_48
#define MAX_VERTS 48
#elif POLYGON_56
#define MAX_VERTS 56
#elif POLYGON_64 || POLYGON_MAP
#define MAX_VERTS 64
#endif
float4 _Verts[MAX_VERTS];
int _NumVerts; // the number of vertices in the polygon
sampler2D _PolyMap;
// this algorithm is slow and kind of silly to have in a shader, but it works!
// returns <0 if the point is outside the poly.
float get_closest_distance(float2 pos, float2 radii) {
// simplifies the algo - hopefully this value is higher than any real value
float closest_distance = 99999999;
int nodes = 0; // used for testing if the point is inside the poly, see below
// you can't do variable-length loops in webgl (or es2 technically I think),
// hence the constant...so we have to iterate through MAX_VERTS rather than
// _NumVerts which is the number of vertices actually in our poly
for (int i = 0; i < MAX_VERTS; i++) {
// loop_over is 1 when we're past the number of sides in the poly
float loop_over = when_ge(i, _NumVerts);
// each "vert" contains its edge partner in zw
float4 side = _Verts[i];
// todo - try and do angular line joins instead of curved? for concave
// line joins we get curves, but for convex we get angles. :(
float dist = distance_to_line_segment(pos, side.xy, side.zw) + 99999999 * loop_over;
closest_distance = min(dist, closest_distance);
// thanks to http://alienryderflex.com/polygon/. if the number of nodes (points
// where a polygon line crosses the horizontal axis of the test position) to the
// left of the test position is odd, then the point is inside the poly!
// avoid conditionals - they tend to break on some platforms :/
float intersects_y = or(and(when_lt(side.y, pos.y), when_ge(side.w, pos.y)),
and(when_lt(side.w, pos.y), when_ge(side.y, pos.y)));
float node_x = side.x + (pos.y - side.y) / (side.w - side.y) * (side.z - side.x);
nodes += when_lt(node_x, pos.x) * intersects_y * (1 - loop_over);
// I think conditionals based on uniforms are ok? but trying to return
// or break out of this loop early makes galaxy 5 phones hang at the
// unity splash screen. works fine on other platforms I've tested, but
// I don't actually know if it's a speed boost anyway.
// if (i == _NumVerts - 1)
// break;
}
return closest_distance * when_eq(nodes % 2, 1) + -1 * when_neq(nodes % 2, 1);
}
// tells you which side of the line p1->p2 pos is
int point_line_test(float2 pos, float2 p1, float2 p2) {
return when_gt((pos.x - p1.x) * (p2.y - p1.y) - (pos.y - p1.y) * (p2.x - p1.x), 0);
}
fixed4 frag(v2f i) : SV_Target {
float2 pos = prepare(i.uv, i.modelPos.z);
#if !POLYGON_MAP
// dist is < 0 when pos is outside the poly
float dist = get_closest_distance(pos, float2(_XScale, _YScale) / 2);
float is_inside = when_ge(dist, 0);
#else
// use the polygon map texture optimization. the texture gives us the pre-computed
// two closest sides to the center of the texel, and what operation to perform.
fixed4 data = tex2D(_PolyMap, i.uv + 0.5);
// the indices of the two closest lines are in the r & g components,
// with 100 added to the index if the line test result is positive
int index1 = (int) (data.r * 256.0);
int index2 = (int) (data.g * 256.0);
float4 l1 = _Verts[index1];
float4 l2 = _Verts[index2];
// check if the points are inside the lines
int ptest1 = point_line_test(pos, l1.xy, l1.zw);
int ptest2 = point_line_test(pos, l2.xy, l2.zw);
// shortest distance to one of the lines
float dist1 = distance_to_line_segment(pos, l1.xy, l1.zw);
float dist2 = distance_to_line_segment(pos, l2.xy, l2.zw);
float dist = min(dist1, dist2);
// mode == 0, all pixels in the texel are outside the poly
// mode == 1, all pixels in the texel are inside the poly
// mode == 2, must be inside first line in texel
// mode == 3, must be inside both lines
// mode == 4, must be inside at least one of the lines
// mode == 5, must be inside closest line in the texel
int mode = (int) (data.a * 256.0);
float is_inside = when_eq(mode, 1)
+ when_eq(mode, 2) * ptest1
+ when_eq(mode, 3) * and(ptest1, ptest2)
+ when_eq(mode, 4) * or(ptest1, ptest2);
// + when_eq(mode, 5) *
// (when_lt(dist1, dist2) * ptest1
// + when_gt(dist1, dist2) * ptest2
// + when_eq(dist1, dist2) * and(ptest1, ptest2));
#endif
fixed4 color = color_from_distance(dist, fill(i.uv), _OutlineColor) * i.color;
if (_PreMultiplyAlpha == 1)
color.rgb *= color.a;
if (_UseClipRect == 1)
color.a *= UnityGet2DClipping(i.modelPos.xy, _ClipRect);
clip(color.a - 0.001);
return is_inside * color;
}