Improve Indigo and FRAPI Test Mod ()

* Improve flat shade

- Use AO mode to make flat shade calculation consistent with shade applied by smooth lighting
- Use face normal to calculate shade if necessary
- Use normal shade even if no custom normals are set

* Improve FRAPI test mod

- Add octagonal column to test irregular face lighting
- Use obsidian sprite instead of missing sprite for frame mesh
- Simplify and organize registration
- Inline `simple` package

* Fix crumbling on 45 degree faces

- Fix checkstyle
- Give octagonal column a non-zero hardness

* Fix checkstyle

* Improve PillarBakedModel to fully support custom block appearance

* Explain OverlayVertexConsumer fix
This commit is contained in:
PepperCode1 2023-08-13 09:01:37 -06:00 committed by GitHub
parent 8536527b69
commit 6bdb2ed00f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 861 additions and 361 deletions
fabric-renderer-api-v1/src
fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer

View file

@ -0,0 +1,60 @@
/*
* 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.renderer.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import net.minecraft.client.render.OverlayVertexConsumer;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathConstants;
@Mixin(OverlayVertexConsumer.class)
public class OverlayVertexConsumerMixin {
@Unique
private static final Direction[] DIRECTIONS = Direction.values();
/*
The original method call is used to get the closest axis-aligned direction of the world-space
normal vector for a certain face. The world-space normal vector is computed using matrices
that change when the camera values change. Due to precision errors during matrix
multiplication, the computed world-space normal of a face will not remain constant, so the
closest axis-aligned direction may flicker. This issue only affects faces that are directly
between two axis-aligned directions (45 degree faces) or three axis-aligned directions.
The fix involves requiring the dot product of each axis-aligned direction to be a small
amount greater than the previous maximum dot product to be set as the new maximum.
*/
@Redirect(method = "next()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Direction;getFacing(FFF)Lnet/minecraft/util/math/Direction;"))
private Direction redirectGetFacing(float x, float y, float z) {
Direction closestDir = Direction.NORTH;
float maxDot = 1.4E-45F;
for (Direction direction : DIRECTIONS) {
float dot = x * direction.getOffsetX() + y * direction.getOffsetY() + z * direction.getOffsetZ();
if (dot > maxDot + MathConstants.EPSILON) {
maxDot = dot;
closestDir = direction;
}
}
return closestDir;
}
}

View file

