Something renders! And introduce mem leaks, missing texture color, and inverted lights! What a great set of features!
This commit is contained in:
@@ -1,327 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2022 James Seibel
|
||||
*
|
||||
* 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, version 3.
|
||||
*
|
||||
* 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 Lesser 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.common.wrappers.block;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import com.seibel.lod.common.wrappers.McObjectConverter;
|
||||
import com.seibel.lod.common.wrappers.chunk.ChunkWrapper;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.enums.ELodDirection;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.util.ColorUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockDetailWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.*;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.FlowerBlock;
|
||||
import net.minecraft.world.level.block.LeavesBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.RotatedPillarBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
#if POST_MC_1_19
|
||||
import net.minecraft.util.RandomSource;
|
||||
#endif
|
||||
|
||||
/*-- WARN: This class should NEVER hold reference to anything large,
|
||||
as this is never dealloc until the end of runtime!! --*/
|
||||
public class BlockDetailWrapper extends IBlockDetailWrapper
|
||||
{
|
||||
public static final int FLOWER_COLOR_SCALE = 5;
|
||||
|
||||
#if PRE_MC_1_19
|
||||
public static final Random random = new Random(0);
|
||||
#else
|
||||
public static final RandomSource random = RandomSource.create();
|
||||
#endif
|
||||
|
||||
enum ColorMode {
|
||||
Default,
|
||||
Flower,
|
||||
Leaves;
|
||||
static ColorMode getColorMode(Block b) {
|
||||
if (b instanceof LeavesBlock) return Leaves;
|
||||
if (b instanceof FlowerBlock) return Flower;
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
//TODO: Perhaps make this not just use the first frame?
|
||||
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode) {
|
||||
|
||||
int count = 0;
|
||||
double alpha = 0;
|
||||
double red = 0;
|
||||
double green = 0;
|
||||
double blue = 0;
|
||||
int tempColor;
|
||||
|
||||
{
|
||||
// textures normally use u and v instead of x and y
|
||||
for (int u = 0; u < texture.getWidth(); u++)
|
||||
{
|
||||
for (int v = 0; v < texture.getHeight(); v++)
|
||||
{
|
||||
//note: Minecraft color format is: 0xAA BB GG RR
|
||||
//________ DH mod color format is: 0xAA RR GG BB
|
||||
//OpenGL RGBA format native order: 0xRR GG BB AA
|
||||
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
|
||||
tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, 0, u, v);
|
||||
|
||||
double r = ((tempColor & 0x000000FF) )/255.;
|
||||
double g = ((tempColor & 0x0000FF00) >>> 8)/255.;
|
||||
double b = ((tempColor & 0x00FF0000) >>> 16)/255.;
|
||||
double a = ((tempColor & 0xFF000000) >>> 24)/255.;
|
||||
int scale = 1;
|
||||
|
||||
if (colorMode == ColorMode.Leaves) {
|
||||
r *= a;
|
||||
g *= a;
|
||||
b *= a;
|
||||
a = 1.;
|
||||
} else if (a==0.) {
|
||||
continue;
|
||||
} else if (colorMode == ColorMode.Flower && (g+0.1<b || g+0.1<r)) {
|
||||
scale = FLOWER_COLOR_SCALE;
|
||||
}
|
||||
|
||||
count += scale;
|
||||
alpha += a*a*scale;
|
||||
red += r*r*scale;
|
||||
green += g*g*scale;
|
||||
blue += b*b*scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
// this block is entirely transparent
|
||||
tempColor = ColorUtil.rgbToInt(255,255,0,255);
|
||||
else
|
||||
{
|
||||
// determine the average color
|
||||
tempColor = ColorUtil.rgbToInt(
|
||||
(int) (Math.sqrt(alpha/count)*255.),
|
||||
(int) (Math.sqrt(red / count)*255.),
|
||||
(int) (Math.sqrt(green / count)*255.),
|
||||
(int) (Math.sqrt(blue / count)*255.));
|
||||
}
|
||||
// TODO: Remove this when transparency is added!
|
||||
double colorAlpha = ColorUtil.getAlpha(tempColor)/255.;
|
||||
tempColor = ColorUtil.rgbToInt(
|
||||
ColorUtil.getAlpha(tempColor),
|
||||
(int)(ColorUtil.getRed(tempColor) * colorAlpha),
|
||||
(int)(ColorUtil.getGreen(tempColor) * colorAlpha),
|
||||
(int)(ColorUtil.getBlue(tempColor) * colorAlpha)
|
||||
);
|
||||
return tempColor;
|
||||
}
|
||||
|
||||
private static final Block[] BLOCK_TO_AVOID = {Blocks.AIR, Blocks.CAVE_AIR, Blocks.BARRIER};
|
||||
|
||||
private static final Direction[] DIRECTION_ORDER = {Direction.UP, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.SOUTH, Direction.DOWN};
|
||||
|
||||
private static boolean isBlockToBeAvoid(Block b) {
|
||||
for (Block bta : BLOCK_TO_AVOID)
|
||||
if (bta==b) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
final BlockState state;
|
||||
|
||||
//boolean isShapeResolved = false;
|
||||
//^ Shapes no longer lazy resolved due to memory leaks from needing
|
||||
// to keep a reference to the block getter
|
||||
|
||||
boolean[] dontOccludeFaces = null;
|
||||
boolean noCollision = false;
|
||||
boolean noFullFace = false;
|
||||
|
||||
boolean isColorResolved = false;
|
||||
int baseColor = 0; //TODO: Impl per-face color
|
||||
boolean needShade = true;
|
||||
boolean needPostTinting = false;
|
||||
int tintIndex = 0;
|
||||
|
||||
public static BlockDetailWrapper NULL_BLOCK_DETAIL = new BlockDetailWrapper();
|
||||
|
||||
public BlockDetailWrapper(BlockState state, BlockPos pos, LevelReader getter) {
|
||||
this.state = state;
|
||||
resolveShapes(getter, pos);
|
||||
//ApiShared.LOGGER.info("Created BlockDetailWrapper for blockstate {} at {}", state, pos);
|
||||
}
|
||||
|
||||
private BlockDetailWrapper() {
|
||||
this.state = null;
|
||||
}
|
||||
|
||||
static BlockDetailWrapper make(BlockState bs, BlockPos pos, LevelReader getter) {
|
||||
if(!bs.getFluidState().isEmpty()) { // Is a fluidBlock
|
||||
if (isBlockToBeAvoid(bs.getBlock())) return NULL_BLOCK_DETAIL;
|
||||
if (bs.isAir()) return NULL_BLOCK_DETAIL;
|
||||
return new BlockDetailWrapper(bs, pos, getter);
|
||||
} else {
|
||||
if (bs.getRenderShape() != RenderShape.MODEL) return NULL_BLOCK_DETAIL;
|
||||
if (isBlockToBeAvoid(bs.getBlock())) return NULL_BLOCK_DETAIL;
|
||||
return new BlockDetailWrapper(bs, pos, getter);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveShapes(LevelReader sampleGetter, BlockPos samplePos) {
|
||||
//if (isShapeResolved) return;
|
||||
if (state.getFluidState().isEmpty()) {
|
||||
noCollision = state.getCollisionShape(sampleGetter, samplePos).isEmpty();
|
||||
dontOccludeFaces = new boolean[6];
|
||||
if (state.canOcclude()) {
|
||||
/* FIXME: Figure out how or if needed to impl per-face culling?
|
||||
for (Direction dir : Direction.values()) {
|
||||
dontOccludeFaces[McObjectConverter.Convert(dir).ordinal()]
|
||||
= state.getFaceOcclusionShape(sampleGetter, samplePos, dir).isEmpty();
|
||||
}*/
|
||||
} else {
|
||||
Arrays.fill(dontOccludeFaces, true);
|
||||
}
|
||||
|
||||
VoxelShape voxelShape = state.getShape(sampleGetter, samplePos);
|
||||
if (voxelShape.isEmpty()) {
|
||||
noFullFace = true;
|
||||
} else {
|
||||
AABB bbox = voxelShape.bounds();
|
||||
double xWidth = (bbox.maxX - bbox.minX);
|
||||
double yWidth = (bbox.maxY - bbox.minY);
|
||||
double zWidth = (bbox.maxZ - bbox.minZ);
|
||||
noFullFace = xWidth < 1 && zWidth < 1 && yWidth < 1;
|
||||
}
|
||||
} else { // Liquid Block
|
||||
dontOccludeFaces = new boolean[6];
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveColors() {
|
||||
if (isColorResolved) return;
|
||||
if (state.getFluidState().isEmpty()) {
|
||||
List<BakedQuad> quads = null;
|
||||
for (Direction direction : DIRECTION_ORDER)
|
||||
{
|
||||
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
|
||||
getBlockModel(state).getQuads(state, direction, random);
|
||||
if (!quads.isEmpty() &&
|
||||
!(state.getBlock() instanceof RotatedPillarBlock && direction == Direction.UP))
|
||||
break;
|
||||
};
|
||||
if (quads == null || quads.isEmpty()) {
|
||||
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
|
||||
getBlockModel(state).getQuads(state, null, random);
|
||||
}
|
||||
if (quads != null && !quads.isEmpty()) {
|
||||
needPostTinting = quads.get(0).isTinted();
|
||||
needShade = quads.get(0).isShade();
|
||||
tintIndex = quads.get(0).getTintIndex();
|
||||
baseColor = calculateColorFromTexture(
|
||||
#if PRE_MC_1_17_1 quads.get(0).sprite,
|
||||
#else quads.get(0).getSprite(), #endif
|
||||
ColorMode.getColorMode(state.getBlock()));
|
||||
} else { // Backup method.
|
||||
needPostTinting = false;
|
||||
needShade = false;
|
||||
tintIndex = 0;
|
||||
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(state),
|
||||
ColorMode.getColorMode(state.getBlock()));
|
||||
}
|
||||
} else { // Liquid Block
|
||||
needPostTinting = true;
|
||||
needShade = false;
|
||||
tintIndex = 0;
|
||||
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(state),
|
||||
ColorMode.getColorMode(state.getBlock()));
|
||||
}
|
||||
isColorResolved = true;
|
||||
}
|
||||
|
||||
private BlockAndTintGetter wrapColorResolver(LevelReader level) {
|
||||
int blendDistance = Config.Client.Graphics.Quality.lodBiomeBlending.get();
|
||||
if (blendDistance == 0) {
|
||||
return new TintGetterOverrideFast(level);
|
||||
} else {
|
||||
return new TintGetterOverrideSmooth(level, blendDistance);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAndResolveFaceColor(ELodDirection dir, IChunkWrapper chunk, DHBlockPos blockPos)
|
||||
{
|
||||
// FIXME: impl per-face colors
|
||||
resolveColors();
|
||||
if (!needPostTinting) return baseColor;
|
||||
int tintColor = Minecraft.getInstance().getBlockColors()
|
||||
.getColor(state, wrapColorResolver(((ChunkWrapper)chunk).getColorResolver()),
|
||||
McObjectConverter.Convert(blockPos), tintIndex);
|
||||
if (tintColor == -1) return baseColor;
|
||||
return ColorUtil.multiplyARGBwithRGB(baseColor, tintColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFaceCullingFor(ELodDirection dir)
|
||||
{
|
||||
//resolveShapes();
|
||||
return !dontOccludeFaces[dir.ordinal()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNoCollision()
|
||||
{
|
||||
//resolveShapes();
|
||||
return noCollision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean noFaceIsFullFace()
|
||||
{
|
||||
//resolveShapes();
|
||||
return noFullFace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialize()
|
||||
{
|
||||
// FIXME: Impl this for the blockState Storage stuff
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSame(IBlockDetailWrapper iBlockDetail)
|
||||
{
|
||||
return ((BlockDetailWrapper)iBlockDetail).state.getBlock().equals(state.getBlock());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "BlockDetail{" + state + "}";
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class BlockStateWrapper implements IBlockStateWrapper {
|
||||
}
|
||||
|
||||
public final BlockState blockState;
|
||||
private BlockStateWrapper(BlockState blockState) {
|
||||
BlockStateWrapper(BlockState blockState) {
|
||||
this.blockState = blockState;
|
||||
}
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.seibel.lod.common.wrappers.block.cache;
|
||||
|
||||
import com.seibel.lod.common.wrappers.block.BiomeWrapper;
|
||||
import com.seibel.lod.common.wrappers.world.ClientLevelWrapper;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ClientBlockDetailMap {
|
||||
private final ConcurrentHashMap<BlockState, ClientBlockStateCache> blockCache = new ConcurrentHashMap<>();
|
||||
//private final ConcurrentHashMap<#if PRE_MC_1_18_2 Biome #else Holder<Biome> #endif, Biome> biomeMap = new ConcurrentHashMap<>();
|
||||
private final ClientLevelWrapper level;
|
||||
public ClientBlockDetailMap(ClientLevelWrapper level) { this.level = level; }
|
||||
|
||||
public ClientBlockStateCache getBlockStateData(BlockState state, DHBlockPos pos) { //TODO: Allow a per pos unique setting
|
||||
return blockCache.computeIfAbsent(state, (s) -> new ClientBlockStateCache(s, level, new DHBlockPos(0,0,0)));
|
||||
}
|
||||
|
||||
public void clear() { blockCache.clear(); }
|
||||
|
||||
public int getColor(BlockState state, BiomeWrapper biome, DHBlockPos pos) {
|
||||
return getBlockStateData(state, pos).getAndResolveFaceColor(biome);
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package com.seibel.lod.common.wrappers.block.cache;
|
||||
|
||||
import com.seibel.lod.common.wrappers.McObjectConverter;
|
||||
import com.seibel.lod.common.wrappers.block.*;
|
||||
import com.seibel.lod.core.config.Config;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.util.ColorUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.FlowerBlock;
|
||||
import net.minecraft.world.level.block.LeavesBlock;
|
||||
import net.minecraft.world.level.block.RotatedPillarBlock;
|
||||
#if POST_MC_1_19
|
||||
import net.minecraft.util.RandomSource;
|
||||
#endif
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class ClientBlockStateCache {
|
||||
|
||||
#if PRE_MC_1_19
|
||||
public static final Random random = new Random(0);
|
||||
#else
|
||||
public static final RandomSource random = RandomSource.create();
|
||||
#endif
|
||||
|
||||
public final BlockState state;
|
||||
public final LevelReader level;
|
||||
public final BlockPos pos;
|
||||
public ClientBlockStateCache(BlockState blockState, IClientLevelWrapper samplingLevel, DHBlockPos samplingPos) {
|
||||
state = blockState;
|
||||
level = (LevelReader) samplingLevel.unwrapLevel();
|
||||
pos = McObjectConverter.Convert(samplingPos);
|
||||
resolveColors();
|
||||
}
|
||||
|
||||
boolean isColorResolved = false;
|
||||
int baseColor = 0; //TODO: Impl per-face color
|
||||
boolean needShade = true;
|
||||
boolean needPostTinting = false;
|
||||
int tintIndex = 0;
|
||||
|
||||
|
||||
public static final int FLOWER_COLOR_SCALE = 5;
|
||||
|
||||
enum ColorMode {
|
||||
Default,
|
||||
Flower,
|
||||
Leaves;
|
||||
static ColorMode getColorMode(Block b) {
|
||||
if (b instanceof LeavesBlock) return Leaves;
|
||||
if (b instanceof FlowerBlock) return Flower;
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
//TODO: Perhaps make this not just use the first frame?
|
||||
private static int calculateColorFromTexture(TextureAtlasSprite texture, ColorMode colorMode) {
|
||||
int count = 0;
|
||||
double alpha = 0;
|
||||
double red = 0;
|
||||
double green = 0;
|
||||
double blue = 0;
|
||||
int tempColor;
|
||||
{
|
||||
// textures normally use u and v instead of x and y
|
||||
for (int u = 0; u < texture.getWidth(); u++)
|
||||
{
|
||||
for (int v = 0; v < texture.getHeight(); v++)
|
||||
{
|
||||
//note: Minecraft color format is: 0xAA BB GG RR
|
||||
//________ DH mod color format is: 0xAA RR GG BB
|
||||
//OpenGL RGBA format native order: 0xRR GG BB AA
|
||||
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
|
||||
tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, 0, u, v);
|
||||
|
||||
double r = ((tempColor & 0x000000FF) )/255.;
|
||||
double g = ((tempColor & 0x0000FF00) >>> 8)/255.;
|
||||
double b = ((tempColor & 0x00FF0000) >>> 16)/255.;
|
||||
double a = ((tempColor & 0xFF000000) >>> 24)/255.;
|
||||
int scale = 1;
|
||||
|
||||
if (colorMode == ColorMode.Leaves) {
|
||||
r *= a;
|
||||
g *= a;
|
||||
b *= a;
|
||||
a = 1.;
|
||||
} else if (a==0.) {
|
||||
continue;
|
||||
} else if (colorMode == ColorMode.Flower && (g+0.1<b || g+0.1<r)) {
|
||||
scale = FLOWER_COLOR_SCALE;
|
||||
}
|
||||
|
||||
count += scale;
|
||||
alpha += a*a*scale;
|
||||
red += r*r*scale;
|
||||
green += g*g*scale;
|
||||
blue += b*b*scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
// this block is entirely transparent
|
||||
tempColor = ColorUtil.rgbToInt(255,255,0,255);
|
||||
else
|
||||
{
|
||||
// determine the average color
|
||||
tempColor = ColorUtil.rgbToInt(
|
||||
(int) (Math.sqrt(alpha/count)*255.),
|
||||
(int) (Math.sqrt(red / count)*255.),
|
||||
(int) (Math.sqrt(green / count)*255.),
|
||||
(int) (Math.sqrt(blue / count)*255.));
|
||||
}
|
||||
// TODO: Remove this when transparency is added!
|
||||
double colorAlpha = ColorUtil.getAlpha(tempColor)/255.;
|
||||
tempColor = ColorUtil.rgbToInt(
|
||||
ColorUtil.getAlpha(tempColor),
|
||||
(int)(ColorUtil.getRed(tempColor) * colorAlpha),
|
||||
(int)(ColorUtil.getGreen(tempColor) * colorAlpha),
|
||||
(int)(ColorUtil.getBlue(tempColor) * colorAlpha)
|
||||
);
|
||||
return tempColor;
|
||||
}
|
||||
private static final Direction[] DIRECTION_ORDER = {Direction.UP, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.SOUTH, Direction.DOWN};
|
||||
|
||||
private void resolveColors() {
|
||||
if (isColorResolved) return;
|
||||
if (state.getFluidState().isEmpty()) {
|
||||
List<BakedQuad> quads = null;
|
||||
for (Direction direction : DIRECTION_ORDER)
|
||||
{
|
||||
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
|
||||
getBlockModel(state).getQuads(state, direction, random);
|
||||
if (!quads.isEmpty() &&
|
||||
!(state.getBlock() instanceof RotatedPillarBlock && direction == Direction.UP))
|
||||
break;
|
||||
};
|
||||
if (quads == null || quads.isEmpty()) {
|
||||
quads = Minecraft.getInstance().getModelManager().getBlockModelShaper().
|
||||
getBlockModel(state).getQuads(state, null, random);
|
||||
}
|
||||
if (quads != null && !quads.isEmpty()) {
|
||||
needPostTinting = quads.get(0).isTinted();
|
||||
needShade = quads.get(0).isShade();
|
||||
tintIndex = quads.get(0).getTintIndex();
|
||||
baseColor = calculateColorFromTexture(
|
||||
#if PRE_MC_1_17_1 quads.get(0).sprite,
|
||||
#else quads.get(0).getSprite(), #endif
|
||||
ColorMode.getColorMode(state.getBlock()));
|
||||
} else { // Backup method.
|
||||
needPostTinting = false;
|
||||
needShade = false;
|
||||
tintIndex = 0;
|
||||
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(state),
|
||||
ColorMode.getColorMode(state.getBlock()));
|
||||
}
|
||||
} else { // Liquid Block
|
||||
needPostTinting = true;
|
||||
needShade = false;
|
||||
tintIndex = 0;
|
||||
baseColor = calculateColorFromTexture(Minecraft.getInstance().getModelManager().getBlockModelShaper().getParticleIcon(state),
|
||||
ColorMode.getColorMode(state.getBlock()));
|
||||
}
|
||||
isColorResolved = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private BlockAndTintGetter wrapColorResolver(LevelReader level) {
|
||||
int blendDistance = Config.Client.Graphics.Quality.lodBiomeBlending.get();
|
||||
if (blendDistance == 0) {
|
||||
return new TintGetterOverrideFast(level);
|
||||
} else {
|
||||
return new TintGetterOverrideSmooth(level, blendDistance);
|
||||
}
|
||||
}
|
||||
|
||||
public int getAndResolveFaceColor(BiomeWrapper biome)
|
||||
{
|
||||
// FIXME: impl per-face colors
|
||||
if (!needPostTinting) return baseColor;
|
||||
int tintColor = Minecraft.getInstance().getBlockColors()
|
||||
.getColor(state, wrapColorResolver(level), pos, tintIndex); //FIXME: Use biome? Hack the ColorResolver?
|
||||
if (tintColor == -1) return baseColor;
|
||||
return ColorUtil.multiplyARGBwithRGB(baseColor, tintColor);
|
||||
}
|
||||
|
||||
}
|
||||
+14
-19
@@ -17,30 +17,25 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.common.wrappers.block;
|
||||
package com.seibel.lod.common.wrappers.block.cache;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import com.seibel.lod.common.wrappers.world.ServerLevelWrapper;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
public class BlockDetailMap
|
||||
|
||||
public class ServerBlockDetailMap
|
||||
{
|
||||
|
||||
private static ConcurrentHashMap<BlockState, BlockDetailWrapper> map = new ConcurrentHashMap<BlockState, BlockDetailWrapper>();
|
||||
|
||||
private BlockDetailMap() {}
|
||||
|
||||
public static BlockDetailWrapper getOrMakeBlockDetailCache(BlockState bs, BlockPos pos, LevelReader getter) {
|
||||
if (!bs.getFluidState().isEmpty()) {
|
||||
bs = bs.getFluidState().createLegacyBlock();
|
||||
}
|
||||
BlockDetailWrapper cache = map.get(bs);
|
||||
if (cache != null) return cache;
|
||||
cache = BlockDetailWrapper.make(bs, pos, getter);
|
||||
//ApiShared.LOGGER.info("New blockDetail cache for {} to {} ", bs, cache);
|
||||
BlockDetailWrapper cacheCAS = map.putIfAbsent(bs, cache);
|
||||
return cacheCAS==null ? cache : cacheCAS;
|
||||
private final ConcurrentHashMap<BlockState, ServerBlockStateCache> blockCache = new ConcurrentHashMap<>();
|
||||
//private final ConcurrentHashMap<#if PRE_MC_1_18_2 Biome #else Holder<Biome> #endif, Biome> biomeMap = new ConcurrentHashMap<>();
|
||||
private final ServerLevelWrapper level;
|
||||
public ServerBlockDetailMap(ServerLevelWrapper level) { this.level = level; }
|
||||
|
||||
public ServerBlockStateCache getBlockStateData(BlockState state, DHBlockPos pos) { //TODO: Allow a per pos unique setting
|
||||
return blockCache.computeIfAbsent(state, (s) -> new ServerBlockStateCache(s, level, new DHBlockPos(0,0,0)));
|
||||
}
|
||||
|
||||
public void clear() { blockCache.clear(); }
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.seibel.lod.common.wrappers.block.cache;
|
||||
|
||||
import com.seibel.lod.common.wrappers.McObjectConverter;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ServerBlockStateCache {
|
||||
public final BlockState state;
|
||||
public final LevelReader level;
|
||||
public final BlockPos pos;
|
||||
|
||||
public ServerBlockStateCache(BlockState blockState, ILevelWrapper samplingLevel, DHBlockPos samplingPos) {
|
||||
state = blockState;
|
||||
level = (LevelReader) samplingLevel.unwrapLevel();
|
||||
pos = McObjectConverter.Convert(samplingPos);
|
||||
resolveShapes();
|
||||
}
|
||||
|
||||
boolean noCollision = false;
|
||||
boolean[] occludeFaces = null;
|
||||
boolean[] fullFaces = null;
|
||||
boolean isShapeResolved = false;
|
||||
public void resolveShapes() {
|
||||
if (isShapeResolved) return;
|
||||
if (state.getFluidState().isEmpty()) {
|
||||
noCollision = state.getCollisionShape(level, pos).isEmpty();
|
||||
occludeFaces = new boolean[6];
|
||||
if (state.canOcclude()) {
|
||||
for (Direction dir : Direction.values()) {
|
||||
// Note: isEmpty() isn't quite correct... best would be a isFull() or something...
|
||||
occludeFaces[McObjectConverter.Convert(dir).ordinal()]
|
||||
= !state.getFaceOcclusionShape(level, pos, dir).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
VoxelShape voxelShape = state.getShape(level, pos);
|
||||
fullFaces = new boolean[6];
|
||||
if (!voxelShape.isEmpty()) {
|
||||
for (Direction dir : Direction.values()) {
|
||||
VoxelShape faceShape = voxelShape.getFaceShape(dir);
|
||||
AABB aabb = faceShape.bounds();
|
||||
boolean xFull = aabb.minX <= 0.01 && aabb.maxX >= 0.99;
|
||||
boolean yFull = aabb.minY <= 0.01 && aabb.maxY >= 0.99;
|
||||
boolean zFull = aabb.minZ <= 0.01 && aabb.maxZ >= 0.99;
|
||||
fullFaces[McObjectConverter.Convert(dir).ordinal()] =
|
||||
(xFull || dir.getAxis().equals(Direction.Axis.X))
|
||||
&& (yFull || dir.getAxis().equals(Direction.Axis.Y))
|
||||
&& (zFull || dir.getAxis().equals(Direction.Axis.Z));
|
||||
}
|
||||
}
|
||||
} else { // Liquid Block. Treat as full block
|
||||
occludeFaces = new boolean[6];
|
||||
Arrays.fill(occludeFaces, true);
|
||||
fullFaces = new boolean[6];
|
||||
Arrays.fill(fullFaces, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,20 +19,22 @@
|
||||
|
||||
package com.seibel.lod.common.wrappers.chunk;
|
||||
|
||||
import com.seibel.lod.common.wrappers.block.BlockDetailWrapper;
|
||||
import com.seibel.lod.common.wrappers.block.BlockStateWrapper;
|
||||
import com.seibel.lod.core.enums.ELodDirection;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.util.LevelPosUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockDetailWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
|
||||
import com.seibel.lod.common.wrappers.WrapperUtil;
|
||||
import com.seibel.lod.common.wrappers.block.BlockDetailMap;
|
||||
import com.seibel.lod.common.wrappers.block.cache.ServerBlockDetailMap;
|
||||
import com.seibel.lod.common.wrappers.block.BiomeWrapper;
|
||||
import com.seibel.lod.common.wrappers.worldGeneration.mimicObject.LightedWorldGenRegion;
|
||||
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
#if POST_MC_1_17_1
|
||||
import net.minecraft.core.QuartPos;
|
||||
@@ -48,6 +50,8 @@ import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
@@ -57,12 +61,14 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
{
|
||||
private final ChunkAccess chunk;
|
||||
private final LevelReader lightSource;
|
||||
private final ILevelWrapper wrappedLevel;
|
||||
|
||||
|
||||
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource)
|
||||
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource, @Nullable ILevelWrapper wrappedLevel)
|
||||
{
|
||||
this.chunk = chunk;
|
||||
this.lightSource = lightSource;
|
||||
this.wrappedLevel = wrappedLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,6 +104,8 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
@Override
|
||||
public IBiomeWrapper getBiome(int x, int y, int z)
|
||||
{
|
||||
if (wrappedLevel != null) return wrappedLevel.getBiome(new DHBlockPos(x + getMinX(), y, z + getMinZ()));
|
||||
|
||||
#if PRE_MC_1_17_1
|
||||
return BiomeWrapper.getBiomeWrapper(chunk.getBiomes().getNoiseBiome(
|
||||
x >> 2, y >> 2, z >> 2));
|
||||
@@ -113,32 +121,6 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
#endif
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public IBlockDetailWrapper getBlockDetail(int x, int y, int z) {
|
||||
BlockPos pos = new BlockPos(x,y,z);
|
||||
BlockState blockState = chunk.getBlockState(pos);
|
||||
IBlockDetailWrapper blockDetail = BlockDetailMap.getOrMakeBlockDetailCache(blockState, pos, lightSource);
|
||||
return blockDetail == BlockDetailWrapper.NULL_BLOCK_DETAIL ? null : blockDetail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public IBlockDetailWrapper getBlockDetailAtFace(int x, int y, int z, ELodDirection dir) {
|
||||
int fy = y+dir.getNormal().y;
|
||||
if (fy < getMinBuildHeight() || fy > getMaxBuildHeight()) return null;
|
||||
BlockPos pos = new BlockPos(x+dir.getNormal().x,fy,z+dir.getNormal().z);
|
||||
BlockState blockState;
|
||||
if (blockPosInsideChunk(x,y,z))
|
||||
blockState = chunk.getBlockState(pos);
|
||||
else {
|
||||
blockState = lightSource.getBlockState(pos);
|
||||
}
|
||||
if (blockState == null || blockState.isAir()) return null;
|
||||
IBlockDetailWrapper blockDetail = BlockDetailMap.getOrMakeBlockDetailCache(blockState, pos, lightSource);
|
||||
return blockDetail == BlockDetailWrapper.NULL_BLOCK_DETAIL ? null : blockDetail;
|
||||
}
|
||||
|
||||
public ChunkAccess getChunk() {
|
||||
return chunk;
|
||||
}
|
||||
@@ -252,7 +234,8 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockStateWrapper getBlockState(int x, int y, int z) {
|
||||
public IBlockStateWrapper getBlockState(int x, int y, int z) {
|
||||
if (wrappedLevel != null) return wrappedLevel.getBlockState(new DHBlockPos(x + getMinX(), y, z + getMinZ()));
|
||||
return BlockStateWrapper.fromBlockState(chunk.getBlockState(new BlockPos(x,y,z)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.seibel.lod.common.wrappers.world;
|
||||
|
||||
import com.seibel.lod.common.wrappers.McObjectConverter;
|
||||
import com.seibel.lod.common.wrappers.block.BiomeWrapper;
|
||||
import com.seibel.lod.common.wrappers.block.BlockStateWrapper;
|
||||
import com.seibel.lod.common.wrappers.block.cache.ClientBlockDetailMap;
|
||||
import com.seibel.lod.common.wrappers.chunk.ChunkWrapper;
|
||||
import com.seibel.lod.common.wrappers.minecraft.MinecraftClientWrapper;
|
||||
import com.seibel.lod.core.api.internal.a7.ClientApi;
|
||||
@@ -40,6 +44,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
this.level = level;
|
||||
}
|
||||
final ClientLevel level;
|
||||
ClientBlockDetailMap blockMap = new ClientBlockDetailMap(this);
|
||||
@Nullable
|
||||
@Override
|
||||
public IServerLevelWrapper tryGetServerSideWrapper() {
|
||||
@@ -60,7 +65,8 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
|
||||
@Override
|
||||
public int computeBaseColor(DHBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper blockState) {
|
||||
return 0;
|
||||
return blockMap.getColor(((BlockStateWrapper)blockState).blockState,
|
||||
(BiomeWrapper)biome, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,7 +121,7 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
public IChunkWrapper tryGetChunk(DHChunkPos pos) {
|
||||
ChunkAccess chunk = level.getChunk(pos.getX(), pos.getZ(), ChunkStatus.EMPTY, false);
|
||||
if (chunk == null) return null;
|
||||
return new ChunkWrapper(chunk, level);
|
||||
return new ChunkWrapper(chunk, level, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,12 +132,12 @@ public class ClientLevelWrapper implements IClientLevelWrapper
|
||||
|
||||
@Override
|
||||
public IBlockStateWrapper getBlockState(DHBlockPos pos) {
|
||||
return null;
|
||||
return BlockStateWrapper.fromBlockState(level.getBlockState(McObjectConverter.Convert(pos)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBiomeWrapper getBiome(DHBlockPos pos) {
|
||||
return null;
|
||||
return BiomeWrapper.getBiomeWrapper(level.getBiome(McObjectConverter.Convert(pos)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,12 @@ package com.seibel.lod.common.wrappers.world;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.seibel.lod.common.wrappers.McObjectConverter;
|
||||
import com.seibel.lod.common.wrappers.block.BiomeWrapper;
|
||||
import com.seibel.lod.common.wrappers.block.BlockStateWrapper;
|
||||
import com.seibel.lod.common.wrappers.block.cache.ServerBlockDetailMap;
|
||||
import com.seibel.lod.common.wrappers.minecraft.MinecraftClientWrapper;
|
||||
import com.seibel.lod.core.a7.world.WorldEnvironment;
|
||||
import com.seibel.lod.core.api.internal.a7.ServerApi;
|
||||
import com.seibel.lod.core.objects.DHBlockPos;
|
||||
import com.seibel.lod.core.objects.DHChunkPos;
|
||||
@@ -67,11 +72,13 @@ public class ServerLevelWrapper implements IServerLevelWrapper
|
||||
}
|
||||
}
|
||||
|
||||
final ServerLevel level;
|
||||
ServerBlockDetailMap blockMap = new ServerBlockDetailMap(this);
|
||||
|
||||
public ServerLevelWrapper(ServerLevel level)
|
||||
{
|
||||
this.level = level;
|
||||
}
|
||||
final ServerLevel level;
|
||||
@Nullable
|
||||
@Override
|
||||
public IClientLevelWrapper tryGetClientSideWrapper() {
|
||||
@@ -144,7 +151,7 @@ public class ServerLevelWrapper implements IServerLevelWrapper
|
||||
public IChunkWrapper tryGetChunk(DHChunkPos pos) {
|
||||
ChunkAccess chunk = level.getChunk(pos.getX(), pos.getZ(), ChunkStatus.EMPTY, false);
|
||||
if (chunk == null) return null;
|
||||
return new ChunkWrapper(chunk, level);
|
||||
return new ChunkWrapper(chunk, level, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,12 +163,12 @@ public class ServerLevelWrapper implements IServerLevelWrapper
|
||||
|
||||
@Override
|
||||
public IBlockStateWrapper getBlockState(DHBlockPos pos) {
|
||||
return null;
|
||||
return BlockStateWrapper.fromBlockState(level.getBlockState(McObjectConverter.Convert(pos)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBiomeWrapper getBiome(DHBlockPos pos) {
|
||||
return null;
|
||||
return BiomeWrapper.getBiomeWrapper(level.getBiome(McObjectConverter.Convert(pos)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -454,7 +454,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
|
||||
{
|
||||
for (int ox = 0; ox < e.size; ox++)
|
||||
{
|
||||
result.set(ox, oy, new ChunkWrapper(genChunks.get(ox, oy), region));
|
||||
result.set(ox, oy, new ChunkWrapper(genChunks.get(ox, oy), region, null));
|
||||
}
|
||||
}
|
||||
e.timer.complete();
|
||||
|
||||
+1
-1
Submodule core updated: 823da76896...6fe2b6f331
@@ -77,17 +77,23 @@ public class FabricClientProxy
|
||||
// TODO: Is using setClientLightReady one still better?
|
||||
//#if PRE_MC_1_18_1 // in 1.18+, we use mixin hook in setClientLightReady(true)
|
||||
ClientChunkEvents.CHUNK_LOAD.register((level, chunk) ->
|
||||
ClientApi.INSTANCE.clientChunkLoadEvent(
|
||||
new ChunkWrapper(chunk, level),
|
||||
ClientLevelWrapper.getWrapper(level)
|
||||
));
|
||||
{
|
||||
ClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper(level);
|
||||
ClientApi.INSTANCE.clientChunkLoadEvent(
|
||||
new ChunkWrapper(chunk, level, wrappedLevel),
|
||||
wrappedLevel
|
||||
);
|
||||
});
|
||||
//#endif
|
||||
// ClientChunkSaveEvent
|
||||
ClientChunkEvents.CHUNK_UNLOAD.register((level, chunk)->
|
||||
ClientApi.INSTANCE.clientChunkSaveEvent(
|
||||
new ChunkWrapper(chunk, level),
|
||||
ClientLevelWrapper.getWrapper(level)
|
||||
));
|
||||
ClientChunkEvents.CHUNK_UNLOAD.register((level, chunk) ->
|
||||
{
|
||||
ClientLevelWrapper wrappedLevel = ClientLevelWrapper.getWrapper(level);
|
||||
ClientApi.INSTANCE.clientChunkSaveEvent(
|
||||
new ChunkWrapper(chunk, level, wrappedLevel),
|
||||
wrappedLevel
|
||||
);
|
||||
});
|
||||
|
||||
// RendererStartupEvent - Done in MixinGameRenderer
|
||||
// RendererShutdownEvent - Done in MixinGameRenderer
|
||||
|
||||
@@ -98,7 +98,7 @@ public class FabricServerProxy {
|
||||
-> {
|
||||
ILevelWrapper level = getLevelWrapper((ServerLevel) chunk.getLevel());
|
||||
if (isValidTime()) ServerApi.INSTANCE.serverChunkLoadEvent(
|
||||
new ChunkWrapper(chunk, chunk.getLevel()),
|
||||
new ChunkWrapper(chunk, chunk.getLevel(), level),
|
||||
level);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ public class MixinClientLevel {
|
||||
ClientLevel l = (ClientLevel) (Object) this;
|
||||
LevelChunk chunk = l.getChunkSource().getChunk(x, z, false);
|
||||
if (chunk!=null&& !chunk.isClientLightReady())
|
||||
ClientApi.INSTANCE.clientChunkLoadEvent(new ChunkWrapper(chunk, l), ClientLevelWrapper.getWrapper(l));
|
||||
ClientApi.INSTANCE.clientChunkLoadEvent(new ChunkWrapper(chunk, l, ClientLevelWrapper.getWrapper(l)), ClientLevelWrapper.getWrapper(l));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class MixinChunkMap {
|
||||
@Inject(method = "save", at = @At(value = "INVOKE", target = CHUNK_SERIALIZER_WRITE))
|
||||
private void onChunkSave(ChunkAccess chunk, CallbackInfoReturnable<Boolean> ci) {
|
||||
ServerApi.INSTANCE.serverChunkSaveEvent(
|
||||
new ChunkWrapper(chunk, level),
|
||||
new ChunkWrapper(chunk, level, ServerLevelWrapper.getWrapper(level)),
|
||||
ServerLevelWrapper.getWrapper(level)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user