From b023be16817c7c1f2bed02f4e233fb8fc4e0f071 Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Tue, 20 Jan 2026 21:03:59 -0500 Subject: [PATCH 1/6] Part 1 of helical fusion render. --- .../java/net/neganote/monilabs/MoniLabs.java | 9 +- .../client/render/HelicalFusionRenderer.java | 323 ++++++++++++++++++ .../client/render/HelicalRenderHelpers.java | 37 ++ .../render/MoniDynamicRenderHelper.java | 4 + 4 files changed, 367 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java create mode 100644 src/main/java/net/neganote/monilabs/client/render/HelicalRenderHelpers.java diff --git a/src/main/java/net/neganote/monilabs/MoniLabs.java b/src/main/java/net/neganote/monilabs/MoniLabs.java index f3d9b11b..5c79f07b 100644 --- a/src/main/java/net/neganote/monilabs/MoniLabs.java +++ b/src/main/java/net/neganote/monilabs/MoniLabs.java @@ -31,12 +31,7 @@ import net.neganote.monilabs.capability.recipe.ChromaIngredient; import net.neganote.monilabs.capability.recipe.MapColorIngredient; import net.neganote.monilabs.capability.recipe.MapMicroverseIngredient; -import net.neganote.monilabs.client.render.CreativeDataRender; -import net.neganote.monilabs.client.render.CreativeEnergyRender; -import net.neganote.monilabs.client.render.MicroverseProjectorRender; -import net.neganote.monilabs.client.render.MoniShaders; -import net.neganote.monilabs.client.render.PrismaticCrucibleRender; -import net.neganote.monilabs.client.render.SculkVatRender; +import net.neganote.monilabs.client.render.*; import net.neganote.monilabs.client.render.effects.MoniTrails; import net.neganote.monilabs.client.render.effects.ParticleTypes; import net.neganote.monilabs.client.render.effects.PrismFX; @@ -182,6 +177,8 @@ private void initializeDynamicRenders() { .register(MoniLabs.id("creative_data"), CreativeDataRender.TYPE); DynamicRenderManager .register(MoniLabs.id("sculk_vat"), SculkVatRender.TYPE); + DynamicRenderManager + .register(MoniLabs.id("helical_fusion"), HelicalFusionRenderer.TYPE); } // You MUST have this for custom materials. diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java new file mode 100644 index 00000000..e56244b0 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java @@ -0,0 +1,323 @@ +package net.neganote.monilabs.client.render; + +import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; +import com.gregtechceu.gtceu.client.renderer.machine.DynamicRender; +import com.gregtechceu.gtceu.client.renderer.machine.DynamicRenderType; +import com.gregtechceu.gtceu.common.machine.multiblock.electric.FusionReactorMachine; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.util.FastColor; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.NotNull; +import org.joml.Matrix4f; + +public class HelicalFusionRenderer extends DynamicRender { + + public static final HelicalFusionRenderer INSTANCE = new HelicalFusionRenderer( + 1f, + Vec3.ZERO, + 0.45f * 0.2f, + 0.60f * 0.2f, + 100f); + // ---------------- LOD & Fade ---------------- + private static final float FADEOUT = 60f; + private static final double LOD_NEAR = 48.0; + private static final double LOD_MID = 96.0; + + private static final int RING_SEGMENTS = 16; + private static final int RING_VERTS = RING_SEGMENTS + 1; + + private static final Vec3[] BASE_RING = new Vec3[RING_VERTS]; + static { + for (int i = 0; i <= RING_SEGMENTS; i++) { + float a = (float) (2.0 * Math.PI * i / RING_SEGMENTS); + BASE_RING[i] = new Vec3(Mth.cos(a), Mth.sin(a), 0); + } + } + + // ---------------- Parameters ---------------- + private final float scale; + private final Vec3 offset; // now relative to machine orientation + private final float outerRadius; + private final float innerRadius; + private final float twistSpeed; + + private float delta = 0f; + private int lastColor = -1; + + public HelicalFusionRenderer(float scale, Vec3 offset, float outerRadius, float innerRadius, float twistSpeed) { + this.scale = scale; + this.offset = offset; + this.outerRadius = outerRadius; + this.innerRadius = innerRadius; + this.twistSpeed = twistSpeed; + } + + // ---------------- DynamicRenderType / Codec ---------------- + public static final Codec CODEC = Codec + .unit(() -> new HelicalFusionRenderer(1f, Vec3.ZERO, 0.45f * 0.2f, 0.6f * 0.2f, 100f)); + public static final DynamicRenderType TYPE = new DynamicRenderType<>( + CODEC); + + @Override + public @NotNull DynamicRenderType getType() { + return TYPE; + } + + @Override + public boolean shouldRender(FusionReactorMachine machine, @NotNull Vec3 cameraPos) { + return machine.isFormed() && (machine.getRecipeLogic().isWorking() || delta > 0); + } + + @Override + public void render(FusionReactorMachine machine, float partialTick, + @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, + int packedLight, int packedOverlay) { + if (!machine.isFormed()) return; + + RecipeLogic logic = machine.getRecipeLogic(); + int recipeColor = machine.getColor(); + + if (logic.isWorking()) { + lastColor = recipeColor; + delta = FADEOUT; + } else if (delta > 0 && lastColor != -1) { + delta -= Minecraft.getInstance().getDeltaFrameTime(); + } else return; + + float alpha = Mth.clamp(delta / FADEOUT, 0f, 1f); + + // ---------------- COLOR STABILIZATION ---------------- + float rf = FastColor.ARGB32.red(lastColor) / 255f; + float gf = FastColor.ARGB32.green(lastColor) / 255f; + float bf = FastColor.ARGB32.blue(lastColor) / 255f; + + float lum = 0.2126f * rf + 0.7152f * gf + 0.0722f * bf; + final float MAX_LUM = 0.65f; + if (lum > MAX_LUM) { + float scaleLum = MAX_LUM / lum; + rf *= scaleLum; + gf *= scaleLum; + bf *= scaleLum; + } + if (bf > rf && bf > gf && lum > 0.55f) { + bf *= 0.90f; + gf *= 0.95f; + } + + int rBase = (int) (rf * 255f); + int gBase = (int) (gf * 255f); + int bBase = (int) (bf * 255f); + + float time = (machine.getOffsetTimer() + partialTick) * 0.02f; + + Vec3 cam = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); + Vec3 center = Vec3.atCenterOf(machine.getPos()); + double distSq = cam.distanceToSqr(center); + + int segments, crossSections; + if (distSq < LOD_NEAR * LOD_NEAR) { + segments = 400; + crossSections = 12; + } else if (distSq < LOD_MID * LOD_MID) { + segments = 260; + crossSections = 10; + } else { + segments = 160; + crossSections = 8; + } + + poseStack.pushPose(); + + // ---------------- Apply Relative Offset ---------------- + Vec3 frontStep = new Vec3( + machine.getFrontFacing().step().x(), + machine.getFrontFacing().step().y(), + machine.getFrontFacing().step().z()).normalize().scale(offset.length()); + + Vec3 rotatedOffset = new Vec3( + frontStep.x * offset.x - frontStep.z * offset.z, + offset.y, + frontStep.x * offset.z + frontStep.z * offset.x); + + poseStack.translate(rotatedOffset.x, rotatedOffset.y, rotatedOffset.z); + + // ---------------- Apply scale ---------------- + poseStack.scale(scale, scale, scale); + + VertexConsumer vc = buffer.getBuffer(HelicalRenderHelpers.LIGHT_RING()); + + renderHelix(poseStack, vc, time, 0f, rBase, gBase, bBase, alpha, segments, crossSections); + renderHelix(poseStack, vc, time, Mth.PI, rBase, gBase, bBase, alpha, segments, crossSections); + + poseStack.popPose(); + } + + // ---------------- HELIX / RING FUNCTIONS ---------------- + private void renderHelix(PoseStack stack, VertexConsumer vc, + float time, float phase, + int rBase, int gBase, int bBase, float alpha, + int segments, int crossSections) { + final float OUTER_ALPHA = 0.35f; + final float INNER_ALPHA = 0.15f; + final float OUTER_DEPTH_NUDGE = 0.015f; + final float INNER_DEPTH_NUDGE = 0.0f; + final float INNER_RADIUS_SCALE = 0.85f; + + int ringCount = segments + 1; + + Vec3[] centers = new Vec3[ringCount]; + Vec3[] tangents = new Vec3[ringCount]; + Vec3[] normals = new Vec3[ringCount]; + Vec3[] binorms = new Vec3[ringCount]; + + computeCurve(time, phase, segments, centers, tangents); + computeBishopFrame(segments, tangents, normals, binorms); + + centers[segments] = centers[0]; + tangents[segments] = tangents[0]; + normals[segments] = normals[0]; + binorms[segments] = binorms[0]; + + Vec3[][] outer = new Vec3[ringCount][crossSections + 1]; + Vec3[][] inner = new Vec3[ringCount][crossSections + 1]; + + float innerRadiusVal = innerRadius * INNER_RADIUS_SCALE; + + buildRings(time, ringCount, crossSections, centers, tangents, normals, binorms, outer, inner, innerRadiusVal); + + renderTube(stack, vc, outer, segments, crossSections, rBase, gBase, bBase, alpha * OUTER_ALPHA, + OUTER_DEPTH_NUDGE); + renderTube(stack, vc, inner, segments, crossSections, rBase, gBase, bBase, alpha * INNER_ALPHA, + INNER_DEPTH_NUDGE); + } + + private void buildRings(float time, int ringCount, int crossSections, + Vec3[] c, Vec3[] t, + Vec3[] n, Vec3[] b, + Vec3[][] o, Vec3[][] in, + float innerRadius) { + for (int i = 0; i < ringCount; i++) { + float twist = time * twistSpeed + i * 0.12f; + + Vec3 nt = rotate(n[i], t[i], twist); + Vec3 bt = t[i].cross(nt).normalize(); + + for (int v = 0; v <= crossSections; v++) { + float angle = v * Mth.TWO_PI / crossSections; + float cos = Mth.cos(angle); + float sin = Mth.sin(angle); + + o[i][v] = c[i].add(nt.scale(cos * outerRadius)).add(bt.scale(sin * outerRadius)); + + float pulse = 0.96f + 0.04f * Mth.sin(i * 0.1f + time * 3f); + in[i][v] = c[i].add(nt.scale(innerRadius * pulse)).add(bt.scale(sin * innerRadius * pulse)); + } + } + } + + private void renderTube(PoseStack stack, VertexConsumer vc, + Vec3[][] rings, int segments, int crossSections, + int rBase, int gBase, int bBase, float alpha, float depthNudge) { + Matrix4f pose = stack.last().pose(); + + for (int i = 0; i < segments; i++) { + int nextI = i + 1; + + for (int v = 0; v < crossSections; v++) { + int nextV = v + 1; + float s = 1.0f + depthNudge; + + Vec3 v1 = rings[i][v].scale(s); + Vec3 v2 = rings[i][nextV].scale(s); + Vec3 v3 = rings[nextI][nextV].scale(s); + Vec3 v4 = rings[nextI][v].scale(s); + + quad(vc, pose, v1, v2, v3, v4, rBase, gBase, bBase, alpha); + } + } + } + + private void computeCurve(float time, float phase, int segments, + Vec3[] c, Vec3[] t) { + float dt = Mth.TWO_PI / segments; + for (int i = 0; i < segments; i++) { + Vec3 p0 = lissajous(i * dt + phase, time); + Vec3 p1 = lissajous((i + 1) * dt + phase, time); + c[i] = p0; + t[i] = p1.subtract(p0).normalize(); + } + } + + private void computeBishopFrame(int segments, Vec3[] t, + Vec3[] n, Vec3[] b) { + Vec3 up = Math.abs(t[0].y) > 0.9 ? new Vec3(1, 0, 0) : new Vec3(0, 1, 0); + n[0] = up.subtract(t[0].scale(up.dot(t[0]))).normalize(); + b[0] = t[0].cross(n[0]).normalize(); + + for (int i = 1; i < segments; i++) { + Vec3 ni = n[i - 1].subtract(t[i].scale(n[i - 1].dot(t[i]))).normalize(); + n[i] = ni; + b[i] = t[i].cross(ni).normalize(); + } + } + + private Vec3 lissajous(float t, float time) { + float scale = 0.02f; + float x = 2f * scale * Mth.cos(4 * t); + float y = 2f * scale * Mth.sin(4 * t); + float z = 17f * scale * Mth.cos(t); + + float c = Mth.cos(time); + float s = Mth.sin(time); + + return new Vec3( + x * c - y * s, + x * s + y * c, + z * 0.4f); + } + + private Vec3 rotate(Vec3 v, Vec3 axis, float ang) { + double c = Math.cos(ang); + double s = Math.sin(ang); + return v.scale(c).add(axis.cross(v).scale(s)).add(axis.scale(axis.dot(v) * (1 - c))); + } + + private void quad(VertexConsumer vc, Matrix4f pose, + Vec3 a, Vec3 b, Vec3 c, Vec3 d, + int r, int g, int bl, float al) { + vertex(vc, pose, a, r, g, bl, al); + vertex(vc, pose, b, r, g, bl, al); + vertex(vc, pose, c, r, g, bl, al); + vertex(vc, pose, d, r, g, bl, al); + } + + private void vertex(VertexConsumer vc, Matrix4f pose, + Vec3 p, int r, int g, int b, float a) { + vc.vertex(pose, (float) p.x, (float) p.y, (float) p.z) + .color(r / 255f, g / 255f, b / 255f, a) + .endVertex(); + } + + @Override + public boolean shouldRenderOffScreen(FusionReactorMachine m) { + return true; + } + + @Override + public int getViewDistance() { + return 128; + } + + @Override + public @NotNull AABB getRenderBoundingBox(FusionReactorMachine m) { + return new AABB(m.getPos()).inflate(40); + } +} diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalRenderHelpers.java b/src/main/java/net/neganote/monilabs/client/render/HelicalRenderHelpers.java new file mode 100644 index 00000000..d1295953 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalRenderHelpers.java @@ -0,0 +1,37 @@ +package net.neganote.monilabs.client.render; + +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; + +@OnlyIn(Dist.CLIENT) +public class HelicalRenderHelpers extends RenderType { + + private static final RenderType LIGHT_RING = RenderType.create( + "helical_light_ring", + DefaultVertexFormat.POSITION_COLOR, + VertexFormat.Mode.QUADS, + 256, + false, + false, + RenderType.CompositeState.builder() + .setShaderState(RenderStateShard.POSITION_COLOR_SHADER) + .setTransparencyState(RenderStateShard.ADDITIVE_TRANSPARENCY) + .setLayeringState(RenderStateShard.NO_LAYERING) + .setCullState(RenderStateShard.NO_CULL) + .createCompositeState(false)); + + private HelicalRenderHelpers(String name, VertexFormat format, VertexFormat.Mode mode, + int bufferSize, boolean affectsCrumbling, boolean sortOnUpload, + Runnable setupState, Runnable clearState) { + super(name, format, mode, bufferSize, affectsCrumbling, sortOnUpload, setupState, clearState); + } + + public static RenderType LIGHT_RING() { + return LIGHT_RING; + } +} diff --git a/src/main/java/net/neganote/monilabs/client/render/MoniDynamicRenderHelper.java b/src/main/java/net/neganote/monilabs/client/render/MoniDynamicRenderHelper.java index 127b52d0..63979707 100644 --- a/src/main/java/net/neganote/monilabs/client/render/MoniDynamicRenderHelper.java +++ b/src/main/java/net/neganote/monilabs/client/render/MoniDynamicRenderHelper.java @@ -40,4 +40,8 @@ public class MoniDynamicRenderHelper { .setForcedLight(LightTexture.FULL_BRIGHT) .getRenderer(), drawFaces); } + + public static DynamicRender createHelicalFusionRenderer() { + return HelicalFusionRenderer.INSTANCE; + } } From a10ae9c150cabec6f52f8f90bdc5dcc18fd73ed2 Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Wed, 21 Jan 2026 00:26:49 -0500 Subject: [PATCH 2/6] Part 1 of helical fusion render. --- .../client/render/HelicalFusionRenderer.java | 190 ++++++------------ 1 file changed, 60 insertions(+), 130 deletions(-) diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java index e56244b0..0b2b3e95 100644 --- a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java @@ -7,6 +7,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.core.Direction; import net.minecraft.util.FastColor; import net.minecraft.util.Mth; import net.minecraft.world.phys.AABB; @@ -14,6 +15,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; import com.mojang.serialization.Codec; import org.jetbrains.annotations.NotNull; import org.joml.Matrix4f; @@ -21,30 +23,18 @@ public class HelicalFusionRenderer extends DynamicRender { public static final HelicalFusionRenderer INSTANCE = new HelicalFusionRenderer( - 1f, - Vec3.ZERO, - 0.45f * 0.2f, - 0.60f * 0.2f, - 100f); - // ---------------- LOD & Fade ---------------- - private static final float FADEOUT = 60f; - private static final double LOD_NEAR = 48.0; - private static final double LOD_MID = 96.0; - - private static final int RING_SEGMENTS = 16; - private static final int RING_VERTS = RING_SEGMENTS + 1; + 2.0f, + new Vec3(-2.0, -1, 0), + 0.09f, + 0.12f, + 10f); - private static final Vec3[] BASE_RING = new Vec3[RING_VERTS]; - static { - for (int i = 0; i <= RING_SEGMENTS; i++) { - float a = (float) (2.0 * Math.PI * i / RING_SEGMENTS); - BASE_RING[i] = new Vec3(Mth.cos(a), Mth.sin(a), 0); - } - } + private static final float FADEOUT = 60f; + private static final double LOD_NEAR = 64.0; + private static final double LOD_MID = 128.0; - // ---------------- Parameters ---------------- private final float scale; - private final Vec3 offset; // now relative to machine orientation + private final Vec3 offset; private final float outerRadius; private final float innerRadius; private final float twistSpeed; @@ -60,9 +50,7 @@ public HelicalFusionRenderer(float scale, Vec3 offset, float outerRadius, float this.twistSpeed = twistSpeed; } - // ---------------- DynamicRenderType / Codec ---------------- - public static final Codec CODEC = Codec - .unit(() -> new HelicalFusionRenderer(1f, Vec3.ZERO, 0.45f * 0.2f, 0.6f * 0.2f, 100f)); + public static final Codec CODEC = Codec.unit(() -> INSTANCE); public static final DynamicRenderType TYPE = new DynamicRenderType<>( CODEC); @@ -83,10 +71,8 @@ public void render(FusionReactorMachine machine, float partialTick, if (!machine.isFormed()) return; RecipeLogic logic = machine.getRecipeLogic(); - int recipeColor = machine.getColor(); - if (logic.isWorking()) { - lastColor = recipeColor; + lastColor = machine.getColor(); delta = FADEOUT; } else if (delta > 0 && lastColor != -1) { delta -= Minecraft.getInstance().getDeltaFrameTime(); @@ -94,24 +80,16 @@ public void render(FusionReactorMachine machine, float partialTick, float alpha = Mth.clamp(delta / FADEOUT, 0f, 1f); - // ---------------- COLOR STABILIZATION ---------------- float rf = FastColor.ARGB32.red(lastColor) / 255f; float gf = FastColor.ARGB32.green(lastColor) / 255f; float bf = FastColor.ARGB32.blue(lastColor) / 255f; - float lum = 0.2126f * rf + 0.7152f * gf + 0.0722f * bf; - final float MAX_LUM = 0.65f; - if (lum > MAX_LUM) { - float scaleLum = MAX_LUM / lum; - rf *= scaleLum; - gf *= scaleLum; - bf *= scaleLum; - } - if (bf > rf && bf > gf && lum > 0.55f) { - bf *= 0.90f; - gf *= 0.95f; + if (lum > 0.65f) { + float s = 0.65f / lum; + rf *= s; + gf *= s; + bf *= s; } - int rBase = (int) (rf * 255f); int gBase = (int) (gf * 255f); int bBase = (int) (bf * 255f); @@ -119,37 +97,21 @@ public void render(FusionReactorMachine machine, float partialTick, float time = (machine.getOffsetTimer() + partialTick) * 0.02f; Vec3 cam = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); - Vec3 center = Vec3.atCenterOf(machine.getPos()); - double distSq = cam.distanceToSqr(center); - - int segments, crossSections; - if (distSq < LOD_NEAR * LOD_NEAR) { - segments = 400; - crossSections = 12; - } else if (distSq < LOD_MID * LOD_MID) { - segments = 260; - crossSections = 10; - } else { - segments = 160; - crossSections = 8; - } + double distSq = cam.distanceToSqr(Vec3.atCenterOf(machine.getPos())); + int segments = distSq < LOD_NEAR * LOD_NEAR ? 400 : (distSq < LOD_MID * LOD_MID ? 260 : 160); + int crossSections = distSq < LOD_NEAR * LOD_NEAR ? 12 : 8; poseStack.pushPose(); - // ---------------- Apply Relative Offset ---------------- - Vec3 frontStep = new Vec3( - machine.getFrontFacing().step().x(), - machine.getFrontFacing().step().y(), - machine.getFrontFacing().step().z()).normalize().scale(offset.length()); + poseStack.translate(0.5, 0.5, 0.5); - Vec3 rotatedOffset = new Vec3( - frontStep.x * offset.x - frontStep.z * offset.z, - offset.y, - frontStep.x * offset.z + frontStep.z * offset.x); + Direction facing = machine.getFrontFacing(); + poseStack.mulPose(facing.getRotation()); - poseStack.translate(rotatedOffset.x, rotatedOffset.y, rotatedOffset.z); + poseStack.mulPose(Axis.XP.rotationDegrees(90)); + poseStack.mulPose(Axis.YP.rotationDegrees(90)); - // ---------------- Apply scale ---------------- + poseStack.translate(offset.x, offset.y, offset.z); poseStack.scale(scale, scale, scale); VertexConsumer vc = buffer.getBuffer(HelicalRenderHelpers.LIGHT_RING()); @@ -160,19 +122,15 @@ public void render(FusionReactorMachine machine, float partialTick, poseStack.popPose(); } - // ---------------- HELIX / RING FUNCTIONS ---------------- - private void renderHelix(PoseStack stack, VertexConsumer vc, - float time, float phase, + private void renderHelix(PoseStack stack, VertexConsumer vc, float time, float phase, int rBase, int gBase, int bBase, float alpha, int segments, int crossSections) { final float OUTER_ALPHA = 0.35f; final float INNER_ALPHA = 0.15f; final float OUTER_DEPTH_NUDGE = 0.015f; - final float INNER_DEPTH_NUDGE = 0.0f; final float INNER_RADIUS_SCALE = 0.85f; int ringCount = segments + 1; - Vec3[] centers = new Vec3[ringCount]; Vec3[] tangents = new Vec3[ringCount]; Vec3[] normals = new Vec3[ringCount]; @@ -189,24 +147,18 @@ private void renderHelix(PoseStack stack, VertexConsumer vc, Vec3[][] outer = new Vec3[ringCount][crossSections + 1]; Vec3[][] inner = new Vec3[ringCount][crossSections + 1]; - float innerRadiusVal = innerRadius * INNER_RADIUS_SCALE; - - buildRings(time, ringCount, crossSections, centers, tangents, normals, binorms, outer, inner, innerRadiusVal); + buildRings(time, ringCount, crossSections, centers, tangents, normals, binorms, outer, inner, + innerRadius * INNER_RADIUS_SCALE); renderTube(stack, vc, outer, segments, crossSections, rBase, gBase, bBase, alpha * OUTER_ALPHA, OUTER_DEPTH_NUDGE); - renderTube(stack, vc, inner, segments, crossSections, rBase, gBase, bBase, alpha * INNER_ALPHA, - INNER_DEPTH_NUDGE); + renderTube(stack, vc, inner, segments, crossSections, rBase, gBase, bBase, alpha * INNER_ALPHA, 0.0f); } - private void buildRings(float time, int ringCount, int crossSections, - Vec3[] c, Vec3[] t, - Vec3[] n, Vec3[] b, - Vec3[][] o, Vec3[][] in, - float innerRadius) { + private void buildRings(float time, int ringCount, int crossSections, Vec3[] c, Vec3[] t, Vec3[] n, Vec3[] b, + Vec3[][] o, Vec3[][] in, float inRad) { for (int i = 0; i < ringCount; i++) { float twist = time * twistSpeed + i * 0.12f; - Vec3 nt = rotate(n[i], t[i], twist); Vec3 bt = t[i].cross(nt).normalize(); @@ -216,72 +168,54 @@ private void buildRings(float time, int ringCount, int crossSections, float sin = Mth.sin(angle); o[i][v] = c[i].add(nt.scale(cos * outerRadius)).add(bt.scale(sin * outerRadius)); - float pulse = 0.96f + 0.04f * Mth.sin(i * 0.1f + time * 3f); - in[i][v] = c[i].add(nt.scale(innerRadius * pulse)).add(bt.scale(sin * innerRadius * pulse)); + in[i][v] = c[i].add(nt.scale(cos * inRad * pulse)).add(bt.scale(sin * inRad * pulse)); } } } - private void renderTube(PoseStack stack, VertexConsumer vc, - Vec3[][] rings, int segments, int crossSections, - int rBase, int gBase, int bBase, float alpha, float depthNudge) { + private void renderTube(PoseStack stack, VertexConsumer vc, Vec3[][] rings, int segments, int crossSections, int r, + int g, int b, float a, float depthNudge) { Matrix4f pose = stack.last().pose(); - for (int i = 0; i < segments; i++) { - int nextI = i + 1; - for (int v = 0; v < crossSections; v++) { - int nextV = v + 1; float s = 1.0f + depthNudge; - - Vec3 v1 = rings[i][v].scale(s); - Vec3 v2 = rings[i][nextV].scale(s); - Vec3 v3 = rings[nextI][nextV].scale(s); - Vec3 v4 = rings[nextI][v].scale(s); - - quad(vc, pose, v1, v2, v3, v4, rBase, gBase, bBase, alpha); + quad(vc, pose, rings[i][v].scale(s), rings[i][v + 1].scale(s), rings[i + 1][v + 1].scale(s), + rings[i + 1][v].scale(s), r, g, b, a); } } } - private void computeCurve(float time, float phase, int segments, - Vec3[] c, Vec3[] t) { + private void computeCurve(float time, float phase, int segments, Vec3[] c, Vec3[] t) { float dt = Mth.TWO_PI / segments; for (int i = 0; i < segments; i++) { - Vec3 p0 = lissajous(i * dt + phase, time); - Vec3 p1 = lissajous((i + 1) * dt + phase, time); - c[i] = p0; - t[i] = p1.subtract(p0).normalize(); + c[i] = lissajous(i * dt + phase, time); + } + for (int i = 0; i < segments; i++) { + int next = (i + 1) % segments; + t[i] = lissajous((next * dt) + phase, time).subtract(c[i]).normalize(); } } - private void computeBishopFrame(int segments, Vec3[] t, - Vec3[] n, Vec3[] b) { + private void computeBishopFrame(int segments, Vec3[] t, Vec3[] n, Vec3[] b) { Vec3 up = Math.abs(t[0].y) > 0.9 ? new Vec3(1, 0, 0) : new Vec3(0, 1, 0); n[0] = up.subtract(t[0].scale(up.dot(t[0]))).normalize(); b[0] = t[0].cross(n[0]).normalize(); - for (int i = 1; i < segments; i++) { - Vec3 ni = n[i - 1].subtract(t[i].scale(n[i - 1].dot(t[i]))).normalize(); - n[i] = ni; - b[i] = t[i].cross(ni).normalize(); + n[i] = n[i - 1].subtract(t[i].scale(n[i - 1].dot(t[i]))).normalize(); + b[i] = t[i].cross(n[i]).normalize(); } } private Vec3 lissajous(float t, float time) { - float scale = 0.02f; - float x = 2f * scale * Mth.cos(4 * t); - float y = 2f * scale * Mth.sin(4 * t); - float z = 17f * scale * Mth.cos(t); - - float c = Mth.cos(time); - float s = Mth.sin(time); - - return new Vec3( - x * c - y * s, - x * s + y * c, - z * 0.4f); + float bx = 0.5f * Mth.cos(4 * t); + float by = 0.5f * Mth.sin(4 * t); + float bz = 4.0f * Mth.cos(t); + + float cosT = Mth.cos(time); + float sinT = Mth.sin(time); + + return new Vec3(bx * cosT - by * sinT, bx * sinT + by * cosT, bz * 0.4f); } private Vec3 rotate(Vec3 v, Vec3 axis, float ang) { @@ -290,20 +224,16 @@ private Vec3 rotate(Vec3 v, Vec3 axis, float ang) { return v.scale(c).add(axis.cross(v).scale(s)).add(axis.scale(axis.dot(v) * (1 - c))); } - private void quad(VertexConsumer vc, Matrix4f pose, - Vec3 a, Vec3 b, Vec3 c, Vec3 d, - int r, int g, int bl, float al) { + private void quad(VertexConsumer vc, Matrix4f pose, Vec3 a, Vec3 b, Vec3 c, Vec3 d, int r, int g, int bl, + float al) { vertex(vc, pose, a, r, g, bl, al); vertex(vc, pose, b, r, g, bl, al); vertex(vc, pose, c, r, g, bl, al); vertex(vc, pose, d, r, g, bl, al); } - private void vertex(VertexConsumer vc, Matrix4f pose, - Vec3 p, int r, int g, int b, float a) { - vc.vertex(pose, (float) p.x, (float) p.y, (float) p.z) - .color(r / 255f, g / 255f, b / 255f, a) - .endVertex(); + private void vertex(VertexConsumer vc, Matrix4f pose, Vec3 p, int r, int g, int b, float a) { + vc.vertex(pose, (float) p.x, (float) p.y, (float) p.z).color(r / 255f, g / 255f, b / 255f, a).endVertex(); } @Override @@ -318,6 +248,6 @@ public int getViewDistance() { @Override public @NotNull AABB getRenderBoundingBox(FusionReactorMachine m) { - return new AABB(m.getPos()).inflate(40); + return new AABB(m.getPos()).inflate(60); } } From 02a5106bc64777be1fa888aa05865f4ba3dc1fef Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Wed, 21 Jan 2026 01:14:44 -0500 Subject: [PATCH 3/6] Part 1 of helical fusion render. --- .../client/render/HelicalFusionRenderer.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java index 0b2b3e95..0b564777 100644 --- a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java @@ -208,14 +208,14 @@ private void computeBishopFrame(int segments, Vec3[] t, Vec3[] n, Vec3[] b) { } private Vec3 lissajous(float t, float time) { - float bx = 0.5f * Mth.cos(4 * t); - float by = 0.5f * Mth.sin(4 * t); - float bz = 4.0f * Mth.cos(t); + float rotationSpeed = 2.0f; - float cosT = Mth.cos(time); - float sinT = Mth.sin(time); + float bx = 0.5f * Mth.cos(4 * t + time * rotationSpeed); + float by = 0.5f * Mth.sin(4 * t + time * rotationSpeed); - return new Vec3(bx * cosT - by * sinT, bx * sinT + by * cosT, bz * 0.4f); + float bz = 6.0f * Mth.cos(t); + + return new Vec3(bx, by, bz * 0.8f); } private Vec3 rotate(Vec3 v, Vec3 axis, float ang) { From 7c0d1334bab77192efceb6beb42119f571e99ea4 Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Sun, 25 Jan 2026 20:32:57 -0500 Subject: [PATCH 4/6] Requested changes. --- .../monilabs/client/render/HelicalFusionRenderer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java index 0b564777..458e61ba 100644 --- a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java @@ -22,7 +22,7 @@ public class HelicalFusionRenderer extends DynamicRender { - public static final HelicalFusionRenderer INSTANCE = new HelicalFusionRenderer( + static final HelicalFusionRenderer INSTANCE = new HelicalFusionRenderer( 2.0f, new Vec3(-2.0, -1, 0), 0.09f, @@ -68,8 +68,6 @@ public boolean shouldRender(FusionReactorMachine machine, @NotNull Vec3 cameraPo public void render(FusionReactorMachine machine, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) { - if (!machine.isFormed()) return; - RecipeLogic logic = machine.getRecipeLogic(); if (logic.isWorking()) { lastColor = machine.getColor(); From 6822689f2404a9d31767090bef5ce96ea21f9b7d Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Sun, 25 Jan 2026 20:41:21 -0500 Subject: [PATCH 5/6] Fixed requested changes. --- .../client/render/HelicalFusionRenderer.java | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java index 458e61ba..4d768e37 100644 --- a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java @@ -22,33 +22,21 @@ public class HelicalFusionRenderer extends DynamicRender { - static final HelicalFusionRenderer INSTANCE = new HelicalFusionRenderer( - 2.0f, - new Vec3(-2.0, -1, 0), - 0.09f, - 0.12f, - 10f); + public static final HelicalFusionRenderer INSTANCE = new HelicalFusionRenderer(); + private static final float scale = 2.0f; + private static final Vec3 offset = new Vec3(-2.0, -1, 0); + private static final float outerRadius = 0.09f; + private static final float innerRadius = 0.12f; + private static final float twistSpeed = 10f; private static final float FADEOUT = 60f; private static final double LOD_NEAR = 64.0; private static final double LOD_MID = 128.0; - private final float scale; - private final Vec3 offset; - private final float outerRadius; - private final float innerRadius; - private final float twistSpeed; - private float delta = 0f; private int lastColor = -1; - public HelicalFusionRenderer(float scale, Vec3 offset, float outerRadius, float innerRadius, float twistSpeed) { - this.scale = scale; - this.offset = offset; - this.outerRadius = outerRadius; - this.innerRadius = innerRadius; - this.twistSpeed = twistSpeed; - } + public HelicalFusionRenderer() {} public static final Codec CODEC = Codec.unit(() -> INSTANCE); public static final DynamicRenderType TYPE = new DynamicRenderType<>( @@ -68,6 +56,7 @@ public boolean shouldRender(FusionReactorMachine machine, @NotNull Vec3 cameraPo public void render(FusionReactorMachine machine, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) { + RecipeLogic logic = machine.getRecipeLogic(); if (logic.isWorking()) { lastColor = machine.getColor(); From 1e85e7c48cda911bee205bafc3558c9764ecb9e3 Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Sun, 25 Jan 2026 20:41:35 -0500 Subject: [PATCH 6/6] spotless --- .../neganote/monilabs/client/render/HelicalFusionRenderer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java index 4d768e37..73fc617d 100644 --- a/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java +++ b/src/main/java/net/neganote/monilabs/client/render/HelicalFusionRenderer.java @@ -56,7 +56,6 @@ public boolean shouldRender(FusionReactorMachine machine, @NotNull Vec3 cameraPo public void render(FusionReactorMachine machine, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) { - RecipeLogic logic = machine.getRecipeLogic(); if (logic.isWorking()) { lastColor = machine.getColor();