Java tutorial
/** * 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(); } } } }