This commit is contained in:
Jan Racek
2026-03-24 13:01:47 +01:00
parent 55c1d2c39b
commit e51c06b012
51 changed files with 8111 additions and 491 deletions

197
src/triclip.cpp Normal file
View File

@@ -0,0 +1,197 @@
#include "triclip.hh"
namespace psxsplash {
// ============================================================================
// Screen-space Sutherland-Hodgman clipping
// ============================================================================
// Interpolate between two ClipVertex at parameter t (0..256 = 0.0..1.0, fp8).
// Uses fp8 to avoid overflow with int16 coordinates (int16 * 256 fits int32).
static ClipVertex lerpClip(const ClipVertex& a, const ClipVertex& b, int32_t t) {
ClipVertex r;
r.x = (int16_t)(a.x + (((int32_t)(b.x - a.x) * t) >> 8));
r.y = (int16_t)(a.y + (((int32_t)(b.y - a.y) * t) >> 8));
r.z = (int16_t)(a.z + (((int32_t)(b.z - a.z) * t) >> 8));
r.u = (uint8_t)(a.u + (((int)(b.u) - (int)(a.u)) * t >> 8));
r.v = (uint8_t)(a.v + (((int)(b.v) - (int)(a.v)) * t >> 8));
r.r = (uint8_t)(a.r + (((int)(b.r) - (int)(a.r)) * t >> 8));
r.g = (uint8_t)(a.g + (((int)(b.g) - (int)(a.g)) * t >> 8));
r.b = (uint8_t)(a.b + (((int)(b.b) - (int)(a.b)) * t >> 8));
return r;
}
// Clip a polygon (in[] with inCount vertices) against a single edge.
// Edge is defined by axis (0=X, 1=Y), sign (+1 or -1), and threshold.
// Output written to out[], returns output vertex count.
static int clipEdge(const ClipVertex* in, int inCount,
ClipVertex* out, int axis, int sign, int16_t threshold) {
if (inCount == 0) return 0;
int outCount = 0;
for (int i = 0; i < inCount; i++) {
const ClipVertex& cur = in[i];
const ClipVertex& next = in[(i + 1) % inCount];
int16_t curVal = (axis == 0) ? cur.x : cur.y;
int16_t nextVal = (axis == 0) ? next.x : next.y;
bool curInside = (sign > 0) ? (curVal <= threshold) : (curVal >= threshold);
bool nextInside = (sign > 0) ? (nextVal <= threshold) : (nextVal >= threshold);
if (curInside) {
if (outCount < 8) out[outCount++] = cur;
if (!nextInside) {
// Exiting: compute intersection
int32_t den = (int32_t)nextVal - (int32_t)curVal;
if (den != 0) {
int32_t t = ((int32_t)(threshold - curVal) << 8) / den;
if (t < 0) t = 0;
if (t > 256) t = 256;
if (outCount < 8) out[outCount++] = lerpClip(cur, next, t);
}
}
} else if (nextInside) {
// Entering: compute intersection
int32_t den = (int32_t)nextVal - (int32_t)curVal;
if (den != 0) {
int32_t t = ((int32_t)(threshold - curVal) << 8) / den;
if (t < 0) t = 0;
if (t > 256) t = 256;
if (outCount < 8) out[outCount++] = lerpClip(cur, next, t);
}
}
}
return outCount;
}
int clipTriangle(const ClipVertex& v0, const ClipVertex& v1, const ClipVertex& v2,
ClipResult& result) {
// Working buffers for Sutherland-Hodgman (max 8 vertices after 4 clips).
ClipVertex bufA[8], bufB[8];
bufA[0] = v0; bufA[1] = v1; bufA[2] = v2;
int count = 3;
// Clip against 4 edges: left, right, top, bottom.
count = clipEdge(bufA, count, bufB, 0, -1, CLIP_LEFT); // X >= CLIP_LEFT
count = clipEdge(bufB, count, bufA, 0, +1, CLIP_RIGHT); // X <= CLIP_RIGHT
count = clipEdge(bufA, count, bufB, 1, -1, CLIP_TOP); // Y >= CLIP_TOP
count = clipEdge(bufB, count, bufA, 1, +1, CLIP_BOTTOM); // Y <= CLIP_BOTTOM
if (count < 3) return 0;
// Triangulate the convex polygon into a fan from vertex 0.
int triCount = count - 2;
if (triCount > MAX_CLIP_TRIS) triCount = MAX_CLIP_TRIS;
for (int i = 0; i < triCount; i++) {
result.verts[i * 3 + 0] = bufA[0];
result.verts[i * 3 + 1] = bufA[i + 1];
result.verts[i * 3 + 2] = bufA[i + 2];
}
return triCount;
}
// ============================================================================
// Near-plane (3D view-space) clipping
// ============================================================================
ViewVertex lerpViewVertex(const ViewVertex& a, const ViewVertex& b, int32_t t) {
ViewVertex r;
r.x = a.x + (int32_t)(((int64_t)(b.x - a.x) * t) >> 12);
r.y = a.y + (int32_t)(((int64_t)(b.y - a.y) * t) >> 12);
r.z = a.z + (int32_t)(((int64_t)(b.z - a.z) * t) >> 12);
r.u = (uint8_t)(a.u + (((int)(b.u) - (int)(a.u)) * t >> 12));
r.v = (uint8_t)(a.v + (((int)(b.v) - (int)(a.v)) * t >> 12));
r.r = (uint8_t)(a.r + (((int)(b.r) - (int)(a.r)) * t >> 12));
r.g = (uint8_t)(a.g + (((int)(b.g) - (int)(a.g)) * t >> 12));
r.b = (uint8_t)(a.b + (((int)(b.b) - (int)(a.b)) * t >> 12));
r.pad = 0;
return r;
}
static inline int32_t nearPlaneT(int32_t zA, int32_t zB) {
constexpr int32_t nearZ = (int32_t)NEAR_PLANE_Z << 12;
int32_t num = nearZ - zA;
int32_t den = zB - zA;
if (den == 0) return 0;
int32_t absNum = num < 0 ? -num : num;
int shift = 0;
while (absNum > 0x7FFFF) {
absNum >>= 1;
shift++;
}
if (shift > 0) {
num >>= shift;
den >>= shift;
if (den == 0) return num > 0 ? 4096 : 0;
}
return (num << 12) / den;
}
static inline bool isBehind(int32_t z) {
return z < ((int32_t)NEAR_PLANE_Z << 12);
}
int nearPlaneClip(const ViewVertex& v0, const ViewVertex& v1, const ViewVertex& v2,
NearClipResult& result) {
bool b0 = isBehind(v0.z);
bool b1 = isBehind(v1.z);
bool b2 = isBehind(v2.z);
int behindCount = (int)b0 + (int)b1 + (int)b2;
if (behindCount == 3) {
result.triCount = 0;
return 0;
}
if (behindCount == 0) {
result.triCount = 1;
result.verts[0] = v0;
result.verts[1] = v1;
result.verts[2] = v2;
return 1;
}
if (behindCount == 1) {
const ViewVertex* A;
const ViewVertex* B;
const ViewVertex* C;
if (b0) { A = &v0; B = &v1; C = &v2; }
else if (b1) { A = &v1; B = &v2; C = &v0; }
else { A = &v2; B = &v0; C = &v1; }
int32_t tAB = nearPlaneT(A->z, B->z);
int32_t tAC = nearPlaneT(A->z, C->z);
ViewVertex AB = lerpViewVertex(*A, *B, tAB);
ViewVertex AC = lerpViewVertex(*A, *C, tAC);
result.triCount = 2;
result.verts[0] = AB;
result.verts[1] = *B;
result.verts[2] = *C;
result.verts[3] = AB;
result.verts[4] = *C;
result.verts[5] = AC;
return 2;
}
{
const ViewVertex* A;
const ViewVertex* B;
const ViewVertex* C;
if (!b0) { A = &v0; B = &v1; C = &v2; }
else if (!b1) { A = &v1; B = &v2; C = &v0; }
else { A = &v2; B = &v0; C = &v1; }
int32_t tBA = nearPlaneT(B->z, A->z);
int32_t tCA = nearPlaneT(C->z, A->z);
ViewVertex BA = lerpViewVertex(*B, *A, tBA);
ViewVertex CA = lerpViewVertex(*C, *A, tCA);
result.triCount = 1;
result.verts[0] = *A;
result.verts[1] = BA;
result.verts[2] = CA;
return 1;
}
}
} // namespace psxsplash