001package com.fs.starfarer.api.impl.campaign.procgen; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Random; 006 007import org.lwjgl.util.vector.Vector2f; 008 009import com.fs.starfarer.api.campaign.StarSystemAPI; 010import com.fs.starfarer.api.util.Misc; 011 012public class ConstellationGen { 013 014 public static class SpringConnection { 015 public float k; 016 public float d; 017 public boolean push = true; 018 public boolean pull = true; 019 public SpringNode from; 020 public SpringNode to; 021 } 022 023 public static class SpringNode { 024 public Object custom; 025 public float radius; 026 public Vector2f loc = new Vector2f(); 027 public Vector2f vel = new Vector2f(); 028 public Vector2f force = new Vector2f(); 029 public float mass; 030 031 public boolean moveable = true; 032 public List<SpringConnection> connections = new ArrayList<SpringConnection>(); 033 } 034 035 public static class SpringSystem { 036 public List<SpringNode> nodes = new ArrayList<SpringNode>(); 037 public List<SpringConnection> connections = new ArrayList<SpringConnection>(); 038 public int iter = 0; 039 040 041 public SpringNode addNode(Object custom, float radius, float mass, float x, float y) { 042 SpringNode node = new SpringNode(); 043 node.custom = custom; 044 node.radius = radius; 045 node.mass = mass; 046 node.loc.x = x; 047 node.loc.y = y; 048 nodes.add(node); 049 050 return node; 051 } 052 053 public boolean connExists(SpringNode from, SpringNode to) { 054 for (SpringConnection conn : connections) { 055 if (conn.from == from && conn.to == to) return true; 056 if (conn.from == to && conn.to == from) return true; 057 } 058 return false; 059 } 060 061 062 public void addConnection(SpringNode from, SpringNode to, float k, float d, boolean push, boolean pull) { 063 if (from == to) return; 064 if (connExists(from, to)) return; 065 066 SpringConnection conn = new SpringConnection(); 067 conn.from = from; 068 conn.to = to; 069 conn.k = k; 070 conn.d = d; 071 conn.push = push; 072 conn.pull = pull; 073 074 from.connections.add(conn); 075 to.connections.add(conn); 076 077 connections.add(conn); 078 } 079 080 public void advance(float amount) { 081 if (amount <= 0) return; 082 iter++; 083 084 for (SpringNode node : nodes) { 085 node.force.set(0, 0); 086 } 087 088 for (SpringConnection conn : connections) { 089 float dist = Misc.getDistance(conn.from.loc, conn.to.loc); 090 if (!conn.push && dist < conn.d) continue; 091 if (!conn.pull && dist > conn.d) continue; 092 093 float diff = conn.d - dist; 094 095 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(conn.from.loc, conn.to.loc)); 096 097 float force = conn.k * diff; 098 099 conn.to.force.x += dir.x * force; 100 conn.to.force.y += dir.y * force; 101 conn.from.force.x -= dir.x * force; 102 conn.from.force.y -= dir.y * force; 103 } 104 105 float cof = 500f; 106 for (SpringNode node : nodes) { 107 float friction = node.vel.length() * cof; 108 float max = node.vel.length() * node.mass / amount; 109 if (friction > max) friction = max; 110 111 if (friction > 0) { 112 Vector2f dir = new Vector2f(node.vel); 113 dir.negate(); 114 dir.normalise(); 115 dir.scale(friction); 116 node.force.x += dir.x; 117 node.force.y += dir.y; 118 } 119 120// if (node.force.length() < 10f && node.vel.length() > 2f) { 121// Vector2f dir = new Vector2f(node.vel); 122// dir.negate(); 123// dir.normalise(); 124// 125// dir.scale(node.vel.length() * node.mass / amount); 126// node.force.set(dir); 127// } 128 } 129 130 for (SpringNode node : nodes) { 131 if (!node.moveable) continue; 132 133 float ax = node.force.x / node.mass; 134 float ay = node.force.y / node.mass; 135 136 node.vel.x += ax * amount; 137 node.vel.y += ay * amount; 138 139 node.loc.x += node.vel.x * amount; 140 node.loc.y += node.vel.y * amount; 141 } 142 } 143 144 public boolean isDone() { 145 for (SpringNode n1 : nodes) { 146 for (SpringNode n2 : nodes) { 147 if (n1 == n2) continue; 148 float dist = Misc.getDistance(n1.loc, n2.loc); 149 if (dist < n1.radius + n2.radius) { 150 return false; 151 } 152 } 153 } 154 for (SpringConnection conn : connections) { 155 if (!conn.pull) continue; 156 157 float dist = Misc.getDistance(conn.from.loc, conn.to.loc); 158 if (dist > conn.d + 1000f) { 159 return false; 160 } 161 } 162 return true; 163 } 164 } 165 166 167 168 public static class StarNode { 169 public StarSystemAPI system; 170 public Vector2f location; 171 public StarNode(StarSystemAPI system, Vector2f location) { 172 this.system = system; 173 this.location = location; 174 } 175 } 176 177// public static void addSpringScript(final List<StarSystemAPI> systems) { 178// 179// final SpringSystem springs = createSpringSystem(systems, new Random()); 180// 181// Global.getSector().addScript(new EveryFrameScript() { 182// public boolean runWhilePaused() { 183// return false; 184// } 185// public boolean isDone() { 186// boolean done = springs.isDone(); 187// if (done) { 188// for (int i = 0; i < 100; i++) { 189// System.out.println("Done in " + springs.iter + " iterations"); 190// } 191// for (SpringNode node : springs.nodes) { 192// StarSystemAPI system = (StarSystemAPI) node.custom; 193// system.getLocation().set(node.loc); 194// } 195// Global.getSector().setPaused(true); 196// Global.getSector().getHyperspace().updateAllOrbits(); 197// return true; 198// } 199// return false; 200// } 201// public void advance(float amount) { 202// if (isDone()) return; 203// 204// for (int i = 0; i < 1; i++) { 205// springs.advance(0.25f); 206// } 207// 208// for (SpringNode node : springs.nodes) { 209// StarSystemAPI system = (StarSystemAPI) node.custom; 210// system.getLocation().set(node.loc); 211// } 212// } 213// }); 214// 215// } 216 217 public static SpringSystem createSpringSystem(List<StarSystemAPI> systems, Random random) { 218 final SpringSystem springs = new SpringSystem(); 219 if (systems.isEmpty()) return springs; 220 221 Vector2f loc = new Vector2f(0, 0); 222 //loc = new Vector2f(systems.get(0).getLocation()); 223 boolean first = true; 224 for (StarSystemAPI system : systems) { 225 float radius = system.getMaxRadiusInHyperspace(); 226 if (radius < 500) radius = 500; 227 228 float mass = radius; 229 SpringNode node = springs.addNode(system, radius, mass, 230 loc.x + random.nextFloat(), loc.y + random.nextFloat()); 231 if (first) { 232 node.moveable = false; 233 first = false; 234 } 235 } 236 237 List<SpringNode> copy = new ArrayList<SpringNode>(springs.nodes); 238 SpringNode curr = copy.get((int) (copy.size() * random.nextDouble())); 239 copy.remove(curr); 240 241 float pullK = 1000f; 242 float pushK = 500f; 243 // create constellation shape via branching spring thing 244 while (!copy.isEmpty()) { 245 int numBranches = random.nextInt(3) + 1; 246 //numBranches = 3; 247 248 for (int i = 0; i < numBranches; i++) { 249 if (copy.isEmpty()) break; 250 251 SpringNode other = copy.get((int) (copy.size() * random.nextDouble())); 252 copy.remove(other); 253 254 //float d = (curr.radius + other.radius) + 250f + random.nextFloat() * 1000f; 255 float d = (curr.radius + other.radius) + StarSystemGenerator.MIN_STAR_DIST + 256 random.nextFloat() * (StarSystemGenerator.MAX_STAR_DIST - StarSystemGenerator.MIN_STAR_DIST); 257 springs.addConnection(curr, other, pullK, d, true, true); 258 //System.out.println("Dist: " + d); 259 if (i == numBranches - 1) { 260 curr = other; 261 } 262 } 263 } 264 265 // and add push-only connections for anything not connected by the main shape 266 for (SpringNode n1 : springs.nodes) { 267 for (SpringNode n2 : springs.nodes) { 268 if (n1 == n2) continue; 269 float d = (n1.radius + n2.radius) + 5000f; 270 springs.addConnection(n1, n2, pushK, d, true, false); 271 } 272 } 273 274 for (SpringNode node : springs.nodes) { 275 StarSystemAPI system = (StarSystemAPI) node.custom; 276 system.getLocation().set(node.loc); 277 } 278 return springs; 279 } 280 281 282 public static SpringSystem doConstellationLayout(List<StarSystemAPI> systems, Random random, Vector2f centerPoint) { 283 SpringSystem springs = createSpringSystem(systems, random); 284 285 for (int i = 0; i < 1000; i++) { 286 springs.advance(0.25f); 287 if (springs.isDone()) break; 288 } 289 290 if (!springs.isDone()) { 291 springs = createSpringSystem(systems, random); 292 for (int i = 0; i < 1000; i++) { 293 springs.advance(0.25f); 294 if (springs.isDone()) break; 295 } 296 } 297 298 //if (!springs.isDone()) return springs; 299 300 float minX = Float.MAX_VALUE; 301 float maxX = -Float.MAX_VALUE; 302 float minY = Float.MAX_VALUE; 303 float maxY = -Float.MAX_VALUE; 304 305 for (SpringNode node : springs.nodes) { 306 float x = node.loc.x; 307 float y = node.loc.y; 308 if (x < minX) minX = x; 309 if (x > maxX) maxX = x; 310 if (y < minY) minY = y; 311 if (y > maxY) maxY = y; 312 } 313 314 float midX = minX + (maxX - minX) * 0.5f; 315 float midY = minY + (maxY - minY) * 0.5f; 316 317 for (SpringNode node : springs.nodes) { 318 node.loc.x = (int)(node.loc.x - midX + centerPoint.x); 319 node.loc.y = (int)(node.loc.y - midY + centerPoint.y); 320 } 321 322 323 for (SpringNode node : springs.nodes) { 324 StarSystemAPI system = (StarSystemAPI) node.custom; 325 system.getLocation().set(node.loc); 326 } 327 328 return springs; 329 } 330 331 332 333 334} 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350