Skip to content

Commit

Permalink
Entity JSON light loader, refactor CachedBlockLight
Browse files Browse the repository at this point in the history
- Entity JSON light is a simple light without predicate
- CachedBlockLight -> FloodFillBlockLight, make all values consistent
- `blaze` and `glow_squid` light in canvas/default resource pack
- EntityLightProvider API (unused for now)
  • Loading branch information
spiralhalo committed Jan 7, 2024
1 parent cde4831 commit f5f78da
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 98 deletions.
23 changes: 11 additions & 12 deletions src/main/java/grondag/canvas/light/api/BlockLight.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@

package grondag.canvas.light.api;

import grondag.canvas.light.api.impl.FloodFillBlockLight;

/**
* BlockLight API draft.
*
* <p>Similar to Material, this needs to be constructed by a factory provided by implementation.
*/
public interface BlockLight {
/**
Expand All @@ -35,34 +35,33 @@ public interface BlockLight {
* <p>Typical value is in range 0-15. Value outside of this range is implementation-specific.
*
* <p>In JSON format, defaults to the vanilla registered light level when missing.
* Importantly, light level is attached to blocks, so for fluid states
* (not their block counterpart) the default is always 0.
*
* @return Raw light level value
* Importantly, light level is attached to block states. Fluid states will attempt
* to default to their block state counterpart.
*/
float lightLevel();

/**
* Red intensity. Behavior of values outside of range 0-1 is undefined.
* In JSON format, defaults to 0 when missing.
*
* @return Raw red intensity
*/
float red();

/**
* Green intensity. Behavior of values outside of range 0-1 is undefined.
* In JSON format, defaults to 0 when missing.
*
* @return Raw green intensity
*/
float green();

/**
* Blue intensity. Behavior of values outside of range 0-1 is undefined.
* In JSON format, defaults to 0 when missing.
*
* @return Raw blue intensity
*/
float blue();

/**
* Constructs an implementation consistent instance of BlockLight.
*/
static BlockLight of(float lightLevel, float red, float green, float blue) {
return new FloodFillBlockLight(lightLevel, red, green, blue, true);
}
}
31 changes: 31 additions & 0 deletions src/main/java/grondag/canvas/light/api/EntityLightProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This file is part of Canvas Renderer and is licensed to the project under
* terms that are compatible with the GNU Lesser General Public License.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership and licensing.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package grondag.canvas.light.api;

/**
* Implement this interface to treat a particular entity as a dynamic light source.
*/
public interface EntityLightProvider {
/**
* The block light value of this entity in the current frame.
*/
BlockLight getBlockLight();
}
137 changes: 62 additions & 75 deletions src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,36 +32,41 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;

import grondag.canvas.CanvasMod;
import grondag.canvas.light.api.BlockLight;
import grondag.canvas.light.color.LightOp;

public class BlockLightLoader {
public static BlockLightLoader INSTANCE;
private static final CachedBlockLight DEFAULT_LIGHT = new CachedBlockLight(0f, 0f, 0f, 0f, false);
public static final FloodFillBlockLight DEFAULT_LIGHT = new FloodFillBlockLight(0f, 0f, 0f, 0f, false);

public final IdentityHashMap<BlockState, CachedBlockLight> blockLights = new IdentityHashMap<>();
public final IdentityHashMap<FluidState, CachedBlockLight> fluidLights = new IdentityHashMap<>();
public static final IdentityHashMap<BlockState, FloodFillBlockLight> BLOCK_LIGHTS = new IdentityHashMap<>();
public static final IdentityHashMap<FluidState, FloodFillBlockLight> FLUID_LIGHTS = new IdentityHashMap<>();
public static final IdentityHashMap<EntityType<?>, FloodFillBlockLight> ENTITY_LIGHTS = new IdentityHashMap<>();

public static void reload(ResourceManager manager) {
INSTANCE = new BlockLightLoader();
BLOCK_LIGHTS.clear();
FLUID_LIGHTS.clear();
ENTITY_LIGHTS.clear();

for (Block block : BuiltInRegistries.BLOCK) {
INSTANCE.loadBlock(manager, block);
loadBlock(manager, block);
}

for (Fluid fluid : BuiltInRegistries.FLUID) {
INSTANCE.loadFluid(manager, fluid);
loadFluid(manager, fluid);
}

for (EntityType<?> type : BuiltInRegistries.ENTITY_TYPE) {
loadEntity(manager, type);
}
}

private void loadBlock(ResourceManager manager, Block block) {
private static void loadBlock(ResourceManager manager, Block block) {
final ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(block);

final ResourceLocation id = new ResourceLocation(blockId.getNamespace(), "lights/block/" + blockId.getPath() + ".json");
Expand All @@ -70,14 +75,14 @@ private void loadBlock(ResourceManager manager, Block block) {
final var res = manager.getResource(id);

if (res.isPresent()) {
deserialize(block.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), blockLights);
deserialize(block.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), BLOCK_LIGHTS);
}
} catch (final Exception e) {
CanvasMod.LOG.info("Unable to load block light map " + id.toString() + " due to exception " + e.toString());
}
}

