vazkii.b_baubles.client.core.handler.LightningHandler.java Source code

Java tutorial

Introduction

Here is the source code for vazkii.b_baubles.client.core.handler.LightningHandler.java

Source

/**
 * This class was created by <Vazkii/ChickenBones>. It's distributed as
 * part of the Botania Mod. Get the Source Code in github:
 * https://github.com/Vazkii/Botania
 * 
 * Botania is Open Source and distributed under the
 * Botania License: http://botaniamod.net/license.php
 * 
 * File Created @ [Feb 3, 2014, 9:05:38 PM (GMT)]
 */
package vazkii.b_baubles.client.core.handler;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;

import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.entity.Entity;
import net.minecraft.profiler.Profiler;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.ChunkCoordinates;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderWorldLastEvent;

import org.lwjgl.opengl.GL11;

import vazkii.b_baubles.client.core.handler.LightningHandler.LightningBolt.Segment;
import vazkii.b_baubles.client.fx.ParticleRenderDispatcher;
import vazkii.b_baubles.client.lib.LibResources;
import vazkii.b_baubles.common.core.helper.Vector3;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;

public class LightningHandler {

    private static final ResourceLocation outsideResource = new ResourceLocation(LibResources.MISC_WISP_LARGE);
    private static final ResourceLocation insideResource = new ResourceLocation(LibResources.MISC_WISP_SMALL);

    static double interpPosX;
    static double interpPosY;
    static double interpPosZ;

    private static Vector3 getRelativeViewVector(Vector3 pos) {
        Entity renderEntity = Minecraft.getMinecraft().renderViewEntity;
        return new Vector3((float) renderEntity.posX - pos.x,
                (float) renderEntity.posY + renderEntity.getEyeHeight() - pos.y, (float) renderEntity.posZ - pos.z);
    }

    @SubscribeEvent
    public void onRenderWorldLast(RenderWorldLastEvent event) {
        Profiler profiler = Minecraft.getMinecraft().mcProfiler;

        profiler.startSection("botania-particles");
        ParticleRenderDispatcher.dispatch();
        profiler.startSection("redString");

        profiler.endStartSection("lightning");

        float frame = event.partialTicks;
        Entity entity = Minecraft.getMinecraft().thePlayer;
        TextureManager render = Minecraft.getMinecraft().renderEngine;

        interpPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * frame;
        interpPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * frame;
        interpPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * frame;

        GL11.glPushMatrix();
        GL11.glTranslated(-interpPosX, -interpPosY, -interpPosZ);

        Tessellator tessellator = Tessellator.instance;

        GL11.glDepthMask(false);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        ParticleRenderDispatcher.lightningCount = 0;

        render.bindTexture(outsideResource);
        tessellator.startDrawingQuads();
        tessellator.setBrightness(0xF000F0);
        for (LightningBolt bolt : LightningBolt.boltlist)
            renderBolt(bolt, tessellator, frame, ActiveRenderInfo.rotationX, ActiveRenderInfo.rotationXZ,
                    ActiveRenderInfo.rotationZ, ActiveRenderInfo.rotationXY, 0, false);
        tessellator.draw();

        render.bindTexture(insideResource);
        tessellator.startDrawingQuads();
        tessellator.setBrightness(0xF000F0);
        for (LightningBolt bolt : LightningBolt.boltlist)
            renderBolt(bolt, tessellator, frame, ActiveRenderInfo.rotationX, ActiveRenderInfo.rotationXZ,
                    ActiveRenderInfo.rotationZ, ActiveRenderInfo.rotationXY, 1, true);
        tessellator.draw();

        GL11.glDisable(GL11.GL_BLEND);
        GL11.glDepthMask(true);

        GL11.glTranslated(interpPosX, interpPosY, interpPosZ);
        GL11.glPopMatrix();

        profiler.endSection();
        profiler.endSection();

    }

