diff --git a/src/main/java/cam72cam/mod/render/Light.java b/src/main/java/cam72cam/mod/render/Light.java index f950d9b5..9a130360 100644 --- a/src/main/java/cam72cam/mod/render/Light.java +++ b/src/main/java/cam72cam/mod/render/Light.java @@ -7,25 +7,110 @@ 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 = entity.chunkCoordX; + int cz = entity.chunkCoordZ; + 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 = entity.chunkCoordX; + int cz = entity.chunkCoordZ; + 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); + } + } + } + + /** + * 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) { + 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); + 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; + } + private static class LightInfo { + private final Vec3d pos; + private final double level; + private 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 +123,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 +144,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 +191,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 +200,14 @@ public LightEntity(net.minecraft.world.World world) { super.noClip = true; } + private void setSimulateLightLevel(double level) { + this.simulateLightLevel = level; + } + + private double getSimulateLightLevel() { + return simulateLightLevel; + } + @Override public void onEntityUpdate() { this.prevPosX = this.posX; @@ -130,6 +229,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() { diff --git a/src/main/java/cam72cam/mod/render/obj/OBJRender.java b/src/main/java/cam72cam/mod/render/obj/OBJRender.java index bd22e6f7..1313d661 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) {