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