package mekanism.induction.client; import static org.lwjgl.opengl.GL11.GL_BLEND; import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA; import static org.lwjgl.opengl.GL11.GL_SMOOTH; import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA; import static org.lwjgl.opengl.GL11.glBlendFunc; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glShadeModel; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import mekanism.induction.common.MekanismInduction; import net.minecraft.client.Minecraft; import net.minecraft.client.particle.EntityFX; import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import org.lwjgl.opengl.GL11; import universalelectricity.core.vector.Vector3; import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; /** * Electric shock Fxs. * * @author Calclavia * */ @SideOnly(Side.CLIENT) public class FXElectricBolt extends EntityFX { public static final ResourceLocation TEXTURE = new ResourceLocation(MekanismInduction.DOMAIN, MekanismInduction.MODEL_TEXTURE_DIRECTORY + "fadedSphere.png"); public static final ResourceLocation PARTICLE_RESOURCE = new ResourceLocation("textures/particle/particles.png"); /** The width of the electrical bolt. */ private float boltWidth; /** The maximum length of the bolt */ public double boltLength; /** Electric Bolt's start and end positions; */ private BoltPoint start; private BoltPoint end; /** An array of the segments of the bolt. */ private List segments = new ArrayList(); private final Map parentIDMap = new HashMap(); /** Determines how complex the bolt is. */ public float complexity; public int segmentCount; private int maxSplitID; private Random rand; public FXElectricBolt(World world, Vector3 startVec, Vector3 targetVec, boolean doSplits) { super(world, startVec.x, startVec.y, startVec.z); this.rand = new Random(); this.start = new BoltPoint(startVec); this.end = new BoltPoint(targetVec); if (this.end.y == Double.POSITIVE_INFINITY) { this.end.y = Minecraft.getMinecraft().thePlayer.posY + 30; } /** By default, we do an electrical color */ this.segmentCount = 1; this.particleMaxAge = (3 + this.rand.nextInt(3) - 1); this.complexity = 2f; this.boltWidth = 0.05f; this.boltLength = this.start.distance(this.end); this.setUp(doSplits); } public FXElectricBolt(World world, Vector3 startVec, Vector3 targetVec) { this(world, startVec, targetVec, true); } /** * Calculate all required segments of the entire bolt. */ private void setUp(boolean doSplits) { this.segments.add(new BoltSegment(this.start, this.end)); this.recalculate(); if (doSplits) { double offsetRatio = this.boltLength * this.complexity; this.split(2, offsetRatio / 10, 0.7f, 0.1f, 20 / 2); this.split(2, offsetRatio / 15, 0.5f, 0.1f, 25 / 2); this.split(2, offsetRatio / 25, 0.5f, 0.1f, 28 / 2); this.split(2, offsetRatio / 38, 0.5f, 0.1f, 30 / 2); this.split(2, offsetRatio / 55, 0, 0, 0); this.split(2, offsetRatio / 70, 0, 0, 0); this.recalculate(); Collections.sort(this.segments, new Comparator() { public int compare(BoltSegment bolt1, BoltSegment bolt2) { return Float.compare(bolt2.alpha, bolt1.alpha); } @Override public int compare(Object obj1, Object obj2) { return compare((BoltSegment) obj1, (BoltSegment) obj2); } }); } } public FXElectricBolt setColor(float r, float g, float b) { this.particleRed = r + (this.rand.nextFloat() * 0.1f) - 0.1f; this.particleGreen = g + (this.rand.nextFloat() * 0.1f) - 0.1f; this.particleBlue = b + (this.rand.nextFloat() * 0.1f) - 0.1f; return this; } /** * Slits a large segment into multiple smaller ones. * * @param splitAmount - The amount of splits * @param offset - The multiplier scale for the offset. * @param splitChance - The chance of creating a split. * @param splitLength - The length of each split. * @param splitAngle - The angle of the split. */ public void split(int splitAmount, double offset, float splitChance, float splitLength, float splitAngle) { /** Temporarily store old segments in a new array */ List oldSegments = this.segments; this.segments = new ArrayList(); /** Previous segment */ BoltSegment prev = null; for (BoltSegment segment : oldSegments) { prev = segment.prev; /** Length of each subsegment */ Vector3 subSegment = segment.difference.clone().scale(1.0F / splitAmount); /** * Creates an array of new bolt points. The first and last points of the bolts are the * respected start and end points of the current segment. */ BoltPoint[] newPoints = new BoltPoint[splitAmount + 1]; Vector3 startPoint = segment.start; newPoints[0] = segment.start; newPoints[splitAmount] = segment.end; /** * Create bolt points. */ for (int i = 1; i < splitAmount; i++) { Vector3 newOffset = segment.difference.getPerpendicular().rotate(this.rand.nextFloat() * 360, segment.difference).scale((this.rand.nextFloat() - 0.5F) * offset); Vector3 basePoint = startPoint.clone().translate(subSegment.clone().scale(i)); newPoints[i] = new BoltPoint(basePoint, newOffset); } for (int i = 0; i < splitAmount; i++) { BoltSegment next = new BoltSegment(newPoints[i], newPoints[(i + 1)], segment.alpha, segment.id * splitAmount + i, segment.splitID); next.prev = prev; if (prev != null) { prev.next = next; } if ((i != 0) && (this.rand.nextFloat() < splitChance)) { Vector3 splitrot = next.difference.xCrossProduct().rotate(this.rand.nextFloat() * 360, next.difference); Vector3 diff = next.difference.clone().rotate((this.rand.nextFloat() * 0.66F + 0.33F) * splitAngle, splitrot).scale(splitLength); this.maxSplitID += 1; this.parentIDMap.put(this.maxSplitID, next.splitID); BoltSegment split = new BoltSegment(newPoints[i], new BoltPoint(newPoints[(i + 1)].base, newPoints[(i + 1)].offset.clone().translate(diff)), segment.alpha / 2f, next.id, this.maxSplitID); split.prev = prev; this.segments.add(split); } prev = next; this.segments.add(next); } if (segment.next != null) { segment.next.prev = prev; } } this.segmentCount *= splitAmount; } private void recalculate() { HashMap lastActiveSegment = new HashMap(); Collections.sort(this.segments, new Comparator() { public int compare(BoltSegment o1, BoltSegment o2) { int comp = Integer.valueOf(o1.splitID).compareTo(Integer.valueOf(o2.splitID)); if (comp == 0) { return Integer.valueOf(o1.id).compareTo(Integer.valueOf(o2.id)); } return comp; } @Override public int compare(Object obj, Object obj1) { return compare((BoltSegment) obj, (BoltSegment) obj1); } }); int lastSplitCalc = 0; int lastActiveSeg = 0; for (BoltSegment segment : this.segments) { if (segment.splitID > lastSplitCalc) { lastActiveSegment.put(lastSplitCalc, lastActiveSeg); lastSplitCalc = segment.splitID; lastActiveSeg = lastActiveSegment.get(this.parentIDMap.get(segment.splitID)).intValue(); } lastActiveSeg = segment.id; } lastActiveSegment.put(lastSplitCalc, lastActiveSeg); lastSplitCalc = 0; lastActiveSeg = lastActiveSegment.get(0).intValue(); BoltSegment segment; for (Iterator iterator = this.segments.iterator(); iterator.hasNext(); segment.recalculate()) { segment = iterator.next(); if (lastSplitCalc != segment.splitID) { lastSplitCalc = segment.splitID; lastActiveSeg = lastActiveSegment.get(segment.splitID); } if (segment.id > lastActiveSeg) { iterator.remove(); } } } @Override public void onUpdate() { this.prevPosX = this.posX; this.prevPosY = this.posY; this.prevPosZ = this.posZ; if (this.particleAge++ >= this.particleMaxAge) { this.setDead(); } } @Override public void renderParticle(Tessellator tessellator, float partialframe, float cosYaw, float cosPitch, float sinYaw, float sinSinPitch, float cosSinPitch) { EntityPlayer player = Minecraft.getMinecraft().thePlayer; tessellator.draw(); GL11.glPushMatrix(); GL11.glDepthMask(false); GL11.glEnable(3042); glShadeModel(GL_SMOOTH); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); FMLClientHandler.instance().getClient().renderEngine.bindTexture(TEXTURE); /** * Render the actual bolts. */ tessellator.startDrawingQuads(); tessellator.setBrightness(15728880); Vector3 playerVector = new Vector3(sinYaw * -cosPitch, -cosSinPitch / cosYaw, cosYaw * cosPitch); int renderlength = (int) ((this.particleAge + partialframe + (int) (this.boltLength * 3.0F)) / (int) (this.boltLength * 3.0F) * this.segmentCount); for (BoltSegment segment : this.segments) { if (segment != null && segment.id <= renderlength) { double renderWidth = this.boltWidth * ((new Vector3(player).distance(segment.start) / 5f + 1f) * (1 + segment.alpha) * 0.5f); renderWidth = Math.min(this.boltWidth, Math.max(renderWidth, 0)); if (segment.difference.getMagnitude() > 0 && segment.difference.getMagnitude() != Double.NaN && segment.difference.getMagnitude() != Double.POSITIVE_INFINITY && renderWidth > 0 && renderWidth != Double.NaN && renderWidth != Double.POSITIVE_INFINITY) { Vector3 diffPrev = playerVector.crossProduct(segment.prevDiff).scale(renderWidth / segment.sinPrev); Vector3 diffNext = playerVector.crossProduct(segment.nextDiff).scale(renderWidth / segment.sinNext); Vector3 startVec = segment.start; Vector3 endVec = segment.end; float rx1 = (float) (startVec.x - interpPosX); float ry1 = (float) (startVec.y - interpPosY); float rz1 = (float) (startVec.z - interpPosZ); float rx2 = (float) (endVec.x - interpPosX); float ry2 = (float) (endVec.y - interpPosY); float rz2 = (float) (endVec.z - interpPosZ); tessellator.setColorRGBA_F(this.particleRed, this.particleGreen, this.particleBlue, (1.0F - (this.particleAge >= 0 ? ((float) this.particleAge / (float) this.particleMaxAge) : 0.0F) * 0.6f) * segment.alpha); tessellator.addVertexWithUV(rx2 - diffNext.x, ry2 - diffNext.y, rz2 - diffNext.z, 0.5D, 0.0D); tessellator.addVertexWithUV(rx1 - diffPrev.x, ry1 - diffPrev.y, rz1 - diffPrev.z, 0.5D, 0.0D); tessellator.addVertexWithUV(rx1 + diffPrev.x, ry1 + diffPrev.y, rz1 + diffPrev.z, 0.5D, 1.0D); tessellator.addVertexWithUV(rx2 + diffNext.x, ry2 + diffNext.y, rz2 + diffNext.z, 0.5D, 1.0D); /** * Render the bolts balls. */ if (segment.next == null) { Vector3 roundEnd = segment.end.clone().translate(segment.difference.clone().normalize().scale(renderWidth)); float rx3 = (float) (roundEnd.x - interpPosX); float ry3 = (float) (roundEnd.y - interpPosY); float rz3 = (float) (roundEnd.z - interpPosZ); tessellator.addVertexWithUV(rx3 - diffNext.x, ry3 - diffNext.y, rz3 - diffNext.z, 0.0D, 0.0D); tessellator.addVertexWithUV(rx2 - diffNext.x, ry2 - diffNext.y, rz2 - diffNext.z, 0.5D, 0.0D); tessellator.addVertexWithUV(rx2 + diffNext.x, ry2 + diffNext.y, rz2 + diffNext.z, 0.5D, 1.0D); tessellator.addVertexWithUV(rx3 + diffNext.x, ry3 + diffNext.y, rz3 + diffNext.z, 0.0D, 1.0D); } if (segment.prev == null) { Vector3 roundEnd = segment.start.clone().difference(segment.difference.clone().normalize().scale(renderWidth)); float rx3 = (float) (roundEnd.x - interpPosX); float ry3 = (float) (roundEnd.y - interpPosY); float rz3 = (float) (roundEnd.z - interpPosZ); tessellator.addVertexWithUV(rx1 - diffPrev.x, ry1 - diffPrev.y, rz1 - diffPrev.z, 0.5D, 0.0D); tessellator.addVertexWithUV(rx3 - diffPrev.x, ry3 - diffPrev.y, rz3 - diffPrev.z, 0.0D, 0.0D); tessellator.addVertexWithUV(rx3 + diffPrev.x, ry3 + diffPrev.y, rz3 + diffPrev.z, 0.0D, 1.0D); tessellator.addVertexWithUV(rx1 + diffPrev.x, ry1 + diffPrev.y, rz1 + diffPrev.z, 0.5D, 1.0D); } } } } tessellator.draw(); GL11.glDisable(3042); GL11.glDepthMask(true); GL11.glPopMatrix(); FMLClientHandler.instance().getClient().renderEngine.bindTexture(PARTICLE_RESOURCE); tessellator.startDrawingQuads(); } private class BoltPoint extends Vector3 { public Vector3 base; public Vector3 offset; public BoltPoint(Vector3 base, Vector3 offset) { super(base.clone().translate(offset)); this.base = base; this.offset = offset; } public BoltPoint(Vector3 base) { this(base, new Vector3()); } } private class BoltSegment { public BoltPoint start; public BoltPoint end; public BoltSegment prev; public BoltSegment next; public float alpha; public int id; public int splitID; /** * All differences are cached. */ public Vector3 difference; public Vector3 prevDiff; public Vector3 nextDiff; public double sinPrev; public double sinNext; public BoltSegment(BoltPoint start, BoltPoint end) { this(start, end, 1, 0, 0); } public BoltSegment(BoltPoint start, BoltPoint end, float alpha, int id, int splitID) { this.start = start; this.end = end; this.alpha = alpha; this.id = id; this.splitID = splitID; this.difference = this.end.clone().difference(this.start); } public void recalculate() { if (this.prev != null) { Vector3 prevDiffNorm = this.prev.difference.clone().normalize(); Vector3 diffNorm = this.difference.clone().normalize(); this.prevDiff = diffNorm.clone().translate(prevDiffNorm).normalize(); this.sinPrev = Math.sin(diffNorm.anglePreNorm(prevDiffNorm.clone().scale(-1)) / 2); } else { this.prevDiff = this.difference.clone().normalize(); this.sinPrev = 1; } if (this.next != null) { Vector3 nextDiffNorm = this.next.difference.clone().normalize(); Vector3 diffNorm = this.difference.clone().normalize(); this.nextDiff = diffNorm.clone().translate(nextDiffNorm).normalize(); this.sinNext = Math.sin(diffNorm.anglePreNorm(nextDiffNorm.clone().scale(-1)) / 2); } else { this.nextDiff = this.difference.clone().normalize(); this.sinNext = 1; } } } }