    public static void spawnLightningBolt(World world, Vector3 vectorStart, Vector3 vectorEnd, float ticksPerMeter,
            long seed, int colorOuter, int colorInner) {
        LightningBolt bolt = new LightningBolt(world, vectorStart, vectorEnd, ticksPerMeter, seed, colorOuter,
                colorInner);
        bolt.defaultFractal();
        bolt.finalizeBolt();
        LightningBolt.boltlist.add(bolt);
    }

    private void renderBolt(LightningBolt bolt, Tessellator tessellator, float partialframe, float cosyaw,
            float cospitch, float sinyaw, float cossinpitch, int pass, boolean inner) {
        ParticleRenderDispatcher.lightningCount++;
        float boltage = bolt.particleAge < 0 ? 0 : (float) bolt.particleAge / (float) bolt.particleMaxAge;
        float mainalpha = 1;
        if (pass == 0)
            mainalpha = (1 - boltage) * 0.4F;
        else
            mainalpha = 1 - boltage * 0.5F;

        int expandTime = (int) (bolt.length * bolt.speed);

        int renderstart = (int) ((expandTime / 2 - bolt.particleMaxAge + bolt.particleAge)
                / (float) (expandTime / 2) * bolt.numsegments0);
        int renderend = (int) ((bolt.particleAge + expandTime) / (float) expandTime * bolt.numsegments0);

        for (Segment rendersegment : bolt.segments) {
            if (rendersegment.segmentno < renderstart || rendersegment.segmentno > renderend)
                continue;

            Vector3 playervec = getRelativeViewVector(rendersegment.startpoint.point).multiply(-1);

            double width = 0.025F * (playervec.mag() / 5 + 1) * (1 + rendersegment.light) * 0.5F;

            Vector3 diff1 = playervec.copy().crossProduct(rendersegment.prevdiff).normalize()
                    .multiply(width / rendersegment.sinprev);
            Vector3 diff2 = playervec.copy().crossProduct(rendersegment.nextdiff).normalize()
                    .multiply(width / rendersegment.sinnext);

            Vector3 startvec = rendersegment.startpoint.point;
            Vector3 endvec = rendersegment.endpoint.point;

            int color = inner ? bolt.colorInner : bolt.colorOuter;
            tessellator.setColorRGBA_I(color,
                    (int) (mainalpha * rendersegment.light * new Color(color).getAlpha()));

            tessellator.addVertexWithUV(endvec.x - diff2.x, endvec.y - diff2.y, endvec.z - diff2.z, 0.5, 0);
            tessellator.addVertexWithUV(startvec.x - diff1.x, startvec.y - diff1.y, startvec.z - diff1.z, 0.5, 0);
            tessellator.addVertexWithUV(startvec.x + diff1.x, startvec.y + diff1.y, startvec.z + diff1.z, 0.5, 1);
            tessellator.addVertexWithUV(endvec.x + diff2.x, endvec.y + diff2.y, endvec.z + diff2.z, 0.5, 1);

            if (rendersegment.next == null) {
                Vector3 roundend = rendersegment.endpoint.point.copy()
                        .add(rendersegment.diff.copy().normalize().multiply(width));

                tessellator.addVertexWithUV(roundend.x - diff2.x, roundend.y - diff2.y, roundend.z - diff2.z, 0, 0);
                tessellator.addVertexWithUV(endvec.x - diff2.x, endvec.y - diff2.y, endvec.z - diff2.z, 0.5, 0);
                tessellator.addVertexWithUV(endvec.x + diff2.x, endvec.y + diff2.y, endvec.z + diff2.z, 0.5, 1);
                tessellator.addVertexWithUV(roundend.x + diff2.x, roundend.y + diff2.y, roundend.z + diff2.z, 0, 1);
            }

            if (rendersegment.prev == null) {
                Vector3 roundend = rendersegment.startpoint.point.copy()
                        .subtract(rendersegment.diff.copy().normalize().multiply(width));

                tessellator.addVertexWithUV(startvec.x - diff1.x, startvec.y - diff1.y, startvec.z - diff1.z, 0.5,
                        0);
                tessellator.addVertexWithUV(roundend.x - diff1.x, roundend.y - diff1.y, roundend.z - diff1.z, 0, 0);
                tessellator.addVertexWithUV(roundend.x + diff1.x, roundend.y + diff1.y, roundend.z + diff1.z, 0, 1);
                tessellator.addVertexWithUV(startvec.x + diff1.x, startvec.y + diff1.y, startvec.z + diff1.z, 0.5,
                        1);
            }
        }
    }

