| 1 |
using System; |
| 2 |
using System.Collections.Generic; |
| 3 |
using System.IO; |
| 4 |
using Oni.Imaging; |
| 5 |
|
| 6 |
namespace Oni.Akira |
| 7 |
{ |
| 8 |
internal class RoomBuilder |
| 9 |
{ |
| 10 |
private const float roomHeight = 20.0f; |
| 11 |
private readonly PolygonMesh mesh; |
| 12 |
private OctreeNode octtree; |
| 13 |
|
| 14 |
public static void BuildRooms(PolygonMesh mesh) |
| 15 |
{ |
| 16 |
var builder = new RoomBuilder(mesh); |
| 17 |
builder.BuildRooms(); |
| 18 |
} |
| 19 |
|
| 20 |
private RoomBuilder(PolygonMesh mesh) |
| 21 |
{ |
| 22 |
this.mesh = mesh; |
| 23 |
} |
| 24 |
|
| 25 |
private void BuildRooms() |
| 26 |
{ |
| 27 |
foreach (Polygon floor in mesh.Floors) |
| 28 |
mesh.Rooms.Add(CreateRoom(floor, roomHeight)); |
| 29 |
|
| 30 |
ConnectRooms(); |
| 31 |
UpdateRoomsHeight(); |
| 32 |
} |
| 33 |
|
| 34 |
private Room CreateRoom(Polygon floor, float height) |
| 35 |
{ |
| 36 |
var floorPlane = floor.Plane; |
| 37 |
|
| 38 |
var bbox = floor.BoundingBox; |
| 39 |
bbox.Max.Y += height * floorPlane.Normal.Y; |
| 40 |
|
| 41 |
var room = new Room |
| 42 |
{ |
| 43 |
FloorPolygon = floor, |
| 44 |
BoundingBox = bbox, |
| 45 |
FloorPlane = floor.Plane, |
| 46 |
Height = height * floorPlane.Normal.Y, |
| 47 |
BspTree = BuildBspTree(floor, height * floorPlane.Normal.Y) |
| 48 |
}; |
| 49 |
|
| 50 |
if (floor.Material != null) |
| 51 |
room.Grid = CreateRoomGrid(floor); |
| 52 |
|
| 53 |
return room; |
| 54 |
} |
| 55 |
|
| 56 |
private static RoomBspNode BuildBspTree(Polygon floor, float height) |
| 57 |
{ |
| 58 |
var points = floor.Points.ToArray(); |
| 59 |
var floorPlane = floor.Plane; |
| 60 |
|
| 61 |
var bottom = new Plane(-floorPlane.Normal, -floorPlane.D); |
| 62 |
var node = new RoomBspNode(bottom, null, null); |
| 63 |
|
| 64 |
var top = new Plane(floorPlane.Normal, floorPlane.D - height); |
| 65 |
node = new RoomBspNode(top, node, null); |
| 66 |
|
| 67 |
for (int i = 0; i < points.Length; i++) |
| 68 |
{ |
| 69 |
var p0 = points[i]; |
| 70 |
var p1 = points[(i + 1) % points.Length]; |
| 71 |
var p2 = p1 + Vector3.Up; |
| 72 |
|
| 73 |
node = new RoomBspNode(new Plane(p0, p1, p2), node, null); |
| 74 |
} |
| 75 |
|
| 76 |
return node; |
| 77 |
} |
| 78 |
|
| 79 |
private static RoomGrid CreateRoomGrid(Polygon floor) |
| 80 |
{ |
| 81 |
if (!File.Exists(floor.Material.ImageFilePath)) |
| 82 |
return null; |
| 83 |
|
| 84 |
var image = TgaReader.Read(floor.Material.ImageFilePath); |
| 85 |
var grid = RoomGrid.FromImage(image); |
| 86 |
|
| 87 |
//BoundingBox bbox = floor.GetBoundingBox(); |
| 88 |
|
| 89 |
// |
| 90 |
// TODO: don't use hardcoded constants |
| 91 |
// |
| 92 |
|
| 93 |
//int gx = (int)(((bbox.Max.X - bbox.Min.X) / 4) + 5); |
| 94 |
//int gz = (int)(((bbox.Max.Z - bbox.Min.Z) / 4) + 5); |
| 95 |
|
| 96 |
//if (gx != image.Width || gz != image.Height) |
| 97 |
//{ |
| 98 |
// //Console.Error.WriteLine("Warning: Grid {0} has wrong size, expecting {1}x{2}, got {3}x{4}", |
| 99 |
// // floor.Material.Name, |
| 100 |
// // gx, gz, |
| 101 |
// // image.Width, image.Height); |
| 102 |
//} |
| 103 |
|
| 104 |
return grid; |
| 105 |
} |
| 106 |
|
| 107 |
private void ConnectRooms() |
| 108 |
{ |
| 109 |
octtree = OctreeBuilder.BuildRoomsOctree(mesh); |
| 110 |
|
| 111 |
foreach (Polygon ghost in mesh.Ghosts) |
| 112 |
{ |
| 113 |
float minY = ghost.Points.Select(p => p.Y).Min(); |
| 114 |
Vector3[] points = ghost.Points.Where(p => Math.Abs(p.Y - minY) <= 0.1f).ToArray(); |
| 115 |
|
| 116 |
if (points.Length != 2) |
| 117 |
{ |
| 118 |
Console.Error.WriteLine("BNV Builder: Bad ghost, it must have 2 lowest points, it has {0}, ignoring", points.Length); |
| 119 |
continue; |
| 120 |
} |
| 121 |
|
| 122 |
Vector3 mid = (points[0] + points[1]) / 2.0f; |
| 123 |
Vector3 normal = ghost.Plane.Normal; |
| 124 |
|
| 125 |
Vector3 p0 = mid - normal + Vector3.Up * 2.0f; |
| 126 |
Vector3 p1 = mid + normal + Vector3.Up * 2.0f; |
| 127 |
|
| 128 |
RoomPair pair = PairRooms(p0, p1); |
| 129 |
|
| 130 |
if (pair == null) |
| 131 |
{ |
| 132 |
Console.WriteLine("BNV Builder: Ghost '{0}' has no adjacencies at {1} and {2}, ignoring", ghost.ObjectName, p0, p1); |
| 133 |
continue; |
| 134 |
} |
| 135 |
|
| 136 |
if (pair.Room0.IsStairs || pair.Room1.IsStairs) |
| 137 |
{ |
| 138 |
var stairs = pair.Room0; |
| 139 |
|
| 140 |
if (!stairs.IsStairs) |
| 141 |
stairs = pair.Room1; |
| 142 |
|
| 143 |
ghost.Flags &= ~GunkFlags.Ghost; |
| 144 |
|
| 145 |
if (ghost.Material != null) |
| 146 |
ghost.Material.Flags &= ~GunkFlags.Ghost; |
| 147 |
|
| 148 |
if (ghost.BoundingBox.Min.Y > stairs.FloorPolygon.BoundingBox.Max.Y - 1.0f) |
| 149 |
ghost.Flags |= GunkFlags.StairsDown; |
| 150 |
else |
| 151 |
ghost.Flags |= GunkFlags.StairsUp; |
| 152 |
} |
| 153 |
else |
| 154 |
{ |
| 155 |
ghost.Flags |= GunkFlags.Ghost; |
| 156 |
} |
| 157 |
|
| 158 |
pair.Room1.Ajacencies.Add(new RoomAdjacency(pair.Room0, ghost)); |
| 159 |
pair.Room0.Ajacencies.Add(new RoomAdjacency(pair.Room1, ghost)); |
| 160 |
} |
| 161 |
} |
| 162 |
|
| 163 |
#region private class RoomPair |
| 164 |
|
| 165 |
private class RoomPair : IComparable<RoomPair> |
| 166 |
{ |
| 167 |
public readonly Room Room0; |
| 168 |
public readonly Room Room1; |
| 169 |
public readonly float HeightDelta; |
| 170 |
public readonly float VolumeDelta; |
| 171 |
|
| 172 |
public RoomPair(Room r0, Vector3 p0, Room r1, Vector3 p1) |
| 173 |
{ |
| 174 |
Room0 = r0; |
| 175 |
Room1 = r1; |
| 176 |
HeightDelta = r0.FloorPlane.DotCoordinate(p0) - r1.FloorPlane.DotCoordinate(p1); |
| 177 |
VolumeDelta = r0.BoundingBox.Volume() - r1.BoundingBox.Volume(); |
| 178 |
} |
| 179 |
|
| 180 |
int IComparable<RoomPair>.CompareTo(RoomPair other) |
| 181 |
{ |
| 182 |
if (Math.Abs(HeightDelta - other.HeightDelta) < 1e-5f) |
| 183 |
return VolumeDelta.CompareTo(other.VolumeDelta); |
| 184 |
else if (HeightDelta < other.HeightDelta) |
| 185 |
return -1; |
| 186 |
else |
| 187 |
return 1; |
| 188 |
} |
| 189 |
} |
| 190 |
|
| 191 |
#endregion |
| 192 |
|
| 193 |
private RoomPair PairRooms(Vector3 p0, Vector3 p1) |
| 194 |
{ |
| 195 |
var pairs = new List<RoomPair>(); |
| 196 |
|
| 197 |
var rooms0 = FindRooms(p0); |
| 198 |
var rooms1 = FindRooms(p1); |
| 199 |
|
| 200 |
foreach (Room r0 in rooms0) |
| 201 |
{ |
| 202 |
foreach (Room r1 in rooms1) |
| 203 |
{ |
| 204 |
if (r0 != r1) |
| 205 |
pairs.Add(new RoomPair(r0, p0, r1, p1)); |
| 206 |
} |
| 207 |
} |
| 208 |
|
| 209 |
pairs.Sort(); |
| 210 |
|
| 211 |
return pairs.Count > 0 ? pairs[0] : null; |
| 212 |
} |
| 213 |
|
| 214 |
private List<Room> FindRooms(Vector3 point) |
| 215 |
{ |
| 216 |
var rooms = new List<Room>(); |
| 217 |
|
| 218 |
var node = octtree.FindLeaf(point); |
| 219 |
|
| 220 |
if (node != null) |
| 221 |
{ |
| 222 |
foreach (var room in node.Rooms) |
| 223 |
{ |
| 224 |
if (room.Contains(point)) |
| 225 |
rooms.Add(room); |
| 226 |
} |
| 227 |
} |
| 228 |
|
| 229 |
return rooms; |
| 230 |
} |
| 231 |
|
| 232 |
/// <summary> |
| 233 |
/// Set the height of the room to the max height of adjacencies. |
| 234 |
/// </summary> |
| 235 |
private void UpdateRoomsHeight() |
| 236 |
{ |
| 237 |
foreach (var room in mesh.Rooms) |
| 238 |
{ |
| 239 |
float maxFloorY = room.FloorPolygon.Points.Max(p => p.Y); |
| 240 |
float maxRoomY; |
| 241 |
|
| 242 |
if (room.Ajacencies.Count == 0) |
| 243 |
maxRoomY = maxFloorY + 20.0f; |
| 244 |
else |
| 245 |
maxRoomY = room.Ajacencies.Max(a => a.Ghost.Points.Max(p => p.Y)); |
| 246 |
|
| 247 |
var bbox = room.FloorPolygon.BoundingBox; |
| 248 |
bbox.Max.Y = maxRoomY; |
| 249 |
|
| 250 |
room.BoundingBox = bbox; |
| 251 |
room.Height = (maxRoomY - maxFloorY) * room.FloorPlane.Normal.Y; |
| 252 |
room.BspTree = BuildBspTree(room.FloorPolygon, room.Height); |
| 253 |
} |
| 254 |
} |
| 255 |
} |
| 256 |
} |