From 1baeba1c592b589e508a33ace015c9ea6ab6a8ad Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 6 Jun 2018 08:33:41 +0100 Subject: [PATCH] Start working on z-clipping --- GL/clip.c | 66 +++++++++++++++++++++++++++++++++++++++ GL/clip.h | 45 +++++++++++++++++++++++++++ GL/draw.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++-- GL/private.h | 2 ++ 4 files changed, 198 insertions(+), 2 deletions(-) diff --git a/GL/clip.c b/GL/clip.c index a5c101d..152baab 100644 --- a/GL/clip.c +++ b/GL/clip.c @@ -32,3 +32,69 @@ ClipResult clipLineToNearZ(const float* v1, const float* v2, const float dist, f return CLIP_RESULT_ALL_ON_PLANE; } } + + +TriangleClipResult clipTriangleToNearZ( + const float plane_dist, + const unsigned short triangle_n, const pvr_vertex_t* v1, const pvr_vertex_t* v2, const pvr_vertex_t *v3, + pvr_vertex_t* v1out, pvr_vertex_t* v2out, pvr_vertex_t* v3out, pvr_vertex_t* v4out +) { + + /* Fast out. Let's just see if everything is in front of the clip plane (and as in OpenGL Z comes out of the screen + * we check to see if they are all < -dist + */ + + typedef unsigned char uint8; + uint8 visible = ((uint8) v1->z < plane_dist) | ((uint8) v2->z < plane_dist) << 1 | ((uint8) v3->z < plane_dist) << 2; + + switch(visible) { + case 0b000: + /* If behind is zero, then none of the vertices are visible */ + return TRIANGLE_CLIP_RESULT_DROP_TRIANGLE; + case 0b111: + /* If behind is zero, then none of the vertices are visible */ + return TRIANGLE_CLIP_RESULT_NO_CHANGE; + case 0b101: + case 0b110: + case 0b011: { + /* Two vertices are visible */ + /* Tricky case. If two vertices are visible then manipulating the other one is going to change the shape of the + * triangle. So we have to clip both lines, and output a new vertex. + */ + + return TRIANGLE_CLIP_RESULT_ALTERED_AND_CREATED_VERTEX; + } break; + default: { + /* One vertex is visible */ + /* This is the "easy" case, we simply find the vertex which is visible, and clip the lines to the other 2 against the plane */ + + pvr_vertex_t tmp1, tmp2; + float t1, t2; + + if(visible == 0b001) { + ClipResult l1 = clipLineToNearZ(&v1->x, &v2->x, plane_dist, &tmp1, &t1); + ClipResult l2 = clipLineToNearZ(&v1->x, &v3->x, plane_dist, &tmp2, &t2); + + *v1out = *v1; + *v2out = tmp1; + *v3out = tmp2; + } else if(visible == 0b010) { + ClipResult l1 = clipLineToNearZ(&v2->x, &v1->x, plane_dist, &tmp1, &t1); + ClipResult l2 = clipLineToNearZ(&v2->x, &v3->x, plane_dist, &tmp2, &t2); + + *v1out = tmp1; + *v2out = *v2; + *v3out = tmp2; + } else { + ClipResult l1 = clipLineToNearZ(&v3->x, &v1->x, plane_dist, &tmp1, &t1); + ClipResult l2 = clipLineToNearZ(&v3->x, &v2->x, plane_dist, &tmp2, &t2); + + *v1out = tmp1; + *v2out = tmp2; + *v3out = *v3; + } + + return TRIANGLE_CLIP_RESULT_ALTERED_VERTICES; + } + } +} diff --git a/GL/clip.h b/GL/clip.h index c6a496b..1432bc3 100644 --- a/GL/clip.h +++ b/GL/clip.h @@ -5,6 +5,22 @@ extern "C" { #endif +/* If we're not on the Dreamcast then define pvr_vertex_t + * (this if for testing only) + */ +#ifndef _arch_dreamcast +typedef struct { + uint32 flags; /**< \brief TA command (vertex flags) */ + float x; /**< \brief X coordinate */ + float y; /**< \brief Y coordinate */ + float z; /**< \brief Z coordinate */ + float u; /**< \brief Texture U coordinate */ + float v; /**< \brief Texture V coordinate */ + uint32 argb; /**< \brief Vertex color */ + uint32 oargb; /**< \brief Vertex offset color */ +} pvr_vertex_t; +#endif + typedef enum { CLIP_RESULT_ALL_IN_FRONT, CLIP_RESULT_ALL_BEHIND, @@ -15,6 +31,35 @@ typedef enum { ClipResult clipLineToNearZ(const float* v1, const float* v2, const float dist, float* vout, float* t); + +/* There are 4 possible situations we'll hit when clipping triangles: + * + * 1. The entire triangle was in front of the near plane, so we do nothing + * 2. The entire triangle was behind the near plane, so we drop it completely + * 3. One vertex was behind the clip plane. In this case we need to create a new vertex and an additional triangle + * 4. Two vertices were behind the clip plane, we can simply move them so the triangle no longer intersects + */ +typedef enum { + TRIANGLE_CLIP_RESULT_NO_CHANGE, + TRIANGLE_CLIP_RESULT_DROP_TRIANGLE, + TRIANGLE_CLIP_RESULT_ALTERED_AND_CREATED_VERTEX, + TRIANGLE_CLIP_RESULT_ALTERED_VERTICES +} TriangleClipResult; + +/* Clips a triangle from a triangle strip to a near-z plane. Alternating triangles in a strip switch vertex ordering + * so the number of the triangle must be passed in (vn - 2). + * + * Note that clipping a triangle with a plane may create a quadrilateral, so this function must have + * a space to output the 4th vertex. + * + * The outputs can be the same as the inputs. + */ +TriangleClipResult clipTriangleToNearZ( + const float plane_dist, + const unsigned short triangle_n, const pvr_vertex_t* v1, const pvr_vertex_t* v2, const pvr_vertex_t *v3, + pvr_vertex_t* v1out, pvr_vertex_t* v2out, pvr_vertex_t* v3out, pvr_vertex_t* v4out +); + #ifdef __cplusplus } #endif diff --git a/GL/draw.c b/GL/draw.c index e1e861b..aa1c1d2 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -190,12 +190,23 @@ inline void transformNormalToEyeSpace(GLfloat* normal) { mat_trans_normal3(normal[0], normal[1], normal[2]); } +/* If this has a value other than zero, it must be negative! */ +#define NEAR_DEPTH 0.0f static void submitVertices(GLenum mode, GLsizei first, GLsizei count, GLenum type, const GLvoid* indices) { static GLfloat normal[3] = {0.0f, 0.0f, -1.0f}; static GLfloat eye_P[3]; static GLfloat eye_N[3]; + /* When clipping triangle strips we need to keep a stash of the last two vertices *before* + * they were manipulated, because, imagine this scenario: + * - A triangle has 2 vertices hidden + * - We manipulate those 2 vertices to coincide with the near plane + * - We end that triangle strip, then start a new one with the next triangle + * - That triangle needs to be formed with the original vertices + */ + static pvr_vertex_t clip_stash[2]; + if(!(ENABLED_VERTEX_ATTRIBUTES & VERTEX_ENABLED_FLAG)) { return; } @@ -235,8 +246,10 @@ static void submitVertices(GLenum mode, GLsizei first, GLsizei count, GLenum typ GLboolean lighting_enabled = isLightingEnabled(); - GLushort i, last_vertex; - for(i = first; i < count; ++i) { + GLushort i, last_vertex + GLshort rel; // Has to be signed as we drop below zero so we can re-enter the loop at 1. + + for(rel = 0, i = first; i < count; ++i, ++rel) { pvr_vertex_t* vertex = (pvr_vertex_t*) dst; vertex->u = vertex->v = 0.0f; vertex->argb = 0; @@ -308,6 +321,8 @@ static void submitVertices(GLenum mode, GLsizei first, GLsizei count, GLenum typ } _applyRenderMatrix(); /* Apply the Render Matrix Stack */ + + // FIXME: Don't perspective divide! transformVertex(&vertex->x, &vertex->x, &vertex->y, &vertex->z); /* The PVR doesn't support quads, only triangle strips, so we need to @@ -325,6 +340,74 @@ static void submitVertices(GLenum mode, GLsizei first, GLsizei count, GLenum typ *(vertex - 1) = tmp; } + // Store this for the clip stash + pvr_vertex_t original_vertex = *dst; + + if(rel >= 2) { + /* We have at least one complete triangle, let's start clipping! */ + pvr_vertex_t* v1 = &clip_stash[0]; + pvr_vertex_t* v2 = &clip_stash[1]; + pvr_vertex_t* v3 = dst; + + pvr_vertex_t* v1out = dst - 2; + pvr_vertex_t* v2out = dst - 1; + pvr_vertex_t* v3out = v3; + pvr_vertex_t v4out; + + TriangleClipResult ret = clipTriangleToNearZ( + NEAR_DEPTH, + rel - 2, + v1, v2, v3, + v1out, v2out, v3out, &v4out + ); + + if(ret == TRIANGLE_CLIP_RESULT_DROP_TRIANGLE) { + /* If we're here, then none of the 3 points were visible, that means we drop the entire triangle and start + * anew with the next one. We rollback rel as we always need 3 vertices and if this was the first triangle in the strip + * we need to build up the stash again + * we point dst back at the first vertex. We would actually have access space at the end of the array + * so we move that back too (this won't reallocate, we never reallocate on shrink unless we call vector_shrink_to_fit + */ + dst = dst - 3; // This might seem like we're going back too many, but we increment further down + rel = rel - 3; // This might drop down to -1, but the next loop will go up to 0 again + + aligned_vector_resize( + &activePolyList()->vector, + activePolyList->size - 3 + ); + + } else if(ret == TRIANGLE_CLIP_RESULT_ALTERED_VERTICES) { + /* + * Two vertices were behind the clip plane, we just manipulated them. + we have to end the triangle strip here and pick up next vertex */ + + v3out->flags = PVR_CMD_VERTEX_EOL; + + /* Now we push back the original 2 vertices, so that the next triangle strip will be properly + * formed (or dropped) next time around */ + aligned_vector_resize( + &activePolyList()->vector, + activePolyList->size + 2 + ); + + *(++dst) = clip_stash[1]; + *(++dst) = original_vertex; + + } else if(ret == TRIANGLE_CLIP_RESULT_ALTERED_AND_CREATED_VERTEX) { + /* One vertex was behind the clip plane, we need to create another triangle */ + /* We need to push back v4 and then deal with a possible reallocation by updating dst */ + + } else { + /* OK nothing changed, don't do anything */ + } + } + + /* Update the clip stash */ + clip_stash[0] = clip_stash[1]; + clip_stash[1] = original_vertex; + + //FIXME: Peform perspective division + ++dst; } } diff --git a/GL/private.h b/GL/private.h index eac62de..fa9c262 100644 --- a/GL/private.h +++ b/GL/private.h @@ -4,7 +4,9 @@ #include "../include/gl.h" #include "../containers/aligned_vector.h" #include "../containers/named_array.h" + #include "./clip.h" +#include "./pvr.h" #define TRACE_ENABLED 0 #define TRACE() if(TRACE_ENABLED) {fprintf(stderr, "%s\n", __func__);}