    public static class LightningBolt {

        public ArrayList<Segment> segments = new ArrayList<Segment>();
        public Vector3 start;
        public Vector3 end;
        ChunkCoordinates target;
        HashMap<Integer, Integer> splitparents = new HashMap<Integer, Integer>();

        public double length;
        public int numsegments0;
        private int numsplits;
        private boolean finalized;
        private Random rand;
        public long seed;

        public int particleAge;
        public int particleMaxAge;
        public boolean isDead;
        private AxisAlignedBB boundingBox;

        private World world;
        private Entity source;

        public static ConcurrentLinkedQueue<LightningBolt> boltlist = new ConcurrentLinkedQueue<LightningBolt>();

        public float speed = 1.5F;
        public static final int fadetime = 20;

        public static int playerdamage = 1;
        public static int entitydamage = 1;

        public int colorOuter;
        public int colorInner;

        public class BoltPoint {

            public BoltPoint(Vector3 basepoint, Vector3 offsetvec) {
                point = basepoint.copy().add(offsetvec);
                this.basepoint = basepoint;
                this.offsetvec = offsetvec;
            }

            public Vector3 point;
            Vector3 basepoint;
            Vector3 offsetvec;
        }

        public class SegmentSorter implements Comparator<Segment> {

            @Override
            public int compare(Segment o1, Segment o2) {
                int comp = Integer.valueOf(o1.splitno).compareTo(o2.splitno);
                if (comp == 0)
                    return Integer.valueOf(o1.segmentno).compareTo(o2.segmentno);
                else
                    return comp;
            }
        }

        public class SegmentLightSorter implements Comparator<Segment> {
            @Override
            public int compare(Segment o1, Segment o2) {
                return Float.compare(o2.light, o1.light);
            }
        }

        public class Segment {
            public Segment(BoltPoint start, BoltPoint end, float light, int segmentnumber, int splitnumber) {
                startpoint = start;
                endpoint = end;
                this.light = light;
                segmentno = segmentnumber;
                splitno = splitnumber;

                calcDiff();
            }

            public Segment(Vector3 start, Vector3 end) {
                this(new BoltPoint(start, new Vector3(0, 0, 0)), new BoltPoint(end, new Vector3(0, 0, 0)), 1, 0, 0);
            }

            public void calcDiff() {
                diff = endpoint.point.copy().subtract(startpoint.point);
            }

            public void calcEndDiffs() {
                if (prev != null) {
                    Vector3 prevdiffnorm = prev.diff.copy().normalize();
                    Vector3 thisdiffnorm = diff.copy().normalize();

                    prevdiff = thisdiffnorm.copy().add(prevdiffnorm).normalize();
                    sinprev = (float) Math.sin(thisdiffnorm.angle(prevdiffnorm.multiply(-1)) / 2);
                } else {
                    prevdiff = diff.copy().normalize();
                    sinprev = 1;
                }

                if (next != null) {
                    Vector3 nextdiffnorm = next.diff.copy().normalize();
                    Vector3 thisdiffnorm = diff.copy().normalize();

                    nextdiff = thisdiffnorm.add(nextdiffnorm).normalize();
                    sinnext = (float) Math.sin(thisdiffnorm.angle(nextdiffnorm.multiply(-1)) / 2);
                } else {
                    nextdiff = diff.copy().normalize();
                    sinnext = 1;
                }
            }

