Multipart model loading

This commit is contained in:
Shadowfacts 2018-12-24 11:29:06 -05:00
parent 7c059deb02
commit 46abfcba3b
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
20 changed files with 296 additions and 235 deletions

View File

@ -18,5 +18,6 @@ dependencies {
modCompile "net.fabricmc:fabric-loader:0.3.0.73"
// Fabric API. This is technically optional, but you probably want it anyway.
modCompile "net.fabricmc:fabric:0.1.2.62"
// modCompile "net.fabricmc:fabric:0.1.2.62"
modCompile files("fabric-0.1.2.local.jar") // temporary, until fabric-api #39 is merged
}

View File

@ -1,24 +0,0 @@
package net.shadowfacts.simplemultipart.client;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.util.math.Direction;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import java.util.List;
import java.util.Random;
/**
* @author shadowfacts
*/
public class MissingMultipartBakedModel implements MultipartBakedModel {
// TODO: actual missing model
@Override
public List<BakedQuad> getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) {
return ImmutableList.of();
}
}

View File

@ -1,8 +1,9 @@
package net.shadowfacts.simplemultipart.client;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.util.math.Direction;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import java.util.List;
@ -11,8 +12,13 @@ import java.util.Random;
/**
* @author shadowfacts
*/
public interface MultipartBakedModel {
public interface MultipartBakedModel extends BakedModel {
List<BakedQuad> getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random);
List<BakedQuad> getMultipartQuads(MultipartState state, Direction side, Random random);
@Override
default List<BakedQuad> getQuads(BlockState state, Direction side, Random random) {
return getMultipartQuads(null, side, random);
}
}

View File