@ -6,7 +6,8 @@
"client.BakedModelMixin",
"client.MultipartBakedModelMixin",
"client.WeightedBakedModelMixin",
"client.SpriteAtlasTextureMixin"
"client.SpriteAtlasTextureMixin",
"client.OverlayVertexConsumerMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.simple;
package net.fabricmc.fabric.test.renderer;
import org.jetbrains.annotations.Nullable;
@ -27,7 +27,6 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
@ -35,16 +34,12 @@ import net.minecraft.world.BlockRenderView;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.block.v1.FabricBlock;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
// Need to implement FabricBlock manually because this is a testmod for another Fabric module, otherwise it would be injected.
public final class FrameBlock extends Block implements BlockEntityProvider, FabricBlock {
public final Identifier id;
public FrameBlock(Identifier id) {
super(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque());
this.id = id;
public class FrameBlock extends Block implements BlockEntityProvider, FabricBlock {
public FrameBlock(Settings settings) {
super(settings);
}
@Override

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.simple;
package net.fabricmc.fabric.test.renderer;
import org.jetbrains.annotations.Nullable;
@ -31,12 +31,12 @@ import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
public final class FrameBlockEntity extends BlockEntity implements RenderAttachmentBlockEntity {
public class FrameBlockEntity extends BlockEntity implements RenderAttachmentBlockEntity {
@Nullable
private Block block = null;
public FrameBlockEntity(BlockPos blockPos, BlockState blockState) {
super(RendererTest.FRAME_BLOCK_ENTITY, blockPos, blockState);
super(Registration.FRAME_BLOCK_ENTITY_TYPE, blockPos, blockState);
}
@Override

View file

@ -0,0 +1,65 @@
/*
* 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.renderer;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
public final class Registration {
public static final FrameBlock FRAME_BLOCK = register("frame", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque()));
public static final FrameBlock FRAME_MULTIPART_BLOCK = register("frame_multipart", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque()));
public static final FrameBlock FRAME_VARIANT_BLOCK = register("frame_variant", new FrameBlock(FabricBlockSettings.copyOf(Blocks.IRON_BLOCK).nonOpaque()));
public static final Block PILLAR_BLOCK = register("pillar", new Block(FabricBlockSettings.create()));
public static final Block OCTAGONAL_COLUMN_BLOCK = register("octagonal_column", new Block(FabricBlockSettings.create().nonOpaque().strength(1.8F)));
public static final FrameBlock[] FRAME_BLOCKS = new FrameBlock[] {
FRAME_BLOCK,
FRAME_MULTIPART_BLOCK,
FRAME_VARIANT_BLOCK,
};
public static final Item FRAME_ITEM = register("frame", new BlockItem(FRAME_BLOCK, new Item.Settings()));
public static final Item FRAME_MULTIPART_ITEM = register("frame_multipart", new BlockItem(FRAME_MULTIPART_BLOCK, new Item.Settings()));
public static final Item FRAME_VARIANT_ITEM = register("frame_variant", new BlockItem(FRAME_VARIANT_BLOCK, new Item.Settings()));
public static final Item PILLAR_ITEM = register("pillar", new BlockItem(PILLAR_BLOCK, new Item.Settings()));
public static final Item OCTAGONAL_COLUMN_ITEM = register("octagonal_column", new BlockItem(OCTAGONAL_COLUMN_BLOCK, new Item.Settings()));
public static final BlockEntityType<FrameBlockEntity> FRAME_BLOCK_ENTITY_TYPE = register("frame", FabricBlockEntityTypeBuilder.create(FrameBlockEntity::new, FRAME_BLOCKS).build(null));
private static <T extends Block> T register(String path, T block) {
return Registry.register(Registries.BLOCK, RendererTest.id(path), block);
}
private static <T extends Item> T register(String path, T item) {
return Registry.register(Registries.ITEM, RendererTest.id(path), item);
}
private static <T extends BlockEntityType<?>> T register(String path, T blockEntityType) {
return Registry.register(Registries.BLOCK_ENTITY_TYPE, RendererTest.id(path), blockEntityType);
}
public static void init() {
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.renderer;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
/**
* The testmod for the Fabric Renderer API. These tests are used to validate that
* Indigo's implementation is correct, but they may also be useful for other
* implementations of the Fabric Renderer API.
*
* <h3>Tests</h3>
*
* <ul>
* <li>Frame blocks display another block inside, scaled down and made translucent.
* Blocks that provide a block entity cannot be placed inside frames.
*
* <li>Pillars connect vertically with each other by changing textures. They also
* connect vertically to frame blocks containing a pillar, and vice versa.
*
* <li>Octagonal columns have irregular faces to test enhanced AO and normal shade. The
* octagonal item column has glint force enabled on all faces except the top and bottom
* faces.
* </ul>
*/
public final class RendererTest implements ModInitializer {
@Override
public void onInitialize() {
Registration.init();
}
public static Identifier id(String path) {
return new Identifier("fabric-renderer-api-v1-testmod", path);
}
}

View file

@ -1,26 +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.
*/
/**
* The testmod for the Fabric Renderer API.
* Right now there is only one test here, but more tests may come to exist in the future.
* These tests are used to validate Indigo's implementation is correct, but these tests may also be useful for other implementations of the Fabric Renderer API.
*
* <p>Right now there is a simple test in the {@code simple} package which validates that simple meshes and quad emitters function.
* Future tests may look into testing things such as render materials or creating more advanced models.
*/
package net.fabricmc.fabric.test.renderer;

View file

@ -1,68 +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.fabric.test.renderer.simple;
import net.minecraft.block.Block;
import net.minecraft.block.entity.BlockEntityType;
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;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
/**
* A simple testmod that renders a simple block rendered using the fabric renderer api.
* The block that is rendered is a simple frame that another block is rendered in.
* Blocks that provide a block entity cannot be placed inside the frame.
*
* <p>There are no fancy shaders or glow that is provided by this renderer test.
*/
public final class RendererTest implements ModInitializer {
public static final FrameBlock[] FRAMES = new FrameBlock[]{
new FrameBlock(id("frame")),
new FrameBlock(id("frame_multipart")),
new FrameBlock(id("frame_weighted")),
};
public static final BlockEntityType<FrameBlockEntity> FRAME_BLOCK_ENTITY = FabricBlockEntityTypeBuilder.create(FrameBlockEntity::new, FRAMES).build(null);
public static final Identifier PILLAR_ID = id("pillar");
public static final Block PILLAR = new Block(FabricBlockSettings.create());
@Override
public void onInitialize() {
for (FrameBlock frameBlock : FRAMES) {
Registry.register(Registries.BLOCK, frameBlock.id, frameBlock);
Registry.register(Registries.ITEM, frameBlock.id, new BlockItem(frameBlock, new Item.Settings()));
}
// To anyone testing this: pillars are supposed to connect vertically with each other.
// Additionally, they should also connect vertically to frame blocks containing a pillar.
// (The frame block will not change, but adjacent pillars should adjust their textures).
Registry.register(Registries.BLOCK, PILLAR_ID, PILLAR);
Registry.register(Registries.ITEM, PILLAR_ID, new BlockItem(PILLAR, new Item.Settings()));
Registry.register(Registries.BLOCK_ENTITY_TYPE, id("frame"), FRAME_BLOCK_ENTITY);
}
public static Identifier id(String path) {
return new Identifier("fabric-renderer-api-v1-testmod", path);
}
}

View file

@ -7,14 +7,15 @@
"license": "Apache-2.0",
"depends": {
"fabric-renderer-api-v1":"*",
"fabric-model-loading-api-v1":"*",
"fabric-resource-loader-v0": "*"
},
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.renderer.simple.RendererTest"
"net.fabricmc.fabric.test.renderer.RendererTest"
],
"client": [
"net.fabricmc.fabric.test.renderer.simple.client.RendererClientTest"
"net.fabricmc.fabric.test.renderer.client.RendererClientTest"
]
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.simple.client;
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collections;
import java.util.List;
@ -46,13 +46,13 @@ import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
final class FrameBakedModel implements BakedModel {
public class FrameBakedModel implements BakedModel {
private final Mesh frameMesh;
private final Sprite frameSprite;
private final RenderMaterial translucentMaterial;
private final RenderMaterial translucentEmissiveMaterial;
FrameBakedModel(Mesh frameMesh, Sprite frameSprite) {
public FrameBakedModel(Mesh frameMesh, Sprite frameSprite) {
this.frameMesh = frameMesh;
this.frameSprite = frameSprite;
@ -62,46 +62,6 @@ final class FrameBakedModel implements BakedModel {
this.translucentEmissiveMaterial = finder.blendMode(BlendMode.TRANSLUCENT).emissive(true).find();
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) {
return Collections.emptyList(); // Renderer API makes this obsolete, so return no quads
}
@Override
public boolean useAmbientOcclusion() {
return true; // we want the block to have a shadow depending on the adjacent blocks
}
@Override
public boolean hasDepth() {
return false;
}
@Override
public boolean isSideLit() {
return true; // we want the block to be lit from the side when rendered as an item
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getParticleSprite() {
return this.frameSprite;
}
@Override
public ModelTransformation getTransformation() {
return ModelHelper.MODEL_TRANSFORM_BLOCK;
}
@Override
public ModelOverrideList getOverrides() {
return ModelOverrideList.EMPTY;
}
@Override
public boolean isVanillaAdapter() {
return false;
@ -142,7 +102,7 @@ final class FrameBakedModel implements BakedModel {
// Emit a scaled-down fence for testing, trying both materials again.
RenderMaterial material = stack.hasCustomName() ? translucentEmissiveMaterial : translucentMaterial;
ItemStack innerItem = Items.OAK_FENCE.getDefaultStack();
ItemStack innerItem = Items.CRAFTING_TABLE.getDefaultStack();
BakedModel innerModel = MinecraftClient.getInstance().getItemRenderer().getModel(innerItem, null, null, 0);
emitInnerQuads(context, material, () -> {
@ -187,4 +147,44 @@ final class FrameBakedModel implements BakedModel {
// Let's not forget to pop the transform!
context.popTransform();
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) {
return Collections.emptyList(); // Renderer API makes this obsolete, so return no quads
}
@Override
public boolean useAmbientOcclusion() {
return true; // we want the block to have a shadow depending on the adjacent blocks
}
@Override
public boolean hasDepth() {
return false;
}
@Override
public boolean isSideLit() {
return true; // we want the block to be lit from the side when rendered as an item
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getParticleSprite() {
return this.frameSprite;
}
@Override
public ModelTransformation getTransformation() {
return ModelHelper.MODEL_TRANSFORM_BLOCK;
}
@Override
public ModelOverrideList getOverrides() {
return ModelOverrideList.EMPTY;
}
}

View file

@ -0,0 +1,118 @@
/*
* 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.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
public class FrameUnbakedModel implements UnbakedModel {
private static final SpriteIdentifier OBSIDIAN_SPRITE_ID = new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("block/obsidian"));
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}
/*
* Bake the model.
* In this case we can prebake the frame into a mesh, but will render the contained block when we draw the quads.
*/
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
// The renderer API may not have an implementation, so we should check if it exists.
if (!RendererAccess.INSTANCE.hasRenderer()) {
// No renderer implementation is present.
return null;
}
Sprite obsidianSprite = textureGetter.apply(OBSIDIAN_SPRITE_ID);
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
MeshBuilder builder = renderer.meshBuilder();
QuadEmitter emitter = builder.getEmitter();
for (Direction direction : Direction.values()) {
// Draw outer frame
emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.0F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.0F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.0F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.0F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
// Draw inner frame - inset by 0.9 so the frame looks like an actual mesh
emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.9F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.9F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.9F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.9F)
.spriteBake(obsidianSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
}
return new FrameBakedModel(builder.build(), obsidianSprite);
}
}

View file

@ -0,0 +1,66 @@
/*
* 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.renderer.client;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelResolver;
import net.fabricmc.fabric.test.renderer.RendererTest;
public class ModelResolverImpl implements ModelResolver {
private static final Set<Identifier> FRAME_MODEL_LOCATIONS = Set.of(
RendererTest.id("block/frame"),
RendererTest.id("item/frame"),
RendererTest.id("item/frame_multipart"),
RendererTest.id("item/frame_variant")
);
private static final Set<Identifier> PILLAR_MODEL_LOCATIONS = Set.of(
RendererTest.id("block/pillar"),
RendererTest.id("item/pillar")
);
private static final Set<Identifier> OCTAGONAL_COLUMN_MODEL_LOCATIONS = Set.of(
RendererTest.id("block/octagonal_column"),
RendererTest.id("item/octagonal_column")
);
@Override
@Nullable
public UnbakedModel resolveModel(Context context) {
Identifier id = context.id();
if (FRAME_MODEL_LOCATIONS.contains(id)) {
return new FrameUnbakedModel();
}
if (PILLAR_MODEL_LOCATIONS.contains(id)) {
return new PillarUnbakedModel();
}
if (OCTAGONAL_COLUMN_MODEL_LOCATIONS.contains(id)) {
return new OctagonalColumnUnbakedModel();
}
return null;
}
}

View file

@ -0,0 +1,226 @@
/*
* 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.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.util.TriState;
public class OctagonalColumnUnbakedModel implements UnbakedModel {
private static final SpriteIdentifier WHITE_CONCRETE_SPRITE_ID = new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("block/white_concrete"));
// (B - A) is the side length of a regular octagon that fits in a unit square.
// The line from A to B is centered on the line from 0 to 1.
private static final float A = (float) (1 - Math.sqrt(2) / 2);
private static final float B = (float) (Math.sqrt(2) / 2);
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> modelLoader) {
}
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
if (!RendererAccess.INSTANCE.hasRenderer()) {
return null;
}
Sprite whiteConcreteSprite = textureGetter.apply(WHITE_CONCRETE_SPRITE_ID);
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
MaterialFinder finder = renderer.materialFinder();
RenderMaterial glintMaterial = finder.glint(TriState.TRUE).find();
MeshBuilder builder = renderer.meshBuilder();
QuadEmitter emitter = builder.getEmitter();
// up
emitter.pos(0, A, 1, 0);
emitter.pos(1, 0.5f, 1, 0.5f);
emitter.pos(2, 1, 1, A);
emitter.pos(3, B, 1, 0);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
emitter.pos(0, 0, 1, A);
emitter.pos(1, 0, 1, B);
emitter.pos(2, 0.5f, 1, 0.5f);
emitter.pos(3, A, 1, 0);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
emitter.pos(0, 0, 1, B);
emitter.pos(1, A, 1, 1);
emitter.pos(2, B, 1, 1);
emitter.pos(3, 0.5f, 1, 0.5f);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
emitter.pos(0, 0.5f, 1, 0.5f);
emitter.pos(1, B, 1, 1);
emitter.pos(2, 1, 1, B);
emitter.pos(3, 1, 1, A);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// down
emitter.pos(0, A, 0, 1);
emitter.pos(1, 0.5f, 0, 0.5f);
emitter.pos(2, 1, 0, B);
emitter.pos(3, B, 0, 1);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
emitter.pos(0, 0, 0, B);
emitter.pos(1, 0, 0, A);
emitter.pos(2, 0.5f, 0, 0.5f);
emitter.pos(3, A, 0, 1);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
emitter.pos(0, 0, 0, A);
emitter.pos(1, A, 0, 0);
emitter.pos(2, B, 0, 0);
emitter.pos(3, 0.5f, 0, 0.5f);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
emitter.pos(0, 0.5f, 0, 0.5f);
emitter.pos(1, B, 0, 0);
emitter.pos(2, 1, 0, A);
emitter.pos(3, 1, 0, B);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// north
emitter.pos(0, B, 1, 0);
emitter.pos(1, B, 0, 0);
emitter.pos(2, A, 0, 0);
emitter.pos(3, A, 1, 0);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// northwest
emitter.pos(0, A, 1, 0);
emitter.pos(1, A, 0, 0);
emitter.pos(2, 0, 0, A);
emitter.pos(3, 0, 1, A);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// west
emitter.pos(0, 0, 1, A);
emitter.pos(1, 0, 0, A);
emitter.pos(2, 0, 0, B);
emitter.pos(3, 0, 1, B);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// southwest
emitter.pos(0, 0, 1, B);
emitter.pos(1, 0, 0, B);
emitter.pos(2, A, 0, 1);
emitter.pos(3, A, 1, 1);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// south
emitter.pos(0, A, 1, 1);
emitter.pos(1, A, 0, 1);
emitter.pos(2, B, 0, 1);
emitter.pos(3, B, 1, 1);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// southeast
emitter.pos(0, B, 1, 1);
emitter.pos(1, B, 0, 1);
emitter.pos(2, 1, 0, B);
emitter.pos(3, 1, 1, B);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// east
emitter.pos(0, 1, 1, B);
emitter.pos(1, 1, 0, B);
emitter.pos(2, 1, 0, A);
emitter.pos(3, 1, 1, A);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
// northeast
emitter.pos(0, 1, 1, A);
emitter.pos(1, 1, 0, A);
emitter.pos(2, B, 0, 0);
emitter.pos(3, B, 1, 0);
emitter.spriteBake(whiteConcreteSprite, MutableQuadView.BAKE_LOCK_UV);
emitter.material(glintMaterial);
emitter.color(-1, -1, -1, -1);
emitter.emit();
return new SingleMeshBakedModel(builder.build(), whiteConcreteSprite);
}
}

View file

@ -14,8 +14,9 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.simple.client;
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@ -34,15 +35,11 @@ import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.block.v1.FabricBlockState;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
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;
import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.fabric.test.renderer.simple.RendererTest;
import net.fabricmc.fabric.test.renderer.Registration;
/**
* Very crude implementation of a pillar block model that connects with pillars above and below.
@ -54,16 +51,9 @@ public class PillarBakedModel implements BakedModel {
// alone, bottom, middle, top
private final Sprite[] sprites;
private final RenderMaterial defaultMaterial;
private final RenderMaterial glintMaterial;
public PillarBakedModel(Sprite[] sprites) {
this.sprites = sprites;
MaterialFinder finder = RendererAccess.INSTANCE.getRenderer().materialFinder();
defaultMaterial = finder.find();
finder.clear();
glintMaterial = finder.glint(TriState.TRUE).find();
}
@Override
@ -73,52 +63,67 @@ public class PillarBakedModel implements BakedModel {
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
emitQuads(context.getEmitter(), blockView, state, pos);
}
QuadEmitter emitter = context.getEmitter();
// Do not use the passed state to ensure that this model connects
// to and from blocks with a custom appearance correctly.
BlockState worldState = blockView.getBlockState(pos);
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
emitQuads(context.getEmitter(), null, null, null);
}
private void emitQuads(QuadEmitter emitter, @Nullable BlockRenderView blockView, @Nullable BlockState state, @Nullable BlockPos pos) {
for (Direction side : Direction.values()) {
ConnectedTexture texture = ConnectedTexture.ALONE;
RenderMaterial material = defaultMaterial;
if (side.getAxis().isHorizontal()) {
if (blockView != null && state != null && pos != null) {
boolean connectAbove = canConnect(blockView, pos.offset(Direction.UP), side, state, pos);
boolean connectBelow = canConnect(blockView, pos.offset(Direction.DOWN), side, state, pos);
boolean connectAbove = canConnect(blockView, worldState, pos, pos.offset(Direction.UP), side);
boolean connectBelow = canConnect(blockView, worldState, pos, pos.offset(Direction.DOWN), side);
if (connectAbove && connectBelow) {
texture = ConnectedTexture.MIDDLE;
} else if (connectAbove) {
texture = ConnectedTexture.BOTTOM;
} else if (connectBelow) {
texture = ConnectedTexture.TOP;
}
if (connectAbove && connectBelow) {
texture = ConnectedTexture.MIDDLE;
} else if (connectAbove) {
texture = ConnectedTexture.BOTTOM;
} else if (connectBelow) {
texture = ConnectedTexture.TOP;
}
material = glintMaterial;
}
emitter.square(side, 0, 0, 1, 1, 0);
emitter.spriteBake(sprites[texture.ordinal()], MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.material(material);
emitter.emit();
}
}
private static boolean canConnect(BlockRenderView blockView, BlockPos pos, Direction side, BlockState sourceState, BlockPos sourcePos) {
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
QuadEmitter emitter = context.getEmitter();
for (Direction side : Direction.values()) {
emitter.square(side, 0, 0, 1, 1, 0);
emitter.spriteBake(sprites[ConnectedTexture.ALONE.ordinal()], MutableQuadView.BAKE_LOCK_UV);
emitter.color(-1, -1, -1, -1);
emitter.emit();
}
}
private static boolean canConnect(BlockRenderView blockView, BlockState originState, BlockPos originPos, BlockPos otherPos, Direction side) {
BlockState otherState = blockView.getBlockState(otherPos);
// In this testmod we can't rely on injected interfaces - in normal mods the (FabricBlockState) cast will be unnecessary
return ((FabricBlockState) blockView.getBlockState(pos)).getAppearance(blockView, pos, side, sourceState, sourcePos).isOf(RendererTest.PILLAR);
BlockState originAppearance = ((FabricBlockState) originState).getAppearance(blockView, originPos, side, otherState, otherPos);
if (!originAppearance.isOf(Registration.PILLAR_BLOCK)) {
return false;
}
BlockState otherAppearance = ((FabricBlockState) otherState).getAppearance(blockView, otherPos, side, originState, originPos);
if (!otherAppearance.isOf(Registration.PILLAR_BLOCK)) {
return false;
}
return true;
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) {
return List.of();
return Collections.emptyList();
}
@Override

View file

@ -14,9 +14,10 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.simple.client;
package net.fabricmc.fabric.test.renderer.client;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
@ -32,7 +33,7 @@ import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.test.renderer.simple.RendererTest;
import net.fabricmc.fabric.test.renderer.RendererTest;
public class PillarUnbakedModel implements UnbakedModel {
private static final List<SpriteIdentifier> SPRITES = Stream.of("alone", "bottom", "middle", "top")
@ -41,7 +42,7 @@ public class PillarUnbakedModel implements UnbakedModel {
@Override
public Collection<Identifier> getModelDependencies() {
return List.of();
return Collections.emptySet();
}
@Override

View file

@ -14,51 +14,27 @@
* limitations under the License.
*/
package net.fabricmc.fabric.test.renderer.simple.client;
import static net.fabricmc.fabric.test.renderer.simple.RendererTest.id;
import java.util.HashSet;
import java.util.Set;
package net.fabricmc.fabric.test.renderer.client;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
import net.fabricmc.fabric.test.renderer.simple.FrameBlock;
import net.fabricmc.fabric.test.renderer.simple.RendererTest;
import net.fabricmc.fabric.test.renderer.FrameBlock;
import net.fabricmc.fabric.test.renderer.Registration;
public final class RendererClientTest implements ClientModInitializer {
private static final Set<Identifier> FRAME_MODELS = new HashSet<>();
@Override
public void onInitializeClient() {
for (FrameBlock frameBlock : RendererTest.FRAMES) {
ModelLoadingPlugin.register(pluginContext -> {
pluginContext.resolveModel().register(new ModelResolverImpl());
});
for (FrameBlock frameBlock : Registration.FRAME_BLOCKS) {
// We don't specify a material for the frame mesh,
// so it will use the default material, i.e. the one from BlockRenderLayerMap.
BlockRenderLayerMap.INSTANCE.putBlock(frameBlock, RenderLayer.getCutoutMipped());
String itemPath = Registries.ITEM.getId(frameBlock.asItem()).getPath();
FRAME_MODELS.add(id("item/" + itemPath));
}
FRAME_MODELS.add(id("block/frame"));
ModelLoadingPlugin.register(pluginContext -> {
pluginContext.resolveModel().register(context -> {
if (FRAME_MODELS.contains(context.id())) {
return new FrameUnbakedModel();
}
return null;
});
pluginContext.registerBlockStateResolver(RendererTest.PILLAR, context -> {
context.setModel(context.block().getDefaultState(), new PillarUnbakedModel());
});
});
}
}

View file

@ -0,0 +1,104 @@
/*
* 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.renderer.client;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
public class SingleMeshBakedModel implements BakedModel {
private final Mesh mesh;
private final Sprite particleSprite;
public SingleMeshBakedModel(Mesh mesh, Sprite particleSprite) {
this.mesh = mesh;
this.particleSprite = particleSprite;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
mesh.outputTo(context.getEmitter());
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
mesh.outputTo(context.getEmitter());
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, Random random) {
return Collections.emptyList();
}
@Override
public boolean useAmbientOcclusion() {
return true;
}
@Override
public boolean hasDepth() {
return false;
}
@Override
public boolean isSideLit() {
return true;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getParticleSprite() {
return particleSprite;
}
@Override
public ModelTransformation getTransformation() {
return ModelHelper.MODEL_TRANSFORM_BLOCK;
}
@Override
public ModelOverrideList getOverrides() {
return ModelOverrideList.EMPTY;
}
}

View file

@ -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.fabric.test.renderer.simple.client;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
final class FrameUnbakedModel implements UnbakedModel {
FrameUnbakedModel() {
}
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptySet();
}
@Override
public void setParents(Function<Identifier, UnbakedModel> function) {
}
/*
* Bake the model.
* In this case we can prebake the frame into a mesh, but will render the contained block when we draw the quads.
*/
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
// The renderer api may not have an implementation.
// For this reason we will just null check the renderer impl
if (RendererAccess.INSTANCE.hasRenderer()) {
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
MeshBuilder builder = renderer.meshBuilder();
QuadEmitter emitter = builder.getEmitter();
// TODO: Just some random texture to get a missing texture, we should get a proper texture soon
Sprite frameSprite = textureGetter.apply(new SpriteIdentifier(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE, new Identifier("foo:foo")));
for (Direction direction : Direction.values()) {
// Draw outer frame
emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.0F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.0F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.0F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.0F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
// Draw inner frame - inset by 0.9 so the frame looks like an actual mesh
emitter.square(direction, 0.0F, 0.9F, 0.9F, 1.0F, 0.9F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.0F, 0.0F, 0.1F, 0.9F, 0.9F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.9F, 0.1F, 1.0F, 1.0F, 0.9F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
emitter.square(direction, 0.1F, 0.0F, 1.0F, 0.1F, 0.9F)
.spriteBake(frameSprite, MutableQuadView.BAKE_LOCK_UV)
.color(-1, -1, -1, -1)
.emit();
}
return new FrameBakedModel(builder.build(), frameSprite);
}
// No renderer implementation is present.
return null;
}
}

View file

@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "fabric-renderer-api-v1-testmod:block/octagonal_column" }
}
}

View file

@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "fabric-renderer-api-v1-testmod:block/pillar" }
}
}

View file

@ -173,6 +173,11 @@ public class QuadViewImpl implements QuadView {
return normalFlags() != 0;
}
/** True if all vertex normals have been set. */
public boolean hasAllVertexNormals() {
return (normalFlags() & 0b1111) == 0b1111;
}
protected final int normalIndex(int vertexIndex) {
return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL;
}

View file

@ -22,6 +22,7 @@ import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHel
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.LightmapTextureManager;
@ -37,8 +38,10 @@ import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.fabric.impl.client.indigo.Indigo;
import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoConfig;
import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
@ -131,7 +134,7 @@ public abstract class AbstractBlockRenderContext extends AbstractRenderContext {
}
}
} else {
shadeFlatQuad(quad);
shadeFlatQuad(quad, isVanilla);
if (emissive) {
for (int i = 0; i < 4; i++) {
@ -151,30 +154,57 @@ public abstract class AbstractBlockRenderContext extends AbstractRenderContext {
* Starting in 1.16 flat shading uses dimension-specific diffuse factors that can be < 1.0
* even for un-shaded quads. These are also applied with AO shading but that is done in AO calculator.
*/
private void shadeFlatQuad(MutableQuadViewImpl quad) {
if (quad.hasVertexNormals()) {
// Quads that have vertex normals need to be shaded using interpolation - vanilla can't
// handle them. Generally only applies to modded models.
final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade());
private void shadeFlatQuad(MutableQuadViewImpl quad, boolean isVanilla) {
final boolean hasShade = quad.hasShade();
for (int i = 0; i < 4; i++) {
quad.color(i, ColorHelper.multiplyRGB(quad.color(i), vertexShade(quad, i, faceShade)));
// Check the AO mode to match how shade is applied during smooth lighting
if ((Indigo.AMBIENT_OCCLUSION_MODE == AoConfig.HYBRID && !isVanilla) || Indigo.AMBIENT_OCCLUSION_MODE == AoConfig.ENHANCED) {
if (quad.hasAllVertexNormals()) {
for (int i = 0; i < 4; i++) {
float shade = normalShade(quad.normalX(i), quad.normalY(i), quad.normalZ(i), hasShade);
quad.color(i, ColorHelper.multiplyRGB(quad.color(i), shade));
}
} else {
final float faceShade;
if ((quad.geometryFlags() & AXIS_ALIGNED_FLAG) != 0) {
faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), hasShade);
} else {
Vector3f faceNormal = quad.faceNormal();
faceShade = normalShade(faceNormal.x, faceNormal.y, faceNormal.z, hasShade);
}
if (quad.hasVertexNormals()) {
for (int i = 0; i < 4; i++) {
float shade;
if (quad.hasNormal(i)) {
shade = normalShade(quad.normalX(i), quad.normalY(i), quad.normalZ(i), hasShade);
} else {
shade = faceShade;
}
quad.color(i, ColorHelper.multiplyRGB(quad.color(i), shade));
}
} else {
if (faceShade != 1.0f) {
for (int i = 0; i < 4; i++) {
quad.color(i, ColorHelper.multiplyRGB(quad.color(i), faceShade));
}
}
}
}
} else {
final float diffuseShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade());
final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), hasShade);
if (diffuseShade != 1.0f) {
if (faceShade != 1.0f) {
for (int i = 0; i < 4; i++) {
quad.color(i, ColorHelper.multiplyRGB(quad.color(i), diffuseShade));
quad.color(i, ColorHelper.multiplyRGB(quad.color(i), faceShade));
}
}
}
}
private float vertexShade(MutableQuadViewImpl quad, int vertexIndex, float faceShade) {
return quad.hasNormal(vertexIndex) ? normalShade(quad.normalX(vertexIndex), quad.normalY(vertexIndex), quad.normalZ(vertexIndex), quad.hasShade()) : faceShade;
}
/**
* Finds mean of per-face shading factors weighted by normal components.
* Not how light actually works but the vanilla diffuse shading model is a hack to start with