| [1114] | 1 | using System; | 
|---|
|  | 2 | using System.Collections.Generic; | 
|---|
|  | 3 | using System.IO; | 
|---|
|  | 4 | using Oni.Collections; | 
|---|
|  | 5 | using Oni.Imaging; | 
|---|
|  | 6 |  | 
|---|
|  | 7 | namespace Oni.Akira | 
|---|
|  | 8 | { | 
|---|
|  | 9 | internal class RoomGridBuilder | 
|---|
|  | 10 | { | 
|---|
|  | 11 | private readonly Dae.Scene roomsScene; | 
|---|
|  | 12 | private readonly PolygonMesh geometryMesh; | 
|---|
|  | 13 | private PolygonMesh roomsMesh; | 
|---|
|  | 14 | private OctreeNode geometryOcttree; | 
|---|
|  | 15 | private OctreeNode dangerOcttree; | 
|---|
|  | 16 |  | 
|---|
|  | 17 | public RoomGridBuilder(Dae.Scene roomsScene, PolygonMesh geometryMesh) | 
|---|
|  | 18 | { | 
|---|
|  | 19 | this.roomsScene = roomsScene; | 
|---|
|  | 20 | this.geometryMesh = geometryMesh; | 
|---|
|  | 21 | } | 
|---|
|  | 22 |  | 
|---|
|  | 23 | public PolygonMesh Mesh => roomsMesh; | 
|---|
|  | 24 |  | 
|---|
|  | 25 | public void Build() | 
|---|
|  | 26 | { | 
|---|
|  | 27 | roomsMesh = RoomDaeReader.Read(roomsScene); | 
|---|
|  | 28 |  | 
|---|
|  | 29 | RoomBuilder.BuildRooms(roomsMesh); | 
|---|
|  | 30 |  | 
|---|
|  | 31 | Console.Error.WriteLine("Read {0} rooms", roomsMesh.Rooms.Count); | 
|---|
|  | 32 |  | 
|---|
|  | 33 | geometryOcttree = OctreeBuilder.Build(geometryMesh, GunkFlags.NoCollision | GunkFlags.NoCharacterCollision); | 
|---|
|  | 34 | dangerOcttree = OctreeBuilder.Build(geometryMesh, p => (p.Flags & GunkFlags.Danger) != 0); | 
|---|
|  | 35 |  | 
|---|
|  | 36 | ProcessStairsCollision(); | 
|---|
|  | 37 |  | 
|---|
|  | 38 | Parallel.ForEach(roomsMesh.Rooms, room => | 
|---|
|  | 39 | { | 
|---|
|  | 40 | BuildGrid(room); | 
|---|
|  | 41 | }); | 
|---|
|  | 42 | } | 
|---|
|  | 43 |  | 
|---|
|  | 44 | private void ProcessStairsCollision() | 
|---|
|  | 45 | { | 
|---|
|  | 46 | var verticalTolerance1 = new Vector3(0.0f, 0.1f, 0.0f); | 
|---|
|  | 47 | var verticalTolerance2 = new Vector3(0.0f, 7.5f, 0.0f); | 
|---|
|  | 48 |  | 
|---|
|  | 49 | foreach (var stairs in geometryMesh.Polygons.Where(p => p.IsStairs && p.VertexCount == 4)) | 
|---|
|  | 50 | { | 
|---|
|  | 51 | var floorPoints = stairs.Points.Select(v => v + verticalTolerance1).ToArray(); | 
|---|
|  | 52 | var ceilPoints = stairs.Points.Select(v => v + verticalTolerance2).ToArray(); | 
|---|
|  | 53 | var bbox = BoundingBox.CreateFromPoints(floorPoints.Concatenate(ceilPoints)); | 
|---|
|  | 54 |  | 
|---|
|  | 55 | var floorPlane = new Plane(floorPoints[0], floorPoints[1], floorPoints[2]); | 
|---|
|  | 56 | var ceilingPlane = new Plane(ceilPoints[0], ceilPoints[1], ceilPoints[2]); | 
|---|
|  | 57 |  | 
|---|
|  | 58 | foreach (var node in geometryOcttree.FindLeafs(bbox)) | 
|---|
|  | 59 | { | 
|---|
|  | 60 | foreach (var poly in node.Polygons) | 
|---|
|  | 61 | { | 
|---|
|  | 62 | if ((poly.Flags & (GunkFlags.NoCollision | GunkFlags.NoCharacterCollision)) != 0) | 
|---|
|  | 63 | { | 
|---|
|  | 64 | // | 
|---|
|  | 65 | // already a no collision polygon, skip it | 
|---|
|  | 66 | // | 
|---|
|  | 67 |  | 
|---|
|  | 68 | continue; | 
|---|
|  | 69 | } | 
|---|
|  | 70 |  | 
|---|
|  | 71 | if (!poly.BoundingBox.Intersects(bbox)) | 
|---|
|  | 72 | continue; | 
|---|
|  | 73 |  | 
|---|
|  | 74 | var points = poly.Points.ToList(); | 
|---|
|  | 75 |  | 
|---|
|  | 76 | points = PolygonUtils.ClipToPlane(points, floorPlane); | 
|---|
|  | 77 |  | 
|---|
|  | 78 | if (points == null) | 
|---|
|  | 79 | { | 
|---|
|  | 80 | // | 
|---|
|  | 81 | // this polygon is below stairs, skip it | 
|---|
|  | 82 | // | 
|---|
|  | 83 |  | 
|---|
|  | 84 | continue; | 
|---|
|  | 85 | } | 
|---|
|  | 86 |  | 
|---|
|  | 87 | points = PolygonUtils.ClipToPlane(points, ceilingPlane); | 
|---|
|  | 88 |  | 
|---|
|  | 89 | if (points != null) | 
|---|
|  | 90 | { | 
|---|
|  | 91 | // | 
|---|
|  | 92 | // this polygon is too high above the stairs, skip it | 
|---|
|  | 93 | // | 
|---|
|  | 94 |  | 
|---|
|  | 95 | continue; | 
|---|
|  | 96 | } | 
|---|
|  | 97 |  | 
|---|
|  | 98 | poly.Flags |= GunkFlags.NoCharacterCollision; | 
|---|
|  | 99 | } | 
|---|
|  | 100 | } | 
|---|
|  | 101 | } | 
|---|
|  | 102 | } | 
|---|
|  | 103 |  | 
|---|
|  | 104 | private void BuildGrid(Room room) | 
|---|
|  | 105 | { | 
|---|
|  | 106 | var floor = room.FloorPolygon; | 
|---|
|  | 107 | var bbox = room.BoundingBox; | 
|---|
|  | 108 |  | 
|---|
|  | 109 | // | 
|---|
|  | 110 | // Create an empty grid and mark all tiles as 'danger' | 
|---|
|  | 111 | // | 
|---|
|  | 112 |  | 
|---|
|  | 113 | var rasterizer = new RoomGridRasterizer(bbox); | 
|---|
|  | 114 | rasterizer.Clear(RoomGridWeight.Danger); | 
|---|
|  | 115 |  | 
|---|
|  | 116 | // | 
|---|
|  | 117 | // Collect all polygons that intersect the room | 
|---|
|  | 118 | // | 
|---|
|  | 119 |  | 
|---|
|  | 120 | bbox.Inflate(2.0f * new Vector3(rasterizer.TileSize, 0.0f, rasterizer.TileSize)); | 
|---|
|  | 121 |  | 
|---|
|  | 122 | var testbox = bbox; | 
|---|
|  | 123 | testbox.Min.X -= 1.0f; | 
|---|
|  | 124 | testbox.Min.Y = bbox.Min.Y - 6.0f; | 
|---|
|  | 125 | testbox.Min.Z -= 1.0f; | 
|---|
|  | 126 | testbox.Max.X += 1.0f; | 
|---|
|  | 127 | testbox.Max.Y = bbox.Max.Y - 6.0f; | 
|---|
|  | 128 | testbox.Max.Z += 1.0f; | 
|---|
|  | 129 |  | 
|---|
|  | 130 | var polygons = new Set<Polygon>(); | 
|---|
|  | 131 | var dangerPolygons = new Set<Polygon>(); | 
|---|
|  | 132 |  | 
|---|
|  | 133 | foreach (var node in geometryOcttree.FindLeafs(testbox)) | 
|---|
|  | 134 | { | 
|---|
|  | 135 | polygons.UnionWith(node.Polygons); | 
|---|
|  | 136 | } | 
|---|
|  | 137 |  | 
|---|
|  | 138 | foreach (var node in dangerOcttree.FindLeafs(testbox)) | 
|---|
|  | 139 | { | 
|---|
|  | 140 | dangerPolygons.UnionWith(node.Polygons); | 
|---|
|  | 141 | } | 
|---|
|  | 142 |  | 
|---|
|  | 143 | // | 
|---|
|  | 144 | // Draw all the floors on the grid. This will overwrite the 'danger' | 
|---|
|  | 145 | // areas with 'clear' areas. This means danger area will remain | 
|---|
|  | 146 | // where there isn't any floor. | 
|---|
|  | 147 | // | 
|---|
|  | 148 |  | 
|---|
|  | 149 | foreach (var polygon in polygons) | 
|---|
|  | 150 | { | 
|---|
|  | 151 | if (polygon.Plane.Normal.Y > 0.5f) | 
|---|
|  | 152 | rasterizer.DrawFloor(polygon.Points); | 
|---|
|  | 153 | } | 
|---|
|  | 154 |  | 
|---|
|  | 155 | if (room.FloorPlane.Normal.Y >= 0.999f) | 
|---|
|  | 156 | { | 
|---|
|  | 157 | // | 
|---|
|  | 158 | // Draw the walls. | 
|---|
|  | 159 | // | 
|---|
|  | 160 |  | 
|---|
|  | 161 | float floorMaxY = floor.BoundingBox.Max.Y; | 
|---|
|  | 162 | var floorPlane = new Plane(floor.Plane.Normal, floor.Plane.D - 4.0f); | 
|---|
|  | 163 | var ceilingPlane = new Plane(-floor.Plane.Normal, -(floor.Plane.D - 20.0f)); | 
|---|
|  | 164 |  | 
|---|
|  | 165 | foreach (var polygon in polygons) | 
|---|
|  | 166 | { | 
|---|
|  | 167 | if ((polygon.Flags & (GunkFlags.Stairs | GunkFlags.NoCharacterCollision | GunkFlags.Impassable)) == 0) | 
|---|
|  | 168 | { | 
|---|
|  | 169 | // | 
|---|
|  | 170 | // Remove curbs from character collision. | 
|---|
|  | 171 | // | 
|---|
|  | 172 |  | 
|---|
|  | 173 | var polyBox = polygon.BoundingBox; | 
|---|
|  | 174 |  | 
|---|
|  | 175 | if (Math.Abs(polygon.Plane.Normal.Y) < MathHelper.Eps | 
|---|
|  | 176 | && polyBox.Height <= 4.0f | 
|---|
|  | 177 | && Math.Abs(polyBox.Max.Y - floorMaxY) <= 4.0f) | 
|---|
|  | 178 | { | 
|---|
|  | 179 | polygon.Flags |= GunkFlags.NoCharacterCollision; | 
|---|
|  | 180 | continue; | 
|---|
|  | 181 | } | 
|---|
|  | 182 | } | 
|---|
|  | 183 |  | 
|---|
|  | 184 | if ((polygon.Flags & (GunkFlags.Stairs | GunkFlags.GridIgnore | GunkFlags.NoCollision | GunkFlags.NoCharacterCollision)) != 0) | 
|---|
|  | 185 | continue; | 
|---|
|  | 186 |  | 
|---|
|  | 187 | var points = polygon.Points.ToList(); | 
|---|
|  | 188 |  | 
|---|
|  | 189 | points = PolygonUtils.ClipToPlane(points, floorPlane); | 
|---|
|  | 190 |  | 
|---|
|  | 191 | if (points == null) | 
|---|
|  | 192 | continue; | 
|---|
|  | 193 |  | 
|---|
|  | 194 | points = PolygonUtils.ClipToPlane(points, ceilingPlane); | 
|---|
|  | 195 |  | 
|---|
|  | 196 | if (points == null) | 
|---|
|  | 197 | continue; | 
|---|
|  | 198 |  | 
|---|
|  | 199 | if (Math.Abs(polygon.Plane.Normal.Y) <= 0.1f) | 
|---|
|  | 200 | rasterizer.DrawWall(points); | 
|---|
|  | 201 | else | 
|---|
|  | 202 | rasterizer.DrawImpassable(points); | 
|---|
|  | 203 | } | 
|---|
|  | 204 |  | 
|---|
|  | 205 | // | 
|---|
|  | 206 | // Draw ramp entry/exit lines. Hmm, this seems useless. | 
|---|
|  | 207 | // | 
|---|
|  | 208 |  | 
|---|
|  | 209 | //if (room.FloorPlane.Normal.Y >= 0.98f) | 
|---|
|  | 210 | //{ | 
|---|
|  | 211 | //    foreach (RoomAdjacency adj in room.Ajacencies) | 
|---|
|  | 212 | //    { | 
|---|
|  | 213 | //        if (Math.Abs(room.FloorPlane.Normal.Y - adj.AdjacentRoom.FloorPlane.Normal.Y) > 0.001f) | 
|---|
|  | 214 | //            rasterizer.DrawStairsEntry(adj.Ghost.Points); | 
|---|
|  | 215 | //    } | 
|---|
|  | 216 | //} | 
|---|
|  | 217 |  | 
|---|
|  | 218 | // | 
|---|
|  | 219 | // Draw 'danger' quads. | 
|---|
|  | 220 | // | 
|---|
|  | 221 |  | 
|---|
|  | 222 | foreach (var polygon in dangerPolygons) | 
|---|
|  | 223 | { | 
|---|
|  | 224 | rasterizer.DrawDanger(polygon.Points); | 
|---|
|  | 225 | } | 
|---|
|  | 226 |  | 
|---|
|  | 227 | // | 
|---|
|  | 228 | // Draw 'near wall' borders. | 
|---|
|  | 229 | // | 
|---|
|  | 230 |  | 
|---|
|  | 231 | rasterizer.AddBorders(); | 
|---|
|  | 232 | } | 
|---|
|  | 233 |  | 
|---|
|  | 234 | // | 
|---|
|  | 235 | // Finally, get the rasterized pathfinding grid. | 
|---|
|  | 236 | // | 
|---|
|  | 237 |  | 
|---|
|  | 238 | room.Grid = rasterizer.GetGrid(); | 
|---|
|  | 239 |  | 
|---|
|  | 240 | if (room.Grid.XTiles * room.Grid.ZTiles > 256 * 256) | 
|---|
|  | 241 | Console.Error.WriteLine("Warning: pathfinding grid too large"); | 
|---|
|  | 242 | } | 
|---|
|  | 243 | } | 
|---|
|  | 244 | } | 
|---|