| 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 |
} |