Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 108 additions & 3 deletions src/main/java/cam72cam/mod/render/Light.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChunkPos, List<LightEntity>> 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<LightEntity> 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<LightInfo> 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<LightInfo> 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<LightInfo> 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<LightEntity> 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) {
Expand All @@ -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))) {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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() {
Expand Down
91 changes: 90 additions & 1 deletion src/main/java/cam72cam/mod/render/obj/OBJRender.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ public OBJRender(OBJModel model, Supplier<VertexBuffer> buffer) {
this.buffer = buffer;
}

/**
* This constructor allows the caller to override lighting before drawing.
*/
public OBJRender(OBJModel model, Supplier<VertexBuffer> buffer, Consumer<RenderState> 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<PieceRange> pieceRanges = new ArrayList<>();


public Binding bind(RenderState state) {
return bind(state, false);
}
Expand Down Expand Up @@ -75,11 +97,27 @@ public void draw(Collection<String> 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<RenderState> settings;
private final List<Consumer<Buffer>> actions = new ArrayList<>();
private final List<PieceRange> ranges = new ArrayList<>();

private Builder(Consumer<RenderState> settings) {
this.settings = settings;
Expand All @@ -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();
Expand All @@ -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);

Expand Down Expand Up @@ -193,7 +258,11 @@ public void draw(Collection<String> groups) {
}

public void draw(Collection<String> 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() {
Expand All @@ -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<Buffer> 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<RenderState> settings) {
Expand Down