3dgfx code, untested
[metatoy] / src / 3dgfx / polyclip.c
1 #include <stdio.h>
2 #include <math.h>
3 #include <assert.h>
4 #include "polyclip.h"
5
6 struct ray {
7         float origin[3];
8         float dir[3];
9 };
10
11 static int clip_edge(struct g3d_vertex *poly, int *vnumptr,
12                 const struct g3d_vertex *v0, const struct g3d_vertex *v1,
13                 const struct cplane *plane);
14 static int check_clip_edge(const struct g3d_vertex *v0,
15                 const struct g3d_vertex *v1, const struct cplane *plane);
16 static int clip_edge_frustum(struct g3d_vertex *poly, int *vnumptr,
17                 const struct g3d_vertex *v0, const struct g3d_vertex *v1, int fplane);
18 static float distance_signed(float *pos, const struct cplane *plane);
19 static int intersect(const struct ray *ray, const struct cplane *plane, float *t);
20 static int inside_frustum_plane(const struct g3d_vertex *v, int fplane);
21
22
23 int clip_poly(struct g3d_vertex *vout, int *voutnum,
24                 const struct g3d_vertex *vin, int vnum, struct cplane *plane)
25 {
26         int i, nextidx, res;
27         int edges_clipped = 0;
28
29         *voutnum = 0;
30
31         for(i=0; i<vnum; i++) {
32                 nextidx = i + 1;
33                 if(nextidx >= vnum) nextidx = 0;
34                 res = clip_edge(vout, voutnum, vin + i, vin + nextidx, plane);
35                 if(res == 0) {
36                         ++edges_clipped;
37                 }
38         }
39
40         if(*voutnum <= 0) {
41                 assert(edges_clipped == 0);
42                 return -1;
43         }
44
45         return edges_clipped > 0 ? 0 : 1;
46 }
47
48 int check_clip_poly(const struct g3d_vertex *v, int vnum, struct cplane *plane)
49 {
50         int i, nextidx, res = 0;
51         int edges_clipped = 0;
52
53         for(i=0; i<vnum; i++) {
54                 nextidx = i + 1;
55                 if(nextidx >= vnum) nextidx = 0;
56                 res = check_clip_edge(v + i, v + nextidx, plane);
57                 if(res == 0) {
58                         ++edges_clipped;
59                 }
60         }
61         return edges_clipped ? 0 : res;
62 }
63
64 int clip_frustum(struct g3d_vertex *vout, int *voutnum,
65                 const struct g3d_vertex *vin, int vnum, int fplane)
66 {
67         int i, nextidx, res;
68         int edges_clipped = 0;
69
70         if(vnum == 1) {
71                 /* special case: point clipping */
72                 return inside_frustum_plane(vin, fplane) ? 1 : -1;
73         }
74
75         *voutnum = 0;
76
77         for(i=0; i<vnum; i++) {
78                 nextidx = i + 1;
79                 if(nextidx >= vnum) nextidx = 0;
80                 res = clip_edge_frustum(vout, voutnum, vin + i, vin + nextidx, fplane);
81                 if(res == 0) {
82                         ++edges_clipped;
83                 }
84         }
85
86         if(*voutnum <= 0) {
87                 assert(edges_clipped == 0);
88                 return -1;
89         }
90
91         return edges_clipped > 0 ? 0 : 1;
92 }
93
94 #define LERP_VATTR(res, v0, v1, t) \
95         do { \
96                 (res)->nx = (v0)->nx + ((v1)->nx - (v0)->nx) * (t); \
97                 (res)->ny = (v0)->ny + ((v1)->ny - (v0)->ny) * (t); \
98                 (res)->nz = (v0)->nz + ((v1)->nz - (v0)->nz) * (t); \
99                 (res)->u = (v0)->u + ((v1)->u - (v0)->u) * (t); \
100                 (res)->v = (v0)->v + ((v1)->v - (v0)->v) * (t); \
101                 (res)->l = (v0)->l + ((v1)->l - (v0)->l) * (t); \
102                 (res)->a = (v0)->a + ((v1)->a - (v0)->a) * (t); \
103         } while(0)
104
105
106 /* returns:
107  *  1 -> both inside
108  *  0 -> straddling and clipped
109  * -1 -> both outside
110  *
111  *  also returns the size of the polygon through vnumptr
112  */
113 static int clip_edge(struct g3d_vertex *poly, int *vnumptr,
114                 const struct g3d_vertex *v0, const struct g3d_vertex *v1,
115                 const struct cplane *plane)
116 {
117         float pos0[3], pos1[3];
118         float d0, d1, t;
119         struct ray ray;
120         int i, vnum = *vnumptr;
121
122         pos0[0] = v0->x; pos0[1] = v0->y; pos0[2] = v0->z;
123         pos1[0] = v1->x; pos1[1] = v1->y; pos1[2] = v1->z;
124
125         d0 = distance_signed(pos0, plane);
126         d1 = distance_signed(pos1, plane);
127
128         for(i=0; i<3; i++) {
129                 ray.origin[i] = pos0[i];
130                 ray.dir[i] = pos1[i] - pos0[i];
131         }
132
133         if(d0 >= 0.0) {
134                 /* start inside */
135                 if(d1 >= 0.0) {
136                         /* all inside */
137                         poly[vnum++] = *v1;     /* append v1 */
138                         *vnumptr = vnum;
139                         return 1;
140                 } else {
141                         /* going out */
142                         struct g3d_vertex *vptr = poly + vnum;
143
144                         intersect(&ray, plane, &t);
145
146                         vptr->x = ray.origin[0] + ray.dir[0] * t;
147                         vptr->y = ray.origin[1] + ray.dir[1] * t;
148                         vptr->z = ray.origin[2] + ray.dir[2] * t;
149                         vptr->w = 1.0f;
150
151                         LERP_VATTR(vptr, v0, v1, t);
152                         vnum++; /* append new vertex on the intersection point */
153                 }
154         } else {
155                 /* start outside */
156                 if(d1 >= 0) {
157                         /* going in */
158                         struct g3d_vertex *vptr = poly + vnum;
159
160                         intersect(&ray, plane, &t);
161
162                         vptr->x = ray.origin[0] + ray.dir[0] * t;
163                         vptr->y = ray.origin[1] + ray.dir[1] * t;
164                         vptr->z = ray.origin[2] + ray.dir[2] * t;
165                         vptr->w = 1.0f;
166
167                         LERP_VATTR(vptr, v0, v1, t);
168                         vnum++; /* append new vertex on the intersection point */
169
170                         /* then append v1 ... */
171                         poly[vnum++] = *v1;
172                 } else {
173                         /* all outside */
174                         return -1;
175                 }
176         }
177
178         *vnumptr = vnum;
179         return 0;
180 }
181
182 /* same as above, but only checks for clipping and classifies the edge */
183 static int check_clip_edge(const struct g3d_vertex *v0,
184                 const struct g3d_vertex *v1, const struct cplane *plane)
185 {
186         float pos0[3], pos1[3];
187         float d0, d1;
188
189         pos0[0] = v0->x; pos0[1] = v0->y; pos0[2] = v0->z;
190         pos1[0] = v1->x; pos1[1] = v1->y; pos1[2] = v1->z;
191
192         d0 = distance_signed(pos0, plane);
193         d1 = distance_signed(pos1, plane);
194
195         if(d0 > 0.0f && d1 > 0.0f) {
196                 return 1;
197         }
198         if(d0 < 0.0f && d1 < 0.0f) {
199                 return -1;
200         }
201         return 0;
202 }
203
204 static float distance_signed(float *pos, const struct cplane *plane)
205 {
206         float dx = pos[0] - plane->x;
207         float dy = pos[1] - plane->y;
208         float dz = pos[2] - plane->z;
209         return dx * plane->nx + dy * plane->ny + dz * plane->nz;
210 }
211
212 static int intersect(const struct ray *ray, const struct cplane *plane, float *t)
213 {
214         float orig_pt_dir[3];
215
216         float ndotdir = plane->nx * ray->dir[0] + plane->ny * ray->dir[1] + plane->nz * ray->dir[2];
217         if(fabs(ndotdir) < 1e-6) {
218                 *t = 0.0f;
219                 return 0;
220         }
221
222         orig_pt_dir[0] = plane->x - ray->origin[0];
223         orig_pt_dir[1] = plane->y - ray->origin[1];
224         orig_pt_dir[2] = plane->z - ray->origin[2];
225
226         *t = (plane->nx * orig_pt_dir[0] + plane->ny * orig_pt_dir[1] + plane->nz * orig_pt_dir[2]) / ndotdir;
227         return 1;
228 }
229
230 /* homogeneous frustum clipper helpers */
231
232 static int inside_frustum_plane(const struct g3d_vertex *v, int fplane)
233 {
234         switch(fplane) {
235         case CLIP_LEFT:
236                 return v->x >= -v->w;
237         case CLIP_RIGHT:
238                 return v->x <= v->w;
239         case CLIP_BOTTOM:
240                 return v->y >= -v->w;
241         case CLIP_TOP:
242                 return v->y <= v->w;
243         case CLIP_NEAR:
244                 return v->z >= -v->w;
245         case CLIP_FAR:
246                 return v->z <= v->w;
247         }
248         assert(0);
249         return 0;
250 }
251
252 static float intersect_frustum(const struct g3d_vertex *a, const struct g3d_vertex *b, int fplane)
253 {
254         switch(fplane) {
255         case CLIP_LEFT:
256                 return (-a->w - a->x) / (b->x - a->x + b->w - a->w);
257         case CLIP_RIGHT:
258                 return (a->w - a->x) / (b->x - a->x - b->w + a->w);
259         case CLIP_BOTTOM:
260                 return (-a->w - a->y) / (b->y - a->y + b->w - a->w);
261         case CLIP_TOP:
262                 return (a->w - a->y) / (b->y - a->y - b->w + a->w);
263         case CLIP_NEAR:
264                 return (-a->w - a->z) / (b->z - a->z + b->w - a->w);
265         case CLIP_FAR:
266                 return (a->w - a->z) / (b->z - a->z - b->w + a->w);
267         }
268
269         assert(0);
270         return 0;
271 }
272
273 static int clip_edge_frustum(struct g3d_vertex *poly, int *vnumptr,
274                 const struct g3d_vertex *v0, const struct g3d_vertex *v1, int fplane)
275 {
276         int vnum = *vnumptr;
277         int in0, in1;
278         float t;
279
280         in0 = inside_frustum_plane(v0, fplane);
281         in1 = inside_frustum_plane(v1, fplane);
282
283         if(in0) {
284                 /* start inside */
285                 if(in1) {
286                         /* all inside */
287                         poly[vnum++] = *v1;     /* append v1 */
288                         *vnumptr = vnum;
289                         return 1;
290                 } else {
291                         /* going out */
292                         struct g3d_vertex *vptr = poly + vnum;
293
294                         t = intersect_frustum(v0, v1, fplane);
295
296                         vptr->x = v0->x + (v1->x - v0->x) * t;
297                         vptr->y = v0->y + (v1->y - v0->y) * t;
298                         vptr->z = v0->z + (v1->z - v0->z) * t;
299                         vptr->w = v0->w + (v1->w - v0->w) * t;
300
301                         LERP_VATTR(vptr, v0, v1, t);
302                         ++vnum; /* append new vertex on the intersection point */
303                 }
304         } else {
305                 /* start outside */
306                 if(in1) {
307                         /* going in */
308                         struct g3d_vertex *vptr = poly + vnum;
309
310                         t = intersect_frustum(v0, v1, fplane);
311
312                         vptr->x = v0->x + (v1->x - v0->x) * t;
313                         vptr->y = v0->y + (v1->y - v0->y) * t;
314                         vptr->z = v0->z + (v1->z - v0->z) * t;
315                         vptr->w = v0->w + (v1->w - v0->w) * t;
316
317                         LERP_VATTR(vptr, v0, v1, t);
318                         ++vnum; /* append new vertex on the intersection point */
319
320                         /* then append v1 ... */
321                         poly[vnum++] = *v1;
322                 } else {
323                         /* all outside */
324                         return -1;
325                 }
326         }
327
328         *vnumptr = vnum;
329         return 0;
330 }