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