R8-h: a flat-tipped flag for the 3D solver (default off, byte-identical when off)

DeviceSdf gains `tip_corner: Option<f64>` (None = the capsule, as before;
Some(r_c) = a FLAT tip through the centreline's last point, normal to the
last segment, corners rounded to r_c): the last segment becomes a ray for
the lateral distance and the strip is cut by the tip plane with the span
cut's rounded intersection. Host twin (plate.rs: closest/tip_axial/
flat_cap, polyline and plate bodies) and the device φ and velocity
(e3_geom.cu geom_phi_at / body_velocity / plate_dist; GeomSdf flat_tip +
tip_corner) expression for expression.

Knobs: flag test RTX_E3_FLAG_TIP=flat + RTX_E3_FLAG_TIP_CORNER (default
0.00125 m; the tip inset defaults to 0 with the flat tip; the host φ is
the device form's); R8-a harness RTX_E3FSI_TIP=flat + RTX_E3FSI_TIP_CORNER
(the centreline gains node A as a 36th station).
New host test embedded3_flat_tip (G2 geometry: tip plane at the last
point, r_c = half = the capsule pulled back by half to 4e-17, cut volume
and wall area vs the analytic rounded rectangle at ny 62/124/248).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-25 22:32:31 -05:00
co-authored by Claude Opus 5.5
parent 171da41ed1
commit 02ab155022
9 changed files with 401 additions and 69 deletions
@@ -25,6 +25,8 @@ struct GeomSdf {
int cyl_cut, flag_cut; /* cut to the span */
int npts; /* polyline points (x, y interleaved) */
int nst, ns; /* R8-c plate: stations and points per station (nst 0: the polyline) */
int flat_tip; /* R8-h: 1 = the flat tip (corners rounded to tip_corner); 0 = the capsule */
double tip_corner;
};
struct GeomGrid {
@@ -46,6 +48,24 @@ __device__ __forceinline__ double span_cut(double d2, double z, const GeomSdf& s
return outside + rs_min(rs_max(q1, q2), 0.0) - r;
}
/* R8-h: the tip's axial distance along the last segment a→b from b (plate.rs tip_axial). */
__device__ __forceinline__ double tip_axial(double x, double y, double ax, double ay, double bx, double by)
{
double ex = bx - ax, ey = by - ay;
double l = sqrt(ex * ex + ey * ey);
return ((x - bx) * ex + (y - by) * ey) / l;
}
/* R8-h: the flat tip's rounded cap (plate.rs flat_cap). */
__device__ __forceinline__ double flat_cap(double dl, double a, double r)
{
double q1 = dl + r;
double q2 = a + r;
double m1 = rs_max(q1, 0.0), m2 = rs_max(q2, 0.0);
double outside = sqrt(m1 * m1 + m2 * m2);
return outside + rs_min(rs_max(q1, q2), 0.0) - r;
}
/*
* R8-c: the deformed plate (`plate.rs`, the host twin expression for
* expression): `poly` holds the stations' rows (x, y interleaved, row-major
@@ -57,7 +77,7 @@ __device__ __forceinline__ double span_cut(double d2, double z, const GeomSdf& s
*/
__device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
const double* __restrict__ P, const double* __restrict__ V,
double* vx, double* vy)
double* vx, double* vy, double* axial)
{
int nst = s.nst, ns = s.ns;
const double* Z = P + 2 * (long long) nst * ns;
@@ -97,14 +117,14 @@ __device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
double l2 = ex * ex + ey * ey;
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
if (u < 0.0) u = 0.0;
if (u > 1.0) u = 1.0;
if (u > 1.0 && !(s.flat_tip && m + 2 == ns)) u = 1.0;
double px = ax + u * ex, py = ay + u * ey;
double qx = x - px, qy = y - py;
double d = sqrt(qx * qx + qy * qy);
if (d < best) {
best = d;
mb = m;
ub = u;
ub = u > 1.0 ? 1.0 : u;
qbx = qx;
qby = qy;
}
@@ -118,6 +138,20 @@ __device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
double qn = (qbx * cx + qby * cy) / best;
best = best / sqrt(1.0 + qn * qn);
}
if (s.flat_tip && axial) {
int m = ns - 2;
double ax, ay, bx, by;
if (interp) {
ax = R0[2 * m] + w * (R1[2 * m] - R0[2 * m]);
ay = R0[2 * m + 1] + w * (R1[2 * m + 1] - R0[2 * m + 1]);
bx = R0[2 * m + 2] + w * (R1[2 * m + 2] - R0[2 * m + 2]);
by = R0[2 * m + 3] + w * (R1[2 * m + 3] - R0[2 * m + 3]);
} else {
ax = R0[2 * m]; ay = R0[2 * m + 1];
bx = R0[2 * m + 2]; by = R0[2 * m + 3];
}
*axial = tip_axial(x, y, ax, ay, bx, by);
}
if (V) {
const double* V0 = V + 2 * (long long) k * ns;
const double* V1 = interp ? V0 + 2 * ns : V0;
@@ -144,22 +178,29 @@ __device__ double geom_phi_at(double x, double y, double z, const GeomSdf& s, co
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
if (s.cyl_cut) dc = span_cut(dc, z, s);
/* the capsule: distance to the polyline (or the plate, R8-c) */
double best = 1.0 / 0.0;
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, nullptr, nullptr, nullptr);
else for (int m = 0; m + 1 < s.npts; ++m) {
double ax = poly[2 * m], ay = poly[2 * m + 1];
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
double ex = bx - ax, ey = by - ay;
double l2 = ex * ex + ey * ey;
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
if (u < 0.0) u = 0.0;
if (u > 1.0) u = 1.0;
double px = ax + u * ex, py = ay + u * ey;
double qx = x - px, qy = y - py;
double d = sqrt(qx * qx + qy * qy);
if (d < best) best = d;
double best = 1.0 / 0.0, axial = 0.0;
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, nullptr, nullptr, nullptr, &axial);
else {
for (int m = 0; m + 1 < s.npts; ++m) {
double ax = poly[2 * m], ay = poly[2 * m + 1];
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
double ex = bx - ax, ey = by - ay;
double l2 = ex * ex + ey * ey;
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
if (u < 0.0) u = 0.0;
if (u > 1.0 && !(s.flat_tip && m + 2 == s.npts)) u = 1.0;
double px = ax + u * ex, py = ay + u * ey;
double qx = x - px, qy = y - py;
double d = sqrt(qx * qx + qy * qy);
if (d < best) best = d;
}
if (s.flat_tip) {
int m = s.npts - 2;
axial = tip_axial(x, y, poly[2 * m], poly[2 * m + 1], poly[2 * m + 2], poly[2 * m + 3]);
}
}
double df = best - s.half;
if (s.flat_tip) df = flat_cap(df, axial, s.tip_corner);
if (s.flag_cut) df = span_cut(df, z, s);
double r = s.fillet;
if (r > 0.0 && dc < r && df < r) {
@@ -391,28 +432,36 @@ extern "C" __global__ void e3_geom_gather(
__device__ double body_velocity(double x, double y, double z, int c, const GeomSdf& s,
const double* __restrict__ poly, const double* __restrict__ vel)
{
double best = 1.0 / 0.0, vx = 0.0, vy = 0.0;
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, vel, &vx, &vy);
else for (int m = 0; m + 1 < s.npts; ++m) {
double ax = poly[2 * m], ay = poly[2 * m + 1];
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
double ex = bx - ax, ey = by - ay;
double l2 = ex * ex + ey * ey;
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
if (u < 0.0) u = 0.0;
if (u > 1.0) u = 1.0;
double px = ax + u * ex, py = ay + u * ey;
double qx = x - px, qy = y - py;
double d = sqrt(qx * qx + qy * qy);
if (d < best) {
best = d;
double avx = vel[2 * m], avy = vel[2 * m + 1];
double bvx = vel[2 * m + 2], bvy = vel[2 * m + 3];
vx = avx + u * (bvx - avx);
vy = avy + u * (bvy - avy);
double best = 1.0 / 0.0, vx = 0.0, vy = 0.0, axial = 0.0;
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, vel, &vx, &vy, &axial);
else {
for (int m = 0; m + 1 < s.npts; ++m) {
double ax = poly[2 * m], ay = poly[2 * m + 1];
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
double ex = bx - ax, ey = by - ay;
double l2 = ex * ex + ey * ey;
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
if (u < 0.0) u = 0.0;
if (u > 1.0 && !(s.flat_tip && m + 2 == s.npts)) u = 1.0;
double px = ax + u * ex, py = ay + u * ey;
double qx = x - px, qy = y - py;
double d = sqrt(qx * qx + qy * qy);
if (d < best) {
best = d;
double uv = u > 1.0 ? 1.0 : u;
double avx = vel[2 * m], avy = vel[2 * m + 1];
double bvx = vel[2 * m + 2], bvy = vel[2 * m + 3];
vx = avx + uv * (bvx - avx);
vy = avy + uv * (bvy - avy);
}
}
if (s.flat_tip) {
int m = s.npts - 2;
axial = tip_axial(x, y, poly[2 * m], poly[2 * m + 1], poly[2 * m + 2], poly[2 * m + 3]);
}
}
double df = best - s.half;
if (s.flat_tip) df = flat_cap(df, axial, s.tip_corner);
if (s.flag_cut) df = span_cut(df, z, s);
double ex0 = x - s.cx, ey0 = y - s.cy;
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;