@ -2,12 +2,16 @@ package net.shadowfacts.simplemultipart.client;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.block.BlockModels;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelItemPropertyOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.container.MultipartContainerBlockState;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
@ -28,13 +32,18 @@ public class MultipartContainerBakedModel implements BakedModel {
return null;
}
MultipartContainerBlockState containerState = (MultipartContainerBlockState)state;
Map<MultipartSlot, MultipartState> parts = containerState.getParts();
MultipartModels models = SimpleMultipartClient.getMultipartModels();
// TODO: would manually building the list be more efficient?
return parts.entrySet().stream()
.flatMap(entry -> {
MultipartBakedModel partModel = models.getModel(entry.getValue());
return partModel.getQuads(entry.getValue(), entry.getKey(), side, random).stream();
return containerState.getParts().values().stream()
.flatMap(partState -> {
Identifier partId = SimpleMultipart.MULTIPART.getId(partState.getMultipart());
String variant = BlockModels.propertyMapToString(partState.getEntries());
ModelIdentifier modelId = new ModelIdentifier(partId, variant);
BakedModel model = MinecraftClient.getInstance().getBakedModelManager().getModel(modelId);
if (model instanceof MultipartBakedModel) {
return ((MultipartBakedModel)model).getMultipartQuads(partState, side, random).stream();
} else {
return model.getQuads(null, side, random).stream();
}
})
.collect(Collectors.toList());
}

View File

@ -0,0 +1,87 @@
package net.shadowfacts.simplemultipart.client;
import net.fabricmc.fabric.api.client.model.ModelProvider;
import net.fabricmc.fabric.api.client.model.ModelProviderException;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.render.model.json.ModelVariantMap;
import net.minecraft.client.render.model.json.WeightedUnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.resource.Resource;
import net.minecraft.util.Identifier;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
/**
* @author shadowfacts
*/
public class MultipartModelProvider implements ModelProvider {
private final Map<ModelIdentifier, UnbakedModel> unbakedModels = new HashMap<>();
@Override
public UnbakedModel load(Identifier id, Context context) throws ModelProviderException {
if (!(id instanceof ModelIdentifier)) {
return null;
}
ModelIdentifier modelId = (ModelIdentifier)id;
if (!SimpleMultipartClient.multipartModels.contains(modelId)) {
return null;
}
try {
return getOrLoadPartModel(modelId);
} catch (IOException e) {
throw new ModelProviderException("Exception encountered while loading model " + id, e);
}
}
private UnbakedModel getOrLoadPartModel(ModelIdentifier id) throws ModelProviderException, IOException {
UnbakedModel existing = unbakedModels.get(id);
if (existing != null) {
return existing;
}
return loadModel(id);
}
private UnbakedModel loadModel(ModelIdentifier id) throws ModelProviderException, IOException {
Identifier partStateId = new Identifier(id.getNamespace(), "multipartstates/" + id.getPath() + ".json");
ModelVariantMap variantMap = loadVariantMap(partStateId);
Map<String, WeightedUnbakedModel> variants = variantMap.method_3423();
variants.forEach((variant, model) -> {
unbakedModels.put(new ModelIdentifier(new Identifier(id.getNamespace(), id.getPath()), variant), model);
});
UnbakedModel model = unbakedModels.get(id);
if (model == null) {
throw new ModelProviderException("Loaded multipart state " + partStateId + " for model " + id + " but still missing model");
}
return model;
}
private ModelVariantMap loadVariantMap(Identifier id) throws IOException {
Resource resource = null;
Reader reader = null;
try {
resource = MinecraftClient.getInstance().getResourceManager().getResource(id);
reader = new InputStreamReader(resource.getInputStream());
ModelVariantMap.class_791 context = new ModelVariantMap.class_791();
// context.stateFactory =
// TODO: ^ blockstate translation
return ModelVariantMap.method_3424(context, reader);
} finally {
IOUtils.closeQuietly(reader);
IOUtils.closeQuietly(resource);
}
}
}

View File

@ -1,31 +0,0 @@
package net.shadowfacts.simplemultipart.client;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* @author shadowfacts
*/
public class MultipartModels {
private final Map<MultipartState, MultipartBakedModel> models = new IdentityHashMap<>();
private final MultipartBakedModel missingModel = new MissingMultipartBakedModel();
public MultipartBakedModel getMissingModel() {
return missingModel;
}
public MultipartBakedModel getModel(MultipartState state) {
MultipartBakedModel model = models.get(state);
if (model == null) {
return getMissingModel();
}
return model;
}
public void register(MultipartState state, MultipartBakedModel model) {
models.put(state, model);
}
}

View File

@ -1,25 +1,42 @@
package net.shadowfacts.simplemultipart.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.impl.client.model.ModelLoadingRegistryImpl;
import net.minecraft.client.render.block.BlockModels;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
/**
* @author shadowfacts
*/
public class SimpleMultipartClient implements ClientModInitializer {
private static MultipartModels multipartModels = new MultipartModels();
public static final Set<ModelIdentifier> multipartModels = new HashSet<>();
@Override
public void onInitializeClient() {
ModelLoadingRegistryImpl.INSTANCE.registerAppender(SimpleMultipartClient::registerMultipartModels);
ModelLoadingRegistryImpl.INSTANCE.registerProvider(resourceManager -> new MultipartModelProvider());
}
public static MultipartModels getMultipartModels() {
return multipartModels;
}
public static void registerModel(MultipartState state, MultipartBakedModel model) {
getMultipartModels().register(state, model);
private static void registerMultipartModels(ResourceManager resourceManager, Consumer<ModelIdentifier> adder) {
for (Multipart part : SimpleMultipart.MULTIPART) {
Identifier partId = SimpleMultipart.MULTIPART.getId(part);
for (MultipartState state : part.getStateFactory().getStates()) {
String variant = BlockModels.propertyMapToString(state.getEntries());
ModelIdentifier id = new ModelIdentifier(partId, variant);
multipartModels.add(id);
adder.accept(id);
}
}
}
}

View File

@ -7,6 +7,7 @@ import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.shadowfacts.simplemultipart.client.MultipartContainerBakedModel;
import net.shadowfacts.simplemultipart.client.SimpleMultipartClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@ -19,14 +20,25 @@ import java.util.Map;
* @author shadowfacts
*/
@Mixin(ModelLoader.class)
public class MixinModelLoader {
public abstract class MixinModelLoader {
@Shadow
private Map<Identifier, BakedModel> bakedModels;
@Shadow
public abstract void addModel(ModelIdentifier id);
@Inject(method = "<init>", at = @At("RETURN"))
public void addMultipartModel(ResourceManager manager, SpriteAtlasTexture texture, CallbackInfo info) {
bakedModels.put(new ModelIdentifier("simplemultipart:container#"), new MultipartContainerBakedModel());
}
// // temporary workaround until fabric-api #39 is merged
// @Inject(method = "addModel", at = @At("HEAD"))
// public void injectMultipartModels(ModelIdentifier id, CallbackInfo info) {
// if (id == ModelLoader.MISSING) {
// SimpleMultipartClient.getMultipartModelIds().forEach(this::addModel);
// }
// }
}

View File

@ -5,6 +5,7 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateFactory;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
@ -22,6 +23,8 @@ import java.util.List;
*/
public abstract class Multipart {
public static final EnumProperty<MultipartSlot> SLOT = EnumProperty.create("slot", MultipartSlot.class);
private StateFactory<Multipart, MultipartState> stateFactory;
private MultipartState defaultState;

View File

@ -1,11 +1,12 @@
package net.shadowfacts.simplemultipart.multipart;
import net.minecraft.util.StringRepresentable;
import net.minecraft.util.math.Direction;
/**
* @author shadowfacts
*/
public enum MultipartSlot {
public enum MultipartSlot implements StringRepresentable {
TOP,
BOTTOM,
NORTH,
@ -31,4 +32,9 @@ public enum MultipartSlot {
}
throw new RuntimeException("Unreachable: got direction outside of DUNSWE");
}
@Override
public String asString() {
return name().toLowerCase();
}
}

View File

@ -7,7 +7,7 @@
"initializers": [
"net.shadowfacts.simplemultipart.SimpleMultipart",
"net.shadowfacts.simplemultipart.client.SimpleMultipartClient",
"net.shadowfacts.simplemultipart.test.MultipartTest"
"net.shadowfacts.simplemultipart.test.MultipartTestMod"
],
"requires": {
"fabric": "*"

View File

@ -1,34 +0,0 @@
package net.shadowfacts.simplemultipart.test;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Hand;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
/**
* @author shadowfacts
*/
public class GreenMultipart extends Multipart {
@Override
public boolean isValidSlot(MultipartSlot slot) {
return slot == MultipartSlot.NORTH;
}
@Override
@Deprecated
public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) {
return VoxelShapes.cube(0, 0, 0, 1, 1, 1/16f);
}
@Override
@Deprecated
public boolean activate(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container, PlayerEntity player, Hand hand) {
System.out.println("part activated");
return true;
}
}

View File

@ -1,35 +0,0 @@
package net.shadowfacts.simplemultipart.test;
import net.fabricmc.api.ModInitializer;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.client.SimpleMultipartClient;
import net.shadowfacts.simplemultipart.item.ItemMultipart;
/**
* @author shadowfacts
*/
public class MultipartTest implements ModInitializer {
public static final String MODID = "multipart_test";
public static final RedMultipart red = new RedMultipart();
public static final GreenMultipart green = new GreenMultipart();
public static final ItemMultipart redItem = new ItemMultipart(red);
public static final ItemMultipart greenItem = new ItemMultipart(green);
@Override
public void onInitialize() {
Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "red"), red);
Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "green"), green);
Registry.register(Registry.ITEM, new Identifier(MODID, "red"), redItem);
Registry.register(Registry.ITEM, new Identifier(MODID, "green"), greenItem);
SimpleMultipartClient.registerModel(red.getDefaultState(), new TestMultipartModel(true));
SimpleMultipartClient.registerModel(green.getDefaultState(), new TestMultipartModel(false));
}
}

