001package com.fs.starfarer.api.impl.campaign.velfield; 002 003import java.awt.Color; 004import java.util.ArrayList; 005import java.util.Collections; 006import java.util.Comparator; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010 011import org.lwjgl.opengl.GL11; 012import org.lwjgl.util.vector.Vector2f; 013 014import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2.SlipstreamSegment; 015import com.fs.starfarer.api.util.Misc; 016 017public class BoundingBox { 018 019 public static BoundingBox create(List<SlipstreamSegment> segments) { 020 float padding = 0; 021 List<Vector2f> points = new ArrayList<Vector2f>(); 022 SlipstreamSegment prev = null; 023 float unitsPerNormals = 2000f; 024 float distSoFar = unitsPerNormals; 025 //for (SlipstreamSegment seg : segments) { 026 for (int i = 0; i < segments.size(); i++) { 027 SlipstreamSegment seg = segments.get(i); 028 if (distSoFar >= unitsPerNormals || i == segments.size() - 1) { 029 distSoFar -= unitsPerNormals; 030 Vector2f n1 = new Vector2f(seg.loc); 031 Vector2f n2 = new Vector2f(seg.loc); 032 n1.x += seg.normal.x * seg.width * 0.5f; 033 n1.y += seg.normal.y * seg.width * 0.5f; 034 n2.x -= seg.normal.x * seg.width * 0.5f; 035 n2.y -= seg.normal.y * seg.width * 0.5f; 036 points.add(n1); 037 points.add(n2); 038 } else { 039 points.add(new Vector2f(seg.loc)); 040 } 041 if (seg.width * 0.5f > padding) { 042 padding = seg.width * 0.5f; 043 } 044 if (prev != null) { 045 distSoFar += Misc.getDistance(seg.loc, prev.loc); 046 } 047 prev = seg; 048 } 049 padding *= 0.5f; 050 BoundingBox box = new BoundingBox(padding); 051 box.compute(points); 052 return box; 053 } 054 055 protected transient List<Vector2f> points; 056 protected transient List<Vector2f> convexHull; 057 protected List<Vector2f> box; 058 protected float padding; 059 protected float [] rotatedBox; 060 protected float angle; 061 062 protected boolean boxComputed = false; 063 protected Vector2f center = new Vector2f(); 064 protected float radius; 065 066 public BoundingBox(float padding) { 067 this.padding = padding; 068 } 069 070 public boolean pointNeedsDetailedCheck(Vector2f p) { 071 return pointNeedsDetailedCheck(p, 0f); 072 } 073 public boolean pointNeedsDetailedCheck(Vector2f p, float extraRange) { 074 float distSq = Misc.getDistanceSq(center, p); 075 if (distSq > (radius + extraRange) * (radius + extraRange)) return false; 076 077 if (!boxComputed) return true; 078 079 Vector2f check = Misc.rotateAroundOrigin(p, angle); 080 081 return check.x > rotatedBox[0] - extraRange && check.x < rotatedBox[2] + extraRange && 082 check.y > rotatedBox[1] - extraRange && check.y < rotatedBox[3] + extraRange; 083 } 084 085 086 public void compute(List<Vector2f> points) { 087 boxComputed = false; 088 computeConvexHull(points); 089 computeBox(); 090 computeCenterAndRadius(); 091 } 092 093 public void computeCenterAndRadius() { 094 center.set(0, 0); 095 for (Vector2f p : points) { 096 center.x += p.x; 097 center.y += p.y; 098 } 099 center.scale(1f / (float) Math.max(1f, points.size())); 100 101 float maxDistSq = 0f; 102 for (Vector2f p : points) { 103 float dist = Misc.getDistanceSq(center, p); 104 if (dist > maxDistSq) maxDistSq = dist; 105 } 106 radius = (float) Math.sqrt(maxDistSq) + padding; 107 108 } 109 110 public void computeBox() { 111 if (boxComputed) return; 112 113 if (convexHull == null || convexHull.size() < 3) { 114 boxComputed = false; 115 return; 116 } 117 118 float minArea = Float.MAX_VALUE; 119 float [] best = null; 120 float bestAngle = 0f; 121 for (int i = 0; i < convexHull.size(); i++) { 122 int e1 = i; 123 int e2 = i + 1; 124 if (i + 1 == convexHull.size()) e2 = 0; 125 Vector2f p1 = convexHull.get(e1); 126 Vector2f p2 = convexHull.get(e2); 127 Vector2f edge = Vector2f.sub(p2, p1, new Vector2f()); 128 edge = Misc.normalise(edge); 129 130 float angle = (float) Math.acos(edge.y) * Misc.DEG_PER_RAD; 131 List<Vector2f> rotated = rotate(convexHull, angle); 132 float [] box = getBoundingBox(rotated); 133 float area = (box[2] - box[0]) * (box[3] - box[1]); 134 if (area < minArea) { 135 minArea = area; 136 best = box; 137 bestAngle = angle; 138 } 139 } 140 141 if (best == null) { 142 boxComputed = false; 143 return; 144 } 145 146 best[0] -= padding; 147 best[1] -= padding; 148 best[2] += padding; 149 best[3] += padding; 150 151 Vector2f p1 = new Vector2f(best[0], best[1]); 152 Vector2f p2 = new Vector2f(best[2], best[1]); 153 Vector2f p3 = new Vector2f(best[2], best[3]); 154 Vector2f p4 = new Vector2f(best[0], best[3]); 155 box = new ArrayList<Vector2f>(); 156 box.add(p1); 157 box.add(p2); 158 box.add(p3); 159 box.add(p4); 160 161 rotatedBox = best; 162 this.angle = bestAngle; 163 164 box = rotate(box, -bestAngle); 165 boxComputed = true; 166 } 167 168 public static List<Vector2f> rotate(List<Vector2f> points, float angle) { 169 List<Vector2f> result = new ArrayList<Vector2f>(); 170 for (Vector2f p : points) { 171 result.add(Misc.rotateAroundOrigin(p, angle)); 172 } 173 return result; 174 } 175 176 public static float [] getBoundingBox(List<Vector2f> points) { 177 float minX = Float.MAX_VALUE; 178 float minY = Float.MAX_VALUE; 179 float maxX = -Float.MAX_VALUE; 180 float maxY = -Float.MAX_VALUE; 181 for (Vector2f p : points) { 182 if (p.x < minX) minX = p.x; 183 if (p.y < minY) minY = p.y; 184 if (p.x > maxX) maxX = p.x; 185 if (p.y > maxY) maxY = p.y; 186 } 187 return new float [] {minX, minY, maxX, maxY}; 188 } 189 190 public void computeConvexHull(List<Vector2f> points) { 191 this.points = new ArrayList<Vector2f>(points); 192 193 if (points.size() < 3) { 194 boxComputed = false; 195 return; 196 } 197 198 Vector2f p1 = null; 199 float minY = Float.MAX_VALUE; 200 for (Vector2f p : points) { 201 if (p.y < minY) { 202 p1 = p; 203 minY = p.y; 204 } 205 } 206 final Map<Vector2f, Float> angles = new HashMap<Vector2f, Float>(); 207 for (Vector2f p : points) { 208 if (p == p1) continue; 209 float angle = Misc.getAngleInDegreesStrict(p1, p); 210 //float angle = 1f; 211 angles.put(p, angle); 212 } 213 214 List<Vector2f> sorted = new ArrayList<Vector2f>(points); 215 sorted.remove(p1); 216 Collections.sort(sorted, new Comparator<Vector2f>() { 217 public int compare(Vector2f o1, Vector2f o2) { 218 float diff = angles.get(o1) - angles.get(o2); 219 if (diff < 0) return -1; 220 if (diff > 0) return 1; 221 return 0; 222 } 223 }); 224 225 convexHull = new ArrayList<Vector2f>(); 226 convexHull.add(p1); 227 for (Vector2f p : sorted) { 228 if (convexHull.size() == 1) { 229 convexHull.add(p); 230 continue; 231 } 232 while (true) { 233 float turnDir = getTurnDir(convexHull.get(convexHull.size() - 2), 234 convexHull.get(convexHull.size() - 1), 235 p); 236 if (turnDir <= 0) { 237 convexHull.remove(convexHull.size() - 1); 238 if (convexHull.size() < 2) break; 239 } else { 240 convexHull.add(p); 241 break; 242 } 243 } 244 } 245 246 if (convexHull.size() < 3) { 247 boxComputed = false; 248 return; 249 } 250 251 } 252 253 public static float getTurnDir(Vector2f p1, Vector2f p2, Vector2f p3) { 254 float r = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x); 255 if (r > 0) return 1f; // left turn 256 if (r < 0) return -1f; // right turn 257 return 0; // collinear 258 } 259 260 261 public void renderDebug(float alpha) { 262 GL11.glDisable(GL11.GL_TEXTURE_2D); 263 GL11.glEnable(GL11.GL_BLEND); 264 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 265 266 GL11.glEnable(GL11.GL_POINT_SMOOTH); 267 GL11.glPointSize(10f); 268 GL11.glBegin(GL11.GL_POINTS); 269 Misc.setColor(Color.yellow); 270 if (points != null) { 271 for (Vector2f p : points) { 272 GL11.glVertex2f(p.x, p.y); 273 } 274 } 275 GL11.glEnd(); 276 277 Misc.setColor(Color.white); 278 GL11.glEnable(GL11.GL_LINE_SMOOTH); 279 GL11.glLineWidth(3f); 280 GL11.glBegin(GL11.GL_LINE_LOOP); 281 if (convexHull != null) { 282 for (Vector2f p : convexHull) { 283 GL11.glVertex2f(p.x, p.y); 284 } 285 } 286 GL11.glEnd(); 287 288 Misc.setColor(Color.cyan); 289 GL11.glEnable(GL11.GL_LINE_SMOOTH); 290 GL11.glLineWidth(3f); 291 GL11.glBegin(GL11.GL_LINE_LOOP); 292 if (box != null) { 293 for (Vector2f p : box) { 294 GL11.glVertex2f(p.x, p.y); 295 } 296 } 297 GL11.glEnd(); 298 299 } 300 301 302 303 304} 305 306 307 308