Connecting the Quads

- Added models and a block interface for connected textures
- Added Framed glass as an example block
This commit is contained in:
simibubi 2019-11-17 21:25:59 +01:00
parent 720a370f2d
commit 4e1b33c0ea
13 changed files with 386 additions and 4 deletions

View file

@ -61,6 +61,7 @@ import com.simibubi.create.modules.logistics.management.base.LogisticalControlle
import com.simibubi.create.modules.logistics.management.index.LogisticalIndexBlock;
import com.simibubi.create.modules.logistics.transport.villager.LogisticiansTableBlock;
import com.simibubi.create.modules.logistics.transport.villager.PackageFunnelBlock;
import com.simibubi.create.modules.palettes.CTGlassBlock;
import com.simibubi.create.modules.palettes.GlassPaneBlock;
import com.simibubi.create.modules.schematics.block.CreativeCrateBlock;
import com.simibubi.create.modules.schematics.block.SchematicTableBlock;
@ -173,6 +174,7 @@ public enum AllBlocks {
__PALETTES__(),
TILED_GLASS(new GlassBlock(Properties.from(Blocks.GLASS))),
TILED_GLASS_PANE(new GlassPaneBlock(Properties.from(Blocks.GLASS))),
FRAMED_GLASS(new CTGlassBlock(true)),
ANDESITE_BRICKS(new Block(Properties.from(Blocks.ANDESITE))),
DIORITE_BRICKS(new Block(Properties.from(Blocks.DIORITE))),
@ -264,7 +266,7 @@ public enum AllBlocks {
blockItem = new MechanicalMixerBlockItem(standardItemProperties);
else
blockItem = new BlockItem(blockIn, standardItemProperties);
registry.register(blockItem.setRegistryName(blockIn.getRegistryName()));
}

View file

@ -3,6 +3,9 @@ package com.simibubi.create;
import java.util.Map;
import java.util.function.Function;
import com.simibubi.create.foundation.block.CTModel;
import com.simibubi.create.foundation.block.IHaveConnectedTextures;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.modules.contraptions.CachedBufferReloader;
import com.simibubi.create.modules.contraptions.WrenchModel;
import com.simibubi.create.modules.contraptions.receivers.EncasedFanParticleHandler;
@ -27,6 +30,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.DistExecutor;
@ -41,7 +45,7 @@ public class CreateClient {
public static SchematicAndQuillHandler schematicAndQuillHandler;
public static EncasedFanParticleHandler fanParticles;
public static int renderTicks;
public static ModConfig config;
public static void addListeners(IEventBus modEventBus) {
@ -50,6 +54,7 @@ public class CreateClient {
modEventBus.addListener(CreateClient::createConfigs);
modEventBus.addListener(CreateClient::onModelBake);
modEventBus.addListener(CreateClient::onModelRegistry);
modEventBus.addListener(CreateClient::onTextureStitch);
});
}
@ -86,16 +91,33 @@ public class CreateClient {
schematicHologram.tick();
}
@OnlyIn(Dist.CLIENT)
public static void onTextureStitch(TextureStitchEvent.Pre event) {
if (!event.getMap().getBasePath().equals("textures"))
return;
for (AllBlocks allBlocks : AllBlocks.values()) {
if (!(allBlocks.get() instanceof IHaveConnectedTextures))
continue;
event.addSprite(new ResourceLocation(Create.ID, "block/connected/" + Lang.asId(allBlocks.name())));
}
}
@OnlyIn(Dist.CLIENT)
public static void onModelBake(ModelBakeEvent event) {
Map<ResourceLocation, IBakedModel> modelRegistry = event.getModelRegistry();
for (AllBlocks allBlocks : AllBlocks.values()) {
if (!(allBlocks.get() instanceof IHaveConnectedTextures))
continue;
swapModels(modelRegistry, getBlockModelLocation(allBlocks, ""),
t -> new CTModel(t, Lang.asId(allBlocks.name())));
}
swapModels(modelRegistry, getItemModelLocation(AllItems.SYMMETRY_WAND),
t -> new SymmetryWandModel(t).loadPartials(event));
swapModels(modelRegistry, getItemModelLocation(AllItems.PLACEMENT_HANDGUN),
t -> new BuilderGunModel(t).loadPartials(event));
swapModels(modelRegistry, getItemModelLocation(AllItems.WRENCH),
t -> new WrenchModel(t).loadPartials(event));
swapModels(modelRegistry, getItemModelLocation(AllItems.WRENCH), t -> new WrenchModel(t).loadPartials(event));
swapModels(modelRegistry, getItemModelLocation(AllItems.DEFORESTER),
t -> new DeforesterModel(t).loadPartials(event));
swapModels(modelRegistry,

View file

@ -0,0 +1,109 @@
package com.simibubi.create.foundation.block;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import com.simibubi.create.foundation.block.CTModelTextureHandler.TextureEntry;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraftforge.client.model.BakedModelWrapper;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.data.ModelDataMap;
import net.minecraftforge.client.model.data.ModelProperty;
public class CTModel extends BakedModelWrapper<IBakedModel> {
private static ModelProperty<CTData> CT_PROPERTY = new ModelProperty<>();
private TextureEntry texture;
private class CTData {
int[] textures;
public CTData() {
textures = new int[6];
Arrays.fill(textures, -1);
}
void put(Direction face, int texture) {
textures[face.getIndex()] = texture;
}
int get(Direction face) {
return textures[face.getIndex()];
}
}
public CTModel(IBakedModel originalModel, String blockId) {
super(originalModel);
texture = CTModelTextureHandler.get(blockId);
}
@Override
public IModelData getModelData(IEnviromentBlockReader world, BlockPos pos, BlockState state, IModelData tileData) {
if (!(state.getBlock() instanceof IHaveConnectedTextures))
return EmptyModelData.INSTANCE;
CTData data = new CTData();
IHaveConnectedTextures texDef = (IHaveConnectedTextures) state.getBlock();
for (Direction face : Direction.values()) {
if (!Block.shouldSideBeRendered(state, world, pos, face))
continue;
data.put(face, texDef.getTextureIndex(world, pos, state, face));
}
return new ModelDataMap.Builder().withInitial(CT_PROPERTY, data).build();
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction side, Random rand, IModelData extraData) {
List<BakedQuad> quads = new ArrayList<>(super.getQuads(state, side, rand, extraData));
if (!extraData.hasProperty(CT_PROPERTY))
return quads;
IHaveConnectedTextures texDef = (IHaveConnectedTextures) state.getBlock();
CTData data = extraData.getData(CT_PROPERTY);
for (int i = 0; i < quads.size(); i++) {
BakedQuad quad = quads.get(i);
if (!texDef.appliesTo(quad))
continue;
int index = data.get(quad.getFace());
if (index == -1)
return quads;
float textureSize = 16f / 128f / 8f;
float uShift = (index % 8) * textureSize;
float vShift = (index / 8) * textureSize * 2;
uShift = texture.connectedTextures.getInterpolatedU((index % 8) * 2) - texture.originalTexture.getMinU();
vShift = texture.connectedTextures.getInterpolatedV((index / 8) * 2) - texture.originalTexture.getMinV();
BakedQuad newQuad = new BakedQuad(Arrays.copyOf(quad.getVertexData(), quad.getVertexData().length),
quad.getTintIndex(), quad.getFace(), quad.getSprite(), quad.shouldApplyDiffuseLighting(),
quad.getFormat());
VertexFormat format = quad.getFormat();
int[] vertexData = newQuad.getVertexData();
for (int vertex = 0; vertex < vertexData.length; vertex += format.getIntegerSize()) {
int uvOffset = format.getUvOffsetById(0) / 4;
int uIndex = vertex + uvOffset;
int vIndex = vertex + uvOffset + 1;
float u = Float.intBitsToFloat(vertexData[uIndex]);
float v = Float.intBitsToFloat(vertexData[vIndex]);
u += uShift;
v += vShift;
vertexData[uIndex] = Float.floatToIntBits(u);
vertexData[vIndex] = Float.floatToIntBits(v);
}
quads.set(i, newQuad);
}
return quads;
}
}

View file

@ -0,0 +1,46 @@
package com.simibubi.create.foundation.block;
import java.util.HashMap;
import java.util.Map;
import com.simibubi.create.Create;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.ResourceLocation;
public class CTModelTextureHandler {
static class TextureEntry {
ResourceLocation originalTextureLocation;
ResourceLocation connectedTextureLocation;
TextureAtlasSprite originalTexture;
TextureAtlasSprite connectedTextures;
void loadTextures() {
AtlasTexture textureMap = Minecraft.getInstance().getTextureMap();
originalTexture = textureMap.getSprite(originalTextureLocation);
connectedTextures = textureMap.getSprite(connectedTextureLocation);
}
}
static Map<String, TextureEntry> textures = new HashMap<>();
public static TextureEntry get(String blockId) {
if (textures.containsKey(blockId))
return textures.get(blockId);
TextureEntry entry = new TextureEntry();
entry.originalTextureLocation = new ResourceLocation(Create.ID, "block/" + blockId);
entry.connectedTextureLocation = new ResourceLocation(Create.ID, "block/connected/" + blockId);
entry.loadTextures();
textures.put(blockId, entry);
return entry;
}
public static void reloadUVs() {
textures.values().forEach(TextureEntry::loadTextures);
}
}

View file

@ -0,0 +1,133 @@
package com.simibubi.create.foundation.block;
import java.util.function.BiPredicate;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.Direction.AxisDirection;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IEnviromentBlockReader;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
public interface IHaveConnectedTextures {
class CTContext {
boolean up, down, left, right;
boolean topLeft, topRight, bottomLeft, bottomRight;
}
@OnlyIn(Dist.CLIENT)
public boolean appliesTo(BakedQuad quad);
default boolean connectsTo(BlockState state, BlockState other, IEnviromentBlockReader reader, BlockPos pos,
BlockPos otherPos, Direction face) {
BlockPos blockingPos = otherPos.offset(face);
if ((face.getAxis().getCoordinate(pos.getX(), pos.getY(), pos.getZ()) == face.getAxis()
.getCoordinate(otherPos.getX(), otherPos.getY(), otherPos.getZ()))
&& connectsTo(state, reader.getBlockState(blockingPos), reader, pos, blockingPos, face))
return false;
return state.getBlock() == other.getBlock();
}
default int getTextureIndex(IEnviromentBlockReader reader, BlockPos pos, BlockState state, Direction face) {
return getTextureIndexForContext(reader, pos, state, face, buildContext(reader, pos, state, face));
}
default CTContext buildContext(IEnviromentBlockReader reader, BlockPos pos, BlockState state, Direction face) {
Axis axis = face.getAxis();
boolean positive = face.getAxisDirection() == AxisDirection.POSITIVE;
Direction h = axis == Axis.X ? Direction.SOUTH : Direction.WEST;
Direction v = axis.isHorizontal() ? Direction.UP : Direction.NORTH;
h = positive ? h.getOpposite() : h;
if (face == Direction.DOWN) {
v = v.getOpposite();
h = h.getOpposite();
}
final Direction horizontal = h;
final Direction vertical = v;
BiPredicate<Integer, Integer> connection = (x, y) -> {
BlockPos p = pos.offset(horizontal, x).offset(vertical, y);
return connectsTo(state, reader.getBlockState(p), reader, pos, p, face);
};
CTContext context = new CTContext();
context.up = connection.test(0, 1);
context.down = connection.test(0, -1);
context.left = connection.test(-1, 0);
context.right = connection.test(1, 0);
context.topLeft = connection.test(-1, 1);
context.topRight = connection.test(1, 1);
context.bottomLeft = connection.test(-1, -1);
context.bottomRight = connection.test(1, -1);
return context;
}
default int getTextureIndexForContext(IEnviromentBlockReader reader, BlockPos pos, BlockState state, Direction face,
CTContext c) {
int tileX = 0, tileY = 0;
int borders = (!c.up ? 1 : 0) + (!c.down ? 1 : 0) + (!c.left ? 1 : 0) + (!c.right ? 1 : 0);
if (c.up)
tileX++;
if (c.down)
tileX += 2;
if (c.left)
tileY++;
if (c.right)
tileY += 2;
if (borders == 0) {
if (c.topRight)
tileX++;
if (c.topLeft)
tileX += 2;
if (c.bottomRight)
tileY += 2;
if (c.bottomLeft)
tileY++;
}
if (borders == 1) {
if (!c.right) {
if (c.topLeft || c.bottomLeft) {
tileY = 4;
tileX = -1 + (c.bottomLeft ? 1 : 0) + (c.topLeft ? 1 : 0) * 2;
}
}
if (!c.left) {
if (c.topRight || c.bottomRight) {
tileY = 5;
tileX = -1 + (c.bottomRight ? 1 : 0) + (c.topRight ? 1 : 0) * 2;
}
}
if (!c.down) {
if (c.topLeft || c.topRight) {
tileY = 6;
tileX = -1 + (c.topLeft ? 1 : 0) + (c.topRight ? 1 : 0) * 2;
}
}
if (!c.up) {
if (c.bottomLeft || c.bottomRight) {
tileY = 7;
tileX = -1 + (c.bottomLeft ? 1 : 0) + (c.bottomRight ? 1 : 0) * 2;
}
}
}
if (borders == 2) {
if ((c.up && c.left && c.topLeft) || (c.down && c.left && c.bottomLeft) || (c.up && c.right && c.topRight)
|| (c.down && c.right && c.bottomRight))
tileX += 3;
}
return tileX + 8 * tileY;
}
}

View file

@ -1,5 +1,6 @@
package com.simibubi.create.modules.contraptions;
import com.simibubi.create.foundation.block.CTModelTextureHandler;
import com.simibubi.create.foundation.utility.ColoredIndicatorRenderer;
import com.simibubi.create.modules.contraptions.base.KineticTileEntityRenderer;
import com.simibubi.create.modules.contraptions.receivers.constructs.ContraptionRenderer;
@ -22,6 +23,7 @@ public class CachedBufferReloader extends ReloadListener<String> {
ContraptionRenderer.invalidateCache();
MechanicalBearingTileEntityRenderer.invalidateCache();
ColoredIndicatorRenderer.invalidateCache();
CTModelTextureHandler.reloadUVs();
}

View file

@ -0,0 +1,37 @@
package com.simibubi.create.modules.palettes;
import java.util.function.Supplier;
import com.simibubi.create.Create;
import com.simibubi.create.foundation.block.IHaveConnectedTextures;
import net.minecraft.block.Blocks;
import net.minecraft.block.GlassBlock;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.ResourceLocation;
public class CTGlassBlock extends GlassBlock implements IHaveConnectedTextures {
private Supplier<ResourceLocation> textureToReplace;
private boolean hasAlpha;
public CTGlassBlock(boolean hasAlpha) {
super(Properties.from(Blocks.GLASS));
textureToReplace = () -> {
return new ResourceLocation(Create.ID, "block/" + getRegistryName().getPath());
};
this.hasAlpha = hasAlpha;
}
@Override
public boolean appliesTo(BakedQuad quad) {
return quad.getSprite().getName().equals(textureToReplace.get());
}
@Override
public BlockRenderLayer getRenderLayer() {
return hasAlpha ? BlockRenderLayer.TRANSLUCENT : super.getRenderLayer();
}
}

View file

@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "create:block/palettes/framed_glass" }
}
}

View file

@ -0,0 +1,17 @@
{
"parent": "block/block",
"elements": [
{
"from": [0, 0, 0],
"to": [16, 16, 16],
"faces": {
"north": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "north"},
"east": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "east"},
"south": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "south"},
"west": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "west"},
"up": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "up"},
"down": {"uv": [0, 0, 2, 2], "texture": "#sheet", "cullface": "down"}
}
}
]
}

View file

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "create:block/framed_glass"
}
}

View file

@ -0,0 +1,3 @@
{
"parent": "create:block/palettes/framed_glass"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B