001package com.fs.starfarer.api.impl.combat; 002 003import java.awt.Color; 004 005import org.lwjgl.util.vector.Vector2f; 006 007import com.fs.starfarer.api.Global; 008import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin; 009import com.fs.starfarer.api.combat.CombatEngineAPI; 010import com.fs.starfarer.api.util.Misc; 011 012/** 013 * Used for ship explosions when there's no whiteout. 014 * 015 * @author Alex 016 * 017 * Copyright 2022 Fractal Softworks, LLC 018 */ 019public class ShockwaveVisual extends BaseCombatLayeredRenderingPlugin { 020 021 public static class ShockwaveParams implements Cloneable { 022 public Color color; 023 public float radius; 024 public Vector2f loc; 025 026 public ShockwaveParams() { 027 } 028 029 public ShockwaveParams(Color color, float radius, Vector2f loc) { 030 super(); 031 this.color = color; 032 this.radius = radius; 033 this.loc = loc; 034 } 035 036 @Override 037 protected ShockwaveParams clone() { 038 try { 039 return (ShockwaveParams) super.clone(); 040 } catch (CloneNotSupportedException e) { 041 return null; // should never happen 042 } 043 } 044 045 } 046 047 048 protected ShockwaveParams p; 049 050 051 public static void spawnShockwave(ShockwaveParams params) { 052 CombatEngineAPI engine = Global.getCombatEngine(); 053 054 float baseSize = params.radius * 0.08f; 055 float maxParticleSize = baseSize * 2f; 056 057 float fullArea = (float) (Math.PI * params.radius * params.radius); 058 float particleArea = (float) (Math.PI * baseSize * baseSize); 059 060 int count = (int) Math.round(fullArea / particleArea * 1f); 061 count *= 2; 062 if (count > 300) count = 300; 063 064 float durMult = 2f; 065 //durMult = params.durationMult; 066 067 float maxSpeed = params.radius * 2f; 068 069 Color negativeColor = new Color(0, 0, 0, 255); 070 071 //baseSize *= 0.5f; 072 for (int i = 0; i < count; i++) { 073 float size = baseSize * (1f + (float) Math.random()); 074 075 Color randomColor = new Color(Misc.random.nextInt(256), 076 Misc.random.nextInt(256), Misc.random.nextInt(256), params.color.getAlpha()); 077 Color adjustedColor = Misc.interpolateColor(params.color, randomColor, 0.2f); 078 adjustedColor = params.color; 079 //adjustedColor = Misc.setAlpha(adjustedColor, 50); 080// ParticleData data = new ParticleData(adjustedColor, size, 081// (0.25f + (float) Math.random()) * 2f * durMult, 3f); 082 083 float r = (float) Math.random(); 084 float dist = params.radius * 0.2f * (0.1f + r * 0.9f); 085 float dir = (float) Math.random() * 360f; 086 //data.setOffset(dir, dist, dist); 087 Vector2f offset = Misc.getUnitVectorAtDegreeAngle(dir); 088 //offset.scale(dist + (dist - dist) * (float) Math.random()); 089 offset.scale(dist); 090 091// dir = Misc.getAngleInDegrees(data.offset); 092// data.setVelocity(dir, baseSize * 0.25f, baseSize * 0.5f); 093// data.vel.scale(1f / durMult); 094 095// data.swImpact = (float) Math.random(); 096// if (i > count / 2) data.swImpact = 1; 097 098 Vector2f loc = Vector2f.add(params.loc, offset, new Vector2f()); 099 100 float speed = maxSpeed * (0.25f + 0.75f * (float) Math.random()); 101 Vector2f vel = Misc.getUnitVectorAtDegreeAngle(dir); 102 vel.scale(speed); 103 104 float rampUp = 0f; 105 float dur = 1f; 106 107 float mult = 0.33f; 108 mult = 1f; 109 mult = 0.5f; 110 rampUp *= mult; 111 dur *= mult; 112 113 engine.addNebulaParticle(loc, vel, size, 3f, rampUp, 0f, dur, adjustedColor); 114 //engine.addNegativeNebulaParticle(loc, vel, size, 3f, rampUp, 0f, dur, negativeColor); 115 //engine.addNebulaSmokeParticle(loc, vel, size, 3f, rampUp, 0f, dur, negativeColor); 116 } 117 118 119 } 120 121 /* 122 public ShockwaveVisual(ShockwaveParams p) { 123 this.p = p; 124 } 125 126 public float getRenderRadius() { 127 return p.radius + 500f; 128 } 129 130 131 @Override 132 public EnumSet<CombatEngineLayers> getActiveLayers() { 133 return EnumSet.of(CombatEngineLayers.ABOVE_PARTICLES_LOWER); 134 } 135 136 public void advance(float amount) { 137 if (Global.getCombatEngine().isPaused()) return; 138 139 140 CombatEngineAPI engine = Global.getCombatEngine(); 141 engine.addNebulaParticle(loc, entity.getVelocity(), s, 1.5f, rampUp, 0f, dur, c); 142 } 143 144 public void init(CombatEntityAPI entity) { 145 super.init(entity); 146 } 147 148 public boolean isExpired() { 149 return false; 150 } 151 152 public void render(CombatEngineLayers layer, ViewportAPI viewport) { 153 float x = entity.getLocation().x; 154 float y = entity.getLocation().y; 155 156 float f = fader.getBrightness(); 157 float alphaMult = viewport.getAlphaMult(); 158 if (f < 0.5f) { 159 alphaMult *= f * 2f; 160 } 161 162 float r = p.radius; 163 float tSmall = p.thickness; 164 165 if (fader.isFadingIn()) { 166 r *= 0.75f + Math.sqrt(f) * 0.25f; 167 } else { 168 r *= 0.1f + 0.9f * f; 169 tSmall = Math.min(r * 1f, p.thickness); 170 } 171 172// GL11.glPushMatrix(); 173// GL11.glTranslatef(x, y, 0); 174// GL11.glScalef(6f, 6f, 1f); 175// x = y = 0; 176 177 //GL14.glBlendEquation(GL14.GL_FUNC_REVERSE_SUBTRACT); 178 if (layer == CombatEngineLayers.ABOVE_PARTICLES_LOWER) { 179 float a = 1f; 180 renderAtmosphere(x, y, r, tSmall, alphaMult * a, segments, atmosphereTex, noise, p.color, true); 181 renderAtmosphere(x, y, r - 2f, tSmall, alphaMult * a, segments, atmosphereTex, noise, p.color, true); 182 } else if (layer == CombatEngineLayers.ABOVE_PARTICLES) { 183 float circleAlpha = 1f; 184 if (alphaMult < 0.5f) { 185 circleAlpha = alphaMult * 2f; 186 } 187 float tCircleBorder = 1f; 188 renderCircle(x, y, r, circleAlpha, segments, Color.black); 189 renderAtmosphere(x, y, r, tCircleBorder, circleAlpha, segments, atmosphereTex, noise, Color.black, false); 190 } 191 //GL14.glBlendEquation(GL14.GL_FUNC_ADD); 192 193// GL11.glPopMatrix(); 194 } 195 196 197 private void renderCircle(float x, float y, float radius, float alphaMult, int segments, Color color) { 198 if (fader.isFadingIn()) alphaMult = 1f; 199 200 float startRad = (float) Math.toRadians(0); 201 float endRad = (float) Math.toRadians(360); 202 float spanRad = Misc.normalizeAngle(endRad - startRad); 203 float anglePerSegment = spanRad / segments; 204 205 GL11.glPushMatrix(); 206 GL11.glTranslatef(x, y, 0); 207 GL11.glRotatef(0, 0, 0, 1); 208 GL11.glDisable(GL11.GL_TEXTURE_2D); 209 210 GL11.glEnable(GL11.GL_BLEND); 211 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 212 213 214 GL11.glColor4ub((byte)color.getRed(), 215 (byte)color.getGreen(), 216 (byte)color.getBlue(), 217 (byte)((float) color.getAlpha() * alphaMult)); 218 219 GL11.glBegin(GL11.GL_TRIANGLE_FAN); 220 GL11.glVertex2f(0, 0); 221 for (float i = 0; i < segments + 1; i++) { 222 boolean last = i == segments; 223 if (last) i = 0; 224 float theta = anglePerSegment * i; 225 float cos = (float) Math.cos(theta); 226 float sin = (float) Math.sin(theta); 227 228 float m1 = 0.75f + 0.65f * noise[(int)i]; 229 if (p.noiseMag <= 0) { 230 m1 = 1f; 231 } 232 233 float x1 = cos * radius * m1; 234 float y1 = sin * radius * m1; 235 236 GL11.glVertex2f(x1, y1); 237 238 if (last) break; 239 } 240 241 242 GL11.glEnd(); 243 GL11.glPopMatrix(); 244 245 } 246 247 248 private void renderAtmosphere(float x, float y, float radius, float thickness, float alphaMult, int segments, SpriteAPI tex, float [] noise, Color color, boolean additive) { 249 250 float startRad = (float) Math.toRadians(0); 251 float endRad = (float) Math.toRadians(360); 252 float spanRad = Misc.normalizeAngle(endRad - startRad); 253 float anglePerSegment = spanRad / segments; 254 255 GL11.glPushMatrix(); 256 GL11.glTranslatef(x, y, 0); 257 GL11.glRotatef(0, 0, 0, 1); 258 GL11.glEnable(GL11.GL_TEXTURE_2D); 259 260 tex.bindTexture(); 261 262 GL11.glEnable(GL11.GL_BLEND); 263 if (additive) { 264 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); 265 } else { 266 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); 267 } 268 269 GL11.glColor4ub((byte)color.getRed(), 270 (byte)color.getGreen(), 271 (byte)color.getBlue(), 272 (byte)((float) color.getAlpha() * alphaMult)); 273 float texX = 0f; 274 float incr = 1f / segments; 275 GL11.glBegin(GL11.GL_QUAD_STRIP); 276 for (float i = 0; i < segments + 1; i++) { 277 boolean last = i == segments; 278 if (last) i = 0; 279 float theta = anglePerSegment * i; 280 float cos = (float) Math.cos(theta); 281 float sin = (float) Math.sin(theta); 282 283 float m1 = 0.75f + 0.65f * noise[(int)i]; 284 float m2 = m1; 285 if (p.noiseMag <= 0) { 286 m1 = 1f; 287 m2 = 1f; 288 } 289 290 float x1 = cos * radius * m1; 291 float y1 = sin * radius * m1; 292 float x2 = cos * (radius + thickness * m2); 293 float y2 = sin * (radius + thickness * m2); 294 295 GL11.glTexCoord2f(0.5f, 0.05f); 296 GL11.glVertex2f(x1, y1); 297 298 GL11.glTexCoord2f(0.5f, 0.95f); 299 GL11.glVertex2f(x2, y2); 300 301 texX += incr; 302 if (last) break; 303 } 304 305 GL11.glEnd(); 306 GL11.glPopMatrix(); 307 } 308 */ 309} 310 311