Skip to content

Instantly share code, notes, and snippets.

@Haven-King
Created May 13, 2021 22:29
Show Gist options
  • Save Haven-King/4f3f838e4e99abc875ac7e3f48149e54 to your computer and use it in GitHub Desktop.
Save Haven-King/4f3f838e4e99abc875ac7e3f48149e54 to your computer and use it in GitHub Desktop.
package com.aether.client.model;
import com.mojang.datafixers.util.Pair;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
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.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.*;
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.client.util.SpriteIdentifier;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.BlockRenderView;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class TessellatingBlockModel implements BakedModel, FabricBakedModel, UnbakedModel {
private static final Direction[] DIRECTIONS = Direction.values();
private final List<SpriteIdentifier> spriteIdentifiers;
private final Map<Direction, Mesh[]> meshes = new EnumMap<>(Direction.class);
private final int width, height;
private Sprite sprite;
private final SpriteIdentifier spriteId;
private final SpriteIdentifier overlay;
private Mesh overlayMesh;
private TessellatingBlockModel(SpriteIdentifier spriteId, int width, int height, List<SpriteIdentifier> spriteIdentifiers, @Nullable SpriteIdentifier overlay) {
this.width = width;
this.height = height;
this.spriteIdentifiers = spriteIdentifiers;
this.spriteId = spriteId;
this.overlay = overlay;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
int x = pos.getX(), y = pos.getY(), z = pos.getZ();
Consumer<Mesh> consumer = context.meshConsumer();
for (Direction direction : DIRECTIONS) {
consumer.accept(this.meshes.get(direction)[this.indexOf(direction, x, y, z)]);
}
if (this.overlayMesh != null) {
consumer.accept(overlayMesh);
}
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
}
@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 false;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getSprite() {
return this.sprite;
}
@Override
public ModelTransformation getTransformation() {
return ModelTransformation.NONE;
}
@Override
public ModelOverrideList getOverrides() {
return ModelOverrideList.EMPTY;
}
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.emptyList();
}
@Override
public Collection<SpriteIdentifier> getTextureDependencies(Function<Identifier, UnbakedModel> unbakedModelGetter, Set<Pair<String, String>> unresolvedTextureReferences) {
return this.spriteIdentifiers;
}
@Nullable
@Override
public BakedModel bake(ModelLoader loader, Function<SpriteIdentifier, Sprite> textureGetter, ModelBakeSettings rotationContainer, Identifier modelId) {
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
if (renderer != null) {
this.sprite = textureGetter.apply(this.spriteId);
MeshBuilder builder = renderer.meshBuilder();
QuadEmitter emitter = builder.getEmitter();
for (Direction direction : DIRECTIONS) {
Mesh[] meshes = new Mesh[this.width * this.height];
for (int i = 0; i < this.width * this.height; ++i) {
emitter.square(direction, 0, 0, 1, 1, 0);
emitter.spriteBake(0, textureGetter.apply(this.spriteIdentifiers.get(i)), MutableQuadView.BAKE_LOCK_UV);
emitter.spriteColor(0, -1, -1, -1, -1);
emitter.emit();
meshes[i] = builder.build();
}
this.meshes.put(direction, meshes);
}
if (this.overlay != null) {
RenderMaterial material = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).find();
for (Direction direction : DIRECTIONS) {
emitter.square(direction, 0, 0, 1, 1, 0);
emitter.spriteBake(0, textureGetter.apply(this.overlay), MutableQuadView.BAKE_LOCK_UV);
emitter.spriteColor(0, -1, -1, -1, -1);
emitter.material(material);
emitter.emit();
}
this.overlayMesh = builder.build();
}
}
return this;
}
private int indexOf(Direction direction, int x, int y, int z) {
Direction.Axis axis = direction.getAxis();
Vec3i vec3i = direction.getVector();
int mod = axis.choose(vec3i.getX(), vec3i.getY(), vec3i.getZ());
switch (axis) {
case X:
return MathHelper.floorMod(z * -mod + x, this.width) + (this.height - MathHelper.floorMod(y + x, this.height) - 1) * this.width;
case Y:
return MathHelper.floorMod(x + y, this.width) + (this.height - MathHelper.floorMod(z * -mod + y, this.height) - 1) * this.width;
case Z:
return MathHelper.floorMod(x * mod + z, this.width) + (this.height - MathHelper.floorMod(y + z, this.height) - 1) * this.width;
}
return 0;
}
public static final class Builder {
private final SpriteIdentifier spriteId;
private final List<SpriteIdentifier> spriteIdentifiers = new ArrayList<>();
private int width, height;
private SpriteIdentifier overlay;
public Builder(SpriteIdentifier spriteId) {
this.spriteId = spriteId;
}
public Builder row(SpriteIdentifier... spriteIdentifiers) {
if (spriteIdentifiers.length > 0) {
if (this.width > 0 && spriteIdentifiers.length != this.width) {
throw new RuntimeException("Row is not of correct size! Expected " + this.width + " found " + spriteIdentifiers.length + ".");
}
if (this.width == 0) this.width = spriteIdentifiers.length;
++this.height;
this.spriteIdentifiers.addAll(Arrays.asList(spriteIdentifiers));
}
return this;
}
public Builder of(SpriteIdentifier base, int startingIndex, int width, int height) {
this.width = width;
this.height = height;
for (int i = startingIndex; i < startingIndex + width * height; ++i) {
this.spriteIdentifiers.add(new SpriteIdentifier(base.getAtlasId(), new Identifier(base.getTextureId().toString() + i)));
}
return this;
}
public Builder overlay(SpriteIdentifier overlay) {
this.overlay = overlay;
return this;
}
public TessellatingBlockModel build() {
return new TessellatingBlockModel(this.spriteId, this.width, this.height, this.spriteIdentifiers, this.overlay);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment