From 6cdbbf95342947b2ad89e22995d87e329dbeb57f Mon Sep 17 00:00:00 2001 From: Deepseasaltyfish Date: Mon, 30 Mar 2026 18:46:15 +0800 Subject: [PATCH 1/5] make OBJRender supports independent light map for track parts. --- .../cam72cam/mod/render/obj/OBJRender.java | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/main/java/cam72cam/mod/render/obj/OBJRender.java b/src/main/java/cam72cam/mod/render/obj/OBJRender.java index bd22e6f76..1313d6610 100644 --- a/src/main/java/cam72cam/mod/render/obj/OBJRender.java +++ b/src/main/java/cam72cam/mod/render/obj/OBJRender.java @@ -26,6 +26,28 @@ public OBJRender(OBJModel model, Supplier buffer) { this.buffer = buffer; } + /** + * This constructor allows the caller to override lighting before drawing. + */ + public OBJRender(OBJModel model, Supplier buffer, Consumer settings) { + super(buffer, settings); + this.model = model; + this.buffer = buffer; + } + + public static class PieceRange { + public final int startVertex; + public final int vertexCount; + public final Matrix4 localMatrix; + public PieceRange(int startVertex, int vertexCount, Matrix4 localMatrix) { + this.startVertex = startVertex; + this.vertexCount = vertexCount; + this.localMatrix = localMatrix; + } + } + public List pieceRanges = new ArrayList<>(); + + public Binding bind(RenderState state) { return bind(state, false); } @@ -75,11 +97,27 @@ public void draw(Collection groups) { GL11.glDrawArrays(GL11.GL_TRIANGLES, start * 3, (stop - start) * 3); } } + + /** + * Draws a single piece (vertex range) with per‑piece lightmap override. + * The lightmap coordinates are temporarily set to the provided block and sky light + * values, then restored after drawing. + * @param range The piece range (start vertex and count) to draw. + * @param blockLight Block light intensity in [0,1] (0=dark, 1=full bright). + * @param skyLight Sky light intensity in [0,1]. + */ + public void drawPiece(PieceRange range, float blockLight, float skyLight) { + if (!isLoaded()) return; + try (With pus = push(s -> s.lightmap(blockLight, skyLight))) { + GL11.glDrawArrays(GL11.GL_TRIANGLES, range.startVertex, range.vertexCount); + } + } } public class Builder { private final Consumer settings; private final List> actions = new ArrayList<>(); + private final List ranges = new ArrayList<>(); private Builder(Consumer settings) { this.settings = settings; @@ -89,6 +127,8 @@ private class Buffer { private VertexBuffer vb; private float[] built; private int builtIdx; + private int currentPieceStart = -1; + private Matrix4 currentPieceMatrix; private Buffer() { this.vb = buffer.get(); @@ -104,6 +144,31 @@ private void require(int size) { } } + /** + * Marks the beginning of a new piece in the vertex buffer. + * Records the current vertex index as the start of the piece and stores + * the local transformation matrix for later light calculation. + */ + public void startPiece(Matrix4 matrix) { + this.currentPieceStart = builtIdx / (vb.stride); + this.currentPieceMatrix = matrix; + } + + /** + * Marks the end of the current piece. + * Computes the vertex count since the last startPiece call and adds a + * PieceRange entry to the builder's list. + */ + public void endPiece() { + if (currentPieceStart != -1) { + int currentEnd = builtIdx / (vb.stride); + int vertexCount = currentEnd - currentPieceStart; + Builder.this.ranges.add(new PieceRange(currentPieceStart, vertexCount, currentPieceMatrix)); + currentPieceStart = -1; + currentPieceMatrix = null; + } + } + private void add(float[] buff, Matrix4 m) { require(buff.length); @@ -193,7 +258,11 @@ public void draw(Collection groups) { } public void draw(Collection groups, Matrix4 m) { - actions.add(b -> b.draw(groups, m)); + actions.add(b -> { + b.startPiece(m); + b.draw(groups, m); + b.endPiece(); + }); } public VBO build() { @@ -204,6 +273,26 @@ public VBO build() { return buff.build(); }, settings); } + + /** + * Synchronously builds an OBJRender instance that supports per‑piece lightmap. + * Unlike build(), this method processes all geometry immediately on the calling + * thread, ensuring that pieceRanges are fully populated before returning. + * The resulting OBJRender can then be used with drawPiece() for independent + * lighting per piece. + * @return An OBJRender instance with pieceRanges correctly filled. + */ + public OBJRender buildWithLight() { + Buffer buff = new Buffer(); + for (Consumer action : actions) { + action.accept(buff); + } + VertexBuffer vertexBuffer = buff.build(); + + OBJRender objRender = new OBJRender(model, () -> vertexBuffer, settings); + objRender.pieceRanges = new ArrayList<>(this.ranges); + return objRender; + } } public Builder subModel(Consumer settings) { From fba16f3f2424f6d801cc9598172b7a56e61c8335 Mon Sep 17 00:00:00 2001 From: Deepseasaltyfish Date: Mon, 30 Mar 2026 23:37:20 +0800 Subject: [PATCH 2/5] make stock headlights work --- src/main/java/cam72cam/mod/render/Light.java | 92 +++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/src/main/java/cam72cam/mod/render/Light.java b/src/main/java/cam72cam/mod/render/Light.java index f950d9b53..b9d9624ef 100644 --- a/src/main/java/cam72cam/mod/render/Light.java +++ b/src/main/java/cam72cam/mod/render/Light.java @@ -7,25 +7,91 @@ import net.minecraft.entity.Entity; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.ChunkPos; import java.lang.reflect.InvocationTargetException; -import java.util.Objects; +import java.util.*; public class Light { private LightEntity internal; private double lightLevel; + private static final Map> CHUNK_LIGHTS = new HashMap<>(); + private static final Object LOCK = new Object(); + private static void registerInChunk(LightEntity entity) { + if (entity == null) return; + int cx = (int) Math.floor(entity.posX) >> 4; + int cz = (int) Math.floor(entity.posZ) >> 4; + ChunkPos cp = new ChunkPos(cx, cz); + synchronized (LOCK) { + CHUNK_LIGHTS.computeIfAbsent(cp, k -> new ArrayList<>()).add(entity); + } + } + private static void unregisterFromChunk(LightEntity entity) { + if (entity == null) return; + int cx = (int) Math.floor(entity.posX) >> 4; + int cz = (int) Math.floor(entity.posZ) >> 4; + ChunkPos cp = new ChunkPos(cx, cz); + synchronized (LOCK) { + List list = CHUNK_LIGHTS.get(cp); + if (list != null) { + list.remove(entity); + if (list.isEmpty()) CHUNK_LIGHTS.remove(cp); + } + } + } + public static List getLightsInRange(Vec3d center, double radius) { + int minX = (int)Math.floor((center.x - radius) / 16); + int maxX = (int)Math.floor((center.x + radius) / 16); + int minZ = (int)Math.floor((center.z - radius) / 16); + int maxZ = (int)Math.floor((center.z + radius) / 16); + List result = new ArrayList<>(); + synchronized (LOCK) { + for (int cx = minX; cx <= maxX; cx++) { + for (int cz = minZ; cz <= maxZ; cz++) { + ChunkPos cp = new ChunkPos(cx, cz); + List entities = CHUNK_LIGHTS.get(cp); + if (entities != null) { + for (LightEntity e : entities) { + if (e.isDead) continue;//can avoid some ghost entities? + double dx = e.posX - center.x; + double dy = e.posY - center.y; + double dz = e.posZ - center.z; + if (dx*dx + dy*dy + dz*dz <= radius*radius) { + result.add(new LightInfo(new Vec3d(e.posX, e.posY, e.posZ), e.getSimulateLightLevel())); + } + } + } + } + } + } + return result; + } + public static class LightInfo { + public final Vec3d pos; + public final double level; + public LightInfo(Vec3d pos, double level) { + this.pos = pos; + this.level = level; + } + } public Light(World world, Vec3d pos, double lightLevel) { init(world.internal, pos.internal(), lightLevel); } public void remove() { - internal.setDead(); - internal = null; + if (internal != null) { + unregisterFromChunk(internal); + internal.setDead(); + internal = null; + } } public void setPosition(Vec3d pos) { + if (internal == null) return; + unregisterFromChunk(internal); internal.setPosition(pos.x, pos.y, pos.z); + registerInChunk(internal); } public void setLightLevel(double lightLevel) { @@ -38,6 +104,7 @@ private void init(net.minecraft.world.World world, net.minecraft.util.math.Vec3d return; } if (internal != null) { + unregisterFromChunk(internal); internal.setDead(); } switch ((int) Math.ceil((lightLevel * 15))) { @@ -58,6 +125,10 @@ private void init(net.minecraft.world.World world, net.minecraft.util.math.Vec3d default: case 15: internal = new LightEntity15(world); break; } + + internal.setSimulateLightLevel(lightLevel); + registerInChunk(internal); + internal.setPosition(pos.x, pos.y, pos.z); world.spawnEntity(internal); this.lightLevel = lightLevel; @@ -101,6 +172,7 @@ public static void register() { // Client only private static class LightEntity extends Entity { + private double simulateLightLevel; public LightEntity(net.minecraft.world.World world) { super(world); super.width = 0; @@ -109,6 +181,14 @@ public LightEntity(net.minecraft.world.World world) { super.noClip = true; } + public void setSimulateLightLevel(double level) { + this.simulateLightLevel = level; + } + + public double getSimulateLightLevel() { + return simulateLightLevel; + } + @Override public void onEntityUpdate() { this.prevPosX = this.posX; @@ -130,6 +210,12 @@ protected void readEntityFromNBT(NBTTagCompound compound) { protected void writeEntityToNBT(NBTTagCompound compound) { } + + @Override + public void setDead() {//can avoid some ghost entities? + unregisterFromChunk(this); + super.setDead(); + } } public static boolean enabled() { From ea1463c284e430c26f155e971afd3b48beee4cff Mon Sep 17 00:00:00 2001 From: Deepseasaltyfish Date: Wed, 1 Apr 2026 12:02:21 +0800 Subject: [PATCH 3/5] package --- src/main/java/cam72cam/mod/render/Light.java | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/main/java/cam72cam/mod/render/Light.java b/src/main/java/cam72cam/mod/render/Light.java index b9d9624ef..bfcfeff9a 100644 --- a/src/main/java/cam72cam/mod/render/Light.java +++ b/src/main/java/cam72cam/mod/render/Light.java @@ -39,7 +39,24 @@ private static void unregisterFromChunk(LightEntity entity) { } } } - public static List getLightsInRange(Vec3d center, double radius) { + + /** + * return simulate Of dynamic light level, should only 0 in MC version on and above 1.20 + * */ + public static double getSimulateOfDynamicLightLevel(Vec3d center) { + double extra = 0.0; + List lights = getLightsInRange(center, 8.0); + for (LightInfo light : lights) { + double distSq = center.distanceToSquared(light.pos); + if (distSq < 64.0) { + double dist = Math.sqrt(distSq); + double factor = 1.0 - dist / 8.0; + extra = Math.max(light.level * factor, extra); + } + } + return extra; + } + private static List getLightsInRange(Vec3d center, double radius) { int minX = (int)Math.floor((center.x - radius) / 16); int maxX = (int)Math.floor((center.x + radius) / 16); int minZ = (int)Math.floor((center.z - radius) / 16); @@ -66,10 +83,10 @@ public static List getLightsInRange(Vec3d center, double radius) { } return result; } - public static class LightInfo { - public final Vec3d pos; - public final double level; - public LightInfo(Vec3d pos, double level) { + private static class LightInfo { + private final Vec3d pos; + private final double level; + private LightInfo(Vec3d pos, double level) { this.pos = pos; this.level = level; } @@ -181,11 +198,11 @@ public LightEntity(net.minecraft.world.World world) { super.noClip = true; } - public void setSimulateLightLevel(double level) { + private void setSimulateLightLevel(double level) { this.simulateLightLevel = level; } - public double getSimulateLightLevel() { + private double getSimulateLightLevel() { return simulateLightLevel; } From 079bccab8281b4f6d92e5c5e731d3d296814bb37 Mon Sep 17 00:00:00 2001 From: Deepseasaltyfish Date: Wed, 1 Apr 2026 21:08:51 +0800 Subject: [PATCH 4/5] clean --- src/main/java/cam72cam/mod/render/Light.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/cam72cam/mod/render/Light.java b/src/main/java/cam72cam/mod/render/Light.java index bfcfeff9a..3a1fc8f8e 100644 --- a/src/main/java/cam72cam/mod/render/Light.java +++ b/src/main/java/cam72cam/mod/render/Light.java @@ -19,8 +19,8 @@ public class Light { private static final Object LOCK = new Object(); private static void registerInChunk(LightEntity entity) { if (entity == null) return; - int cx = (int) Math.floor(entity.posX) >> 4; - int cz = (int) Math.floor(entity.posZ) >> 4; + int cx = entity.chunkCoordX; + int cz = entity.chunkCoordZ; ChunkPos cp = new ChunkPos(cx, cz); synchronized (LOCK) { CHUNK_LIGHTS.computeIfAbsent(cp, k -> new ArrayList<>()).add(entity); @@ -28,8 +28,9 @@ private static void registerInChunk(LightEntity entity) { } private static void unregisterFromChunk(LightEntity entity) { if (entity == null) return; - int cx = (int) Math.floor(entity.posX) >> 4; - int cz = (int) Math.floor(entity.posZ) >> 4; + + int cx = entity.chunkCoordX; + int cz = entity.chunkCoordZ; ChunkPos cp = new ChunkPos(cx, cz); synchronized (LOCK) { List list = CHUNK_LIGHTS.get(cp); @@ -41,7 +42,7 @@ private static void unregisterFromChunk(LightEntity entity) { } /** - * return simulate Of dynamic light level, should only 0 in MC version on and above 1.20 + * return simulate Of dynamic light level * */ public static double getSimulateOfDynamicLightLevel(Vec3d center) { double extra = 0.0; From 24f5fc82712f7ca56ee9c7820cbf7d390dfc1505 Mon Sep 17 00:00:00 2001 From: Deepseasaltyfish Date: Wed, 1 Apr 2026 22:54:04 +0800 Subject: [PATCH 5/5] simplify --- src/main/java/cam72cam/mod/render/Light.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/cam72cam/mod/render/Light.java b/src/main/java/cam72cam/mod/render/Light.java index 3a1fc8f8e..9a1303603 100644 --- a/src/main/java/cam72cam/mod/render/Light.java +++ b/src/main/java/cam72cam/mod/render/Light.java @@ -45,6 +45,7 @@ private static void unregisterFromChunk(LightEntity entity) { * return simulate Of dynamic light level * */ public static double getSimulateOfDynamicLightLevel(Vec3d center) { + if(!enabled()) return 0; double extra = 0.0; List lights = getLightsInRange(center, 8.0); for (LightInfo light : lights) {