private void loadFluid(ResourceManager manager, Fluid fluid) {
private static void loadFluid(ResourceManager manager, Fluid fluid) {
final ResourceLocation blockId = BuiltInRegistries.FLUID.getKey(fluid);

final ResourceLocation id = new ResourceLocation(blockId.getNamespace(), "lights/fluid/" + blockId.getPath() + ".json");
Expand All @@ -86,20 +91,35 @@ private void loadFluid(ResourceManager manager, Fluid fluid) {
final var res = manager.getResource(id);

if (res.isPresent()) {
deserialize(fluid.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), fluidLights);
deserialize(fluid.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), FLUID_LIGHTS);
}
} catch (final Exception e) {
CanvasMod.LOG.info("Unable to load fluid light map " + id.toString() + " due to exception " + e.toString());
}
}

public static <T extends StateHolder<?, ?>> void deserialize(List<T> states, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap<T, CachedBlockLight> map) {
private static void loadEntity(ResourceManager manager, EntityType<?> entityType) {
final ResourceLocation entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entityType);
final ResourceLocation id = new ResourceLocation(entityId.getNamespace(), "lights/entity/" + entityId.getPath() + ".json");

try {
final var res = manager.getResource(id);

if (res.isPresent()) {
deserialize(entityType, id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), ENTITY_LIGHTS);
}
} catch (final Exception e) {
CanvasMod.LOG.info("Unable to load block light map " + id.toString() + " due to exception " + e.toString());
}
}

private static <T extends StateHolder<?, ?>> void deserialize(List<T> states, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap<T, FloodFillBlockLight> map) {
try {
final JsonObject json = GsonHelper.parse(reader);
final String idString = idForLog.toString();

final CachedBlockLight globalDefaultLight = DEFAULT_LIGHT;
final CachedBlockLight defaultLight;
final FloodFillBlockLight globalDefaultLight = DEFAULT_LIGHT;
final FloodFillBlockLight defaultLight;

if (json.has("defaultLight")) {
defaultLight = loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight);
Expand All @@ -119,7 +139,7 @@ private void loadFluid(ResourceManager manager, Fluid fluid) {
}

for (final T state : states) {
CachedBlockLight result = defaultLight;
FloodFillBlockLight result = defaultLight;

if (!result.levelIsSet && state instanceof BlockState blockState) {
result = result.withLevel(blockState.getLightEmission());
Expand All @@ -139,7 +159,27 @@ private void loadFluid(ResourceManager manager, Fluid fluid) {
}
}

public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaultValue) {
private static <T> void deserialize(T type, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap<T, FloodFillBlockLight> map) {
try {
final JsonObject json = GsonHelper.parse(reader);
final FloodFillBlockLight globalDefaultLight = BlockLightLoader.DEFAULT_LIGHT;
final FloodFillBlockLight result;

if (json.has("defaultLight")) {
result = BlockLightLoader.loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight);
} else {
result = BlockLightLoader.loadLight(json, globalDefaultLight);
}

if (!result.equals(globalDefaultLight) && result.levelIsSet) {
map.put(type, result);
}
} catch (final Exception e) {
CanvasMod.LOG.warn("Unable to load lights for " + idForLog.toString() + " due to unhandled exception:", e);
}
}

private static FloodFillBlockLight loadLight(JsonObject obj, FloodFillBlockLight defaultValue) {
if (obj == null) {
return defaultValue;
}
Expand All @@ -149,71 +189,18 @@ public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaul
final var greenObj = obj.get("green");
final var blueObj = obj.get("blue");

final float lightLevel = lightLevelObj == null ? defaultValue.lightLevel() : lightLevelObj.getAsFloat();
final float defaultLightLevel = defaultValue.levelIsSet ? defaultValue.lightLevel() : 15f;
final float lightLevel = lightLevelObj == null ? defaultLightLevel : lightLevelObj.getAsFloat();
final float red = redObj == null ? defaultValue.red() : redObj.getAsFloat();
final float green = greenObj == null ? defaultValue.green() : greenObj.getAsFloat();
final float blue = blueObj == null ? defaultValue.blue() : blueObj.getAsFloat();
final boolean levelIsSet = lightLevelObj == null ? defaultValue.levelIsSet() : true;
final var result = new CachedBlockLight(lightLevel, red, green, blue, levelIsSet);
final boolean levelIsSet = lightLevelObj != null || defaultValue.levelIsSet;
final var result = new FloodFillBlockLight(lightLevel, red, green, blue, levelIsSet);

if (result.equals(defaultValue)) {
return defaultValue;
} else {
return result;
}
}

private static int clampLight(float light) {
return org.joml.Math.clamp(0, 15, Math.round(light));
}

public static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value, boolean levelIsSet) implements BlockLight {
CachedBlockLight(float lightLevel, float red, float green, float blue, boolean levelIsSet) {
this(lightLevel, red, green, blue, computeValue(lightLevel, red, green, blue), levelIsSet);
}

public CachedBlockLight withLevel(float lightEmission) {
if (this.lightLevel == lightEmission && this.levelIsSet) {
return this;
} else {
return new CachedBlockLight(lightEmission, red, green, blue, true);
}
}

static short computeValue(float lightLevel, float red, float green, float blue) {
final int blockRadius = lightLevel == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(lightLevel));
return LightOp.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (obj == null || getClass() != obj.getClass()) {
return false;
}

CachedBlockLight that = (CachedBlockLight) obj;

if (that.lightLevel != lightLevel) {
return false;
}

if (that.red != red) {
return false;
}

if (that.green != green) {
return false;
}

if (that.blue != blue) {
return false;
}

return value == that.value;
}
}
}
Loading

0 comments on commit f5f78da

Please sign in to comment.