View File

@ -0,0 +1,25 @@
package net.shadowfacts.simplemultipart.test;
import net.fabricmc.api.ModInitializer;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.shadowfacts.simplemultipart.SimpleMultipart;
import net.shadowfacts.simplemultipart.item.ItemMultipart;
/**
* @author shadowfacts
*/
public class MultipartTestMod implements ModInitializer {
public static final String MODID = "multipart_test";
public static final TestMultipart testPart = new TestMultipart();
public static final ItemMultipart testItem = new ItemMultipart(testPart);
@Override
public void onInitialize() {
Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "test_part"), testPart);
Registry.register(Registry.ITEM, new Identifier(MODID, "test_part"), testItem);
}
}

View File

@ -1,25 +0,0 @@
package net.shadowfacts.simplemultipart.test;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
/**
* @author shadowfacts
*/
public class RedMultipart extends Multipart {
@Override
public boolean isValidSlot(MultipartSlot slot) {
return slot == MultipartSlot.BOTTOM;
}
@Override
@Deprecated
public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) {
return VoxelShapes.cube(0, 0, 0, 1, 1/16f, 1);
}
}

View File

@ -0,0 +1,64 @@
package net.shadowfacts.simplemultipart.test;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.state.StateFactory;
import net.minecraft.util.Hand;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity;
import net.shadowfacts.simplemultipart.multipart.Multipart;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
/**
* @author shadowfacts
*/
public class TestMultipart extends Multipart {
public TestMultipart() {
setDefaultState(getDefaultState().with(SLOT, MultipartSlot.BOTTOM));
}
@Override
protected void appendProperties(StateFactory.Builder<Multipart, MultipartState> builder) {
super.appendProperties(builder);
builder.with(SLOT);
}
@Override
public boolean isValidSlot(MultipartSlot slot) {
return slot != MultipartSlot.CENTER;
}
@Override
public MultipartState getPlacementState(MultipartSlot slot, MultipartContainerBlockEntity container) {
return getDefaultState().with(SLOT, slot);
}
@Override
@Deprecated
public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) {
switch (slot) {
case TOP:
return VoxelShapes.cube(0, 15/16f, 0, 1, 1, 1);
case BOTTOM:
return VoxelShapes.cube(0, 0, 0, 1, 1/16f, 1);
case NORTH:
return VoxelShapes.cube(0, 0, 0, 1, 1, 1/16f);
case SOUTH:
return VoxelShapes.cube(0, 0, 15/16f, 1, 1, 1);
case WEST:
return VoxelShapes.cube(0, 0, 0, 1/16f, 1, 1);
case EAST:
return VoxelShapes.cube(15/16f, 0, 0, 1, 1, 1);
}
return VoxelShapes.empty();
}
@Override
@Deprecated
public boolean activate(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container, PlayerEntity player, Hand hand) {
System.out.println("part activated: " + slot);
return true;
}
}

