380 lines
14 KiB
Java

package com.hbm.render.entity.effect;
import java.util.Random;
import org.lwjgl.opengl.GL11;
import com.hbm.entity.effect.EntityNukeCloudSmall;
import com.hbm.entity.effect.EntityNukeCloudSmall.Cloudlet;
import com.hbm.lib.RefStrings;
import com.hbm.main.ResourceManager;
import com.hbm.render.loader.HFRWavefrontObject;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.IModelCustom;
public class RenderSmallNukeMK4 extends Render {
public static final IModelCustom mush = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/mush.obj"));
public static final IModelCustom shockwave = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/ring_roller.obj"));
public static final IModelCustom thinring = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/ring_thin.obj"));
private static final ResourceLocation cloudlet = new ResourceLocation(RefStrings.MODID + ":textures/particle/particle_base.png");
/*
* // // ////// ////// // //
* //// //// // // // //// //
* // // // ////// // // ////
* // // // // // // //
* // // // // ////// // //
*/
/**
* Look how nice and clean this is!
*/
@Override
public void doRender(Entity entity, double x, double y, double z, float f0, float interp) {
GL11.glPushMatrix();
GL11.glTranslated(x, y, z);
EntityNukeCloudSmall cloud = (EntityNukeCloudSmall)entity;
mushWrapper(cloud, interp);
cloudletWrapper(cloud, interp);
flashWrapper(cloud, interp);
GL11.glPopMatrix();
}
@Override
protected ResourceLocation getEntityTexture(Entity entity) {
return null;
}
/*
* // // ////// ////// ////// ////// ////// ////// //////
* // // // // // // // // // // // // // //
* // // // //// ////// ////// ////// //// //// //////
* //// //// // // // // // // // // // //
* // // // // // // // // ////// // // //////
*/
/**
* Wrapper for the initial flash
* Caps the rendering at 60 ticks and sets the alpha function
* @param cloud
* @param interp
*/
private void flashWrapper(EntityNukeCloudSmall cloud, float interp) {
if(cloud.age < 60) {
GL11.glPushMatrix();
//Function [0, 1] that determines the scale and intensity (inverse!) of the flash
double scale = (cloud.ticksExisted + interp) / 60D;
GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.0F);
//Euler function to slow down the scale as it progresses
//Makes it start fast and the fade-out is nice and smooth
scale = scale * Math.pow(Math.E, -scale) * 2.717391304D;
renderFlash(scale);
GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GL11.glPopMatrix();
}
}
/**
* Wrapper for the entire mush (head + stem)
* Renders the entire thing twice to allow for smooth color gradients
* @param cloud
* @param interp
*/
private void mushWrapper(EntityNukeCloudSmall cloud, float interp) {
float size = cloud.getDataWatcher().getWatchableObjectFloat(18) * 5;
GL11.glPushMatrix();
GL11.glScalef(size, size, size);
boolean balefire = cloud.getDataWatcher().getWatchableObjectByte(19) == 1;
if(balefire)
bindTexture(ResourceManager.balefire);
else
bindTexture(ResourceManager.fireball);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glDisable(GL11.GL_LIGHTING);
//Float [0, 1] for the initial solid-colored layer fade-in
float func = MathHelper.clamp_float((cloud.ticksExisted + interp) * 0.0075F, 0, 1);
//Function that determines how high the cloud has risen. The values are the results of trial and error and i forgot what they mean
double height = Math.max(20 - 30 * 20 / ((((cloud.ticksExisted + interp) * 0.5) - 60 * 0.1) + 1), 0);
if(balefire)
GL11.glColor4f(1.0F - (1.0F - 0.64F) * func, 1.0F, 1.0F - (1.0F - 0.5F) * func, 1F);
else
GL11.glColor4f(1.0F, 1.0F - (1.0F - 0.7F) * func, 1.0F - (1.0F - 0.48F) * func, 1F);
renderMushHead(cloud.ticksExisted + interp, height);
renderMushStem(cloud.ticksExisted + interp, height);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_TEXTURE_2D);
//Float [0.75, 0] That determines the occupancy of the texture layer
float texAlpha = func * 0.875F;
GL11.glColor4f(1F, 1F, 1F, texAlpha);
//Sets blend to "how you'd expect it" mode
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GL11.glEnable(GL11.GL_BLEND);
//And now we fuck with texture transformations
GL11.glMatrixMode(GL11.GL_TEXTURE);
GL11.glLoadIdentity();
GL11.glTranslated(0, -(cloud.ticksExisted + interp) * 0.035, 0);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glPushMatrix();
//It's the thing that makes glow-in-the-dark work
GL11.glPushAttrib(GL11.GL_LIGHTING_BIT);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240F, 240F);
renderMushHead(cloud.ticksExisted + interp, height);
renderMushStem(cloud.ticksExisted + interp, height);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glPopAttrib();
GL11.glPopMatrix();
//Clean this up otherwise the game becomes one-dimensional
GL11.glMatrixMode(GL11.GL_TEXTURE);
GL11.glLoadIdentity();
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glDisable(GL11.GL_BLEND);
GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glPopMatrix();
}
/**
* Adds all cloudlets to the tessellator and then draws them
* @param cloud
* @param interp
*/
private void cloudletWrapper(EntityNukeCloudSmall cloud, float interp) {
GL11.glPushMatrix();
GL11.glEnable(GL11.GL_BLEND);
//To prevent particles cutting off before fully fading out
GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.01F);
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
RenderHelper.disableStandardItemLighting();
GL11.glDisable(GL11.GL_ALPHA_TEST);
GL11.glDepthMask(false);
bindTexture(cloudlet);
Tessellator tess = Tessellator.instance;
tess.startDrawingQuads();
for(Cloudlet cloudlet : cloud.cloudlets) {
float scale = cloud.age + interp - cloudlet.age;
tessellateCloudlet(tess, cloudlet.posX, cloudlet.posY - cloud.posY + 2, cloudlet.posZ, scale, cloud.getDataWatcher().getWatchableObjectByte(19));
}
/*Random rand = new Random(cloud.getEntityId());
float size = cloud.getDataWatcher().getWatchableObjectFloat(18);
for(int i = 0; i < 300 * size; i++) {
float scale = size * 10;
Vec3 vec = Vec3.createVectorHelper(rand.nextGaussian() * scale, 0, rand.nextGaussian() * scale);
tessellateCloudlet(tess, vec.xCoord, (scale - vec.lengthVector()) * rand.nextDouble() * 0.5, vec.zCoord - 10, (float)(cloud.age * cloud.cloudletLife) / cloud.maxAge, cloud.getDataWatcher().getWatchableObjectByte(19));
}*/
tess.draw();
GL11.glDepthMask(true);
GL11.glEnable(GL11.GL_ALPHA_TEST);
RenderHelper.enableStandardItemLighting();
GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GL11.glDisable(GL11.GL_BLEND);
GL11.glPopMatrix();
}
/*
* ////// ////// // // //// ////// ////// ////// ////// //////
* // // // //// // // // // // // // // // //
* //// //// // //// // // //// //// //// //// //////
* // // // // // // // // // // // // // //
* // // ////// // // //// ////// // // ////// // // //////
*/
/**
* Once again the recycled ender dragon death animation
* It worked so well the last 14 times, let's go for 15
* @param intensity Double [0, 1] that determines scale and alpha
*/
private void renderFlash(double intensity) {
GL11.glScalef(0.2F, 0.2F, 0.2F);
double inverse = 1.0D - intensity;
Tessellator tessellator = Tessellator.instance;
RenderHelper.disableStandardItemLighting();
Random random = new Random(432L);
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glShadeModel(GL11.GL_SMOOTH);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
GL11.glDisable(GL11.GL_ALPHA_TEST);
GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glDepthMask(false);
GL11.glPushMatrix();
float scale = 100;
for(int i = 0; i < 300; i++) {
GL11.glRotatef(random.nextFloat() * 360.0F, 1.0F, 0.0F, 0.0F);
GL11.glRotatef(random.nextFloat() * 360.0F, 0.0F, 1.0F, 0.0F);
GL11.glRotatef(random.nextFloat() * 360.0F, 0.0F, 0.0F, 1.0F);
GL11.glRotatef(random.nextFloat() * 360.0F, 1.0F, 0.0F, 0.0F);
GL11.glRotatef(random.nextFloat() * 360.0F, 0.0F, 1.0F, 0.0F);
float vert1 = (random.nextFloat() * 20.0F + 5.0F + 1 * 10.0F) * (float)(intensity * scale);
float vert2 = (random.nextFloat() * 2.0F + 1.0F + 1 * 2.0F) * (float)(intensity * scale);
tessellator.startDrawing(6);
tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F, (float) inverse);
tessellator.addVertex(0.0D, 0.0D, 0.0D);
tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F, 0.0F);
tessellator.addVertex(-0.866D * vert2, vert1, -0.5F * vert2);
tessellator.addVertex(0.866D * vert2, vert1, -0.5F * vert2);
tessellator.addVertex(0.0D, vert1, 1.0F * vert2);
tessellator.addVertex(-0.866D * vert2, vert1, -0.5F * vert2);
tessellator.draw();
}
GL11.glPopMatrix();
GL11.glDepthMask(true);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glDisable(GL11.GL_BLEND);
GL11.glShadeModel(GL11.GL_FLAT);
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_ALPHA_TEST);
RenderHelper.enableStandardItemLighting();
}
/**
* Render call for the mush head model
* Includes offset and smoothing
* Also scales the fireball along XZ
* @param progress Lifetime + interpolation number
* @param height The current animation offset
*/
private void renderMushHead(float progress, double height) {
GL11.glPushMatrix();
double expansion = 100;
double width = Math.min(progress, expansion) / expansion * 0.3 + 0.7;
GL11.glTranslated(0, -26 + height, 0);
GL11.glScaled(width, 1, width);
GL11.glShadeModel(GL11.GL_SMOOTH);
GL11.glDisable(GL11.GL_ALPHA_TEST);
mush.renderPart("Ball");
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glShadeModel(GL11.GL_FLAT);
GL11.glPopMatrix();
}
/**
* Render call for the mush stem model
* Includes offset and smoothing
* @param progress Lifetime + interpolation number
* @param height The current animation offset
*/
private void renderMushStem(float progress, double height) {
GL11.glPushMatrix();
GL11.glTranslated(0, -26 + height, 0);
GL11.glShadeModel(GL11.GL_SMOOTH);
GL11.glDisable(GL11.GL_ALPHA_TEST);
mush.renderPart("Stem");
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glShadeModel(GL11.GL_FLAT);
GL11.glPopMatrix();
}
/**
* Adds one cloudlet (one face) to the tessellator.
* Rotation is done using ActiveRenderInfo, which I'd assume runs on magic
* But hey, if it works for particles, why not here too?
* @param tess
* @param posX
* @param posY
* @param posZ
* @param age The mush' age when the cloudlet was created
* @param type DataWatcher byte #19 which differentiates between different mush types
*/
private void tessellateCloudlet(Tessellator tess, double posX, double posY, double posZ, float age, int type) {
float alpha = 1F - Math.max(age / (float)(EntityNukeCloudSmall.cloudletLife), 0F);
float alphaorig = alpha;
float scale = 5F * (alpha * 0.5F + 0.5F);
if(age < 3)
alpha = age * 0.333F;
float f1 = ActiveRenderInfo.rotationX;
float f2 = ActiveRenderInfo.rotationZ;
float f3 = ActiveRenderInfo.rotationYZ;
float f4 = ActiveRenderInfo.rotationXY;
float f5 = ActiveRenderInfo.rotationXZ;
Random rand = new Random((long) ((posX * 5 + posY * 25 + posZ * 125) * 1000D));
float brightness = rand.nextFloat() * 0.25F + 0.25F;
if(type == 1) {
tess.setColorRGBA_F(0.25F * alphaorig, alphaorig - brightness * 0.5F, 0.25F * alphaorig, alpha);
} else {
tess.setColorRGBA_F(brightness, brightness, brightness, alpha);
}
tess.addVertexWithUV((double)(posX - f1 * scale - f3 * scale), (double)(posY - f5 * scale), (double)(posZ - f2 * scale - f4 * scale), 1, 1);
tess.addVertexWithUV((double)(posX - f1 * scale + f3 * scale), (double)(posY + f5 * scale), (double)(posZ - f2 * scale + f4 * scale), 1, 0);
tess.addVertexWithUV((double)(posX + f1 * scale + f3 * scale), (double)(posY + f5 * scale), (double)(posZ + f2 * scale + f4 * scale), 0, 0);
tess.addVertexWithUV((double)(posX + f1 * scale - f3 * scale), (double)(posY - f5 * scale), (double)(posZ + f2 * scale - f4 * scale), 0, 1);
}
}