package vazkii.botania.client.fx; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Random; import gnu.trove.map.hash.TIntIntHashMap; import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.particle.Particle; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.VertexBuffer; import net.minecraft.entity.Entity; import net.minecraft.util.math.RayTraceResult; import net.minecraft.world.World; import vazkii.botania.client.core.handler.LightningHandler; import vazkii.botania.common.core.helper.Vector3; // Originally taken with permission from WRCBE - heavily modified public class FXLightning extends Particle { private static final int fadetime = 20; private final TIntIntHashMap splitParents = new TIntIntHashMap(); private final double length; private final Random rand; private final int colorOuter; private final int colorInner; private List<FXLightningSegment> segments = new ArrayList<>(); private int segmentCount = 1; private int splitCount; private float speed = 1.5F; public FXLightning(World world, Vector3 sourcevec, Vector3 targetvec, float ticksPerMeter, long seed, int colorOuter, int colorInner) { super(world, sourcevec.x, sourcevec.y, sourcevec.z); rand = new Random(seed); speed = ticksPerMeter; this.colorOuter = colorOuter; this.colorInner = colorInner; length = targetvec.subtract(sourcevec).mag(); particleMaxAge = fadetime + rand.nextInt(fadetime) - fadetime / 2; particleAge = -(int) (length * speed); segments.add(new FXLightningSegment(sourcevec, targetvec)); 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); calculateCollisionAndDiffs(); Collections.sort(segments, (o1, o2) -> Float.compare(o2.light, o1.light)); } @Override public void renderParticle(VertexBuffer wr, Entity entity, float partialTicks, float rotX, float rotZ, float rotYZ, float rotXY, float rotXZ) { LightningHandler.queuedLightningBolts.offer(this); } public void renderBolt(int pass, boolean inner) { ParticleRenderDispatcher.lightningCount++; VertexBuffer wr = Tessellator.getInstance().getBuffer(); float boltAge = particleAge < 0 ? 0 : (float) particleAge / (float) particleMaxAge; float mainAlpha; if(pass == 0) mainAlpha = (1 - boltAge) * 0.4F; else mainAlpha = 1 - boltAge * 0.5F; int expandTime = (int) (length * speed); int renderstart = (int) ((expandTime / 2 - particleMaxAge + particleAge) / (float) (expandTime / 2) * segmentCount); int renderend = (int) ((particleAge + expandTime) / (float) expandTime * segmentCount); for(FXLightningSegment rendersegment : 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.crossProduct(rendersegment.prevDiff).normalize().multiply(width / rendersegment.sinPrev); Vector3 diff2 = playerVec.crossProduct(rendersegment.nextDiff).normalize().multiply(width / rendersegment.sinNext); Vector3 startvec = rendersegment.startPoint.point; Vector3 endvec = rendersegment.endPoint.point; int color = inner ? colorInner : colorOuter; int r = (color & 0xFF0000) >> 16; int g = (color & 0xFF00) >> 8; int b = color & 0xFF; int a = (int) (mainAlpha * rendersegment.light * 0xFF); wr.pos(endvec.x - diff2.x, endvec.y - diff2.y, endvec.z - diff2.z).tex(0.5, 0).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(startvec.x - diff1.x, startvec.y - diff1.y, startvec.z - diff1.z).tex(0.5, 0).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(startvec.x + diff1.x, startvec.y + diff1.y, startvec.z + diff1.z).tex(0.5, 1).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(endvec.x + diff2.x, endvec.y + diff2.y, endvec.z + diff2.z).tex(0.5, 1).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); if(rendersegment.next == null) { Vector3 roundend = rendersegment.endPoint.point.add(rendersegment.diff.normalize().multiply(width)); wr.pos(roundend.x - diff2.x, roundend.y - diff2.y, roundend.z - diff2.z).tex(0, 0).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(endvec.x - diff2.x, endvec.y - diff2.y, endvec.z - diff2.z).tex(0.5, 0).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(endvec.x + diff2.x, endvec.y + diff2.y, endvec.z + diff2.z).tex(0.5, 1).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(roundend.x + diff2.x, roundend.y + diff2.y, roundend.z + diff2.z).tex(0, 1).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); } if(rendersegment.prev == null) { Vector3 roundend = rendersegment.startPoint.point.subtract(rendersegment.diff.normalize().multiply(width)); wr.pos(startvec.x - diff1.x, startvec.y - diff1.y, startvec.z - diff1.z).tex(0.5, 0).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(roundend.x - diff1.x, roundend.y - diff1.y, roundend.z - diff1.z).tex(0, 0).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(roundend.x + diff1.x, roundend.y + diff1.y, roundend.z + diff1.z).tex(0, 1).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); wr.pos(startvec.x + diff1.x, startvec.y + diff1.y, startvec.z + diff1.z).tex(0.5, 1).lightmap(0xF0, 0xF0).color(r, g, b, a).endVertex(); } } } private void fractal(int splits, double amount, double splitChance, double splitLength, double splitAngle) { List<FXLightningSegment> oldSegments = segments; segments = new ArrayList<>(); FXLightningSegment prev; for(FXLightningSegment segment : oldSegments) { prev = segment.prev; Vector3 subsegment = segment.diff.multiply(1F / splits); FXLightningBoltPoint[] newpoints = new FXLightningBoltPoint[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.perpendicular().normalize().rotate(rand.nextFloat() * 360, segment.diff); randoff = randoff.multiply((rand.nextFloat() - 0.5F) * amount * 2); Vector3 basepoint = startpoint.add(subsegment.multiply(i)); newpoints[i] = new FXLightningBoltPoint(basepoint, randoff); } for(int i = 0; i < splits; i++) { FXLightningSegment next = new FXLightningSegment(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.xCrossProduct().rotate(rand.nextFloat() * 360, next.diff); Vector3 diff = next.diff.rotate((rand.nextFloat() * 0.66F + 0.33F) * splitAngle, splitrot).multiply(splitLength); splitCount++; splitParents.put(splitCount, next.splitNo); FXLightningSegment split = new FXLightningSegment(newpoints[i], new FXLightningBoltPoint(newpoints[i + 1].basepoint, newpoints[i + 1].offsetvec.add(diff)), segment.light / 2F, next.segmentNo, splitCount); split.prev = prev; segments.add(split); } prev = next; segments.add(next); } if(segment.next != null) segment.next.prev = prev; } segmentCount *= splits; } private float rayTraceResistance(Vector3 start, Vector3 end, float prevresistance) { RayTraceResult mop = world.rayTraceBlocks(start.toVec3D(), end.toVec3D()); if(mop == null) return prevresistance; if(mop.typeOfHit == RayTraceResult.Type.BLOCK) { Block block = world.getBlockState(mop.getBlockPos()).getBlock(); if(world.isAirBlock(mop.getBlockPos())) return prevresistance; return prevresistance + block.getExplosionResistance(null) + 0.3F; } else return prevresistance; } private void calculateCollisionAndDiffs() { TIntIntHashMap lastactivesegment = new TIntIntHashMap(); Collections.sort(segments, (o1, o2) -> { int comp = Integer.valueOf(o1.splitNo).compareTo(o2.splitNo); if(comp == 0) return Integer.valueOf(o1.segmentNo).compareTo(o2.segmentNo); else return comp; }); int lastSplitCalc = 0; int lastActiveSegment = 0;// unterminated float splitResistance = 0; for(FXLightningSegment segment : segments) { if(segment.splitNo > lastSplitCalc) { lastactivesegment.put(lastSplitCalc, lastActiveSegment); lastSplitCalc = segment.splitNo; lastActiveSegment = lastactivesegment.get(splitParents.get(segment.splitNo)); splitResistance = lastActiveSegment < segment.segmentNo ? 50 : 0; } if(splitResistance >= 40 * segment.light) continue; splitResistance = rayTraceResistance(segment.startPoint.point, segment.endPoint.point, splitResistance); lastActiveSegment = segment.segmentNo; } lastactivesegment.put(lastSplitCalc, lastActiveSegment); lastSplitCalc = 0; lastActiveSegment = lastactivesegment.get(0); for(Iterator<FXLightningSegment> iterator = segments.iterator(); iterator.hasNext();) { FXLightningSegment segment = iterator.next(); if(lastSplitCalc != segment.splitNo) { lastSplitCalc = segment.splitNo; lastActiveSegment = lastactivesegment.get(segment.splitNo); } if(segment.segmentNo > lastActiveSegment) iterator.remove(); segment.calcEndDiffs(); } } private static Vector3 getRelativeViewVector(Vector3 pos) { Entity renderEntity = Minecraft.getMinecraft().getRenderViewEntity(); return new Vector3((float) renderEntity.posX - pos.x, (float) renderEntity.posY + renderEntity.getEyeHeight() - pos.y, (float) renderEntity.posZ - pos.z); } }