001package com.fs.starfarer.api.impl.combat; 002 003import java.util.Arrays; 004import java.util.EnumSet; 005 006import java.awt.Color; 007 008import org.lwjgl.opengl.GL11; 009import org.lwjgl.util.vector.Vector2f; 010 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin; 013import com.fs.starfarer.api.combat.CombatEngineAPI; 014import com.fs.starfarer.api.combat.CombatEngineLayers; 015import com.fs.starfarer.api.combat.CombatEntityAPI; 016import com.fs.starfarer.api.combat.ViewportAPI; 017import com.fs.starfarer.api.graphics.SpriteAPI; 018import com.fs.starfarer.api.util.FaderUtil; 019import com.fs.starfarer.api.util.Misc; 020import com.fs.starfarer.api.util.Noise; 021 022public class NegativeExplosionVisual extends BaseCombatLayeredRenderingPlugin { 023 024 public static class NEParams implements Cloneable { 025 //public float fadeIn = 0.25f; 026 public int numRiftsToSpawn = 2; 027 public float fadeIn = 0.1f; 028 public float fadeOut = 0.5f; 029 public float spawnHitGlowAt = 0f; 030 public float hitGlowSizeMult = 0.75f; 031 public float radius = 20f; 032 public float thickness = 25f; 033 public float noiseMag = 1f; 034 public float noiseMult = 1f; 035 public float noisePeriod = 0.1f; 036 public boolean withHitGlow = true; 037 public boolean withNegativeParticles = true; 038 public Color color = new Color(100,100,255); 039 public Color underglow = RiftCascadeEffect.EXPLOSION_UNDERCOLOR; 040 public Color blackColor = Color.black; 041 public Color invertForDarkening = null; 042 public boolean additiveBlend = false; 043 044 public NEParams() { 045 } 046 047 public NEParams(float radius, float thickness, Color color) { 048 super(); 049 this.radius = radius; 050 this.thickness = thickness; 051 this.color = color; 052 } 053 054 @Override 055 protected NEParams clone() { 056 try { 057 return (NEParams) super.clone(); 058 } catch (CloneNotSupportedException e) { 059 return null; // should never happen 060 } 061 } 062 063 } 064 065 protected FaderUtil fader; 066 protected SpriteAPI atmosphereTex; 067 068 protected float [] noise; 069 protected float [] noise1; 070 protected float [] noise2; 071 072// protected float radius; 073// protected float thickness; 074// protected Color color; 075// protected float noiseMag; 076// protected float noisePeriod; 077 078 protected NEParams p; 079 080 protected int segments; 081 protected float noiseElapsed = 0f; 082 083 protected boolean spawnedHitGlow = false; 084 085 public NegativeExplosionVisual(NEParams p) { 086 this.p = p; 087 } 088 089 public float getRenderRadius() { 090 return p.radius + 500f; 091 } 092 093 094 @Override 095 public EnumSet<CombatEngineLayers> getActiveLayers() { 096 return EnumSet.of(CombatEngineLayers.ABOVE_PARTICLES_LOWER, CombatEngineLayers.ABOVE_PARTICLES); 097 } 098 099 public void advance(float amount) { 100 if (Global.getCombatEngine().isPaused()) return; 101 102 fader.advance(amount); 103 104 if (p.noiseMag > 0) { 105 noiseElapsed += amount; 106 if (noiseElapsed > p.noisePeriod) { 107 noiseElapsed = 0; 108 noise1 = Arrays.copyOf(noise2, noise2.length); 109 noise2 = Noise.genNoise(segments, p.noiseMag); 110 } 111 float f = noiseElapsed / p.noisePeriod; 112 for (int i = 0; i < noise.length; i++) { 113 float n1 = noise1[i]; 114 float n2 = noise2[i]; 115 noise[i] = n1 + (n2 - n1) * f; 116 } 117 } 118 119 if (!p.withHitGlow) return; 120 121 float glowSpawnAt = 1f; 122 if (!spawnedHitGlow && (!fader.isFadingIn() || fader.getBrightness() >= p.spawnHitGlowAt)) { 123 float size = Math.min(p.radius * 7f, p.radius + 150f); 124 float coreSize = Math.max(size, p.radius * 4f); 125 if (coreSize > size) size = coreSize; 126 127 size *= p.hitGlowSizeMult; 128 coreSize *= p.hitGlowSizeMult; 129 130 CombatEngineAPI engine = Global.getCombatEngine(); 131 Vector2f point = entity.getLocation(); 132 Vector2f vel = entity.getVelocity(); 133 float dur = fader.getDurationOut() * glowSpawnAt; 134 engine.addHitParticle(point, vel, size * 3f, 1f, dur, p.color); 135 //engine.addHitParticle(point, vel, size * 3.0f, 1f, dur, p.color); 136 engine.addHitParticle(point, vel, coreSize * 1.5f, 1f, dur, Color.white); 137 //engine.addHitParticle(point, vel, coreSize * 1f, 1f, dur, Color.white); 138 139 Color invert = p.color; 140 if (p.invertForDarkening != null) invert = p.invertForDarkening; 141 Color c = new Color(255 - invert.getRed(), 142 255 - invert.getGreen(), 143 255 - invert.getBlue(), 127); 144 c = Misc.interpolateColor(c, Color.white, 0.4f); 145 //c = Misc.setAlpha(c, 80); 146 //c = Misc.scaleAlpha(c, 0.5f); 147 float durMult = 1f; 148 for (int i = 0; i < 7 && p.withNegativeParticles; i++) { 149 dur = 4f + 4f * (float) Math.random(); 150 //dur = p.fadeIn + p.fadeOut + 3f + (float) Math.random() * 2f; 151 dur *= durMult; 152 dur *= 0.5f; 153 //float nSize = size * (1f + 0.0f * (float) Math.random()); 154 //float nSize = size * (0.75f + 0.5f * (float) Math.random()); 155 float nSize = size * 1f; 156 Vector2f pt = Misc.getPointAtRadius(point, nSize * 0.5f); 157 Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 158 v.scale(nSize + nSize * (float) Math.random() * 0.5f); 159 v.scale(0.15f); 160 Vector2f.add(vel, v, v); 161 162// float maxSpeed = nSize * 1.5f * 0.2f; 163// float minSpeed = nSize * 1f * 0.2f; 164// float overMin = v.length() - minSpeed; 165// if (overMin > 0) { 166// float durMult2 = 1f - overMin / (maxSpeed - minSpeed); 167// if (durMult2 < 0.1f) durMult2 = 0.1f; 168// dur *= 0.5f + 0.5f * durMult2; 169// } 170 v = new Vector2f(entity.getVelocity()); 171// engine.addNegativeParticle(pt, v, nSize * 1f, p.fadeIn / dur, dur, c); 172 engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f, 173 p.fadeIn / dur, 0f, dur, c); 174 } 175 176 dur = p.fadeIn + p.fadeOut + 2f; 177 dur *= durMult; 178 float rampUp = (p.fadeIn + p.fadeOut) / dur; 179 rampUp = 0f; 180 //rampUp = rampUp + (1f - rampUp) * 0.25f; 181 //float sizeMult = p.hitGlowSizeMult; 182 183 c = p.underglow; 184 if (c != null) { 185 //c = Misc.setAlpha(c, 255); 186 for (int i = 0; i < 15; i++) { 187 //rampUp = (float) Math.random() * 0.05f + 0.05f; 188 Vector2f loc = new Vector2f(point); 189 loc = Misc.getPointWithinRadius(loc, size * 1f); 190 //loc = Misc.getPointAtRadius(loc, size * 1f); 191 float s = size * 3f * (0.25f + (float) Math.random() * 0.25f); 192 //s *= 0.5f; 193 // engine.addSmoothParticle(loc, entity.getVelocity(), s, 1f, rampUp, dur, c); 194 engine.addNebulaParticle(loc, entity.getVelocity(), s, 1.5f, rampUp, 0f, dur, c); 195 } 196 } 197 spawnedHitGlow = true; 198 } 199 } 200 201 public void init(CombatEntityAPI entity) { 202 super.init(entity); 203 204 fader = new FaderUtil(0f, p.fadeIn, p.fadeOut); 205 fader.setBounceDown(true); 206 fader.fadeIn(); 207 208 atmosphereTex = Global.getSettings().getSprite("combat", "corona_hard"); 209 210 float perSegment = 2f; 211 segments = (int) ((p.radius * 2f * 3.14f) / perSegment); 212 if (segments < 8) segments = 8; 213 214 noise1 = Noise.genNoise(segments, p.noiseMag); 215 noise2 = Noise.genNoise(segments, p.noiseMag); 216 noise = Arrays.copyOf(noise1, noise1.length); 217 } 218 219 public boolean isExpired() { 220 return fader.isFadedOut(); 221 } 222 223 public void render(CombatEngineLayers layer, ViewportAPI viewport) { 224 float x = entity.getLocation().x; 225 float y = entity.getLocation().y; 226 227 float f = fader.getBrightness(); 228 float alphaMult = viewport.getAlphaMult(); 229 if (f < 0.5f) { 230 alphaMult *= f * 2f; 231 } 232 233 float r = p.radius; 234 float tSmall = p.thickness; 235 236 if (fader.isFadingIn()) { 237 r *= 0.75f + Math.sqrt(f) * 0.25f; 238 } else { 239 r *= 0.1f + 0.9f * f; 240 tSmall = Math.min(r * 1f, p.thickness); 241 } 242 243// GL11.glPushMatrix(); 244// GL11.glTranslatef(x, y, 0); 245// GL11.glScalef(6f, 6f, 1f); 246// x = y = 0; 247 248 //GL14.glBlendEquation(GL14.GL_FUNC_REVERSE_SUBTRACT); 249 if (layer == CombatEngineLayers.ABOVE_PARTICLES_LOWER) { 250 float a = 1f; 251 renderAtmosphere(x, y, r, tSmall, alphaMult * a, segments, atmosphereTex, noise, p.color, true); 252 renderAtmosphere(x, y, r - 2f, tSmall, alphaMult * a, segments, atmosphereTex, noise, p.color, true); 253 } else if (layer == CombatEngineLayers.ABOVE_PARTICLES) { 254 float circleAlpha = 1f; 255 if (alphaMult < 0.5f) { 256 circleAlpha = alphaMult * 2f; 257 } 258 float tCircleBorder = 1f; 259 renderCircle(x, y, r, circleAlpha, segments, p.blackColor); 260 renderAtmosphere(x, y, r, tCircleBorder, circleAlpha, segments, atmosphereTex, noise, p.blackColor, p.additiveBlend); 261 } 262 //GL14.glBlendEquation(GL14.GL_FUNC_ADD); 263 264// GL11.glPopMatrix(); 265 } 266 267 268 private void renderCircle(float x, float y, float radius, float alphaMult, int segments, Color color) { 269 if (fader.isFadingIn() && p.blackColor == Color.black) alphaMult = 1f; 270 //if (fader.isFadingIn()) alphaMult = 1f; 271 272 float startRad = (float) Math.toRadians(0); 273 float endRad = (float) Math.toRadians(360); 274 float spanRad = Misc.normalizeAngle(endRad - startRad); 275 float anglePerSegment = spanRad / segments; 276 277 GL11.glPushMatrix(); 278 GL11.glTranslatef(x, y, 0); 279 GL11.glRotatef(0, 0, 0, 1); 280 GL11.glDisable(GL11.GL_TEXTURE_2D); 281 282 GL11.glEnable(GL11.GL_BLEND); 283 if (p.additiveBlend) { 284 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); 285 } else { 286 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 287 } 288 289 290 GL11.glColor4ub((byte)color.getRed(), 291 (byte)color.getGreen(), 292 (byte)color.getBlue(), 293 (byte)((float) color.getAlpha() * alphaMult)); 294 295 GL11.glBegin(GL11.GL_TRIANGLE_FAN); 296 GL11.glVertex2f(0, 0); 297 for (float i = 0; i < segments + 1; i++) { 298 boolean last = i == segments; 299 if (last) i = 0; 300 float theta = anglePerSegment * i; 301 float cos = (float) Math.cos(theta); 302 float sin = (float) Math.sin(theta); 303 304 float m1 = 0.75f + 0.65f * noise[(int)i] * p.noiseMult; 305 if (p.noiseMag <= 0) { 306 m1 = 1f; 307 } 308 309 float x1 = cos * radius * m1; 310 float y1 = sin * radius * m1; 311 312 GL11.glVertex2f(x1, y1); 313 314 if (last) break; 315 } 316 317 318 GL11.glEnd(); 319 GL11.glPopMatrix(); 320 321 } 322 323 324 private void renderAtmosphere(float x, float y, float radius, float thickness, float alphaMult, int segments, SpriteAPI tex, float [] noise, Color color, boolean additive) { 325 326 float startRad = (float) Math.toRadians(0); 327 float endRad = (float) Math.toRadians(360); 328 float spanRad = Misc.normalizeAngle(endRad - startRad); 329 float anglePerSegment = spanRad / segments; 330 331 GL11.glPushMatrix(); 332 GL11.glTranslatef(x, y, 0); 333 GL11.glRotatef(0, 0, 0, 1); 334 GL11.glEnable(GL11.GL_TEXTURE_2D); 335 336 tex.bindTexture(); 337 338 GL11.glEnable(GL11.GL_BLEND); 339 if (additive) { 340 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); 341 } else { 342 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 343 } 344 345 GL11.glColor4ub((byte)color.getRed(), 346 (byte)color.getGreen(), 347 (byte)color.getBlue(), 348 (byte)((float) color.getAlpha() * alphaMult)); 349 float texX = 0f; 350 float incr = 1f / segments; 351 GL11.glBegin(GL11.GL_QUAD_STRIP); 352 for (float i = 0; i < segments + 1; i++) { 353 boolean last = i == segments; 354 if (last) i = 0; 355 float theta = anglePerSegment * i; 356 float cos = (float) Math.cos(theta); 357 float sin = (float) Math.sin(theta); 358 359 float m1 = 0.75f + 0.65f * noise[(int)i] * p.noiseMult; 360 float m2 = m1; 361 if (p.noiseMag <= 0) { 362 m1 = 1f; 363 m2 = 1f; 364 } 365 366 float x1 = cos * radius * m1; 367 float y1 = sin * radius * m1; 368 float x2 = cos * (radius + thickness * m2); 369 float y2 = sin * (radius + thickness * m2); 370 371 GL11.glTexCoord2f(0.5f, 0.05f); 372 GL11.glVertex2f(x1, y1); 373 374 GL11.glTexCoord2f(0.5f, 0.95f); 375 GL11.glVertex2f(x2, y2); 376 377 texX += incr; 378 if (last) break; 379 } 380 381 GL11.glEnd(); 382 GL11.glPopMatrix(); 383 } 384 385 public FaderUtil getFader() { 386 return fader; 387 } 388 389} 390 391