[1114] | 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 | }
|
---|