Skip to content

Commit

Permalink
Add Support for Custom ColorResolvers (FabricMC#3503)
Browse files Browse the repository at this point in the history
* Add support for custom color resolvers

* Add ColorResolverRegistry

* Fix checkstyle

* Statically initialize all BiomeColorCaches
  • Loading branch information
PepperCode1 authored Feb 9, 2024
1 parent a67ffb5 commit 6fd945a
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.fabric.api.client.rendering.v1;

import java.util.Set;

import org.jetbrains.annotations.UnmodifiableView;

import net.minecraft.client.color.world.BiomeColors;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.biome.ColorResolver;

import net.fabricmc.fabric.impl.client.rendering.ColorResolverRegistryImpl;

/**
* The registry for custom {@link ColorResolver}s. Custom resolvers must be registered during client initialization for
* them to be usable in {@link BlockRenderView#getColor}. Calling this method may throw an exception if the passed
* resolver is not registered with this class. Vanilla resolvers found in {@link BiomeColors} are automatically
* registered.
*
* <p>Other mods may also require custom resolvers to be registered if they provide additional functionality related to
* color resolvers.
*/
public final class ColorResolverRegistry {
private ColorResolverRegistry() {
}

/**
* Registers a custom {@link ColorResolver} for use in {@link BlockRenderView#getColor}. This method should be
* called during client initialization.
*
* @param resolver the resolver to register
*/
public static void register(ColorResolver resolver) {
ColorResolverRegistryImpl.register(resolver);
}

/**
* Gets a view of all registered {@link ColorResolver}s, including all vanilla resolvers.
*
* @return a view of all registered resolvers
*/
@UnmodifiableView
public static Set<ColorResolver> getAllResolvers() {
return ColorResolverRegistryImpl.getAllResolvers();
}

/**
* Gets a view of all registered {@link ColorResolver}s, not including vanilla resolvers.
*
* @return a view of all registered custom resolvers
*/
@UnmodifiableView
public static Set<ColorResolver> getCustomResolvers() {
return ColorResolverRegistryImpl.getCustomResolvers();
}

/**
* Checks whether the given {@link ColorResolver} is registered. Vanilla resolvers are always registered.
*
* @param resolver the resolver
* @return whether the given resolver is registered
*/
public static boolean isRegistered(ColorResolver resolver) {
return getAllResolvers().contains(resolver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.fabric.impl.client.rendering;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;

import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import org.jetbrains.annotations.UnmodifiableView;

import net.minecraft.client.color.world.BiomeColors;
import net.minecraft.client.world.BiomeColorCache;
import net.minecraft.world.biome.ColorResolver;

public final class ColorResolverRegistryImpl {
// Includes vanilla resolvers
private static final Set<ColorResolver> ALL_RESOLVERS = new HashSet<>();
// Does not include vanilla resolvers
private static final Set<ColorResolver> CUSTOM_RESOLVERS = new HashSet<>();
private static final Set<ColorResolver> ALL_RESOLVERS_VIEW = Collections.unmodifiableSet(ALL_RESOLVERS);
private static final Set<ColorResolver> CUSTOM_RESOLVERS_VIEW = Collections.unmodifiableSet(CUSTOM_RESOLVERS);

static {
ALL_RESOLVERS.add(BiomeColors.GRASS_COLOR);
ALL_RESOLVERS.add(BiomeColors.FOLIAGE_COLOR);
ALL_RESOLVERS.add(BiomeColors.WATER_COLOR);
}

private ColorResolverRegistryImpl() {
}

public static void register(ColorResolver resolver) {
ALL_RESOLVERS.add(resolver);
CUSTOM_RESOLVERS.add(resolver);
}

@UnmodifiableView
public static Set<ColorResolver> getAllResolvers() {
return ALL_RESOLVERS_VIEW;
}

@UnmodifiableView
public static Set<ColorResolver> getCustomResolvers() {
return CUSTOM_RESOLVERS_VIEW;
}

public static Reference2ReferenceMap<ColorResolver, BiomeColorCache> createCustomCacheMap(Function<ColorResolver, BiomeColorCache> cacheFactory) {
Reference2ReferenceOpenHashMap<ColorResolver, BiomeColorCache> map = new Reference2ReferenceOpenHashMap<>();

for (ColorResolver resolver : CUSTOM_RESOLVERS) {
map.put(resolver, cacheFactory.apply(resolver));
}

map.trim();
return map;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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.fabric.mixin.client.rendering;

import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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.world.BiomeColorCache;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.biome.ColorResolver;

import net.fabricmc.fabric.impl.client.rendering.ColorResolverRegistryImpl;

@Mixin(ClientWorld.class)
public abstract class ClientWorldMixin {
// Do not use the vanilla map because it is an Object2ObjectArrayMap. Array maps have O(n) retrievals compared to
// hash maps' O(1) retrievals. If many custom ColorResolvers are registered, this may have a non-negligible
// performance impact.
@Unique
private final Reference2ReferenceMap<ColorResolver, BiomeColorCache> customColorCache = ColorResolverRegistryImpl.createCustomCacheMap(resolver -> new BiomeColorCache(pos -> calculateColor(pos, resolver)));

@Shadow
public abstract int calculateColor(BlockPos pos, ColorResolver colorResolver);

@Inject(method = "resetChunkColor(Lnet/minecraft/util/math/ChunkPos;)V", at = @At("RETURN"))
private void onResetChunkColor(ChunkPos chunkPos, CallbackInfo ci) {
for (BiomeColorCache cache : customColorCache.values()) {
cache.reset(chunkPos.x, chunkPos.z);
}
}

@Inject(method = "reloadColor()V", at = @At("RETURN"))
private void onReloadColor(CallbackInfo ci) {
for (BiomeColorCache cache : customColorCache.values()) {
cache.reset();
}
}

@ModifyExpressionValue(method = "getColor(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/biome/ColorResolver;)I", at = @At(value = "INVOKE", target = "it/unimi/dsi/fastutil/objects/Object2ObjectArrayMap.get(Ljava/lang/Object;)Ljava/lang/Object;"))
private Object modifyNullCache(/* BiomeColorCache */ Object cache, BlockPos pos, ColorResolver resolver) {
if (cache == null) {
cache = customColorCache.get(resolver);

if (cache == null) {
throw new UnsupportedOperationException("ClientWorld.getColor called with unregistered ColorResolver " + resolver);
}
}

return cache;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"BlockEntityRendererFactoriesMixin",
"BuiltinModelItemRendererMixin",
"CapeFeatureRendererMixin",
"ClientWorldMixin",
"DimensionEffectsAccessor",
"EntityModelLayersAccessor",
"EntityModelsMixin",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.fabric.test.rendering;

import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;

import net.fabricmc.api.ModInitializer;

public class CustomColorResolverTestInit implements ModInitializer {
public static final Block CUSTOM_COLOR_BLOCK = new Block(AbstractBlock.Settings.create());
public static final Item CUSTOM_COLOR_BLOCK_ITEM = new BlockItem(CUSTOM_COLOR_BLOCK, new Item.Settings());

@Override
public void onInitialize() {
Registry.register(Registries.BLOCK, new Identifier("fabric-rendering-v1-testmod", "custom_color_block"), CUSTOM_COLOR_BLOCK);
Registry.register(Registries.ITEM, new Identifier("fabric-rendering-v1-testmod", "custom_color_block"), CUSTOM_COLOR_BLOCK_ITEM);
}
}
2 changes: 2 additions & 0 deletions fabric-rendering-v1/src/testmod/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.rendering.CustomAtlasSourcesTestInit",
"net.fabricmc.fabric.test.rendering.CustomColorResolverTestInit",
"net.fabricmc.fabric.test.rendering.TooltipComponentTestInit"
],
"client": [
"net.fabricmc.fabric.test.rendering.client.ArmorRenderingTests",
"net.fabricmc.fabric.test.rendering.client.CustomAtlasSourcesTest",
"net.fabricmc.fabric.test.rendering.client.CustomColorResolverTest",
"net.fabricmc.fabric.test.rendering.client.DimensionalRenderingTest",
"net.fabricmc.fabric.test.rendering.client.FeatureRendererTest",
"net.fabricmc.fabric.test.rendering.client.HudAndShaderTest",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.fabric.test.rendering.client;

import net.minecraft.world.biome.ColorResolver;

import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.ColorResolverRegistry;
import net.fabricmc.fabric.test.rendering.CustomColorResolverTestInit;

public class CustomColorResolverTest implements ClientModInitializer {
public static final ColorResolver TEST_COLOR_RESOLVER = (biome, x, z) -> {
if (biome.hasPrecipitation()) {
return 0xFFFF00FF;
} else {
return 0xFFFFFF00;
}
};

@Override
public void onInitializeClient() {
ColorResolverRegistry.register(TEST_COLOR_RESOLVER);

ColorProviderRegistry.BLOCK.register((state, world, pos, tintIndex) -> {
if (world != null && pos != null) {
return world.getColor(pos, TEST_COLOR_RESOLVER);
} else {
return -1;
}
}, CustomColorResolverTestInit.CUSTOM_COLOR_BLOCK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "fabric-rendering-v1-testmod:block/custom_color_block" }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"parent": "block/block",
"textures": {
"all": "fabric-rendering-v1-testmod:block/blank",
"particle": "#all"
},
"elements": [
{ "from": [ 0, 0, 0 ],
"to": [ 16, 16, 16 ],
"faces": {
"down": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "down" },
"up": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "up" },
"north": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "north" },
"south": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "south" },
"west": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "west" },
"east": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "east" }
}
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6fd945a

Please sign in to comment.