1 |
using System; |
2 |
using System.IO; |
3 |
using System.Collections.Generic; |
4 |
using Oni.Imaging; |
5 |
|
6 |
namespace Oni.Akira |
7 |
{ |
8 |
internal class RoomGridRasterizer |
9 |
{ |
10 |
private const int origin = -2; |
11 |
private const float tileSize = 4.0f; |
12 |
private const int margin = 3; |
13 |
private readonly int xTiles; |
14 |
private readonly int zTiles; |
15 |
private readonly byte[] data; |
16 |
private readonly Vector3 worldOrigin; |
17 |
//private readonly List<RoomGridDebugData> debugData; |
18 |
|
19 |
private enum RoomGridDebugType : byte |
20 |
{ |
21 |
None, |
22 |
SlopedQuad, |
23 |
StairQuad, |
24 |
Wall, |
25 |
DangerQuad, |
26 |
ImpassableQuad, |
27 |
Floor |
28 |
} |
29 |
|
30 |
//private class RoomGridDebugData |
31 |
//{ |
32 |
// public int quadIndex; |
33 |
// public RoomGridDebugType type; |
34 |
// public RoomGridWeight weight; |
35 |
// public short x, y; |
36 |
//} |
37 |
|
38 |
public RoomGridRasterizer(BoundingBox bbox) |
39 |
{ |
40 |
this.xTiles = (int)(((bbox.Max.X - bbox.Min.X) / tileSize) + (-origin * 2) + 1) + margin * 2; |
41 |
this.zTiles = (int)(((bbox.Max.Z - bbox.Min.Z) / tileSize) + (-origin * 2) + 1) + margin * 2; |
42 |
this.data = new byte[xTiles * zTiles]; |
43 |
this.worldOrigin = bbox.Min; |
44 |
//this.debugData = new List<RoomGridDebugData>(); |
45 |
} |
46 |
|
47 |
public void Clear(RoomGridWeight weight) |
48 |
{ |
49 |
for (int i = 0; i < xTiles * zTiles; i++) |
50 |
data[i] = (byte)weight; |
51 |
} |
52 |
|
53 |
public int XTiles => xTiles; |
54 |
public int ZTiles => ZTiles; |
55 |
|
56 |
public float TileSize => tileSize; |
57 |
|
58 |
public RoomGridWeight this[int x, int z] |
59 |
{ |
60 |
get { return (RoomGridWeight)data[x + z * xTiles]; } |
61 |
set { data[x + z * xTiles] = (byte)value; } |
62 |
} |
63 |
|
64 |
public void DrawFloor(IEnumerable<Vector3> points) |
65 |
{ |
66 |
foreach (var point in ScanPolygon(points.Select(v => WorldToGrid(v)).ToList())) |
67 |
this[point.X, point.Y] = RoomGridWeight.Clear; |
68 |
} |
69 |
|
70 |
public void DrawDanger(IEnumerable<Vector3> points) |
71 |
{ |
72 |
foreach (var point in ScanPolygon(points.Select(v => WorldToGrid(v)).ToList())) |
73 |
this[point.X, point.Y] = RoomGridWeight.Danger; |
74 |
} |
75 |
|
76 |
public void DrawStairsEntry(IEnumerable<Vector3> points) |
77 |
{ |
78 |
var v0 = points.First(); |
79 |
var v1 = v0; |
80 |
|
81 |
foreach (var v in points) |
82 |
{ |
83 |
if (v.X < v0.X || (v.X == v0.X && v.Z < v0.Z)) |
84 |
v0 = v; |
85 |
|
86 |
if (v.X > v1.X || (v.X == v1.X && v.Z > v1.Z)) |
87 |
v1 = v; |
88 |
} |
89 |
|
90 |
Point p0 = WorldToGrid(v0); |
91 |
Point p1 = WorldToGrid(v1); |
92 |
|
93 |
DrawLine(p0, p1, RoomGridWeight.Stairs); |
94 |
|
95 |
DrawLine(p0 - Point.UnitY, p1 - Point.UnitY, RoomGridWeight.Clear); |
96 |
DrawLine(p0 + Point.UnitY, p1 + Point.UnitY, RoomGridWeight.Clear); |
97 |
DrawLine(p0 + Point.UnitX, p1 + Point.UnitX, RoomGridWeight.Clear); |
98 |
DrawLine(p0 - Point.UnitX, p1 - Point.UnitX, RoomGridWeight.Clear); |
99 |
|
100 |
var pp = points.ToArray(); |
101 |
Array.Sort(pp, (x, y) => x.Y.CompareTo(y.Y)); |
102 |
DrawImpassable(pp[0]); |
103 |
DrawImpassable(pp[1]); |
104 |
} |
105 |
|
106 |
public void DrawWall(IEnumerable<Vector3> points) |
107 |
{ |
108 |
var v0 = points.First(); |
109 |
var v1 = v0; |
110 |
|
111 |
foreach (var v in points) |
112 |
{ |
113 |
if (v.X < v0.X || (v.X == v0.X && v.Z < v0.Z)) |
114 |
v0 = v; |
115 |
|
116 |
if (v.X > v1.X || (v.X == v1.X && v.Z > v1.Z)) |
117 |
v1 = v; |
118 |
} |
119 |
|
120 |
Point p0 = WorldToGrid(v0); |
121 |
Point p1 = WorldToGrid(v1); |
122 |
|
123 |
DrawLine(p0, p1, RoomGridWeight.Impassable); |
124 |
|
125 |
DrawLine(p0 - Point.UnitY, p1 - Point.UnitY, RoomGridWeight.SemiPassable); |
126 |
DrawLine(p0 + Point.UnitY, p1 + Point.UnitY, RoomGridWeight.SemiPassable); |
127 |
DrawLine(p0 + Point.UnitX, p1 + Point.UnitX, RoomGridWeight.SemiPassable); |
128 |
DrawLine(p0 - Point.UnitX, p1 - Point.UnitX, RoomGridWeight.SemiPassable); |
129 |
} |
130 |
|
131 |
private void DrawLine(Point p0, Point p1, RoomGridWeight weight) |
132 |
{ |
133 |
foreach (Point p in ScanLine(p0, p1)) |
134 |
{ |
135 |
if (weight > this[p.X, p.Y]) |
136 |
{ |
137 |
this[p.X, p.Y] = weight; |
138 |
|
139 |
//debugData.Add(new RoomGridDebugData { |
140 |
// x = (short)p.X, |
141 |
// y = (short)p.Y, |
142 |
// weight = weight |
143 |
//}); |
144 |
} |
145 |
} |
146 |
} |
147 |
|
148 |
private void FillPolygon(IEnumerable<Vector3> points, RoomGridWeight weight) |
149 |
{ |
150 |
foreach (var point in ScanPolygon(points.Select(v => WorldToGrid(v)).ToList())) |
151 |
{ |
152 |
if (weight > this[point.X, point.Y]) |
153 |
this[point.X, point.Y] = weight; |
154 |
} |
155 |
} |
156 |
|
157 |
public void DrawImpassable(IEnumerable<Vector3> points) |
158 |
{ |
159 |
FillPolygon(points, RoomGridWeight.Impassable); |
160 |
} |
161 |
|
162 |
public void DrawImpassable(Vector3 position) |
163 |
{ |
164 |
var point = WorldToGrid(position); |
165 |
int x = point.X; |
166 |
int y = point.Y; |
167 |
|
168 |
DrawTile(x, y, RoomGridWeight.Impassable); |
169 |
|
170 |
DrawTile(x - 1, y, RoomGridWeight.SemiPassable); |
171 |
DrawTile(x + 1, y, RoomGridWeight.SemiPassable); |
172 |
DrawTile(x, y - 1, RoomGridWeight.SemiPassable); |
173 |
DrawTile(x, y + 1, RoomGridWeight.SemiPassable); |
174 |
DrawTile(x - 1, y - 1, RoomGridWeight.SemiPassable); |
175 |
DrawTile(x + 1, y - 1, RoomGridWeight.SemiPassable); |
176 |
DrawTile(x + 1, y + 1, RoomGridWeight.SemiPassable); |
177 |
DrawTile(x - 1, y + 1, RoomGridWeight.SemiPassable); |
178 |
} |
179 |
|
180 |
private void DrawTile(int x, int y, RoomGridWeight weight) |
181 |
{ |
182 |
if (0 <= x && x < xTiles && 0 <= y && y < zTiles) |
183 |
{ |
184 |
if (weight > this[x, y]) |
185 |
this[x, y] = weight; |
186 |
} |
187 |
} |
188 |
|
189 |
public void AddBorders() |
190 |
{ |
191 |
AddBorder(RoomGridWeight.Danger, RoomGridWeight.Clear, RoomGridWeight.Border4); |
192 |
AddBorder(RoomGridWeight.Border4, RoomGridWeight.Clear, RoomGridWeight.Border3); |
193 |
AddBorder(RoomGridWeight.Border3, RoomGridWeight.Clear, RoomGridWeight.Border2); |
194 |
AddBorder(RoomGridWeight.Border2, RoomGridWeight.Clear, RoomGridWeight.Border1); |
195 |
AddBorder(RoomGridWeight.SemiPassable, RoomGridWeight.Clear, RoomGridWeight.NearWall); |
196 |
} |
197 |
|
198 |
private void AddBorder(RoomGridWeight aroundOf, RoomGridWeight onlyIf, RoomGridWeight border) |
199 |
{ |
200 |
for (int z = 0; z < zTiles; z++) |
201 |
{ |
202 |
for (int x = 0; x < xTiles; x++) |
203 |
{ |
204 |
if (this[x, z] != aroundOf) |
205 |
continue; |
206 |
|
207 |
if (x - 1 >= 0 && this[x - 1, z] == onlyIf) |
208 |
this[x - 1, z] = border; |
209 |
|
210 |
if (x + 1 < xTiles && this[x + 1, z] == onlyIf) |
211 |
this[x + 1, z] = border; |
212 |
|
213 |
if (z - 1 >= 0 && this[x, z - 1] == onlyIf) |
214 |
this[x, z - 1] = border; |
215 |
|
216 |
if (z + 1 < zTiles && this[x, z + 1] == onlyIf) |
217 |
this[x, z + 1] = border; |
218 |
} |
219 |
} |
220 |
} |
221 |
|
222 |
private Point WorldToGrid(Vector3 world) |
223 |
{ |
224 |
return new Point( |
225 |
FMath.TruncateToInt32((world.X - worldOrigin.X) / tileSize) - origin + margin, |
226 |
FMath.TruncateToInt32((world.Z - worldOrigin.Z) / tileSize) - origin + margin); |
227 |
} |
228 |
|
229 |
public RoomGrid GetGrid() |
230 |
{ |
231 |
int gridXTiles = xTiles - 2 * margin; |
232 |
int gridZTiles = zTiles - 2 * margin; |
233 |
|
234 |
var gridData = new byte[gridXTiles * gridZTiles]; |
235 |
|
236 |
for (int z = margin; z < zTiles - margin; z++) |
237 |
{ |
238 |
for (int x = margin; x < xTiles - margin; x++) |
239 |
gridData[(x - margin) + (z - margin) * gridXTiles] = data[x + z * xTiles]; |
240 |
} |
241 |
|
242 |
//var debugStream = new MemoryStream(debugData.Count * 16); |
243 |
|
244 |
//using (var writer = new BinaryWriter(debugStream)) |
245 |
//{ |
246 |
// foreach (var data in debugData) |
247 |
// { |
248 |
// writer.WriteByte((byte)data.type); |
249 |
// writer.WriteByte(0); |
250 |
// writer.Write(data.x); |
251 |
// writer.Write(data.y); |
252 |
// writer.WriteInt16(0); |
253 |
// writer.Write(data.quadIndex); |
254 |
// writer.Write((int)data.weight); |
255 |
// } |
256 |
//} |
257 |
|
258 |
return new RoomGrid(gridXTiles, gridZTiles, gridData, null); |
259 |
} |
260 |
|
261 |
private IEnumerable<Point> ScanLine(Point p0, Point p1) |
262 |
{ |
263 |
return ScanLine(p0.X, p0.Y, p1.X, p1.Y); |
264 |
} |
265 |
|
266 |
private IEnumerable<Point> ScanLine(int x0, int y0, int x1, int y1) |
267 |
{ |
268 |
int dx = (x0 < x1) ? x1 - x0 : x0 - x1; |
269 |
int dy = (y0 < y1) ? y1 - y0 : y0 - y1; |
270 |
int sx = (x0 < x1) ? 1 : -1; |
271 |
int sy = (y0 < y1) ? 1 : -1; |
272 |
int err = dx - dy; |
273 |
|
274 |
while (true) |
275 |
{ |
276 |
if (0 <= x0 && x0 < xTiles && 0 <= y0 && y0 < zTiles) |
277 |
yield return new Point(x0, y0); |
278 |
|
279 |
if (x0 == x1 && y0 == y1) |
280 |
break; |
281 |
|
282 |
int derr = 2 * err; |
283 |
|
284 |
if (derr > -dy) |
285 |
{ |
286 |
err = err - dy; |
287 |
x0 = x0 + sx; |
288 |
} |
289 |
|
290 |
if (derr < dx) |
291 |
{ |
292 |
err = err + dx; |
293 |
y0 = y0 + sy; |
294 |
} |
295 |
} |
296 |
} |
297 |
|
298 |
private IEnumerable<Vector2> ScanLine(Vector2 p0, Vector2 p1) |
299 |
{ |
300 |
return ScanLine(p0.X, p0.Y, p1.X, p1.Y); |
301 |
} |
302 |
|
303 |
private IEnumerable<Vector2> ScanLine(float x0, float y0, float x1, float y1) |
304 |
{ |
305 |
float dx = (x0 < x1) ? x1 - x0 : x0 - x1; |
306 |
float dy = (y0 < y1) ? y1 - y0 : y0 - y1; |
307 |
float sx = (x0 < x1) ? 1 : -1; |
308 |
float sy = (y0 < y1) ? 1 : -1; |
309 |
float err = dx - dy; |
310 |
|
311 |
while (true) |
312 |
{ |
313 |
if (0 <= x0 && x0 < xTiles && 0 <= y0 && y0 < zTiles) |
314 |
yield return new Vector2(x0, y0); |
315 |
|
316 |
if (x0 == x1 && y0 == y1) |
317 |
break; |
318 |
|
319 |
float derr = 2 * err; |
320 |
|
321 |
if (derr > -dy) |
322 |
{ |
323 |
err = err - dy; |
324 |
x0 = x0 + sx; |
325 |
} |
326 |
|
327 |
if (derr < dx) |
328 |
{ |
329 |
err = err + dx; |
330 |
y0 = y0 + sy; |
331 |
} |
332 |
} |
333 |
} |
334 |
|
335 |
private IEnumerable<Point> ScanPolygon(IList<Point> points) |
336 |
{ |
337 |
var activeEdgeList = new List<Edge>(); |
338 |
var activeEdgeTable = new List<List<Edge>>(); |
339 |
int minY = BuildActiveEdgeTable(points, activeEdgeTable); |
340 |
|
341 |
for (int y = 0; y < activeEdgeTable.Count; y++) |
342 |
{ |
343 |
for (int i = 0; i < activeEdgeTable[y].Count; i++) |
344 |
activeEdgeList.Add(activeEdgeTable[y][i]); |
345 |
|
346 |
for (int i = 0; i < activeEdgeList.Count; i++) |
347 |
{ |
348 |
if (activeEdgeList[i].maxY <= y + minY) |
349 |
{ |
350 |
activeEdgeList.RemoveAt(i); |
351 |
i--; |
352 |
} |
353 |
} |
354 |
|
355 |
activeEdgeList.Sort((a, b) => (a.currentX == b.currentX ? a.slopeRecip.CompareTo(b.slopeRecip) : a.currentX.CompareTo(b.currentX))); |
356 |
|
357 |
for (int i = 0; i < activeEdgeList.Count; i += 2) |
358 |
{ |
359 |
int yLine = minY + y; |
360 |
|
361 |
if (0 <= yLine && yLine < zTiles) |
362 |
{ |
363 |
int xStart = Math.Max(0, (int)Math.Ceiling(activeEdgeList[i].currentX)); |
364 |
int xEnd = Math.Min(xTiles - 1, (int)activeEdgeList[i + 1].currentX); |
365 |
|
366 |
for (int x = xStart; x <= xEnd; x++) |
367 |
yield return new Point(x, yLine); |
368 |
} |
369 |
} |
370 |
|
371 |
for (int i = 0; i < activeEdgeList.Count; i++) |
372 |
activeEdgeList[i].Next(); |
373 |
} |
374 |
} |
375 |
|
376 |
private class Edge |
377 |
{ |
378 |
public float maxY; |
379 |
public float currentX; |
380 |
public float slopeRecip; |
381 |
|
382 |
public Edge(Point current, Point next) |
383 |
{ |
384 |
maxY = Math.Max(current.Y, next.Y); |
385 |
slopeRecip = (current.X - next.X) / (float)(current.Y - next.Y); |
386 |
|
387 |
if (current.Y == maxY) |
388 |
currentX = next.X; |
389 |
else |
390 |
currentX = current.X; |
391 |
} |
392 |
|
393 |
public void Next() |
394 |
{ |
395 |
currentX += slopeRecip; |
396 |
} |
397 |
} |
398 |
|
399 |
private static int BuildActiveEdgeTable(IList<Point> points, List<List<Edge>> activeEdgeTable) |
400 |
{ |
401 |
activeEdgeTable.Clear(); |
402 |
|
403 |
int minY = points.Min(p => p.Y); |
404 |
int maxY = points.Max(p => p.Y); |
405 |
|
406 |
for (int i = minY; i <= maxY; i++) |
407 |
activeEdgeTable.Add(new List<Edge>()); |
408 |
|
409 |
for (int i = 0; i < points.Count; i++) |
410 |
{ |
411 |
Point current = points[i]; |
412 |
Point next = points[(i + 1) % points.Count]; |
413 |
|
414 |
if (current.Y == next.Y) |
415 |
continue; |
416 |
|
417 |
var e = new Edge(current, next); |
418 |
|
419 |
if (current.Y == e.maxY) |
420 |
activeEdgeTable[next.Y - minY].Add(e); |
421 |
else |
422 |
activeEdgeTable[current.Y - minY].Add(e); |
423 |
} |
424 |
|
425 |
return minY; |
426 |
} |
427 |
} |
428 |
} |