forked from zeichensystem/GeBurtstAg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclipping.c
206 lines (190 loc) · 7.81 KB
/
clipping.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#include <string.h>
#include <stdlib.h>
#include <tonc.h>
#include "clipping.h"
#include "../globals.h"
#include "../math.h"
#include "../logutils.h"
#include "../model.h"
#define RASTERPOINT_IN_BOUNDS_M5(vert) (vert.x >= 0 && vert.x < M5_SCALED_W && vert.y >= 0 && vert.y < M5_SCALED_H)
typedef enum ClipEdges {
TOP_EDGE=0,
BOTTOM_EDGE,
LEFT_EDGE,
RIGHT_EDGE
} ClipEdges;
/*
Calculates line intersections against the screen borders, used for 2d clipping.
Invariant: To be called only when the given intersection actually exists, which is the case for our use case.
(But otherwise, the assertions should catch the divisions by zero in those cases).
cf. https://www.cs.helsinki.fi/group/goa/viewing/leikkaus/intro2.html
(Their code doesn't take into account division by zero explicity, as they use 1/slope for the top/bottom intersect; my code does by calculating the invslope directly.)
*/
static RasterPoint calcIntersect2d(ClipEdges edge, RasterPoint current, RasterPoint prev)
{
RasterPoint inter;
FIXED slope, invslope;
switch (edge) {
case LEFT_EDGE:
assertion(current.x - prev.x !=0, "calcIntersect div 0");
slope = fxdiv(int2fx(current.y - prev.y), int2fx(current.x - prev.x));
inter.x = 0;
inter.y = fx2int(fxmul(slope, 0 - int2fx(prev.x)) + int2fx(prev.y));
break;
case RIGHT_EDGE:
assertion(current.x - prev.x !=0, "calcIntersect div 0");
slope = fxdiv(int2fx(current.y - prev.y), int2fx(current.x - prev.x));
inter.x = M5_SCALED_W - 1;
inter.y = fx2int(fxmul(slope, int2fx(M5_SCALED_W - 1) - int2fx(prev.x)) + int2fx(prev.y));
break;
case TOP_EDGE:
assertion(current.y - prev.y !=0, "calcIntersect div 0");
invslope = fxdiv(int2fx(current.x - prev.x), int2fx(current.y - prev.y));
inter.y = 0;
inter.x = fx2int(fxmul(0 - int2fx(prev.y), invslope) + int2fx(prev.x));
break;
case BOTTOM_EDGE:
assertion(current.y - prev.y !=0, "calcIntersect div 0");
invslope = fxdiv(int2fx(current.x - prev.x), int2fx(current.y - prev.y));
inter.y = M5_SCALED_H - 1;
inter.x = fx2int(fxmul(int2fx(M5_SCALED_H - 1) - int2fx(prev.y), invslope) + int2fx(prev.x));
break;
default:
panic("calcIntersect: unknown edge");
}
return inter;
}
/*
Used by the Sutherland Hodgman Clipping against the Screen.
Returns true if the given point lies on the same side of the given screen's edge as the remainder of the screen.
This does *not* imply the point actually lies within the screen, that would be incorrect for the purposes of the Sutherland-Hodgman algorithm.
*/
INLINE bool inside_clip(RasterPoint point, ClipEdges edge)
{
switch(edge) {
case TOP_EDGE:
return point.y >= 0;
break;
case BOTTOM_EDGE:
return point.y < M5_SCALED_H;
break;
case LEFT_EDGE:
return point.x >= 0;
break;
case RIGHT_EDGE:
return point.x < M5_SCALED_W;
break;
default:
panic("unknown clipping edge");
return false;
}
}
/*
cf. https://en.wikipedia.org/wiki/Sutherland–Hodgman_algorithm
*/
int clipTriangleVerts2d(RasterPoint outputVertices[CLIPPING_MAX_POLY_LEN])
{
int outputLen = 3;
RasterPoint inputList[CLIPPING_MAX_POLY_LEN];
for (int edge = 0; edge < 4; ++edge) {
memcpy(inputList, outputVertices, sizeof(RasterPoint) * outputLen);
int inputLen = outputLen;
outputLen = 0;
for (int i = 0; i < inputLen; ++i) {
RasterPoint prev = inputList[i];
RasterPoint current = inputList[(i + 1) % inputLen];
if (inside_clip(current, edge)) {
if (!inside_clip(prev, edge) ) {
assertion(outputLen < CLIPPING_MAX_POLY_LEN, "output len 1");
outputVertices[outputLen++] = calcIntersect2d(edge, current, prev);
}
assertion(outputLen < CLIPPING_MAX_POLY_LEN, "output len 1");
outputVertices[outputLen++] = current;
} else if (inside_clip(prev, edge)) {
assertion(outputLen < CLIPPING_MAX_POLY_LEN, "output len 2");
outputVertices[outputLen++] = calcIntersect2d(edge, current, prev);
}
}
}
/*
This is just a sanity check/assertion; the algorithm should not yield out of bounds vertices.
Note that we can only check this after the algorithm has terminated, as the intermediate steps can yield intermediary out-of-screen vertices.
Checking for out-of-screen vertices while we haven't constructed the final outputVertices array *does not make sense* due to the nature of S-H-clipping.
*/
for (int i = 0; i < outputLen; ++i) {
assertion(RASTERPOINT_IN_BOUNDS_M5(outputVertices[i]), "draw.c: clipTriangleVerts2d: Vertex within screen bounds");
}
return outputLen;
}
/*
The following code is copied more or less line by line (no pun intended) from the English wikipedia article
on Cohen-Sutherland line-cliping (with minor modifications), and therefore licensed under the
"Creative Commons Attribution-ShareAlike 3.0 Unported License".
cf. https://en.wikipedia.org/wiki/Cohen–Sutherland_algorithm (retrieved 2021-05-10)
*/
typedef int OutCode;
static const int INSIDE = 0; // 0000
static const int LEFT = 1; // 0001
static const int RIGHT = 2; // 0010
static const int BOTTOM = 4; // 0100
static const int TOP = 8; // 1000
INLINE OutCode computeOutcode(const RasterPoint *a)
{
OutCode code = INSIDE;
if (a->x < 0) {
code |= LEFT;
} else if (a->x >= M5_SCALED_W) {
code |= RIGHT;
}
if (a->y < 0) {
code |= TOP;
} else if (a->y >= M5_SCALED_H) {
code |= BOTTOM;
}
return code;
}
bool clipLineCohenSutherland(RasterPoint *a, RasterPoint *b)
{
OutCode outcodeA = computeOutcode(a);
OutCode outcodeB = computeOutcode(b);
int count = 0;
while (1) {
if (!(outcodeA | outcodeB)) { // Both vertices within the screen.
return true;
} else if (outcodeA & outcodeB) { // Both vertices share an outside zone (and therefore are invisible.)
return false;
}
OutCode outside = outcodeA > outcodeB ? outcodeA : outcodeB; // One of the vertices must be outside, select that one.
int x, y;
if (outside & TOP) {
// (x2 - x1) / (y2 - y1) = (x - x1) / (y - y1)
y = 0;
x = a->x + fx2int(fxmul(fxdiv(int2fx(b->x - a->x), int2fx(b->y - a->y)), 0 - int2fx(a->y)));
} else if (outside & BOTTOM) {
y = M5_SCALED_H - 1;
x = a->x + fx2int(fxmul(fxdiv(int2fx(b->x - a->x), int2fx(b->y - a->y)), int2fx(y) - int2fx(a->y)));
} else if (outside & LEFT) {
// (y2 - y1) / (x2 - x1) = (y - y1) / (x - x1)
x = 0;
y = a->y + fx2int(fxmul(fxdiv(int2fx(b->y - a->y), int2fx(b->x - a->x)), int2fx(x) - int2fx(a->x)));
} else if (outside & RIGHT) {
x = M5_SCALED_W - 1;
y = a->y + fx2int(fxmul(fxdiv(int2fx(b->y - a->y), int2fx(b->x - a->x)), int2fx(x) - int2fx(a->x)));
} else {
panic("clipping.c: clipLineCohenSutherland: Programming error; no clip intersection.");
}
if (outside == outcodeA) {
a->x = x;
a->y = y;
outcodeA = computeOutcode(a);
} else {
b->x = x;
b->y = y;
outcodeB = computeOutcode(b);
}
if (++count > 32) {
panic("clipping.c: clipLineCohenSutherland: infinite loop");
return false;
}
}
}