View File

@ -1,66 +0,0 @@
package net.shadowfacts.simplemultipart.test;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.BakedQuadFactory;
import net.minecraft.client.render.model.ModelRotationContainer;
import net.minecraft.client.render.model.json.ModelElementFace;
import net.minecraft.client.render.model.json.ModelElementTexture;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
import net.shadowfacts.simplemultipart.client.MultipartBakedModel;
import net.shadowfacts.simplemultipart.multipart.MultipartSlot;
import net.shadowfacts.simplemultipart.multipart.MultipartState;
import java.util.List;
import java.util.Random;
/**
* @author shadowfacts
*/
public class TestMultipartModel implements MultipartBakedModel {
private static final BakedQuadFactory QUAD_FACTORY = new BakedQuadFactory();
private boolean flag;
public TestMultipartModel(boolean flag) {
this.flag = flag;
}
@Override
public List<BakedQuad> getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) {
if (side == null) {
return ImmutableList.of(createQuad());
}
return ImmutableList.of();
}
private BakedQuad createQuad() {
ModelElementTexture elementTexture = new ModelElementTexture(new float[]{0, 0, 16, 16}, 0);
ModelElementFace face;
Vector3f from;
Vector3f to;
Direction side;
String texture;
if (flag) {
from = new Vector3f(0, 0, 0);
to = new Vector3f(16, 1, 16);
side = Direction.UP;
texture = "block/iron_block";
} else {
from = new Vector3f(0, 0, 0);
to = new Vector3f(16, 16, 1);
side = Direction.SOUTH;
texture = "block/gold_block";
}
face = new ModelElementFace(null, -1, "#texture", elementTexture);
Sprite sprite = MinecraftClient.getInstance().getSpriteAtlas().getSprite(texture);
ModelRotationContainer rotationContainer = new ModelRotationContainer() {};
return QUAD_FACTORY.bake(from, to, face, sprite, side, rotationContainer, null, false);
}
}

View File

@ -0,0 +1,18 @@
{
"textures": {
"texture": "block/iron_block"
},
"elements": [
{ "from": [ 0, 0, 0 ],
"to": [ 16, 1, 16 ],
"faces": {
"down": { "texture": "#texture", "cullface": "down" },
"up": { "texture": "#texture" },
"north": { "texture": "#texture", "cullface": "north" },
"south": { "texture": "#texture", "cullface": "south" },
"west": { "texture": "#texture", "cullface": "west" },
"east": { "texture": "#texture", "cullface": "east" }
}
}
]
}

View File

@ -0,0 +1,18 @@
{
"textures": {
"texture": "block/gold_block"
},
"elements": [
{ "from": [ 0, 0, 0 ],
"to": [ 16, 16, 1 ],
"faces": {
"down": { "texture": "#texture", "cullface": "down" },
"up": { "texture": "#texture", "cullface": "up" },
"north": { "texture": "#texture", "cullface": "north" },
"south": { "texture": "#texture" },
"west": { "texture": "#texture", "cullface": "west" },
"east": { "texture": "#texture", "cullface": "east" }
}
}
]
}

View File

@ -0,0 +1,10 @@
{
"variants": {
"slot=bottom": { "model": "multipart_test:multipart/bottom" },
"slot=top": { "model": "multipart_test:multipart/bottom", "x": 180 },
"slot=north": { "model": "multipart_test:multipart/vertical" },
"slot=south": { "model": "multipart_test:multipart/vertical", "y": 180 },
"slot=east": { "model": "multipart_test:multipart/vertical", "y": 90 },
"slot=west": { "model": "multipart_test:multipart/vertical", "y": 270 }
}
}