            @Override
            public String toString() {
                return startpoint.point.toString() + " " + endpoint.point.toString();
            }

            public BoltPoint startpoint;
            public BoltPoint endpoint;

            public Vector3 diff;

            public Segment prev;
            public Segment next;

            public Vector3 nextdiff;
            public Vector3 prevdiff;

            public float sinprev;
            public float sinnext;
            public float light;

            public int segmentno;
            public int splitno;
        }

        public LightningBolt(World world, Vector3 sourcevec, Vector3 targetvec, float ticksPerMeter, long seed,
                int colorOuter, int colorInner) {
            this.world = world;
            this.seed = seed;
            rand = new Random(seed);

            start = sourcevec;
            end = targetvec;

            speed = ticksPerMeter;

            this.colorOuter = colorOuter;
            this.colorInner = colorInner;

            numsegments0 = 1;

            length = end.copy().subtract(start).mag();
            particleMaxAge = fadetime + rand.nextInt(fadetime) - fadetime / 2;
            particleAge = -(int) (length * speed);

            boundingBox = AxisAlignedBB.getBoundingBox(0, 0, 0, 0, 0, 0);
            boundingBox.setBB(AxisAlignedBB
                    .getBoundingBox(Math.min(start.x, end.x), Math.min(start.y, end.y), Math.min(start.z, end.z),
                            Math.max(start.x, end.x), Math.max(start.y, end.y), Math.max(start.z, end.z))
                    .expand(length / 2, length / 2, length / 2));

            segments.add(new Segment(start, end));
        }

        public static Vector3 getFocalPoint(TileEntity tile) {
            return Vector3.fromTileEntityCenter(tile);
        }

        public LightningBolt(World world, Vector3 sourcevec, TileEntity target, float ticksPerMeter, long seed,
                int colorOuter, int colorInner) {
            this(world, sourcevec, getFocalPoint(target), ticksPerMeter, seed, colorOuter, colorInner);
            this.target = new ChunkCoordinates(target.xCoord, target.yCoord, target.zCoord);
        }

        public void setWrapper(Entity entity) {
            source = entity;
        }

        public void fractal(int splits, double amount, double splitchance, double splitlength, double splitangle) {
            if (finalized)
                return;

            ArrayList<Segment> oldsegments = segments;
            segments = new ArrayList<Segment>();

            Segment prev = null;

            for (Segment segment : oldsegments) {
                prev = segment.prev;

                Vector3 subsegment = segment.diff.copy().multiply(1F / splits);

                BoltPoint[] newpoints = new BoltPoint[splits + 1];

                Vector3 startpoint = segment.startpoint.point;
                newpoints[0] = segment.startpoint;
                newpoints[splits] = segment.endpoint;

                for (int i = 1; i < splits; i++) {
                    Vector3 randoff = segment.diff.copy().perpendicular().normalize().rotate(rand.nextFloat() * 360,
                            segment.diff);
                    randoff.multiply((rand.nextFloat() - 0.5F) * amount * 2);

                    Vector3 basepoint = startpoint.copy().add(subsegment.copy().multiply(i));

                    newpoints[i] = new BoltPoint(basepoint, randoff);
                }

                for (int i = 0; i < splits; i++) {
                    Segment next = new Segment(newpoints[i], newpoints[i + 1], segment.light,
                            segment.segmentno * splits + i, segment.splitno);
                    next.prev = prev;
                    if (prev != null)
                        prev.next = next;

                    if (i != 0 && rand.nextFloat() < splitchance) {
                        Vector3 splitrot = next.diff.copy().xCrossProduct().rotate(rand.nextFloat() * 360,
                                next.diff);
                        Vector3 diff = next.diff.copy()
                                .rotate((rand.nextFloat() * 0.66F + 0.33F) * splitangle, splitrot)
                                .multiply(splitlength);

                        numsplits++;
                        splitparents.put(numsplits, next.splitno);

                        Segment split = new Segment(newpoints[i],
                                new BoltPoint(newpoints[i + 1].basepoint,
                                        newpoints[i + 1].offsetvec.copy().add(diff)),
                                segment.light / 2F, next.segmentno, numsplits);
                        split.prev = prev;

                        segments.add(split);
                    }

                    prev = next;
                    segments.add(next);
                }

                if (segment.next != null)
                    segment.next.prev = prev;
            }

            numsegments0 *= splits;
        }

