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