forked from FabricMC/fabric
Various Indigo Improvements (#295)
- Fix #199 Vanilla Bug: Glowstone creates AO shade - Fix #289 Rendering on the Cutout and Transparent Layers together interferes with water rendering - Fix #290 Smooth lighting breaks on continuous angled surfaces - Fix #291 QuadEmitter not cleared on MeshBuilder.build() - Fix #292 Render context QuadEmitter crash with flat lighting - Fix #293 Relax vertex order requirements for modded quads - Fix swapped color components for grass and other blocks - Add option to partially support non-standard vertex formats
This commit is contained in:
parent
6519cfb41e
commit
099c1e8b8c
19 changed files with 251 additions and 286 deletions
|
@ -1,5 +1,5 @@
|
|||
archivesBaseName = "fabric-renderer-api-v1"
|
||||
version = getSubprojectVersion(project, "0.1.0")
|
||||
version = getSubprojectVersion(project, "0.1.1")
|
||||
|
||||
dependencies {
|
||||
compile project(path: ':fabric-api-base', configuration: 'dev')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
archivesBaseName = "fabric-renderer-indigo"
|
||||
version = getSubprojectVersion(project, "0.1.7")
|
||||
version = getSubprojectVersion(project, "0.1.8")
|
||||
|
||||
dependencies {
|
||||
compile project(path: ':fabric-api-base', configuration: 'dev')
|
||||
|
|
|
@ -34,13 +34,15 @@ import java.util.Properties;
|
|||
|
||||
public class Indigo implements ClientModInitializer {
|
||||
public static final boolean ALWAYS_TESSELATE_INDIGO;
|
||||
public static final boolean ENSURE_VERTEX_FORMAT_COMPATIBILITY;
|
||||
public static final AoConfig AMBIENT_OCCLUSION_MODE;
|
||||
/** Set true in dev env to confirm results match vanilla when they should */
|
||||
public static final boolean DEBUG_COMPARE_LIGHTING;
|
||||
public static final boolean FIX_SMOOTH_LIGHTING_OFFSET;
|
||||
public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
public static final boolean FIX_LUMINOUS_AO_SHADE;
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static boolean asBoolean(String property, boolean defValue) {
|
||||
switch (asTriState(property)) {
|
||||
|
@ -53,7 +55,8 @@ public class Indigo implements ClientModInitializer {
|
|||
}
|
||||
}
|
||||
|
||||
private static <T extends Enum> T asEnum(String property, T defValue) {
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static <T extends Enum> T asEnum(String property, T defValue) {
|
||||
if (property == null || property.isEmpty()) {
|
||||
return defValue;
|
||||
} else {
|
||||
|
@ -102,12 +105,16 @@ public class Indigo implements ClientModInitializer {
|
|||
}
|
||||
}
|
||||
|
||||
ALWAYS_TESSELATE_INDIGO = asBoolean((String) properties.computeIfAbsent("always-tesselate-blocks", (a) -> "auto"), true);
|
||||
final boolean forceCompatibility = IndigoMixinConfigPlugin.shouldForceCompatibility();
|
||||
ENSURE_VERTEX_FORMAT_COMPATIBILITY = forceCompatibility;
|
||||
// necessary because OF alters the BakedModel vertex format and will confuse the fallback model consumer
|
||||
ALWAYS_TESSELATE_INDIGO = !forceCompatibility && asBoolean((String) properties.computeIfAbsent("always-tesselate-blocks", (a) -> "auto"), true);
|
||||
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "hybrid"), AoConfig.HYBRID);
|
||||
DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (a) -> "auto"), false);
|
||||
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true);
|
||||
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true);
|
||||
|
||||
FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (a) -> "auto"), false);
|
||||
|
||||
try (FileOutputStream stream = new FileOutputStream(configFile)) {
|
||||
properties.store(stream, "Indigo properties file");
|
||||
} catch (IOException e) {
|
||||
|
@ -119,6 +126,9 @@ public class Indigo implements ClientModInitializer {
|
|||
public void onInitializeClient() {
|
||||
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
|
||||
LOGGER.info("[Indigo] Registering Indigo renderer!");
|
||||
if(IndigoMixinConfigPlugin.shouldForceCompatibility()) {
|
||||
LOGGER.info("[Indigo] Compatibility mode enabled.");
|
||||
}
|
||||
RendererAccess.INSTANCE.registerRenderer(IndigoRenderer.INSTANCE);
|
||||
} else {
|
||||
LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");
|
||||
|
|
|
@ -18,6 +18,8 @@ package net.fabricmc.indigo;
|
|||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.metadata.ModMetadata;
|
||||
|
||||
import org.spongepowered.asm.lib.tree.ClassNode;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
|
@ -26,23 +28,39 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
public class IndigoMixinConfigPlugin implements IMixinConfigPlugin {
|
||||
private static final String JSON_ELEMENT = "fabric-renderer-api-v1:contains_renderer";
|
||||
private static Boolean indigoApplicable;
|
||||
/** Set by other renderers to disable loading of Indigo */
|
||||
private static final String JSON_KEY_DISABLE_INDIGO = "fabric-renderer-api-v1:contains_renderer";
|
||||
/** Disables vanilla block tesselation and ensures vertex format compatibility */
|
||||
private static final String JSON_KEY_FORCE_COMPATIBILITY = "fabric-renderer-indigo:force_compatibility";
|
||||
|
||||
private static boolean needsLoad = true;
|
||||
|
||||
private static boolean indigoApplicable = true;
|
||||
private static boolean forceCompatibility = false;
|
||||
|
||||
private static void loadIfNeeded() {
|
||||
if(needsLoad) {
|
||||
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
|
||||
final ModMetadata meta = container.getMetadata();
|
||||
if (meta.containsCustomElement(JSON_KEY_DISABLE_INDIGO)) {
|
||||
indigoApplicable = false;
|
||||
} else if (meta.containsCustomElement(JSON_KEY_FORCE_COMPATIBILITY)) {
|
||||
forceCompatibility = true;
|
||||
}
|
||||
}
|
||||
needsLoad = false;
|
||||
}
|
||||
}
|
||||
static boolean shouldApplyIndigo() {
|
||||
if (indigoApplicable != null) return indigoApplicable;
|
||||
|
||||
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
|
||||
if (container.getMetadata().containsCustomElement(JSON_ELEMENT)) {
|
||||
indigoApplicable = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
indigoApplicable = true;
|
||||
return true;
|
||||
loadIfNeeded();
|
||||
return indigoApplicable;
|
||||
}
|
||||
|
||||
static boolean shouldForceCompatibility() {
|
||||
loadIfNeeded();
|
||||
return forceCompatibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package net.fabricmc.indigo.renderer.accessor;
|
||||
|
||||
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
||||
|
||||
public interface AccessBufferBuilder {
|
||||
void fabric_putVanillaData(int[] data, int start, boolean isItemFormat);
|
||||
void fabric_putQuad(QuadViewImpl quad);
|
||||
}
|
||||
|
|
|
@ -112,31 +112,37 @@ public class AoCalculator {
|
|||
|
||||
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
|
||||
final AoConfig config = Indigo.AMBIENT_OCCLUSION_MODE;
|
||||
boolean shouldMatch = false;
|
||||
final boolean shouldCompare;
|
||||
|
||||
switch(config) {
|
||||
case VANILLA:
|
||||
calcVanilla(quad);
|
||||
// no point in comparing vanilla with itself
|
||||
shouldCompare = false;
|
||||
break;
|
||||
|
||||
case EMULATE:
|
||||
calcFastVanilla(quad);
|
||||
shouldMatch = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
|
||||
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
|
||||
break;
|
||||
|
||||
default:
|
||||
case HYBRID:
|
||||
if(isVanilla) {
|
||||
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
calcFastVanilla(quad);
|
||||
break;
|
||||
} else {
|
||||
shouldCompare = false;
|
||||
calcEnhanced(quad);
|
||||
}
|
||||
// else fall through to enhanced
|
||||
break;
|
||||
|
||||
default:
|
||||
case ENHANCED:
|
||||
shouldMatch = calcEnhanced(quad);
|
||||
shouldCompare = false;
|
||||
calcEnhanced(quad);
|
||||
}
|
||||
|
||||
if (shouldMatch) {
|
||||
if (shouldCompare) {
|
||||
float[] vanillaAo = new float[4];
|
||||
int[] vanillaLight = new int[4];
|
||||
|
||||
|
@ -178,28 +184,21 @@ public class AoCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
/** returns true if should match vanilla results */
|
||||
private boolean calcEnhanced(MutableQuadViewImpl quad) {
|
||||
private void calcEnhanced(MutableQuadViewImpl quad) {
|
||||
switch(quad.geometryFlags()) {
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaFullFace(quad, true);
|
||||
return Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
|
||||
vanillaPartialFace(quad, true);
|
||||
return Indigo.DEBUG_COMPARE_LIGHTING;
|
||||
break;
|
||||
|
||||
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
|
||||
blendedFullFace(quad);
|
||||
return false;
|
||||
|
||||
case AXIS_ALIGNED_FLAG:
|
||||
blendedPartialFace(quad);
|
||||
return false;
|
||||
break;
|
||||
|
||||
default:
|
||||
irregularFace(quad);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,11 +245,6 @@ public class AoCalculator {
|
|||
}
|
||||
}
|
||||
|
||||
private void blendedFullFace(QuadViewImpl quad) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
blendedInsetFace(quad, 0, lightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
|
||||
}
|
||||
|
||||
private void blendedPartialFace(QuadViewImpl quad) {
|
||||
final Direction lightFace = quad.lightFace();
|
||||
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
|
||||
|
@ -283,12 +277,15 @@ public class AoCalculator {
|
|||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = x * x;
|
||||
ao += n * fd.weigtedAo(w);
|
||||
sky += n * fd.weigtedSkyLight(w);
|
||||
block += n * fd.weigtedBlockLight(w);
|
||||
maxAo = fd.maxAo(maxAo);
|
||||
maxSky = fd.maxSkyLight(maxSky);
|
||||
maxBlock = fd.maxBlockLight(maxBlock);
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = a;
|
||||
maxSky = s;
|
||||
maxBlock = b;
|
||||
}
|
||||
|
||||
final float y = normal.y();
|
||||
|
@ -297,12 +294,15 @@ public class AoCalculator {
|
|||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = y * y;
|
||||
ao += n * fd.weigtedAo(w);
|
||||
sky += n * fd.weigtedSkyLight(w);
|
||||
block += n * fd.weigtedBlockLight(w);
|
||||
maxAo = fd.maxAo(maxAo);
|
||||
maxSky = fd.maxSkyLight(maxSky);
|
||||
maxBlock = fd.maxBlockLight(maxBlock);
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = Math.max(maxAo, a);
|
||||
maxSky = Math.max(maxSky, s);
|
||||
maxBlock = Math.max(maxBlock, b);
|
||||
}
|
||||
|
||||
final float z = normal.z();
|
||||
|
@ -311,12 +311,15 @@ public class AoCalculator {
|
|||
final AoFaceData fd = gatherInsetFace(quad, i, face);
|
||||
AoFace.get(face).weightFunc.apply(quad, i, w);
|
||||
final float n = z * z;
|
||||
ao += n * fd.weigtedAo(w);
|
||||
sky += n * fd.weigtedSkyLight(w);
|
||||
block += n * fd.weigtedBlockLight(w);
|
||||
maxAo = fd.maxAo(maxAo);
|
||||
maxSky = fd.maxSkyLight(maxSky);
|
||||
maxBlock = fd.maxBlockLight(maxBlock);
|
||||
final float a = fd.weigtedAo(w);
|
||||
final int s = fd.weigtedSkyLight(w);
|
||||
final int b = fd.weigtedBlockLight(w);
|
||||
ao += n * a;
|
||||
sky += n * s;
|
||||
block += n * b;
|
||||
maxAo = Math.max(maxAo, a);
|
||||
maxSky = Math.max(maxSky, s);
|
||||
maxBlock = Math.max(maxBlock, b);
|
||||
}
|
||||
|
||||
aoResult[i] = (ao + maxAo) * 0.5f;
|
||||
|
|
|
@ -36,8 +36,7 @@ public enum AoConfig {
|
|||
* aligned quads not on the block face will have interpolated brightness based
|
||||
* on depth instead of the all-or-nothing brightness of vanilla.<p>
|
||||
*
|
||||
* Unit (full face) quads must still have the vanilla fixed winding order but smaller
|
||||
* quads can have vertices in any (counter-clockwise) order.<p>
|
||||
* Non-vanilla quads can have vertices in any (counter-clockwise) order.<p>
|
||||
*/
|
||||
ENHANCED,
|
||||
|
||||
|
|
|
@ -58,22 +58,10 @@ class AoFaceData {
|
|||
return (int) (b0 * w[0] + b1 * w[1] + b2 * w[2] + b3 * w[3]) & 0xFF;
|
||||
}
|
||||
|
||||
int maxBlockLight(int oldMax) {
|
||||
final int i = b0 > b1 ? b0 : b1;
|
||||
final int j = b2 > b3 ? b2 : b3;
|
||||
return Math.max(oldMax, i > j ? i : j);
|
||||
}
|
||||
|
||||
int weigtedSkyLight(float[] w) {
|
||||
return (int) (s0 * w[0] + s1 * w[1] + s2 * w[2] + s3 * w[3]) & 0xFF;
|
||||
}
|
||||
|
||||
int maxSkyLight(int oldMax) {
|
||||
final int i = s0 > s1 ? s0 : s1;
|
||||
final int j = s2 > s3 ? s2 : s3;
|
||||
return Math.max(oldMax, i > j ? i : j);
|
||||
}
|
||||
|
||||
int weightedCombinedLight(float[] w) {
|
||||
return weigtedSkyLight(w) << 16 | weigtedBlockLight(w);
|
||||
}
|
||||
|
@ -82,13 +70,6 @@ class AoFaceData {
|
|||
return a0 * w[0] + a1 * w[1] + a2 * w[2] + a3 * w[3];
|
||||
}
|
||||
|
||||
float maxAo(float oldMax) {
|
||||
final float x = a0 > a1 ? a0 : a1;
|
||||
final float y = a2 > a3 ? a2 : a3;
|
||||
final float z = x > y ? x : y;
|
||||
return oldMax > z ? oldMax : z;
|
||||
}
|
||||
|
||||
void toArray(float[] aOut, int[] bOut, int[] vertexMap) {
|
||||
aOut[vertexMap[0]] = a0;
|
||||
aOut[vertexMap[1]] = a1;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.aocalc;
|
||||
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.BlockView;
|
||||
|
||||
/**
|
||||
* Implements a fix to prevent luminous blocks from casting AO shade.
|
||||
* Will give normal result if fix is disabled.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AoLuminanceFix {
|
||||
float apply(BlockView view, BlockPos pos);
|
||||
|
||||
AoLuminanceFix INSTANCE = Indigo.FIX_LUMINOUS_AO_SHADE ? AoLuminanceFix::fixed : AoLuminanceFix::vanilla;
|
||||
|
||||
static float vanilla(BlockView view, BlockPos pos) {
|
||||
return view.getBlockState(pos).getAmbientOcclusionLightLevel(view, pos);
|
||||
}
|
||||
|
||||
static float fixed(BlockView view, BlockPos pos) {
|
||||
final BlockState state = view.getBlockState(pos);
|
||||
return state.getLuminance() == 0 ? state.getAmbientOcclusionLightLevel(view, pos) : 1f;
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.fabricmc.indigo.renderer.helper;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.minecraft.client.render.VertexFormat;
|
||||
import net.minecraft.client.render.VertexFormatElement;
|
||||
import net.minecraft.client.render.VertexFormats;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BufferBuilderTransformHelper {
|
||||
/**
|
||||
* Fast copying mode; used only if the vanilla format is an exact match.
|
||||
*/
|
||||
public static final int MODE_COPY_FAST = 0;
|
||||
|
||||
/**
|
||||
* Padded copying mode; used when the vanilla format is an exact match,
|
||||
* but includes additional data at the end. Will emit a warning.
|
||||
*/
|
||||
public static final int MODE_COPY_PADDED = 1;
|
||||
|
||||
/**
|
||||
* ShadersMod compatibility mode; as MODE_COPY_PADDED, but populates in
|
||||
* the correct normal values as provided by the mod.
|
||||
*
|
||||
* Assumes a format of [vertex, color, texture, lmap, normal], all in
|
||||
* their respective vanilla formats, plus any amount of additional data
|
||||
* afterwards.
|
||||
*/
|
||||
public static final int MODE_COPY_PADDED_SHADERSMOD = 2;
|
||||
|
||||
/**
|
||||
* Unsupported mode; an error will be emitted and no quads will be
|
||||
* pushed to the buffer builder.
|
||||
*/
|
||||
public static final int MODE_UNSUPPORTED = 3;
|
||||
|
||||
private static final Map<VertexFormat, Integer> vertexFormatCache = new ConcurrentHashMap<>();
|
||||
private static final Set<VertexFormat> errorEmittedFormats = Sets.newConcurrentHashSet();
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
|
||||
public static void emitUnsupportedError(VertexFormat format) {
|
||||
// This can be slow, as it's only called on unsupported formats - which is already an error condition.
|
||||
if (errorEmittedFormats.add(format)) {
|
||||
logger.error("[Indigo] Unsupported vertex format! " + format);
|
||||
}
|
||||
}
|
||||
|
||||
private static int computeProcessingMode(VertexFormat f) {
|
||||
if (
|
||||
f.getElementCount() >= 4 && f.getVertexSizeInteger() >= 7
|
||||
&& f.getElement(0).equals(VertexFormats.POSITION_ELEMENT)
|
||||
&& f.getElement(1).equals(VertexFormats.COLOR_ELEMENT)
|
||||
&& f.getElement(2).equals(VertexFormats.UV_ELEMENT)
|
||||
) {
|
||||
if (
|
||||
f.getElement(3).equals(VertexFormats.LMAP_ELEMENT)
|
||||
|| f.getElement(3).equals(VertexFormats.NORMAL_ELEMENT)
|
||||
) {
|
||||
if (
|
||||
f.getElementCount() >= 5
|
||||
&& f.getElement(3).equals(VertexFormats.LMAP_ELEMENT)
|
||||
&& f.getElement(4).equals(VertexFormats.NORMAL_ELEMENT)
|
||||
) {
|
||||
logger.debug("[Indigo] Classified format as ShadersMod-compatible: " + f);
|
||||
return MODE_COPY_PADDED_SHADERSMOD;
|
||||
} else if (f.getElementCount() == 4) {
|
||||
logger.debug("[Indigo] Classified format as vanilla-like: " + f);
|
||||
return MODE_COPY_FAST;
|
||||
} else {
|
||||
logger.debug("[Indigo] Unsupported but likely vanilla-compliant vertex format. " + f);
|
||||
return MODE_COPY_PADDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MODE_UNSUPPORTED;
|
||||
}
|
||||
|
||||
public static int getProcessingMode(VertexFormat format) {
|
||||
// Fast passthrough for the most common vanilla block/item formats.
|
||||
if (format == VertexFormats.POSITION_COLOR_UV_LMAP || format == VertexFormats.POSITION_COLOR_UV_NORMAL) {
|
||||
return MODE_COPY_FAST;
|
||||
} else {
|
||||
Integer cached = vertexFormatCache.get(format);
|
||||
|
||||
if (cached == null) {
|
||||
// VertexFormats are mutable, so we need to make an immutable copy.
|
||||
format = new VertexFormat(format);
|
||||
cached = computeProcessingMode(format);
|
||||
vertexFormatCache.put(format, cached);
|
||||
}
|
||||
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ public class MeshBuilderImpl implements MeshBuilder {
|
|||
int[] packed = new int[index];
|
||||
System.arraycopy(data, 0, packed, 0, index);
|
||||
index = 0;
|
||||
maker.begin(data, index);
|
||||
return new MeshImpl(packed);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,18 +18,15 @@ package net.fabricmc.indigo.renderer.mixin;
|
|||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.helper.BufferBuilderTransformHelper;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
import net.minecraft.client.render.VertexFormat;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
|
||||
import net.minecraft.client.render.BufferBuilder;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import net.minecraft.client.render.VertexFormat;
|
||||
import net.minecraft.client.render.VertexFormatElement;
|
||||
|
||||
@Mixin(BufferBuilder.class)
|
||||
public abstract class MixinBufferBuilder implements AccessBufferBuilder {
|
||||
|
@ -40,73 +37,71 @@ public abstract class MixinBufferBuilder implements AccessBufferBuilder {
|
|||
@Shadow public abstract VertexFormat getVertexFormat();
|
||||
|
||||
private static final int VERTEX_STRIDE_INTS = 7;
|
||||
private static final int VERTEX_STRIDE_BYTES = VERTEX_STRIDE_INTS * 4;
|
||||
private static final int QUAD_STRIDE_INTS = VERTEX_STRIDE_INTS * 4;
|
||||
private static final int QUAD_STRIDE_BYTES = QUAD_STRIDE_INTS * 4;
|
||||
|
||||
private int fabric_processingMode;
|
||||
@Override
|
||||
public void fabric_putQuad(QuadViewImpl quad) {
|
||||
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
|
||||
bufferCompatibly(quad);
|
||||
} else {
|
||||
bufferFast(quad);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "begin")
|
||||
private void afterBegin(int mode, VertexFormat passedFormat, CallbackInfo info) {
|
||||
fabric_processingMode = BufferBuilderTransformHelper.getProcessingMode(getVertexFormat());
|
||||
}
|
||||
private void bufferFast(QuadViewImpl quad) {
|
||||
grow(QUAD_STRIDE_BYTES);
|
||||
bufInt.position(getCurrentSize());
|
||||
bufInt.put(quad.data(), quad.vertexStart(), QUAD_STRIDE_INTS);
|
||||
vertexCount += 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link BufferBuilder#putVertexData(int[])} but
|
||||
* accepts an array index so that arrays containing more than one
|
||||
* quad don't have to be copied to a transfer array before the call.
|
||||
*
|
||||
* It also always assumes the vanilla data format and is capable of
|
||||
* transforming data from it to a different, non-vanilla data format.
|
||||
* Uses buffer vertex format to drive buffer population.
|
||||
* Relies on logic elsewhere to ensure coordinates don't include chunk offset
|
||||
* (because buffer builder will handle that.)<p>
|
||||
*
|
||||
* Calling putVertexData() would likely be a little faster but this approach
|
||||
* gives us a chance to pass vertex normals to shaders, which isn't possible
|
||||
* with the standard block format. It also doesn't require us to encode a specific
|
||||
* custom format directly, which would be prone to breakage outside our control.
|
||||
*/
|
||||
@Override
|
||||
public void fabric_putVanillaData(int[] data, int start, boolean isItemFormat) {
|
||||
switch (fabric_processingMode) {
|
||||
case BufferBuilderTransformHelper.MODE_COPY_FAST: {
|
||||
this.grow(QUAD_STRIDE_BYTES);
|
||||
this.bufInt.position(this.getCurrentSize());
|
||||
this.bufInt.put(data, start, QUAD_STRIDE_INTS);
|
||||
} break;
|
||||
case BufferBuilderTransformHelper.MODE_COPY_PADDED: {
|
||||
int currSize = this.getCurrentSize();
|
||||
int formatSizeBytes = getVertexFormat().getVertexSize();
|
||||
int formatSizeInts = formatSizeBytes / 4;
|
||||
this.grow(formatSizeBytes * 4);
|
||||
private void bufferCompatibly(QuadViewImpl quad) {
|
||||
final VertexFormat format = getVertexFormat();;
|
||||
final int elementCount = format.getElementCount();
|
||||
for(int i = 0; i < 4; i++) {
|
||||
for(int j = 0; j < elementCount; j++) {
|
||||
VertexFormatElement e = format.getElement(j);
|
||||
switch(e.getType()) {
|
||||
case COLOR:
|
||||
final int c = quad.spriteColor(i, 0);
|
||||
((BufferBuilder)(Object)this).color(c & 0xFF, (c >>> 8) & 0xFF, (c >>> 16) & 0xFF, (c >>> 24) & 0xFF);
|
||||
break;
|
||||
case NORMAL:
|
||||
((BufferBuilder)(Object)this).normal(quad.normalX(i), quad.normalY(i), quad.normalZ(i));
|
||||
break;
|
||||
case POSITION:
|
||||
((BufferBuilder)(Object)this).vertex(quad.x(i), quad.y(i), quad.z(i));
|
||||
break;
|
||||
case UV:
|
||||
if(e.getIndex() == 0) {
|
||||
((BufferBuilder)(Object)this).texture(quad.spriteU(i, 0), quad.spriteV(i, 0));
|
||||
} else {
|
||||
final int b = quad.lightmap(i);
|
||||
((BufferBuilder)(Object)this).texture((b >> 16) & 0xFFFF, b & 0xFFFF);
|
||||
}
|
||||
break;
|
||||
|
||||
this.bufInt.position(currSize);
|
||||
this.bufInt.put(data, start, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.position(currSize + formatSizeInts);
|
||||
this.bufInt.put(data, start + 7, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.position(currSize + formatSizeInts * 2);
|
||||
this.bufInt.put(data, start + 14, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.position(currSize + formatSizeInts * 3);
|
||||
this.bufInt.put(data, start + 21, VERTEX_STRIDE_INTS);
|
||||
} break;
|
||||
case BufferBuilderTransformHelper.MODE_COPY_PADDED_SHADERSMOD: {
|
||||
int currSize = this.getCurrentSize();
|
||||
int formatSizeBytes = getVertexFormat().getVertexSize();
|
||||
int formatSizeInts = formatSizeBytes / 4;
|
||||
this.grow(formatSizeBytes * 4);
|
||||
// these types should never occur and/or require no action
|
||||
case MATRIX:
|
||||
case BLEND_WEIGHT:
|
||||
case PADDING:
|
||||
default:
|
||||
break;
|
||||
|
||||
this.bufInt.position(currSize);
|
||||
this.bufInt.put(data, start, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA]);
|
||||
this.bufInt.position(currSize + formatSizeInts);
|
||||
this.bufInt.put(data, start + 7, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 1]);
|
||||
this.bufInt.position(currSize + formatSizeInts * 2);
|
||||
this.bufInt.put(data, start + 14, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 2]);
|
||||
this.bufInt.position(currSize + formatSizeInts * 3);
|
||||
this.bufInt.put(data, start + 21, VERTEX_STRIDE_INTS);
|
||||
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 3]);
|
||||
} break;
|
||||
case BufferBuilderTransformHelper.MODE_UNSUPPORTED:
|
||||
// Don't emit any quads.
|
||||
BufferBuilderTransformHelper.emitUnsupportedError(getVertexFormat());
|
||||
return;
|
||||
}
|
||||
|
||||
this.vertexCount += 4;
|
||||
}
|
||||
}
|
||||
((BufferBuilder)(Object)this).next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
|
|||
import net.fabricmc.indigo.renderer.IndigoRenderer;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
||||
import net.fabricmc.indigo.renderer.helper.ColorHelper;
|
||||
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
import net.fabricmc.indigo.renderer.mesh.MeshImpl;
|
||||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
|
@ -57,6 +59,8 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen
|
|||
// only used via RenderContext.getEmitter()
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace = GeometryHelper.lightFace(this);
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
renderQuad(this);
|
||||
clear();
|
||||
return this;
|
||||
|
|
|
@ -69,7 +69,7 @@ public abstract class AbstractQuadRenderer {
|
|||
|
||||
/** final output step, common to all renders */
|
||||
private void bufferQuad(MutableQuadViewImpl quad, int renderLayer) {
|
||||
bufferFunc.get(renderLayer).fabric_putVanillaData(quad.data(), quad.vertexStart(), false);
|
||||
bufferFunc.get(renderLayer).fabric_putQuad(quad);
|
||||
}
|
||||
|
||||
// routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop
|
||||
|
|
|
@ -27,6 +27,7 @@ import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
|
|||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoLuminanceFix;
|
||||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
import net.fabricmc.indigo.renderer.mixin.BufferBuilderOffsetAccessor;
|
||||
import net.minecraft.block.BlockState;
|
||||
|
@ -67,10 +68,7 @@ public class BlockRenderContext extends AbstractRenderContext implements RenderC
|
|||
|
||||
private float aoLevel(BlockPos pos) {
|
||||
final ExtendedBlockView blockView = blockInfo.blockView;
|
||||
if(blockView == null) {
|
||||
return 1f;
|
||||
}
|
||||
return blockView.getBlockState(pos).getAmbientOcclusionLightLevel(blockView, pos);
|
||||
return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos);
|
||||
}
|
||||
|
||||
private AccessBufferBuilder outputBuffer(int renderLayer) {
|
||||
|
|
|
@ -18,8 +18,10 @@ package net.fabricmc.indigo.renderer.render;
|
|||
|
||||
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessChunkRenderer;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoLuminanceFix;
|
||||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
import net.minecraft.block.Block.OffsetType;
|
||||
import net.minecraft.block.BlockRenderLayer;
|
||||
|
@ -67,6 +69,7 @@ public class ChunkRenderInfo {
|
|||
private final Long2FloatOpenHashMap aoLevelCache;
|
||||
|
||||
private final BlockRenderInfo blockInfo;
|
||||
private final BlockPos.Mutable chunkOrigin = new BlockPos.Mutable();
|
||||
ChunkRenderTask chunkTask;
|
||||
ChunkRenderData chunkData;
|
||||
ChunkRenderer chunkRenderer;
|
||||
|
@ -102,6 +105,7 @@ public class ChunkRenderInfo {
|
|||
}
|
||||
|
||||
void prepare(ChunkRenderer chunkRenderer, BlockPos.Mutable chunkOrigin, boolean [] resultFlags) {
|
||||
this.chunkOrigin.set(chunkOrigin);
|
||||
this.chunkData = chunkTask.getRenderData();
|
||||
this.chunkRenderer = chunkRenderer;
|
||||
this.resultFlags = resultFlags;
|
||||
|
@ -129,10 +133,19 @@ public class ChunkRenderInfo {
|
|||
void beginBlock() {
|
||||
final BlockState blockState = blockInfo.blockState;
|
||||
final BlockPos blockPos = blockInfo.blockPos;
|
||||
offsetX = (float) (chunkOffsetX + blockPos.getX());
|
||||
offsetY = (float) (chunkOffsetY + blockPos.getY());
|
||||
offsetZ = (float) (chunkOffsetZ + blockPos.getZ());
|
||||
|
||||
|
||||
// When we are using the BufferBuilder input methods, the builder will
|
||||
// add the chunk offset for us, so we should only apply the block offset.
|
||||
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
|
||||
offsetX = (float) (blockPos.getX());
|
||||
offsetY = (float) (blockPos.getY());
|
||||
offsetZ = (float) (blockPos.getZ());
|
||||
} else {
|
||||
offsetX = (float) (chunkOffsetX + blockPos.getX());
|
||||
offsetY = (float) (chunkOffsetY + blockPos.getY());
|
||||
offsetZ = (float) (chunkOffsetZ + blockPos.getZ());
|
||||
}
|
||||
|
||||
if(blockState.getBlock().getOffsetType() != OffsetType.NONE) {
|
||||
Vec3d offset = blockState.getOffsetPos(blockInfo.blockView, blockPos);
|
||||
offsetX += (float)offset.x;
|
||||
|
@ -153,7 +166,7 @@ public class ChunkRenderInfo {
|
|||
BlockRenderLayer layer = LAYERS[layerIndex];
|
||||
if (!chunkData.isBufferInitialized(layer)) {
|
||||
chunkData.markBufferInitialized(layer); // start buffer
|
||||
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, blockInfo.blockPos);
|
||||
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, chunkOrigin);
|
||||
}
|
||||
result = (AccessBufferBuilder) builder;
|
||||
}
|
||||
|
@ -188,7 +201,7 @@ public class ChunkRenderInfo {
|
|||
long key = pos.asLong();
|
||||
float result = aoLevelCache.get(key);
|
||||
if (result == Float.MAX_VALUE) {
|
||||
result = blockView.getBlockState(pos).getAmbientOcclusionLightLevel(blockView, pos);
|
||||
result = AoLuminanceFix.INSTANCE.apply(blockView, pos);
|
||||
aoLevelCache.put(key, result);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -33,6 +33,7 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
|||
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
|
||||
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
|
||||
import net.fabricmc.indigo.renderer.helper.ColorHelper;
|
||||
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
|
||||
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
|
||||
import net.fabricmc.indigo.renderer.mesh.MeshImpl;
|
||||
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
|
||||
|
@ -127,6 +128,8 @@ public class ItemRenderContext extends AbstractRenderContext implements RenderCo
|
|||
|
||||
@Override
|
||||
public Maker emit() {
|
||||
lightFace = GeometryHelper.lightFace(this);
|
||||
ColorHelper.applyDiffuseShading(this, false);
|
||||
renderQuad();
|
||||
clear();
|
||||
return this;
|
||||
|
@ -182,7 +185,7 @@ public class ItemRenderContext extends AbstractRenderContext implements RenderCo
|
|||
c = ColorHelper.multiplyColor(quadColor, c);
|
||||
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(c));
|
||||
}
|
||||
fabricBuffer.fabric_putVanillaData(quadData, EncodingFormat.VERTEX_START_OFFSET, true);
|
||||
fabricBuffer.fabric_putQuad(q);
|
||||
}
|
||||
|
||||
private void renderQuad() {
|
||||
|
|
|
@ -25,6 +25,7 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
|||
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
|
||||
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
|
||||
import net.fabricmc.indigo.Indigo;
|
||||
import net.fabricmc.indigo.renderer.IndigoRenderer;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
||||
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
|
||||
|
@ -58,6 +59,20 @@ public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Con
|
|||
private static Value MATERIAL_FLAT = (Value) IndigoRenderer.INSTANCE.materialFinder().disableAo(0, true).find();
|
||||
private static Value MATERIAL_SHADED = (Value) IndigoRenderer.INSTANCE.materialFinder().find();
|
||||
|
||||
/**
|
||||
* Controls 1x warning for vanilla quad vertex format when running in compatibility mode.
|
||||
*/
|
||||
private static boolean logCompatibilityWarning = true;
|
||||
|
||||
private static boolean isCompatible(int[] vertexData) {
|
||||
final boolean result = vertexData.length == 28;
|
||||
if(!result && logCompatibilityWarning) {
|
||||
logCompatibilityWarning = false;
|
||||
Indigo.LOGGER.warn("[Indigo] Encountered baked quad with non-standard vertex format. Some blocks will not be rendered");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final int[] editorBuffer = new int[28];
|
||||
private final ChunkRenderInfo chunkInfo;
|
||||
|
||||
|
@ -109,8 +124,13 @@ public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Con
|
|||
}
|
||||
|
||||
private void renderQuad(BakedQuad quad, Direction cullFace, Value defaultMaterial) {
|
||||
final int[] vertexData = quad.getVertexData();
|
||||
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY && !isCompatible(vertexData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MutableQuadViewImpl editorQuad = this.editorQuad;
|
||||
System.arraycopy(quad.getVertexData(), 0, editorBuffer, 0, 28);
|
||||
System.arraycopy(vertexData, 0, editorBuffer, 0, 28);
|
||||
editorQuad.cullFace(cullFace);
|
||||
final Direction lightFace = quad.getFace();
|
||||
editorQuad.lightFace(lightFace);
|
||||
|
|
|
@ -24,8 +24,6 @@ import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
|
|||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.block.BlockRenderManager;
|
||||
import net.minecraft.client.render.chunk.ChunkRenderTask;
|
||||
import net.minecraft.client.render.chunk.ChunkRenderer;
|
||||
import net.minecraft.client.render.chunk.ChunkRendererRegion;
|
||||
|
@ -34,7 +32,6 @@ import net.minecraft.util.crash.CrashException;
|
|||
import net.minecraft.util.crash.CrashReport;
|
||||
import net.minecraft.util.crash.CrashReportSection;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.ExtendedBlockView;
|
||||
|
||||
/**
|
||||
* Implementation of {@link RenderContext} used during terrain rendering.
|
||||
|
@ -48,7 +45,6 @@ public class TerrainRenderContext extends AbstractRenderContext implements Rende
|
|||
private final AoCalculator aoCalc = new AoCalculator(blockInfo, chunkInfo::cachedBrightness, chunkInfo::cachedAoLevel);
|
||||
private final TerrainMeshConsumer meshConsumer = new TerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
|
||||
private final TerrainFallbackConsumer fallbackConsumer = new TerrainFallbackConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
|
||||
private final BlockRenderManager blockRenderManager = MinecraftClient.getInstance().getBlockRenderManager();
|
||||
|
||||
public void setBlockView(ChunkRendererRegion blockView) {
|
||||
blockInfo.setBlockView(blockView);
|
||||
|
|
Loading…
Reference in a new issue