        public void defaultFractal() {
            fractal(2, length / 1.5, 0.7F, 0.7F, 45);
            fractal(2, length / 4, 0.5F, 0.8F, 50);
            fractal(2, length / 15, 0.5F, 0.9F, 55);
            fractal(2, length / 30, 0.5F, 1.0F, 60);
            fractal(2, length / 60, 0, 0, 0);
            fractal(2, length / 100, 0, 0, 0);
            fractal(2, length / 400, 0, 0, 0);
        }

        private float rayTraceResistance(Vector3 start, Vector3 end, float prevresistance) {
            MovingObjectPosition mop = world.rayTraceBlocks(start.toVec3D(), end.toVec3D());

            if (mop == null)
                return prevresistance;

            if (mop.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
                Block block = world.getBlock(mop.blockX, mop.blockY, mop.blockZ);

                if (world.isAirBlock(mop.blockX, mop.blockY, mop.blockZ))
                    return prevresistance;

                return prevresistance + block.getExplosionResistance(source) + 0.3F;
            } else
                return prevresistance;
        }

        private void calculateCollisionAndDiffs() {
            HashMap<Integer, Integer> lastactivesegment = new HashMap<Integer, Integer>();

            Collections.sort(segments, new SegmentSorter());

            int lastsplitcalc = 0;
            int lastactiveseg = 0;// unterminated
            float splitresistance = 0;

            for (Segment segment : segments) {
                if (segment.splitno > lastsplitcalc) {
                    lastactivesegment.put(lastsplitcalc, lastactiveseg);
                    lastsplitcalc = segment.splitno;
                    lastactiveseg = lastactivesegment.get(splitparents.get(segment.splitno));
                    splitresistance = lastactiveseg < segment.segmentno ? 50 : 0;
                }

                if (splitresistance >= 40 * segment.light)
                    continue;

                splitresistance = rayTraceResistance(segment.startpoint.point, segment.endpoint.point,
                        splitresistance);
                lastactiveseg = segment.segmentno;
            }
            lastactivesegment.put(lastsplitcalc, lastactiveseg);

            lastsplitcalc = 0;
            lastactiveseg = lastactivesegment.get(0);
            for (Iterator<Segment> iterator = segments.iterator(); iterator.hasNext();) {
                Segment segment = iterator.next();
                if (lastsplitcalc != segment.splitno) {
                    lastsplitcalc = segment.splitno;
                    lastactiveseg = lastactivesegment.get(segment.splitno);
                }

                if (segment.segmentno > lastactiveseg)
                    iterator.remove();
                segment.calcEndDiffs();
            }
        }

        public void finalizeBolt() {
            if (finalized)
                return;
            finalized = true;

            calculateCollisionAndDiffs();

            Collections.sort(segments, new SegmentLightSorter());

            boltlist.add(this);
        }

        public void onUpdate() {
            particleAge++;

            if (particleAge >= particleMaxAge)
                isDead = true;
        }

        // Called in ClientTickHandler
        public static void update() {
            for (Iterator<LightningBolt> iterator = boltlist.iterator(); iterator.hasNext();) {
                LightningBolt bolt = iterator.next();

                bolt.onUpdate();
                if (bolt.isDead)
                    iterator.remove();
            }
        }
    }
}