mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-28 16:06:28 +01:00
Separate LightVolume and GPULightVolume
- LightVolumes now can act as a light cache with configurable size - More GridAlignedBB changes - Remove ILightUpdateListeners - Simplify pulley rendering using LightVolume
This commit is contained in:
parent
dc6a9daeb1
commit
99259ff227
11 changed files with 568 additions and 315 deletions
|
@ -6,11 +6,10 @@ import java.util.stream.Stream;
|
||||||
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
|
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
|
||||||
import com.jozufozu.flywheel.backend.material.MaterialManager;
|
import com.jozufozu.flywheel.backend.material.MaterialManager;
|
||||||
import com.jozufozu.flywheel.core.materials.IFlatLight;
|
import com.jozufozu.flywheel.core.materials.IFlatLight;
|
||||||
import com.jozufozu.flywheel.light.GridAlignedBB;
|
|
||||||
import com.jozufozu.flywheel.light.ILightUpdateListener;
|
import com.jozufozu.flywheel.light.ILightUpdateListener;
|
||||||
import com.jozufozu.flywheel.light.LightProvider;
|
import com.jozufozu.flywheel.light.LightProvider;
|
||||||
import com.jozufozu.flywheel.light.ListenerStatus;
|
import com.jozufozu.flywheel.light.ListenerStatus;
|
||||||
import com.jozufozu.flywheel.light.ReadOnlyBox;
|
import com.jozufozu.flywheel.light.ImmutableBox;
|
||||||
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.LightType;
|
||||||
|
@ -74,7 +73,7 @@ public abstract class AbstractInstance implements IInstance, ILightUpdateListene
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLightUpdate(LightProvider world, LightType type, ReadOnlyBox changed) {
|
public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changed) {
|
||||||
updateLight();
|
updateLight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -278,6 +278,8 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
|
||||||
instances.remove(obj);
|
instances.remove(obj);
|
||||||
dynamicInstances.remove(obj);
|
dynamicInstances.remove(obj);
|
||||||
tickableInstances.remove(obj);
|
tickableInstances.remove(obj);
|
||||||
|
LightUpdater.get(instance.world)
|
||||||
|
.removeListener(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -286,7 +288,8 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
|
||||||
|
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.updateLight();
|
renderer.updateLight();
|
||||||
LightUpdater.get(renderer.world).addListener(renderer);
|
LightUpdater.get(renderer.world)
|
||||||
|
.addListener(renderer);
|
||||||
instances.put(obj, renderer);
|
instances.put(obj, renderer);
|
||||||
|
|
||||||
if (renderer instanceof IDynamicInstance) dynamicInstances.put(obj, (IDynamicInstance) renderer);
|
if (renderer instanceof IDynamicInstance) dynamicInstances.put(obj, (IDynamicInstance) renderer);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.jozufozu.flywheel.core.Materials;
|
||||||
import com.jozufozu.flywheel.core.materials.ModelData;
|
import com.jozufozu.flywheel.core.materials.ModelData;
|
||||||
import com.jozufozu.flywheel.core.materials.OrientedData;
|
import com.jozufozu.flywheel.core.materials.OrientedData;
|
||||||
import com.jozufozu.flywheel.light.GridAlignedBB;
|
import com.jozufozu.flywheel.light.GridAlignedBB;
|
||||||
|
import com.jozufozu.flywheel.light.ImmutableBox;
|
||||||
|
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.tileentity.TileEntity;
|
import net.minecraft.tileentity.TileEntity;
|
||||||
|
@ -84,7 +85,7 @@ public abstract class TileEntityInstance<T extends TileEntity> extends AbstractI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GridAlignedBB getVolume() {
|
public ImmutableBox getVolume() {
|
||||||
return GridAlignedBB.from(pos);
|
return GridAlignedBB.from(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class ForgeEvents {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void rwle(TickEvent.ClientTickEvent e) {
|
public static void tickLight(TickEvent.ClientTickEvent e) {
|
||||||
if (e.phase == TickEvent.Phase.END && Backend.isGameActive())
|
if (e.phase == TickEvent.Phase.END && Backend.isGameActive())
|
||||||
LightUpdater.get(Minecraft.getInstance().level).tick();
|
LightUpdater.get(Minecraft.getInstance().level).tick();
|
||||||
}
|
}
|
||||||
|
|
160
src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java
Normal file
160
src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package com.jozufozu.flywheel.light;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_LINEAR;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_MIRRORED_REPEAT;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE0;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE4;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE_3D;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MAG_FILTER;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MIN_FILTER;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_R;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_S;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_T;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNPACK_ALIGNMENT;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNPACK_IMAGE_HEIGHT;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNPACK_ROW_LENGTH;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_IMAGES;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_PIXELS;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_ROWS;
|
||||||
|
import static org.lwjgl.opengl.GL20.GL_UNSIGNED_BYTE;
|
||||||
|
import static org.lwjgl.opengl.GL20.glActiveTexture;
|
||||||
|
import static org.lwjgl.opengl.GL20.glPixelStorei;
|
||||||
|
import static org.lwjgl.opengl.GL20.glTexImage3D;
|
||||||
|
import static org.lwjgl.opengl.GL20.glTexParameteri;
|
||||||
|
import static org.lwjgl.opengl.GL20.glTexSubImage3D;
|
||||||
|
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.backend.Backend;
|
||||||
|
import com.jozufozu.flywheel.backend.gl.GlTexture;
|
||||||
|
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
||||||
|
import com.jozufozu.flywheel.backend.gl.versioned.RGPixelFormat;
|
||||||
|
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
|
|
||||||
|
public class GPULightVolume extends LightVolume {
|
||||||
|
|
||||||
|
protected final GridAlignedBB sampleVolume = new GridAlignedBB();
|
||||||
|
private final GlTexture glTexture;
|
||||||
|
|
||||||
|
private final RGPixelFormat pixelFormat;
|
||||||
|
protected boolean bufferDirty;
|
||||||
|
|
||||||
|
public GPULightVolume(ImmutableBox sampleVolume) {
|
||||||
|
super(sampleVolume);
|
||||||
|
this.sampleVolume.assign(sampleVolume);
|
||||||
|
|
||||||
|
pixelFormat = Backend.getInstance().compat.pixelFormat;
|
||||||
|
glTexture = new GlTexture(GL_TEXTURE_3D);
|
||||||
|
|
||||||
|
// allocate space for the texture
|
||||||
|
glActiveTexture(GL_TEXTURE4);
|
||||||
|
glTexture.bind();
|
||||||
|
|
||||||
|
int sizeX = box.sizeX();
|
||||||
|
int sizeY = box.sizeY();
|
||||||
|
int sizeZ = box.sizeZ();
|
||||||
|
glTexImage3D(GL_TEXTURE_3D, 0, pixelFormat.internalFormat(), sizeX, sizeY, sizeZ, 0, pixelFormat.format(), GL_UNSIGNED_BYTE, 0);
|
||||||
|
|
||||||
|
glTexture.unbind();
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setBox(ImmutableBox box) {
|
||||||
|
this.box.assign(box);
|
||||||
|
this.box.nextPowerOf2Centered();
|
||||||
|
// called during super ctor
|
||||||
|
if (sampleVolume != null) this.sampleVolume.assign(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind() {
|
||||||
|
// just in case something goes wrong, or we accidentally call this before this volume is properly disposed of.
|
||||||
|
if (lightData == null) return;
|
||||||
|
|
||||||
|
GlTextureUnit.T4.makeActive();
|
||||||
|
glTexture.bind();
|
||||||
|
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
|
||||||
|
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
|
||||||
|
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
|
||||||
|
|
||||||
|
uploadTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uploadTexture() {
|
||||||
|
if (bufferDirty) {
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
|
||||||
|
int sizeX = box.sizeX();
|
||||||
|
int sizeY = box.sizeY();
|
||||||
|
int sizeZ = box.sizeZ();
|
||||||
|
|
||||||
|
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, pixelFormat.format(), GL_UNSIGNED_BYTE, lightData);
|
||||||
|
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
|
||||||
|
bufferDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unbind() {
|
||||||
|
glTexture.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete() {
|
||||||
|
super.delete();
|
||||||
|
glTexture.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(LightProvider world, ImmutableBox newSampleVolume) {
|
||||||
|
if (lightData == null) return;
|
||||||
|
|
||||||
|
if (box.contains(newSampleVolume)) {
|
||||||
|
if (newSampleVolume.intersects(sampleVolume)) {
|
||||||
|
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
|
||||||
|
sampleVolume.assign(newSampleVolume);
|
||||||
|
|
||||||
|
copyLight(world, newArea);
|
||||||
|
} else {
|
||||||
|
sampleVolume.assign(newSampleVolume);
|
||||||
|
initialize(world);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.move(world, newSampleVolume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changedVolume) {
|
||||||
|
super.onLightUpdate(world, type, changedVolume);
|
||||||
|
bufferDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLightPacket(LightProvider world, int chunkX, int chunkZ) {
|
||||||
|
super.onLightPacket(world, chunkX, chunkZ);
|
||||||
|
bufferDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely (re)populate this volume with block and sky lighting data.
|
||||||
|
* This is expensive and should be avoided.
|
||||||
|
*/
|
||||||
|
public void initialize(LightProvider world) {
|
||||||
|
super.initialize(world);
|
||||||
|
bufferDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getStride() {
|
||||||
|
return Backend.getInstance().compat.pixelFormat.byteCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableBox getVolume() {
|
||||||
|
return sampleVolume;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.SectionPos;
|
import net.minecraft.util.math.SectionPos;
|
||||||
import net.minecraft.util.math.vector.Vector3i;
|
import net.minecraft.util.math.vector.Vector3i;
|
||||||
|
|
||||||
public class GridAlignedBB implements ReadOnlyBox {
|
public class GridAlignedBB implements ImmutableBox {
|
||||||
private int minX;
|
private int minX;
|
||||||
private int minY;
|
private int minY;
|
||||||
private int minZ;
|
private int minZ;
|
||||||
|
@ -16,6 +16,10 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
private int maxY;
|
private int maxY;
|
||||||
private int maxZ;
|
private int maxZ;
|
||||||
|
|
||||||
|
public GridAlignedBB() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public GridAlignedBB(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
public GridAlignedBB(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
this.minX = minX;
|
this.minX = minX;
|
||||||
this.minY = minY;
|
this.minY = minY;
|
||||||
|
@ -58,19 +62,19 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fixMinMax() {
|
public void fixMinMax() {
|
||||||
int minX = Math.min(this.getMinX(), this.getMaxX());
|
int minX = Math.min(this.minX, this.maxX);
|
||||||
int minY = Math.min(this.getMinY(), this.getMaxY());
|
int minY = Math.min(this.minY, this.maxY);
|
||||||
int minZ = Math.min(this.getMinZ(), this.getMaxZ());
|
int minZ = Math.min(this.minZ, this.maxZ);
|
||||||
int maxX = Math.max(this.getMinX(), this.getMaxX());
|
int maxX = Math.max(this.minX, this.maxX);
|
||||||
int maxY = Math.max(this.getMinY(), this.getMaxY());
|
int maxY = Math.max(this.minY, this.maxY);
|
||||||
int maxZ = Math.max(this.getMinZ(), this.getMaxZ());
|
int maxZ = Math.max(this.minZ, this.maxZ);
|
||||||
|
|
||||||
this.setMinX(minX);
|
this.minX = minX;
|
||||||
this.setMinY(minY);
|
this.minY = minY;
|
||||||
this.setMinZ(minZ);
|
this.minZ = minZ;
|
||||||
this.setMaxX(maxX);
|
this.maxX = maxX;
|
||||||
this.setMaxY(maxY);
|
this.maxY = maxY;
|
||||||
this.setMaxZ(maxZ);
|
this.maxZ = maxZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void translate(Vector3i by) {
|
public void translate(Vector3i by) {
|
||||||
|
@ -78,12 +82,12 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void translate(int x, int y, int z) {
|
public void translate(int x, int y, int z) {
|
||||||
setMinX(getMinX() + x);
|
minX = minX + x;
|
||||||
setMaxX(getMaxX() + x);
|
maxX = maxX + x;
|
||||||
setMinY(getMinY() + y);
|
minY = minY + y;
|
||||||
setMaxY(getMaxY() + y);
|
maxY = maxY + y;
|
||||||
setMinZ(getMinZ() + z);
|
minZ = minZ + z;
|
||||||
setMaxZ(getMaxZ() + z);
|
maxZ = maxZ + z;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mirrorAbout(Direction.Axis axis) {
|
public void mirrorAbout(Direction.Axis axis) {
|
||||||
|
@ -93,15 +97,15 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
int flipY = axisVec.getY() - 1;
|
int flipY = axisVec.getY() - 1;
|
||||||
int flipZ = axisVec.getZ() - 1;
|
int flipZ = axisVec.getZ() - 1;
|
||||||
|
|
||||||
int maxX = this.getMaxX() * flipX;
|
int maxX = this.maxX * flipX;
|
||||||
int maxY = this.getMaxY() * flipY;
|
int maxY = this.maxY * flipY;
|
||||||
int maxZ = this.getMaxZ() * flipZ;
|
int maxZ = this.maxZ * flipZ;
|
||||||
this.setMaxX(this.getMinX() * flipX);
|
this.maxX = this.minX * flipX;
|
||||||
this.setMaxY(this.getMinY() * flipY);
|
this.maxY = this.minY * flipY;
|
||||||
this.setMaxZ(this.getMinZ() * flipZ);
|
this.maxZ = this.minZ * flipZ;
|
||||||
this.setMinX(maxX);
|
this.minX = maxX;
|
||||||
this.setMinY(maxY);
|
this.minY = maxY;
|
||||||
this.setMinZ(maxZ);
|
this.minZ = maxZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,12 +124,12 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
int diffY = newSizeY - sizeY;
|
int diffY = newSizeY - sizeY;
|
||||||
int diffZ = newSizeZ - sizeZ;
|
int diffZ = newSizeZ - sizeZ;
|
||||||
|
|
||||||
setMinX(getMinX() - diffX / 2); // floor division for the minimums
|
minX = minX - diffX / 2; // floor division for the minimums
|
||||||
setMinY(getMinY() - diffY / 2);
|
minY = minY - diffY / 2;
|
||||||
setMinZ(getMinZ() - diffZ / 2);
|
minZ = minZ - diffZ / 2;
|
||||||
setMaxX(getMaxX() + (diffX + 1) / 2); // ceiling divison for the maximums
|
maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums
|
||||||
setMaxY(getMaxY() + (diffY + 1) / 2);
|
maxY = maxY + (diffY + 1) / 2;
|
||||||
setMaxZ(getMaxZ() + (diffZ + 1) / 2);
|
maxZ = maxZ + (diffZ + 1) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -136,9 +140,9 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
int sizeY = RenderUtil.nextPowerOf2(sizeY());
|
int sizeY = RenderUtil.nextPowerOf2(sizeY());
|
||||||
int sizeZ = RenderUtil.nextPowerOf2(sizeZ());
|
int sizeZ = RenderUtil.nextPowerOf2(sizeZ());
|
||||||
|
|
||||||
this.setMaxX(this.getMinX() + sizeX);
|
maxX = minX + sizeX;
|
||||||
this.setMaxY(this.getMinY() + sizeY);
|
maxY = minY + sizeY;
|
||||||
this.setMaxZ(this.getMinZ() + sizeZ);
|
maxZ = minZ + sizeZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void grow(int s) {
|
public void grow(int s) {
|
||||||
|
@ -146,57 +150,57 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void grow(int x, int y, int z) {
|
public void grow(int x, int y, int z) {
|
||||||
setMinX(getMinX() - x);
|
minX = minX - x;
|
||||||
setMinY(getMinY() - y);
|
minY = minY - y;
|
||||||
setMinZ(getMinZ() - z);
|
minZ = minZ - z;
|
||||||
setMaxX(getMaxX() + x);
|
maxX = maxX + x;
|
||||||
setMaxY(getMaxY() + y);
|
maxY = maxY + y;
|
||||||
setMaxZ(getMaxZ() + z);
|
maxZ = maxZ + z;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void intersectAssign(ReadOnlyBox other) {
|
public void intersectAssign(ImmutableBox other) {
|
||||||
this.setMinX(Math.max(this.getMinX(), other.getMinX()));
|
minX = Math.max(this.minX, other.getMinX());
|
||||||
this.setMinY(Math.max(this.getMinY(), other.getMinY()));
|
minY = Math.max(this.minY, other.getMinY());
|
||||||
this.setMinZ(Math.max(this.getMinZ(), other.getMinZ()));
|
minZ = Math.max(this.minZ, other.getMinZ());
|
||||||
this.setMaxX(Math.min(this.getMaxX(), other.getMaxX()));
|
maxX = Math.min(this.maxX, other.getMaxX());
|
||||||
this.setMaxY(Math.min(this.getMaxY(), other.getMaxY()));
|
maxY = Math.min(this.maxY, other.getMaxY());
|
||||||
this.setMaxZ(Math.min(this.getMaxZ(), other.getMaxZ()));
|
maxZ = Math.min(this.maxZ, other.getMaxZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unionAssign(ReadOnlyBox other) {
|
public void unionAssign(ImmutableBox other) {
|
||||||
this.setMinX(Math.min(this.getMinX(), other.getMinX()));
|
minX = Math.min(this.minX, other.getMinX());
|
||||||
this.setMinY(Math.min(this.getMinY(), other.getMinY()));
|
minY = Math.min(this.minY, other.getMinY());
|
||||||
this.setMinZ(Math.min(this.getMinZ(), other.getMinZ()));
|
minZ = Math.min(this.minZ, other.getMinZ());
|
||||||
this.setMaxX(Math.max(this.getMaxX(), other.getMaxX()));
|
maxX = Math.max(this.maxX, other.getMaxX());
|
||||||
this.setMaxY(Math.max(this.getMaxY(), other.getMaxY()));
|
maxY = Math.max(this.maxY, other.getMaxY());
|
||||||
this.setMaxZ(Math.max(this.getMaxZ(), other.getMaxZ()));
|
maxZ = Math.max(this.maxZ, other.getMaxZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unionAssign(AxisAlignedBB other) {
|
public void unionAssign(AxisAlignedBB other) {
|
||||||
this.setMinX(Math.min(this.getMinX(), (int) Math.floor(other.minX)));
|
minX = Math.min(this.minX, (int) Math.floor(other.minX));
|
||||||
this.setMinY(Math.min(this.getMinY(), (int) Math.floor(other.minY)));
|
minY = Math.min(this.minY, (int) Math.floor(other.minY));
|
||||||
this.setMinZ(Math.min(this.getMinZ(), (int) Math.floor(other.minZ)));
|
minZ = Math.min(this.minZ, (int) Math.floor(other.minZ));
|
||||||
this.setMaxX(Math.max(this.getMaxX(), (int) Math.ceil(other.maxX)));
|
maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX));
|
||||||
this.setMaxY(Math.max(this.getMaxY(), (int) Math.ceil(other.maxY)));
|
maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY));
|
||||||
this.setMaxZ(Math.max(this.getMaxZ(), (int) Math.ceil(other.maxZ)));
|
maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assign(AxisAlignedBB other) {
|
public void assign(AxisAlignedBB other) {
|
||||||
this.setMinX((int) Math.floor(other.minX));
|
minX = (int) Math.floor(other.minX);
|
||||||
this.setMinY((int) Math.floor(other.minY));
|
minY = (int) Math.floor(other.minY);
|
||||||
this.setMinZ((int) Math.floor(other.minZ));
|
minZ = (int) Math.floor(other.minZ);
|
||||||
this.setMaxX((int) Math.ceil(other.maxX));
|
maxX = (int) Math.ceil(other.maxX);
|
||||||
this.setMaxY((int) Math.ceil(other.maxY));
|
maxY = (int) Math.ceil(other.maxY);
|
||||||
this.setMaxZ((int) Math.ceil(other.maxZ));
|
maxZ = (int) Math.ceil(other.maxZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assign(ReadOnlyBox other) {
|
public void assign(ImmutableBox other) {
|
||||||
this.setMinX(other.getMinX());
|
minX = other.getMinX();
|
||||||
this.setMinY(other.getMinY());
|
minY = other.getMinY();
|
||||||
this.setMinZ(other.getMinZ());
|
minZ = other.getMinZ();
|
||||||
this.setMaxX(other.getMaxX());
|
maxX = other.getMaxX();
|
||||||
this.setMaxY(other.getMaxY());
|
maxY = other.getMaxY();
|
||||||
this.setMaxZ(other.getMaxZ());
|
maxZ = other.getMaxZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -204,7 +208,7 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
ReadOnlyBox that = (ReadOnlyBox) o;
|
ImmutableBox that = (ImmutableBox) o;
|
||||||
|
|
||||||
return this.sameAs(that);
|
return this.sameAs(that);
|
||||||
}
|
}
|
||||||
|
@ -279,4 +283,132 @@ public class GridAlignedBB implements ReadOnlyBox {
|
||||||
this.maxZ = maxZ;
|
this.maxZ = maxZ;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GridAlignedBB assign(BlockPos start, BlockPos end) {
|
||||||
|
minX = start.getX();
|
||||||
|
minY = start.getY();
|
||||||
|
minZ = start.getZ();
|
||||||
|
maxX = end.getX() + 1;
|
||||||
|
maxY = end.getY() + 1;
|
||||||
|
maxZ = end.getZ() + 1;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridAlignedBB setMax(Vector3i v) {
|
||||||
|
return setMax(v.getX(), v.getY(), v.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridAlignedBB setMin(Vector3i v) {
|
||||||
|
return setMin(v.getX(), v.getY(), v.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridAlignedBB setMax(int x, int y, int z) {
|
||||||
|
maxX = x;
|
||||||
|
maxY = y;
|
||||||
|
maxZ = z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridAlignedBB setMin(int x, int y, int z) {
|
||||||
|
minX = x;
|
||||||
|
minY = y;
|
||||||
|
minZ = z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sizeX() {
|
||||||
|
return maxX - minX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sizeY() {
|
||||||
|
return maxY - minY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sizeZ() {
|
||||||
|
return maxZ - minZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean empty() {
|
||||||
|
// if any dimension has side length 0 this box contains no volume
|
||||||
|
return minX == maxX || minY == maxY || minZ == maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sameAs(ImmutableBox other) {
|
||||||
|
return minX == other.getMinX() && minY == other.getMinY() && minZ == other.getMinZ() && maxX == other.getMaxX() && maxY == other.getMaxY() && maxZ == other.getMaxZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sameAs(AxisAlignedBB other) {
|
||||||
|
return minX == Math.floor(other.minX)
|
||||||
|
&& minY == Math.floor(other.minY)
|
||||||
|
&& minZ == Math.floor(other.minZ)
|
||||||
|
&& maxX == Math.ceil(other.maxX)
|
||||||
|
&& maxY == Math.ceil(other.maxY)
|
||||||
|
&& maxZ == Math.ceil(other.maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GridAlignedBB intersect(ImmutableBox other) {
|
||||||
|
int minX = Math.max(this.minX, other.getMinX());
|
||||||
|
int minY = Math.max(this.minY, other.getMinY());
|
||||||
|
int minZ = Math.max(this.minZ, other.getMinZ());
|
||||||
|
int maxX = Math.min(this.maxX, other.getMaxX());
|
||||||
|
int maxY = Math.min(this.maxY, other.getMaxY());
|
||||||
|
int maxZ = Math.min(this.maxZ, other.getMaxZ());
|
||||||
|
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableBox union(ImmutableBox other) {
|
||||||
|
int minX = Math.min(this.minX, other.getMinX());
|
||||||
|
int minY = Math.min(this.minY, other.getMinY());
|
||||||
|
int minZ = Math.min(this.minZ, other.getMinZ());
|
||||||
|
int maxX = Math.max(this.maxX, other.getMaxX());
|
||||||
|
int maxY = Math.max(this.maxY, other.getMaxY());
|
||||||
|
int maxZ = Math.max(this.maxZ, other.getMaxZ());
|
||||||
|
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(ImmutableBox other) {
|
||||||
|
return other.getMinX() >= this.minX && other.getMaxX() <= this.maxX && other.getMinY() >= this.minY && other.getMaxY() <= this.maxY && other.getMinZ() >= this.minZ && other.getMaxZ() <= this.maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
|
return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEachContained(ICoordinateConsumer func) {
|
||||||
|
if (empty()) return;
|
||||||
|
|
||||||
|
for (int x = minX; x < maxX; x++) {
|
||||||
|
for (int y = Math.max(minY, 0); y < Math.min(maxY, 255); y++) { // clamp to world height limits
|
||||||
|
for (int z = minZ; z < maxZ; z++) {
|
||||||
|
func.consume(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AxisAlignedBB toAABB() {
|
||||||
|
return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GridAlignedBB copy() {
|
||||||
|
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "(" + minX + ", " + minY + ", " + minZ + ")->(" + maxX + ", " + maxY + ", " + maxZ + ')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import net.minecraft.world.LightType;
|
||||||
|
|
||||||
public interface ILightUpdateListener {
|
public interface ILightUpdateListener {
|
||||||
|
|
||||||
ReadOnlyBox getVolume();
|
ImmutableBox getVolume();
|
||||||
|
|
||||||
ListenerStatus status();
|
ListenerStatus status();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a light updates in a chunk the implementor cares about.
|
* Called when a light updates in a chunk the implementor cares about.
|
||||||
*/
|
*/
|
||||||
void onLightUpdate(LightProvider world, LightType type, ReadOnlyBox changed);
|
void onLightUpdate(LightProvider world, LightType type, ImmutableBox changed);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the server sends light data to the client.
|
* Called when the server sends light data to the client.
|
||||||
|
|
|
@ -4,7 +4,7 @@ import static com.jozufozu.flywheel.util.RenderUtil.isPowerOf2;
|
||||||
|
|
||||||
import net.minecraft.util.math.AxisAlignedBB;
|
import net.minecraft.util.math.AxisAlignedBB;
|
||||||
|
|
||||||
public interface ReadOnlyBox {
|
public interface ImmutableBox {
|
||||||
int getMinX();
|
int getMinX();
|
||||||
|
|
||||||
int getMinY();
|
int getMinY();
|
||||||
|
@ -38,7 +38,7 @@ public interface ReadOnlyBox {
|
||||||
return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ();
|
return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean sameAs(ReadOnlyBox other) {
|
default boolean sameAs(ImmutableBox other) {
|
||||||
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
|
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ public interface ReadOnlyBox {
|
||||||
return isPowerOf2(volume());
|
return isPowerOf2(volume());
|
||||||
}
|
}
|
||||||
|
|
||||||
default GridAlignedBB intersect(ReadOnlyBox other) {
|
default GridAlignedBB intersect(ImmutableBox other) {
|
||||||
int minX = Math.max(this.getMinX(), other.getMinX());
|
int minX = Math.max(this.getMinX(), other.getMinX());
|
||||||
int minY = Math.max(this.getMinY(), other.getMinY());
|
int minY = Math.max(this.getMinY(), other.getMinY());
|
||||||
int minZ = Math.max(this.getMinZ(), other.getMinZ());
|
int minZ = Math.max(this.getMinZ(), other.getMinZ());
|
||||||
|
@ -66,7 +66,7 @@ public interface ReadOnlyBox {
|
||||||
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
|
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
default ReadOnlyBox union(ReadOnlyBox other) {
|
default ImmutableBox union(ImmutableBox other) {
|
||||||
int minX = Math.min(this.getMinX(), other.getMinX());
|
int minX = Math.min(this.getMinX(), other.getMinX());
|
||||||
int minY = Math.min(this.getMinY(), other.getMinY());
|
int minY = Math.min(this.getMinY(), other.getMinY());
|
||||||
int minZ = Math.min(this.getMinZ(), other.getMinZ());
|
int minZ = Math.min(this.getMinZ(), other.getMinZ());
|
||||||
|
@ -77,12 +77,26 @@ public interface ReadOnlyBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
default boolean intersects(ReadOnlyBox other) {
|
default boolean intersects(ImmutableBox other) {
|
||||||
return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ());
|
return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean contains(ReadOnlyBox other) {
|
default boolean contains(int x, int y, int z) {
|
||||||
return other.getMinX() >= this.getMinX() && other.getMaxX() <= this.getMaxX() && other.getMinY() >= this.getMinY() && other.getMaxY() <= this.getMaxY() && other.getMinZ() >= this.getMinZ() && other.getMaxZ() <= this.getMaxZ();
|
return x >= getMinX()
|
||||||
|
&& x <= getMaxX()
|
||||||
|
&& y >= getMinY()
|
||||||
|
&& y <= getMaxY()
|
||||||
|
&& z >= getMinZ()
|
||||||
|
&& z <= getMaxZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean contains(ImmutableBox other) {
|
||||||
|
return other.getMinX() >= this.getMinX()
|
||||||
|
&& other.getMaxX() <= this.getMaxX()
|
||||||
|
&& other.getMinY() >= this.getMinY()
|
||||||
|
&& other.getMaxY() <= this.getMaxY()
|
||||||
|
&& other.getMinZ() >= this.getMinZ()
|
||||||
|
&& other.getMaxZ() <= this.getMaxZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean isContainedBy(GridAlignedBB other) {
|
default boolean isContainedBy(GridAlignedBB other) {
|
|
@ -14,7 +14,7 @@ import net.minecraft.world.IBlockDisplayReader;
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.LightType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps track of what chunks/sections each listener is in so we can update exactly what needs to be updated.
|
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
|
||||||
*/
|
*/
|
||||||
public class LightUpdater {
|
public class LightUpdater {
|
||||||
|
|
||||||
|
@ -33,6 +33,10 @@ public class LightUpdater {
|
||||||
provider = BasicProvider.get(world);
|
provider = BasicProvider.get(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LightProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
for (IMovingListener listener : movingListeners) {
|
for (IMovingListener listener : movingListeners) {
|
||||||
if (listener.update(provider)) {
|
if (listener.update(provider)) {
|
||||||
|
@ -50,7 +54,7 @@ public class LightUpdater {
|
||||||
if (listener instanceof IMovingListener)
|
if (listener instanceof IMovingListener)
|
||||||
movingListeners.add(((IMovingListener) listener));
|
movingListeners.add(((IMovingListener) listener));
|
||||||
|
|
||||||
ReadOnlyBox box = listener.getVolume();
|
ImmutableBox box = listener.getVolume();
|
||||||
|
|
||||||
LongSet sections = this.sections.getAndResetContainment(listener);
|
LongSet sections = this.sections.getAndResetContainment(listener);
|
||||||
LongSet chunks = this.chunks.getAndResetContainment(listener);
|
LongSet chunks = this.chunks.getAndResetContainment(listener);
|
||||||
|
@ -76,6 +80,11 @@ public class LightUpdater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeListener(ILightUpdateListener listener) {
|
||||||
|
this.sections.remove(listener);
|
||||||
|
this.chunks.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch light updates to all registered {@link ILightUpdateListener}s.
|
* Dispatch light updates to all registered {@link ILightUpdateListener}s.
|
||||||
* @param type The type of light that changed.
|
* @param type The type of light that changed.
|
||||||
|
@ -88,7 +97,7 @@ public class LightUpdater {
|
||||||
|
|
||||||
set.removeIf(l -> l.status().shouldRemove());
|
set.removeIf(l -> l.status().shouldRemove());
|
||||||
|
|
||||||
ReadOnlyBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
|
ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
|
||||||
|
|
||||||
for (ILightUpdateListener listener : set) {
|
for (ILightUpdateListener listener : set) {
|
||||||
listener.onLightUpdate(provider, type, chunkBox);
|
listener.onLightUpdate(provider, type, chunkBox);
|
||||||
|
@ -122,7 +131,7 @@ public class LightUpdater {
|
||||||
return sectionPos & 0xFFFFFFFFFFF_00000L;
|
return sectionPos & 0xFFFFFFFFFFF_00000L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<ReadOnlyBox> getAllBoxes() {
|
public Stream<ImmutableBox> getAllBoxes() {
|
||||||
return chunks.stream().map(ILightUpdateListener::getVolume);
|
return chunks.stream().map(ILightUpdateListener::getVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,165 +1,89 @@
|
||||||
package com.jozufozu.flywheel.light;
|
package com.jozufozu.flywheel.light;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_LINEAR;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_MIRRORED_REPEAT;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE0;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE4;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE_3D;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MAG_FILTER;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MIN_FILTER;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_R;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_S;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_T;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNPACK_ALIGNMENT;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNPACK_IMAGE_HEIGHT;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNPACK_ROW_LENGTH;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_IMAGES;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_PIXELS;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_ROWS;
|
|
||||||
import static org.lwjgl.opengl.GL20.GL_UNSIGNED_BYTE;
|
|
||||||
import static org.lwjgl.opengl.GL20.glActiveTexture;
|
|
||||||
import static org.lwjgl.opengl.GL20.glPixelStorei;
|
|
||||||
import static org.lwjgl.opengl.GL20.glTexImage3D;
|
|
||||||
import static org.lwjgl.opengl.GL20.glTexParameteri;
|
|
||||||
import static org.lwjgl.opengl.GL20.glTexSubImage3D;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.backend.Backend;
|
|
||||||
import com.jozufozu.flywheel.backend.gl.GlTexture;
|
|
||||||
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
|
|
||||||
import com.jozufozu.flywheel.backend.gl.versioned.RGPixelFormat;
|
|
||||||
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.world.IBlockDisplayReader;
|
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.LightType;
|
||||||
|
|
||||||
public class LightVolume {
|
public class LightVolume implements ImmutableBox, ILightUpdateListener {
|
||||||
|
|
||||||
private GridAlignedBB sampleVolume;
|
protected final GridAlignedBB box = new GridAlignedBB();
|
||||||
private GridAlignedBB textureVolume;
|
protected ByteBuffer lightData;
|
||||||
private ByteBuffer lightData;
|
|
||||||
|
|
||||||
private boolean bufferDirty;
|
public LightVolume(ImmutableBox sampleVolume) {
|
||||||
private boolean removed;
|
this.setBox(sampleVolume);
|
||||||
|
|
||||||
private final GlTexture glTexture;
|
this.lightData = MemoryUtil.memAlloc(this.box.volume() * getStride());
|
||||||
|
|
||||||
private final RGPixelFormat pixelFormat;
|
|
||||||
|
|
||||||
public LightVolume(GridAlignedBB sampleVolume) {
|
|
||||||
setSampleVolume(sampleVolume);
|
|
||||||
|
|
||||||
pixelFormat = Backend.getInstance().compat.pixelFormat;
|
|
||||||
|
|
||||||
this.glTexture = new GlTexture(GL_TEXTURE_3D);
|
|
||||||
this.lightData = MemoryUtil.memAlloc(this.textureVolume.volume() * pixelFormat.byteCount());
|
|
||||||
|
|
||||||
// allocate space for the texture
|
|
||||||
glActiveTexture(GL_TEXTURE4);
|
|
||||||
glTexture.bind();
|
|
||||||
|
|
||||||
int sizeX = textureVolume.sizeX();
|
|
||||||
int sizeY = textureVolume.sizeY();
|
|
||||||
int sizeZ = textureVolume.sizeZ();
|
|
||||||
glTexImage3D(GL_TEXTURE_3D, 0, pixelFormat.internalFormat(), sizeX, sizeY, sizeZ, 0, pixelFormat.format(), GL_UNSIGNED_BYTE, 0);
|
|
||||||
|
|
||||||
glTexture.unbind();
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSampleVolume(GridAlignedBB sampleVolume) {
|
protected void setBox(ImmutableBox box) {
|
||||||
this.sampleVolume = sampleVolume;
|
this.box.assign(box);
|
||||||
this.textureVolume = sampleVolume.copy();
|
|
||||||
this.textureVolume.nextPowerOf2Centered();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBox getTextureVolume() {
|
public short getPackedLight(int x, int y, int z) {
|
||||||
return textureVolume;
|
if (box.contains(x, y, z)) {
|
||||||
}
|
return lightData.getShort(worldPosToBufferIndex(x, y, z));
|
||||||
|
|
||||||
public ReadOnlyBox getSampleVolume() {
|
|
||||||
return sampleVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMinX() {
|
|
||||||
return textureVolume.getMinX();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMinY() {
|
|
||||||
return textureVolume.getMinY();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMinZ() {
|
|
||||||
return textureVolume.getMinZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxX() {
|
|
||||||
return textureVolume.getMaxX();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxY() {
|
|
||||||
return textureVolume.getMaxY();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxZ() {
|
|
||||||
return textureVolume.getMaxZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSizeX() {
|
|
||||||
return textureVolume.sizeX();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSizeY() {
|
|
||||||
return textureVolume.sizeY();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSizeZ() {
|
|
||||||
return textureVolume.sizeZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void move(IBlockDisplayReader world, GridAlignedBB newSampleVolume) {
|
|
||||||
if (removed) return;
|
|
||||||
|
|
||||||
if (textureVolume.contains(newSampleVolume)) {
|
|
||||||
BasicProvider basicProvider = BasicProvider.get(world);
|
|
||||||
if (newSampleVolume.intersects(sampleVolume)) {
|
|
||||||
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
|
|
||||||
sampleVolume = newSampleVolume;
|
|
||||||
|
|
||||||
copyLight(basicProvider, newArea);
|
|
||||||
} else {
|
|
||||||
sampleVolume = newSampleVolume;
|
|
||||||
initialize(world);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setSampleVolume(newSampleVolume);
|
return 0;
|
||||||
int volume = textureVolume.volume();
|
|
||||||
if (volume * 2 > lightData.capacity()) {
|
|
||||||
lightData = MemoryUtil.memRealloc(lightData, volume * 2);
|
|
||||||
}
|
|
||||||
initialize(world);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyLightUpdate(LightProvider world, LightType type, ReadOnlyBox changedVolume) {
|
public int getMinX() {
|
||||||
if (removed) return;
|
return box.getMinX();
|
||||||
|
|
||||||
if (!changedVolume.intersects(sampleVolume)) return;
|
|
||||||
changedVolume = changedVolume.intersect(sampleVolume); // compute the region contained by us that has dirty lighting data.
|
|
||||||
|
|
||||||
if (type == LightType.BLOCK) copyBlock(world, changedVolume);
|
|
||||||
else if (type == LightType.SKY) copySky(world, changedVolume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyLightPacket(LightProvider world, int chunkX, int chunkZ) {
|
public int getMinY() {
|
||||||
if (removed) return;
|
return box.getMinY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinZ() {
|
||||||
|
return box.getMinZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxX() {
|
||||||
|
return box.getMaxX();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxY() {
|
||||||
|
return box.getMaxY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxZ() {
|
||||||
|
return box.getMaxZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(LightProvider world, ImmutableBox newSampleVolume) {
|
||||||
|
if (lightData == null) return;
|
||||||
|
|
||||||
|
setBox(newSampleVolume);
|
||||||
|
int volume = box.volume();
|
||||||
|
if (volume * 2 > lightData.capacity()) {
|
||||||
|
lightData = MemoryUtil.memRealloc(lightData, volume * 2);
|
||||||
|
}
|
||||||
|
initialize(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changedVolume) {
|
||||||
|
if (lightData == null) return;
|
||||||
|
|
||||||
|
GridAlignedBB vol = changedVolume.copy();
|
||||||
|
if (!vol.intersects(getVolume())) return;
|
||||||
|
vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
|
||||||
|
|
||||||
|
if (type == LightType.BLOCK) copyBlock(world, vol);
|
||||||
|
else if (type == LightType.SKY) copySky(world, vol);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLightPacket(LightProvider world, int chunkX, int chunkZ) {
|
||||||
|
if (lightData == null) return;
|
||||||
|
|
||||||
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
|
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
|
||||||
if (!changedVolume.intersects(sampleVolume)) return;
|
if (!changedVolume.intersects(getVolume())) return;
|
||||||
changedVolume.intersectAssign(sampleVolume); // compute the region contained by us that has dirty lighting data.
|
changedVolume.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
|
||||||
|
|
||||||
copyLight(world, changedVolume);
|
copyLight(world, changedVolume);
|
||||||
}
|
}
|
||||||
|
@ -168,25 +92,20 @@ public class LightVolume {
|
||||||
* Completely (re)populate this volume with block and sky lighting data.
|
* Completely (re)populate this volume with block and sky lighting data.
|
||||||
* This is expensive and should be avoided.
|
* This is expensive and should be avoided.
|
||||||
*/
|
*/
|
||||||
public void initialize(IBlockDisplayReader world) {
|
public void initialize(LightProvider world) {
|
||||||
if (removed) return;
|
if (lightData == null) return;
|
||||||
|
|
||||||
BlockPos.Mutable pos = new BlockPos.Mutable();
|
ImmutableBox box = getVolume();
|
||||||
|
int shiftX = box.getMinX();
|
||||||
|
int shiftY = box.getMinY();
|
||||||
|
int shiftZ = box.getMinZ();
|
||||||
|
|
||||||
int shiftX = textureVolume.getMinX();
|
box.forEachContained((x, y, z) -> {
|
||||||
int shiftY = textureVolume.getMinY();
|
int blockLight = world.getLight(LightType.BLOCK, x, y, z);
|
||||||
int shiftZ = textureVolume.getMinZ();
|
int skyLight = world.getLight(LightType.SKY, x, y, z);
|
||||||
|
|
||||||
sampleVolume.forEachContained((x, y, z) -> {
|
|
||||||
pos.set(x, y, z);
|
|
||||||
|
|
||||||
int blockLight = world.getBrightness(LightType.BLOCK, pos);
|
|
||||||
int skyLight = world.getBrightness(LightType.SKY, pos);
|
|
||||||
|
|
||||||
writeLight(x - shiftX, y - shiftY, z - shiftZ, blockLight, skyLight);
|
writeLight(x - shiftX, y - shiftY, z - shiftZ, blockLight, skyLight);
|
||||||
});
|
});
|
||||||
|
|
||||||
bufferDirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -194,18 +113,16 @@ public class LightVolume {
|
||||||
*
|
*
|
||||||
* @param worldVolume the region in the world to copy data from.
|
* @param worldVolume the region in the world to copy data from.
|
||||||
*/
|
*/
|
||||||
public void copyBlock(LightProvider world, ReadOnlyBox worldVolume) {
|
public void copyBlock(LightProvider world, ImmutableBox worldVolume) {
|
||||||
int xShift = textureVolume.getMinX();
|
int xShift = box.getMinX();
|
||||||
int yShift = textureVolume.getMinY();
|
int yShift = box.getMinY();
|
||||||
int zShift = textureVolume.getMinZ();
|
int zShift = box.getMinZ();
|
||||||
|
|
||||||
worldVolume.forEachContained((x, y, z) -> {
|
worldVolume.forEachContained((x, y, z) -> {
|
||||||
int light = world.getLight(LightType.BLOCK, x, y, z);
|
int light = world.getLight(LightType.BLOCK, x, y, z);
|
||||||
|
|
||||||
writeBlock(x - xShift, y - yShift, z - zShift, light);
|
writeBlock(x - xShift, y - yShift, z - zShift, light);
|
||||||
});
|
});
|
||||||
|
|
||||||
bufferDirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -213,18 +130,16 @@ public class LightVolume {
|
||||||
*
|
*
|
||||||
* @param worldVolume the region in the world to copy data from.
|
* @param worldVolume the region in the world to copy data from.
|
||||||
*/
|
*/
|
||||||
public void copySky(LightProvider world, ReadOnlyBox worldVolume) {
|
public void copySky(LightProvider world, ImmutableBox worldVolume) {
|
||||||
int xShift = textureVolume.getMinX();
|
int xShift = box.getMinX();
|
||||||
int yShift = textureVolume.getMinY();
|
int yShift = box.getMinY();
|
||||||
int zShift = textureVolume.getMinZ();
|
int zShift = box.getMinZ();
|
||||||
|
|
||||||
worldVolume.forEachContained((x, y, z) -> {
|
worldVolume.forEachContained((x, y, z) -> {
|
||||||
int light = world.getLight(LightType.SKY, x, y, z);
|
int light = world.getLight(LightType.SKY, x, y, z);
|
||||||
|
|
||||||
writeSky(x - xShift, y - yShift, z - zShift, light);
|
writeSky(x - xShift, y - yShift, z - zShift, light);
|
||||||
});
|
});
|
||||||
|
|
||||||
bufferDirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -232,12 +147,12 @@ public class LightVolume {
|
||||||
*
|
*
|
||||||
* @param worldVolume the region in the world to copy data from.
|
* @param worldVolume the region in the world to copy data from.
|
||||||
*/
|
*/
|
||||||
public void copyLight(LightProvider world, ReadOnlyBox worldVolume) {
|
public void copyLight(LightProvider world, ImmutableBox worldVolume) {
|
||||||
BlockPos.Mutable pos = new BlockPos.Mutable();
|
BlockPos.Mutable pos = new BlockPos.Mutable();
|
||||||
|
|
||||||
int xShift = textureVolume.getMinX();
|
int xShift = box.getMinX();
|
||||||
int yShift = textureVolume.getMinY();
|
int yShift = box.getMinY();
|
||||||
int zShift = textureVolume.getMinZ();
|
int zShift = box.getMinZ();
|
||||||
|
|
||||||
worldVolume.forEachContained((x, y, z) -> {
|
worldVolume.forEachContained((x, y, z) -> {
|
||||||
pos.set(x, y, z);
|
pos.set(x, y, z);
|
||||||
|
@ -247,77 +162,80 @@ public class LightVolume {
|
||||||
|
|
||||||
writeLight(x - xShift, y - yShift, z - zShift, block, sky);
|
writeLight(x - xShift, y - yShift, z - zShift, block, sky);
|
||||||
});
|
});
|
||||||
|
|
||||||
bufferDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind() {
|
|
||||||
// just in case something goes wrong or we accidentally call this before this volume is properly disposed of.
|
|
||||||
if (lightData == null || removed) return;
|
|
||||||
|
|
||||||
GlTextureUnit.T4.makeActive();
|
|
||||||
glTexture.bind();
|
|
||||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
|
|
||||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
|
|
||||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
|
|
||||||
|
|
||||||
uploadTexture();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void uploadTexture() {
|
|
||||||
if (bufferDirty) {
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
|
|
||||||
int sizeX = textureVolume.sizeX();
|
|
||||||
int sizeY = textureVolume.sizeY();
|
|
||||||
int sizeZ = textureVolume.sizeZ();
|
|
||||||
|
|
||||||
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, pixelFormat.format(), GL_UNSIGNED_BYTE, lightData);
|
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
|
|
||||||
bufferDirty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unbind() {
|
|
||||||
glTexture.unbind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
removed = true;
|
|
||||||
glTexture.delete();
|
|
||||||
MemoryUtil.memFree(lightData);
|
MemoryUtil.memFree(lightData);
|
||||||
lightData = null;
|
lightData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLight(int x, int y, int z, int block, int sky) {
|
protected void writeLight(int x, int y, int z, int block, int sky) {
|
||||||
byte b = (byte) ((block & 0xF) << 4);
|
byte b = (byte) ((block & 0xF) << 4);
|
||||||
byte s = (byte) ((sky & 0xF) << 4);
|
byte s = (byte) ((sky & 0xF) << 4);
|
||||||
|
|
||||||
int i = posToIndex(x, y, z);
|
int i = boxPosToBufferIndex(x, y, z);
|
||||||
lightData.put(i, b);
|
lightData.put(i, b);
|
||||||
lightData.put(i + 1, s);
|
lightData.put(i + 1, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeBlock(int x, int y, int z, int block) {
|
protected void writeBlock(int x, int y, int z, int block) {
|
||||||
byte b = (byte) ((block & 0xF) << 4);
|
byte b = (byte) ((block & 0xF) << 4);
|
||||||
|
|
||||||
lightData.put(posToIndex(x, y, z), b);
|
lightData.put(boxPosToBufferIndex(x, y, z), b);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeSky(int x, int y, int z, int sky) {
|
protected void writeSky(int x, int y, int z, int sky) {
|
||||||
byte b = (byte) ((sky & 0xF) << 4);
|
byte b = (byte) ((sky & 0xF) << 4);
|
||||||
|
|
||||||
lightData.put(posToIndex(x, y, z) + 1, b);
|
lightData.put(boxPosToBufferIndex(x, y, z) + 1, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int posToIndex(int x, int y, int z) {
|
protected int worldPosToBufferIndex(int x, int y, int z) {
|
||||||
return (x + textureVolume.sizeX() * (y + z * textureVolume.sizeY())) * pixelFormat.byteCount();
|
x -= box.getMinX();
|
||||||
|
y -= box.getMinY();
|
||||||
|
z -= box.getMinZ();
|
||||||
|
return boxPosToBufferIndex(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int boxPosToBufferIndex(int x, int y, int z) {
|
||||||
|
return (x + box.sizeX() * (y + z * box.sizeY())) * getStride();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The stride of the texels, in bytes.
|
||||||
|
*/
|
||||||
|
protected int getStride() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableBox getVolume() {
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenerStatus status() {
|
||||||
|
return ListenerStatus.OKAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int unpackBlock(short packed) {
|
||||||
|
return (packed >> 4) & 0xF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int unpackSky(short packed) {
|
||||||
|
return (packed >> 12) & 0xF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte packLight(byte block, byte sky) {
|
||||||
|
return (byte) (block | (sky << 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int unpackBlock(byte packed) {
|
||||||
|
return packed & 0xF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int unpackSky(byte packed) {
|
||||||
|
return (packed >> 4) & 0xF;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.jozufozu.flywheel.light;
|
package com.jozufozu.flywheel.light;
|
||||||
|
|
||||||
import java.util.AbstractCollection;
|
import java.util.AbstractCollection;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
@ -57,6 +56,24 @@ public class WeakContainmentMultiMap<T> extends AbstractCollection<T> {
|
||||||
forward.computeIfAbsent(sectionPos, $ -> new WeakHashSet<>()).add(listener);
|
forward.computeIfAbsent(sectionPos, $ -> new WeakHashSet<>()).add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
LongSet containmentSet = reverse.remove(o);
|
||||||
|
|
||||||
|
if (containmentSet != null) {
|
||||||
|
containmentSet.forEach((LongConsumer) l -> {
|
||||||
|
WeakHashSet<T> listeners = forward.get(l);
|
||||||
|
|
||||||
|
if (listeners != null) listeners.remove(o);
|
||||||
|
});
|
||||||
|
|
||||||
|
containmentSet.clear();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return reverse.keySet().iterator();
|
return reverse.keySet().iterator();
|
||||||
|
|
Loading…
Reference in a new issue