1 |
using System; |
2 |
using System.Collections.Generic; |
3 |
|
4 |
namespace Oni.Akira |
5 |
{ |
6 |
internal struct Polygon2Clipper |
7 |
{ |
8 |
private readonly List<Polygon2> result; |
9 |
private readonly RoomBspNode bspTree; |
10 |
|
11 |
#region private struct Line |
12 |
|
13 |
private struct Line |
14 |
{ |
15 |
private float a, c, d; |
16 |
|
17 |
public Line(Plane plane) |
18 |
{ |
19 |
a = plane.Normal.X; |
20 |
c = plane.Normal.Z; |
21 |
d = plane.D; |
22 |
} |
23 |
|
24 |
public int RelativePosition(Vector2 point) |
25 |
{ |
26 |
float r = a * point.X + c * point.Y + d; |
27 |
|
28 |
if (r < -0.00001f) |
29 |
return -1; |
30 |
else if (r > 0.00001f) |
31 |
return 1; |
32 |
else |
33 |
return 0; |
34 |
} |
35 |
|
36 |
public Vector2 Intersect(Vector2 p0, Vector2 p1) |
37 |
{ |
38 |
if (p0.X == p1.X) |
39 |
{ |
40 |
float x = p0.X; |
41 |
float y = (d + a * x) / -c; |
42 |
return new Vector2(x, y); |
43 |
} |
44 |
else if (p0.Y == p1.Y) |
45 |
{ |
46 |
float x = (-c * p0.Y - d) / a; |
47 |
float y = p0.Y; |
48 |
return new Vector2(x, y); |
49 |
} |
50 |
else |
51 |
{ |
52 |
float m = (p1.Y - p0.Y) / (p1.X - p0.X); |
53 |
float x = (c * m * p0.X - c * p0.Y - d) / (a + c * m); |
54 |
float y = m * (x - p0.X) + p0.Y; |
55 |
return new Vector2(x, y); |
56 |
} |
57 |
} |
58 |
} |
59 |
|
60 |
#endregion |
61 |
|
62 |
public Polygon2Clipper(RoomBspNode bspTree) |
63 |
{ |
64 |
this.result = new List<Polygon2>(); |
65 |
this.bspTree = bspTree; |
66 |
} |
67 |
|
68 |
public IEnumerable<Polygon2> Clip(Polygon2 polygon) |
69 |
{ |
70 |
result.Clear(); |
71 |
Clip(new[] { polygon }, bspTree); |
72 |
return result; |
73 |
} |
74 |
|
75 |
private void Clip(IEnumerable<Polygon2> polygons, RoomBspNode node) |
76 |
{ |
77 |
var negative = new List<Polygon2>(); |
78 |
var positive = new List<Polygon2>(); |
79 |
|
80 |
var plane = node.Plane; |
81 |
|
82 |
if (Math.Abs(plane.Normal.Y) > 0.001f) |
83 |
{ |
84 |
negative.AddRange(polygons); |
85 |
positive.AddRange(polygons); |
86 |
} |
87 |
else |
88 |
{ |
89 |
var line = new Line(plane); |
90 |
|
91 |
foreach (Polygon2 polygon in polygons) |
92 |
Clip(polygon, line, negative, positive); |
93 |
} |
94 |
|
95 |
if (node.FrontChild != null) |
96 |
Clip(positive, node.FrontChild); |
97 |
|
98 |
if (node.BackChild != null) |
99 |
Clip(negative, node.BackChild); |
100 |
else |
101 |
result.AddRange(negative); |
102 |
} |
103 |
|
104 |
private static void Clip(Polygon2 polygon, Line line, List<Polygon2> negative, List<Polygon2> positive) |
105 |
{ |
106 |
var signs = new int[polygon.Length]; |
107 |
int positiveCount = 0, negativeCount = 0; |
108 |
|
109 |
for (int i = 0; i < polygon.Length; i++) |
110 |
{ |
111 |
signs[i] = line.RelativePosition(polygon[i]); |
112 |
|
113 |
if (signs[i] >= 0) |
114 |
positiveCount++; |
115 |
|
116 |
if (signs[i] <= 0) |
117 |
negativeCount++; |
118 |
} |
119 |
|
120 |
if (negativeCount == polygon.Length) |
121 |
{ |
122 |
// |
123 |
// All points are in the negative half plane, nothing to clip. |
124 |
// |
125 |
|
126 |
negative.Add(polygon); |
127 |
return; |
128 |
} |
129 |
|
130 |
if (positiveCount == polygon.Length) |
131 |
{ |
132 |
// |
133 |
// All points are in the positive half plane, nothing to clip. |
134 |
// |
135 |
|
136 |
positive.Add(polygon); |
137 |
return; |
138 |
} |
139 |
|
140 |
var negativePoints = new List<Vector2>(); |
141 |
var positivePoints = new List<Vector2>(); |
142 |
|
143 |
int start = 0; |
144 |
Vector2 p0; |
145 |
int s0; |
146 |
|
147 |
do |
148 |
{ |
149 |
p0 = polygon[start]; |
150 |
s0 = signs[start]; |
151 |
start++; |
152 |
|
153 |
// |
154 |
// do not start right on the clip line |
155 |
// |
156 |
|
157 |
} while (s0 == 0); |
158 |
|
159 |
var intersections = new Vector2[2]; |
160 |
int intersectionCount = 0; |
161 |
|
162 |
for (int i = 0; i < polygon.Length; i++) |
163 |
{ |
164 |
Vector2 p1 = polygon[(i + start) % polygon.Length]; |
165 |
int s1 = signs[(i + start) % polygon.Length]; |
166 |
|
167 |
if (s0 == s1) |
168 |
{ |
169 |
// |
170 |
// Same half plane, no intersection, add the existing edge. |
171 |
// |
172 |
|
173 |
if (s0 < 0) |
174 |
negativePoints.Add(p0); |
175 |
else |
176 |
positivePoints.Add(p0); |
177 |
} |
178 |
else if (s0 == 0) |
179 |
{ |
180 |
// |
181 |
// If the previous point was on the clip line then we need |
182 |
// to use the current point sign to figure out the destination polygon. |
183 |
// |
184 |
|
185 |
if (s1 < 0) |
186 |
negativePoints.Add(p0); |
187 |
else |
188 |
positivePoints.Add(p0); |
189 |
} |
190 |
else |
191 |
{ |
192 |
// |
193 |
// Different half plane, split the edge in two. |
194 |
// |
195 |
|
196 |
Vector2 intersection; |
197 |
|
198 |
if (s1 == 0) |
199 |
intersection = p1; |
200 |
else |
201 |
intersection = line.Intersect(p0, p1); |
202 |
|
203 |
intersections[intersectionCount++] = intersection; |
204 |
|
205 |
if (s0 < 0) |
206 |
{ |
207 |
// the negative polygon needs to be closed |
208 |
// the positive polygon needs to have an edge added from the previous intersection to the new one |
209 |
|
210 |
negativePoints.Add(p0); |
211 |
|
212 |
if (intersectionCount == 2) |
213 |
{ |
214 |
negativePoints.Add(intersection); |
215 |
positivePoints.Add(intersections[0]); |
216 |
} |
217 |
|
218 |
if (s1 != 0) |
219 |
positivePoints.Add(intersection); |
220 |
} |
221 |
else |
222 |
{ |
223 |
// the positive polygon needs to be closed |
224 |
// the negative polygon needs to have an edge added from the previous intersection to the new one |
225 |
|
226 |
positivePoints.Add(p0); |
227 |
|
228 |
if (intersectionCount == 2) |
229 |
{ |
230 |
positivePoints.Add(intersection); |
231 |
negativePoints.Add(intersections[0]); |
232 |
} |
233 |
|
234 |
if (s1 != 0) |
235 |
negativePoints.Add(intersection); |
236 |
} |
237 |
} |
238 |
|
239 |
p0 = p1; |
240 |
s0 = s1; |
241 |
} |
242 |
|
243 |
negative.Add(new Polygon2(negativePoints.ToArray())); |
244 |
positive.Add(new Polygon2(positivePoints.ToArray())); |
245 |
} |
246 |
} |
247 |
} |