Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/client/renderer/RenderAbilityLaser.java
T

331 lines
12 KiB
Java
Raw Normal View History

2026-01-25 20:39:01 -05:00
package kamkeel.npcs.client.renderer;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.client.renderer.lightning.AttachedLightningRenderer;
2026-01-25 20:39:01 -05:00
import kamkeel.npcs.entity.EntityAbilityLaser;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.Entity;
import org.lwjgl.opengl.GL11;
import java.util.HashMap;
import java.util.Map;
2026-01-25 20:39:01 -05:00
/**
* Renders the AbilityLaser entity as a rectangular beam with clear inner/outer colors.
2026-02-18 05:13:51 -05:00
* <p>
2026-01-25 20:39:01 -05:00
* Design inspired by LouisXIV's energy rendering system.
*/
@SideOnly(Side.CLIENT)
public class RenderAbilityLaser extends RenderEnergyAbility {
2026-01-25 20:39:01 -05:00
private final Map<Integer, AttachedLightningRenderer.LightningState> tipLightningStates = new HashMap<>();
2026-01-25 20:39:01 -05:00
@Override
public void doRender(Entity entity, double x, double y, double z, float yaw, float partialTicks) {
2026-02-22 15:39:24 -05:00
if (shouldSkipInitialActiveRender(entity)) {
return;
}
2026-01-25 20:39:01 -05:00
EntityAbilityLaser laser = (EntityAbilityLaser) entity;
setupRenderState();
if (laser.isCharging()) {
renderChargingOrb(laser, x, y, z, partialTicks);
restoreRenderState();
return;
}
2026-01-25 20:39:01 -05:00
// Get laser parameters
double startX = laser.getStartX();
double startY = laser.getStartY();
double startZ = laser.getStartZ();
double endX = laser.getEndX();
double endY = laser.getEndY();
double endZ = laser.getEndZ();
float width = laser.getLaserWidth();
float alpha = laser.getLingerAlpha();
// Calculate render offset (entity position is used as reference)
double offsetX = x - laser.posX;
double offsetY = y - laser.posY;
double offsetZ = z - laser.posZ;
// Render start relative to camera
double renderStartX = startX + offsetX;
double renderStartY = startY + offsetY;
double renderStartZ = startZ + offsetZ;
double renderEndX = endX + offsetX;
double renderEndY = endY + offsetY;
double renderEndZ = endZ + offsetZ;
// Calculate perpendicular vectors for the beam rectangle
double dx = renderEndX - renderStartX;
double dy = renderEndY - renderStartY;
double dz = renderEndZ - renderStartZ;
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (len < 0.001) {
restoreRenderState();
return;
}
// Normalize direction
dx /= len;
dy /= len;
dz /= len;
// Calculate perpendicular vectors (one horizontal, one vertical relative to beam)
double upX = 0, upY = 1, upZ = 0;
if (Math.abs(dy) > 0.9) {
2026-02-18 05:13:51 -05:00
upX = 1;
upY = 0;
upZ = 0;
2026-01-25 20:39:01 -05:00
}
// Cross product for horizontal perpendicular
double horzX = dy * upZ - dz * upY;
double horzY = dz * upX - dx * upZ;
double horzZ = dx * upY - dy * upX;
double horzLen = Math.sqrt(horzX * horzX + horzY * horzY + horzZ * horzZ);
if (horzLen > 0) {
horzX /= horzLen;
horzY /= horzLen;
horzZ /= horzLen;
}
// Cross product for vertical perpendicular
double vertX = dy * horzZ - dz * horzY;
double vertY = dz * horzX - dx * horzZ;
double vertZ = dx * horzY - dy * horzX;
// Inner scale defines the core width
float innerScale = 0.6f;
float innerWidth = width * innerScale;
2026-01-26 04:42:23 -05:00
// Render outer rectangle (larger, outer color, translucent) - only if enabled
// outerColorWidth is an additive offset from inner width
2026-01-26 04:42:23 -05:00
if (laser.isOuterColorEnabled()) {
float outerWidth = innerWidth + laser.getOuterColorWidth() * width;
2026-01-26 04:42:23 -05:00
GL11.glDepthMask(false);
renderBeamRectangle(renderStartX, renderStartY, renderStartZ,
2026-02-18 05:13:51 -05:00
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
outerWidth, laser.getOuterColor(), alpha * 0.4f);
2026-01-26 04:42:23 -05:00
// Render middle layer (halfway between inner and outer)
float midWidth = innerWidth + (outerWidth - innerWidth) * 0.5f;
2026-01-26 04:42:23 -05:00
renderBeamRectangle(renderStartX, renderStartY, renderStartZ,
2026-02-18 05:13:51 -05:00
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
midWidth, laser.getOuterColor(), alpha * 0.7f);
2026-01-26 04:42:23 -05:00
GL11.glDepthMask(true);
}
2026-01-25 20:39:01 -05:00
// Render inner core (solid)
2026-01-25 20:39:01 -05:00
renderBeamRectangle(renderStartX, renderStartY, renderStartZ,
2026-02-18 05:13:51 -05:00
renderEndX, renderEndY, renderEndZ,
horzX, horzY, horzZ, vertX, vertY, vertZ,
2026-02-19 01:35:21 -05:00
innerWidth, laser.getInnerColor(), alpha * laser.getInnerAlpha());
// Render lightning along the laser beam
if (laser.hasLightningEffect() && laser.getCurrentLength() > 0.1f) {
renderLaserLightning(laser, renderStartX, renderStartY, renderStartZ,
2026-02-18 05:13:51 -05:00
renderEndX, renderEndY, renderEndZ, width);
}
2026-01-25 20:39:01 -05:00
restoreRenderState();
}
private void renderChargingOrb(EntityAbilityLaser laser, double x, double y, double z, float partialTicks) {
float headSize = laser.getLaserWidth() * 1.5f;
float chargeProgress = laser.getInterpolatedChargeProgress(partialTicks);
float size = headSize * chargeProgress;
if (size <= 0.01f) return;
int innerColor = laser.getInnerColor();
int outerColor = laser.getOuterColor();
GL11.glPushMatrix();
GL11.glTranslated(x, y, z);
// Pulsing effect
float pulseTime = laser.ticksExisted + partialTicks;
float scaleModifier = (float) Math.sin(pulseTime * 0.2f) * 0.08f;
float scale = size * (1.0f + scaleModifier);
// Render lightning during charging if enabled
if (laser.hasLightningEffect()) {
renderAttachedLightning(laser, 0.6f * 0.5f, scale);
}
GL11.glPushMatrix();
GL11.glScalef(scale, scale, scale);
// Inner scale defines the core size
float innerScale = 0.6f;
// Render outer glow only if enabled
if (laser.isOuterColorEnabled()) {
float outerScale = innerScale + laser.getOuterColorWidth();
float outerAlpha = laser.getOuterColorAlpha();
GL11.glDepthMask(false);
GL11.glPushMatrix();
GL11.glScalef(outerScale, outerScale, outerScale);
renderCube(outerColor, outerAlpha, 0.5f);
GL11.glPopMatrix();
GL11.glDepthMask(true);
}
// Render inner core
GL11.glScalef(innerScale, innerScale, innerScale);
renderCube(innerColor, laser.getInnerAlpha(), 0.5f);
GL11.glPopMatrix();
GL11.glPopMatrix();
}
/**
* Render lightning effects along the laser beam at multiple points.
*/
private void renderLaserLightning(EntityAbilityLaser laser,
2026-02-18 05:13:51 -05:00
double startX, double startY, double startZ,
double endX, double endY, double endZ, float width) {
AttachedLightningRenderer.LightningState state = getLightningState(laser);
float density = laser.getLightningDensity();
// Lightning radius extends outward from inner width
float innerRadius = width * 0.6f * 0.5f;
float radius = innerRadius + laser.getLightningRadius() * width;
int outerColor = laser.getOuterColor();
int innerColor = laser.getInnerColor();
int fadeTime = laser.getLightningFadeTime();
// Render lightning at multiple points along the laser
// Start, middle, and end points
double midX = (startX + endX) * 0.5;
double midY = (startY + endY) * 0.5;
double midZ = (startZ + endZ) * 0.5;
GL11.glPushMatrix();
GL11.glTranslated(midX, midY, midZ);
state.update(density, radius, outerColor, innerColor, fadeTime);
state.render();
GL11.glPopMatrix();
// Also render at the tip
GL11.glPushMatrix();
GL11.glTranslated(endX, endY, endZ);
// Use a secondary state for the tip
AttachedLightningRenderer.LightningState tipState = getTipLightningState(laser);
tipState.update(density * 0.7f, radius * 0.8f, outerColor, innerColor, fadeTime);
tipState.render();
GL11.glPopMatrix();
}
/**
* Get or create the tip lightning state for the laser.
* Persisted per entity ID so arcs accumulate and fade properly across frames.
*/
private AttachedLightningRenderer.LightningState getTipLightningState(EntityAbilityLaser laser) {
int id = laser.getEntityId();
AttachedLightningRenderer.LightningState state = tipLightningStates.get(id);
if (state == null) {
state = new AttachedLightningRenderer.LightningState();
tipLightningStates.put(id, state);
}
return state;
}
2026-01-25 20:39:01 -05:00
/**
* Render a beam as a 3D rectangular prism (box along a line).
*/
private void renderBeamRectangle(double x1, double y1, double z1,
2026-02-18 05:13:51 -05:00
double x2, double y2, double z2,
double horzX, double horzY, double horzZ,
double vertX, double vertY, double vertZ,
float width, int color, float alpha) {
2026-01-25 20:39:01 -05:00
float[] rgb = extractRGB(color);
float r = rgb[0], g = rgb[1], b = rgb[2];
float halfWidth = width * 0.5f;
// Calculate 8 corners of the box
// Start corners
double s1x = x1 - horzX * halfWidth - vertX * halfWidth;
double s1y = y1 - horzY * halfWidth - vertY * halfWidth;
double s1z = z1 - horzZ * halfWidth - vertZ * halfWidth;
double s2x = x1 + horzX * halfWidth - vertX * halfWidth;
double s2y = y1 + horzY * halfWidth - vertY * halfWidth;
double s2z = z1 + horzZ * halfWidth - vertZ * halfWidth;
double s3x = x1 + horzX * halfWidth + vertX * halfWidth;
double s3y = y1 + horzY * halfWidth + vertY * halfWidth;
double s3z = z1 + horzZ * halfWidth + vertZ * halfWidth;
double s4x = x1 - horzX * halfWidth + vertX * halfWidth;
double s4y = y1 - horzY * halfWidth + vertY * halfWidth;
double s4z = z1 - horzZ * halfWidth + vertZ * halfWidth;
// End corners
double e1x = x2 - horzX * halfWidth - vertX * halfWidth;
double e1y = y2 - horzY * halfWidth - vertY * halfWidth;
double e1z = z2 - horzZ * halfWidth - vertZ * halfWidth;
double e2x = x2 + horzX * halfWidth - vertX * halfWidth;
double e2y = y2 + horzY * halfWidth - vertY * halfWidth;
double e2z = z2 + horzZ * halfWidth - vertZ * halfWidth;
double e3x = x2 + horzX * halfWidth + vertX * halfWidth;
double e3y = y2 + horzY * halfWidth + vertY * halfWidth;
double e3z = z2 + horzZ * halfWidth + vertZ * halfWidth;
double e4x = x2 - horzX * halfWidth + vertX * halfWidth;
double e4y = y2 - horzY * halfWidth + vertY * halfWidth;
double e4z = z2 - horzZ * halfWidth + vertZ * halfWidth;
Tessellator tess = Tessellator.instance;
tess.startDrawingQuads();
tess.setColorRGBA_F(r, g, b, alpha);
// Bottom face
tess.addVertex(s1x, s1y, s1z);
tess.addVertex(s2x, s2y, s2z);
tess.addVertex(e2x, e2y, e2z);
tess.addVertex(e1x, e1y, e1z);
// Top face
tess.addVertex(s4x, s4y, s4z);
tess.addVertex(e4x, e4y, e4z);
tess.addVertex(e3x, e3y, e3z);
tess.addVertex(s3x, s3y, s3z);
// Left face
tess.addVertex(s1x, s1y, s1z);
tess.addVertex(e1x, e1y, e1z);
tess.addVertex(e4x, e4y, e4z);
tess.addVertex(s4x, s4y, s4z);
// Right face
tess.addVertex(s2x, s2y, s2z);
tess.addVertex(s3x, s3y, s3z);
tess.addVertex(e3x, e3y, e3z);
tess.addVertex(e2x, e2y, e2z);
// Start cap
tess.addVertex(s1x, s1y, s1z);
tess.addVertex(s4x, s4y, s4z);
tess.addVertex(s3x, s3y, s3z);
tess.addVertex(s2x, s2y, s2z);
// End cap
tess.addVertex(e1x, e1y, e1z);
tess.addVertex(e2x, e2y, e2z);
tess.addVertex(e3x, e3y, e3z);
tess.addVertex(e4x, e4y, e4z);
tess.draw();
}
}