From f704d65d5a5eb9e55c39141103d54e1567f86fe3 Mon Sep 17 00:00:00 2001 From: gigaherz Date: Thu, 20 Jan 2022 22:30:39 +0100 Subject: [PATCH] Add renderable API to allow easier rendering of OBJ and other custom models, from Entity and BlockEntity renderers. (#8259) This is a redesign of a discarded section of my initial model system rewrite, back in 1.14. In order to use it with the OBJ loader, you can use OBJLoader.INSTANCE.loadModel to get the OBJModel, and then call OBJModel#bakeRenderable() to get a SimpleRenderable object to render with. The SimpleRenderable support animation, by providing different transformation matrices for each part in the MultipartTransforms. Additionally, a BakedRenderable helper exists to turn an arbitrary BakedModel into a renderable. After trying to get the B3D loader to work, I decided it wasn't worth the trouble and marked it for removal instead. --- .../client/model/MultiLayerModel.java | 8 +- .../model/StandaloneModelConfiguration.java | 119 +++++++++++++ .../client/model/b3d/B3DLoader.java | 5 +- .../client/model/b3d/B3DModel.java | 1 + .../client/model/obj/OBJLoader.java | 2 +- .../client/model/obj/OBJModel.java | 137 ++++++++------ .../model/renderable/BakedRenderable.java | 89 ++++++++++ .../IMultipartRenderValues.java} | 21 ++- .../client/model/renderable/IRenderable.java | 60 +++++++ .../model/renderable/MultipartTransforms.java | 58 ++++++ .../model/renderable/SimpleRenderable.java | 168 ++++++++++++++++++ .../client/textures/UnitSprite.java | 59 ++++++ .../client/rendering/RenderableTest.java | 134 ++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + 14 files changed, 793 insertions(+), 70 deletions(-) create mode 100644 src/main/java/net/minecraftforge/client/model/StandaloneModelConfiguration.java create mode 100644 src/main/java/net/minecraftforge/client/model/renderable/BakedRenderable.java rename src/main/java/net/minecraftforge/client/model/{animation/package-info.java => renderable/IMultipartRenderValues.java} (58%) create mode 100644 src/main/java/net/minecraftforge/client/model/renderable/IRenderable.java create mode 100644 src/main/java/net/minecraftforge/client/model/renderable/MultipartTransforms.java create mode 100644 src/main/java/net/minecraftforge/client/model/renderable/SimpleRenderable.java create mode 100644 src/main/java/net/minecraftforge/client/textures/UnitSprite.java create mode 100644 src/test/java/net/minecraftforge/debug/client/rendering/RenderableTest.java diff --git a/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java b/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java index df611684ee..8cac26e347 100644 --- a/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java +++ b/src/main/java/net/minecraftforge/client/model/MultiLayerModel.java @@ -39,6 +39,7 @@ import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.core.Direction; import net.minecraft.util.GsonHelper; import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.ForgeRenderTypes; import net.minecraftforge.client.MinecraftForgeClient; import net.minecraftforge.client.model.data.IDynamicBakedModel; @@ -234,14 +235,13 @@ public final class MultiLayerModel implements IModelGeometry return itemLayers; } - public static BiMap ITEM_RENDER_TYPE_MAPPING = HashBiMap.create(); - // TODO ForgeRenderTypes -/* static { + public static final BiMap ITEM_RENDER_TYPE_MAPPING = HashBiMap.create(); + static { ITEM_RENDER_TYPE_MAPPING.put(RenderType.solid(), ForgeRenderTypes.ITEM_LAYERED_SOLID.get()); ITEM_RENDER_TYPE_MAPPING.put(RenderType.cutout(), ForgeRenderTypes.ITEM_LAYERED_CUTOUT.get()); ITEM_RENDER_TYPE_MAPPING.put(RenderType.cutoutMipped(), ForgeRenderTypes.ITEM_LAYERED_CUTOUT_MIPPED.get()); ITEM_RENDER_TYPE_MAPPING.put(RenderType.translucent(), ForgeRenderTypes.ITEM_LAYERED_TRANSLUCENT.get()); - }*/ + } } public static final class Loader implements IModelLoader diff --git a/src/main/java/net/minecraftforge/client/model/StandaloneModelConfiguration.java b/src/main/java/net/minecraftforge/client/model/StandaloneModelConfiguration.java new file mode 100644 index 0000000000..b92bbcb94f --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/StandaloneModelConfiguration.java @@ -0,0 +1,119 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model; + +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.textures.UnitSprite; + +import javax.annotation.Nullable; +import java.util.Map; + +/** + * Helper to allow baking models outwise the context of block/item model loading. + */ +public class StandaloneModelConfiguration implements IModelConfiguration +{ + public static final ResourceLocation LOCATION = new ResourceLocation("forge", "standalone"); + + public static final StandaloneModelConfiguration INSTANCE = new StandaloneModelConfiguration(LOCATION, Map.of()); + + public static StandaloneModelConfiguration create(ResourceLocation modelName) + { + return new StandaloneModelConfiguration(modelName, Map.of()); + } + + public static StandaloneModelConfiguration create(Map textures) + { + return new StandaloneModelConfiguration(LOCATION, textures); + } + + public static StandaloneModelConfiguration create(ResourceLocation modelName, Map textures) + { + return new StandaloneModelConfiguration(modelName, textures); + } + + private final Map textures; + + public StandaloneModelConfiguration(ResourceLocation modelName, Map textures) + { + + this.textures = textures; + } + + @Nullable + @Override + public UnbakedModel getOwnerModel() + { + return null; + } + + @Override + public String getModelName() + { + return LOCATION.toString(); + } + + @Override + public boolean isTexturePresent(String name) + { + return textures.containsKey(name); + } + + @Override + public Material resolveTexture(String name) + { + return new Material(UnitSprite.LOCATION, textures.getOrDefault(name, UnitSprite.LOCATION)); + } + + @Override + public boolean isShadedInGui() + { + return true; + } + + @Override + public boolean isSideLit() + { + return true; + } + + @Override + public boolean useSmoothLighting() + { + return true; + } + + @Override + public ItemTransforms getCameraTransforms() + { + return ItemTransforms.NO_TRANSFORMS; + } + + @Override + public ModelState getCombinedTransform() + { + return BlockModelRotation.X0_Y0; + } +} diff --git a/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java b/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java index f4f804af43..f82bf7fe1d 100644 --- a/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java +++ b/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java @@ -72,11 +72,8 @@ import java.util.stream.Collectors; /* * Loader for Blitz3D models. - * To enable for your mod call instance.addDomain(modId). - * If you need more control over accepted resources - extend the class, and register a new instance with ModelLoaderRegistry. */ -// TODO: Implement as a new model loader -@SuppressWarnings("unused") +@Deprecated(forRemoval = true, since = "1.18") public enum B3DLoader { INSTANCE; diff --git a/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java b/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java index 3d8076254b..b5eee40311 100644 --- a/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java +++ b/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java @@ -64,6 +64,7 @@ import com.mojang.math.Vector3f; import com.mojang.math.Vector4f; import net.minecraft.world.phys.Vec2; +@Deprecated(forRemoval = true, since = "1.18") public class B3DModel { static final Logger logger = LogManager.getLogger(ForgeVersion.MOD_ID + ".B3DModel"); diff --git a/src/main/java/net/minecraftforge/client/model/obj/OBJLoader.java b/src/main/java/net/minecraftforge/client/model/obj/OBJLoader.java index f5e7be9616..582a3b9253 100644 --- a/src/main/java/net/minecraftforge/client/model/obj/OBJLoader.java +++ b/src/main/java/net/minecraftforge/client/model/obj/OBJLoader.java @@ -72,7 +72,7 @@ public class OBJLoader implements IModelLoader { return modelCache.computeIfAbsent(settings, (data) -> { - try(Resource resource = manager.getResource(settings.modelLocation); + try(Resource resource = manager.getResource(settings.modelLocation()); LineReader rdr = new LineReader(resource)) { return new OBJModel(rdr, settings); diff --git a/src/main/java/net/minecraftforge/client/model/obj/OBJModel.java b/src/main/java/net/minecraftforge/client/model/obj/OBJModel.java index 77b6e802d3..6383c1982a 100644 --- a/src/main/java/net/minecraftforge/client/model/obj/OBJModel.java +++ b/src/main/java/net/minecraftforge/client/model/obj/OBJModel.java @@ -38,6 +38,8 @@ import net.minecraftforge.client.model.geometry.IModelGeometryPart; import net.minecraftforge.client.model.geometry.IMultipartModelGeometry; import net.minecraftforge.client.model.pipeline.BakedQuadBuilder; import net.minecraftforge.client.model.pipeline.IVertexConsumer; +import net.minecraftforge.client.model.renderable.SimpleRenderable; +import net.minecraftforge.client.textures.UnitSprite; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; @@ -46,6 +48,7 @@ import java.io.IOException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.resources.model.Material; @@ -510,6 +513,20 @@ public class OBJModel implements IMultipartModelGeometry } } + public SimpleRenderable bakeRenderable(IModelConfiguration configuration) + { + var builder = SimpleRenderable.builder(); + + for(var entry : parts.entrySet()) + { + var name = entry.getKey(); + var part = entry.getValue(); + part.bake(builder.child(name), configuration); + } + + return builder.get(); + } + public class ModelObject implements IModelGeometryPart { public final String name; @@ -532,28 +549,26 @@ public class OBJModel implements IMultipartModelGeometry { for(ModelMesh mesh : meshes) { - MaterialLibrary.Material mat = mesh.mat; - if (mat == null) - continue; - TextureAtlasSprite texture = spriteGetter.apply(ModelLoaderRegistry.resolveTexture(mat.diffuseColorMap, owner)); - int tintIndex = mat.diffuseTintIndex; - Vector4f colorTint = mat.diffuseColor; + mesh.addQuads(owner, modelBuilder, spriteGetter, modelTransform); + } + } - for (int[][] face : mesh.faces) - { - Pair quad = makeQuad(face, tintIndex, colorTint, mat.ambientColor, texture, modelTransform.getRotation()); - if (quad.getRight() == null) - modelBuilder.addGeneralQuad(quad.getLeft()); - else - modelBuilder.addFaceQuad(quad.getRight(), quad.getLeft()); - } + public void bake(SimpleRenderable.PartBuilder builder, IModelConfiguration configuration) + { + for (ModelMesh mesh : this.meshes) + { + mesh.bake(builder, configuration); } } @Override public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) { - return meshes.stream().map(mesh -> ModelLoaderRegistry.resolveTexture(mesh.mat.diffuseColorMap, owner)).collect(Collectors.toSet()); + return meshes.stream() + .flatMap(mesh -> mesh.mat != null + ? Stream.of(ModelLoaderRegistry.resolveTexture(mesh.mat.diffuseColorMap, owner)) + : Stream.of()) + .collect(Collectors.toSet()); } } @@ -576,10 +591,23 @@ public class OBJModel implements IMultipartModelGeometry { super.addQuads(owner, modelBuilder, bakery, spriteGetter, modelTransform, modelLocation); - getParts().stream().filter(part -> owner.getPartVisibility(part)) + getParts().stream().filter(owner::getPartVisibility) .forEach(part -> part.addQuads(owner, modelBuilder, bakery, spriteGetter, modelTransform, modelLocation)); } + @Override + public void bake(SimpleRenderable.PartBuilder builder, IModelConfiguration configuration) + { + super.bake(builder, configuration); + + for(var entry : parts.entrySet()) + { + var name = entry.getKey(); + var part = entry.getValue(); + part.bake(builder.child(name), configuration); + } + } + @Override public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) { @@ -604,53 +632,50 @@ public class OBJModel implements IMultipartModelGeometry this.mat = currentMat; this.smoothingGroup = currentSmoothingGroup; } - } - public static class ModelSettings - { - @Nonnull - public final ResourceLocation modelLocation; - public final boolean detectCullableFaces; - public final boolean diffuseLighting; - public final boolean flipV; - public final boolean ambientToFullbright; - @Nullable - public final String materialLibraryOverrideLocation; - - public ModelSettings(@Nonnull ResourceLocation modelLocation, boolean detectCullableFaces, boolean diffuseLighting, boolean flipV, boolean ambientToFullbright, - @Nullable String materialLibraryOverrideLocation) + public void addQuads(IModelConfiguration owner, IModelBuilder modelBuilder, Function spriteGetter, ModelState modelTransform) { - this.modelLocation = modelLocation; - this.detectCullableFaces = detectCullableFaces; - this.diffuseLighting = diffuseLighting; - this.flipV = flipV; - this.ambientToFullbright = ambientToFullbright; - this.materialLibraryOverrideLocation = materialLibraryOverrideLocation; + if (mat == null) + return; + TextureAtlasSprite texture = spriteGetter.apply(ModelLoaderRegistry.resolveTexture(mat.diffuseColorMap, owner)); + int tintIndex = mat.diffuseTintIndex; + Vector4f colorTint = mat.diffuseColor; + + for (int[][] face : faces) + { + Pair quad = makeQuad(face, tintIndex, colorTint, mat.ambientColor, texture, modelTransform.getRotation()); + if (quad.getRight() == null) + modelBuilder.addGeneralQuad(quad.getLeft()); + else + modelBuilder.addFaceQuad(quad.getRight(), quad.getLeft()); + } } - @Override - public boolean equals(Object o) + public void bake(SimpleRenderable.PartBuilder builder, IModelConfiguration configuration) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ModelSettings that = (ModelSettings) o; - return equals(that); - } + MaterialLibrary.Material mat = this.mat; + if (mat == null) + return; + int tintIndex = mat.diffuseTintIndex; + Vector4f colorTint = mat.diffuseColor; - public boolean equals(@Nonnull ModelSettings that) - { - return detectCullableFaces == that.detectCullableFaces && - diffuseLighting == that.diffuseLighting && - flipV == that.flipV && - ambientToFullbright == that.ambientToFullbright && - modelLocation.equals(that.modelLocation) && - Objects.equals(materialLibraryOverrideLocation, that.materialLibraryOverrideLocation); - } + final List quads = new ArrayList<>(); - @Override - public int hashCode() - { - return Objects.hash(modelLocation, detectCullableFaces, diffuseLighting, flipV, ambientToFullbright, materialLibraryOverrideLocation); + for(var face : this.faces) + { + var pair = makeQuad(face, tintIndex, colorTint, mat.ambientColor, UnitSprite.INSTANCE, Transformation.identity()); + quads.add(pair.getLeft()); + } + + ResourceLocation textureLocation = ModelLoaderRegistry.resolveTexture(mat.diffuseColorMap, configuration).texture(); + ResourceLocation texturePath = new ResourceLocation(textureLocation.getNamespace(), "textures/" + textureLocation.getPath() + ".png"); + + builder.addMesh(texturePath, quads); } } + + public record ModelSettings(@Nonnull ResourceLocation modelLocation, + boolean detectCullableFaces, boolean diffuseLighting, boolean flipV, + boolean ambientToFullbright, @Nullable String materialLibraryOverrideLocation) + {} } diff --git a/src/main/java/net/minecraftforge/client/model/renderable/BakedRenderable.java b/src/main/java/net/minecraftforge/client/model/renderable/BakedRenderable.java new file mode 100644 index 0000000000..46b3651eab --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/renderable/BakedRenderable.java @@ -0,0 +1,89 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.renderable; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraftforge.client.model.data.IModelData; + +import java.util.Random; +import java.util.function.Function; + +/** + * Wrapper to make it easier to draw baked models in renderers. + */ +public class BakedRenderable implements IRenderable +{ + private static final ImmutableList MODEL_FACINGS = Util.make(ImmutableList.builder(), builder -> { + builder.add(Direction.values()); + builder.add((Direction)null); + }).build(); + + /** + * Constructs a BakedRenderable from the given model location. + * The model is expected to have been baked ahead of time. + * You can ensure the model is baked by calling {@link net.minecraftforge.client.model.ForgeModelBakery#addSpecialModel(ResourceLocation)} + * from within {@link net.minecraftforge.client.event.ModelRegistryEvent}. + */ + public static BakedRenderable of(ResourceLocation model) + { + return of(Minecraft.getInstance().getModelManager().getModel(model)); + } + + /** + * Constructs a BakedRenderable from the given model. + */ + public static BakedRenderable of(BakedModel model) + { + return new BakedRenderable(model); + } + + private final Random rand = new Random(); + private final BakedModel model; + + private BakedRenderable(BakedModel model) + { + this.model = model; + } + + @Override + public void render(PoseStack poseStack, MultiBufferSource bufferSource, Function renderTypeFunction, int lightmapCoord, int overlayCoord, float partialTicks, IModelData renderValues) + { + var rt = renderTypeFunction.apply(InventoryMenu.BLOCK_ATLAS); + VertexConsumer bb = bufferSource.getBuffer(rt); + for(Direction direction : MODEL_FACINGS) + { + for (BakedQuad quad : model.getQuads(null, direction, rand, renderValues)) + { + bb.putBulkData(poseStack.last(), quad, 1, 1, 1, 1, lightmapCoord, overlayCoord, true); + } + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/animation/package-info.java b/src/main/java/net/minecraftforge/client/model/renderable/IMultipartRenderValues.java similarity index 58% rename from src/main/java/net/minecraftforge/client/model/animation/package-info.java rename to src/main/java/net/minecraftforge/client/model/renderable/IMultipartRenderValues.java index b6b6656129..a5179f76c4 100644 --- a/src/main/java/net/minecraftforge/client/model/animation/package-info.java +++ b/src/main/java/net/minecraftforge/client/model/renderable/IMultipartRenderValues.java @@ -17,10 +17,21 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault -package net.minecraftforge.client.model.animation; +package net.minecraftforge.client.model.renderable; -import net.minecraft.MethodsReturnNonnullByDefault; +import javax.annotation.Nullable; -import javax.annotation.ParametersAreNonnullByDefault; +/** + * A standard interface for renderable context values that support providing different values for parts of the model. + * @param the type of value to be used for each part + */ +public interface IMultipartRenderValues +{ + /** + * Returns the value for the given part. + * @param part the name of the part + * @return the context value for the part, or {@code null} + */ + @Nullable + T getPartValues(String part); +} diff --git a/src/main/java/net/minecraftforge/client/model/renderable/IRenderable.java b/src/main/java/net/minecraftforge/client/model/renderable/IRenderable.java new file mode 100644 index 0000000000..a4b97d2b25 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/renderable/IRenderable.java @@ -0,0 +1,60 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.renderable; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Unit; + +import java.util.function.Function; + +/** + * Defines a standard interface for things that can be rendered. + * @param the type of context object used by the rendering logic + */ +@FunctionalInterface +public interface IRenderable +{ + /** + * Draws the renderable by adding the geometry to the provided {@link MultiBufferSource} + * @param poseStack the pose stack + * @param bufferSource the target buffer source to dump the data into + * @param renderTypeFunction a function that provides a RenderType for the given texture + * @param lightmapCoord the lightmap coordinates representing the current lighting conditions. See {@link net.minecraft.client.renderer.LightTexture} + * @param overlayCoord the overlay coordinates representing the current overlay status. See {@link net.minecraft.client.renderer.texture.OverlayTexture} + * @param partialTicks the current time expressed in the fraction of a tick elapsed since the last client tick. + * @param renderValues the context data used for rendering + */ + void render(PoseStack poseStack, MultiBufferSource bufferSource, Function renderTypeFunction, int lightmapCoord, int overlayCoord, float partialTicks, T renderValues); + + /** + * Wraps the current renderable along with its configuration value. + * Useful for keeping a list of various renderables paired with their configuration values. + * @param renderValues the context data to be used for rendering + * @return a renderable that accepts {@link Unit#INSTANCE} as a configuration value, but uses the provided {@code renderValue} instead + */ + default IRenderable withValues(T renderValues) + { + return (poseStack, bufferSource, renderTypeFunction, lightmapCoord, overlayCoord, partialTicks, unused) -> + this.render(poseStack, bufferSource, renderTypeFunction, lightmapCoord, overlayCoord, partialTicks, renderValues); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/renderable/MultipartTransforms.java b/src/main/java/net/minecraftforge/client/model/renderable/MultipartTransforms.java new file mode 100644 index 0000000000..a9f3f62b04 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/renderable/MultipartTransforms.java @@ -0,0 +1,58 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.renderable; + +import com.google.common.collect.ImmutableMap; +import com.mojang.math.Matrix4f; + +import javax.annotation.Nullable; + +/** + * A context value that provides {@link Matrix4f} transforms for certain parts of the model. + */ +public class MultipartTransforms implements IMultipartRenderValues +{ + /** + * A default instance that has no transforms specified. + */ + public static final MultipartTransforms EMPTY = new MultipartTransforms(ImmutableMap.of()); + + /** + * Builds a MultipartTransforms object with the given mapping. + */ + public static MultipartTransforms of(ImmutableMap parts) + { + return new MultipartTransforms(parts); + } + + private final ImmutableMap parts; + + private MultipartTransforms(ImmutableMap parts) + { + this.parts = parts; + } + + @Nullable + @Override + public Matrix4f getPartValues(String part) + { + return parts.get(part); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/renderable/SimpleRenderable.java b/src/main/java/net/minecraftforge/client/model/renderable/SimpleRenderable.java new file mode 100644 index 0000000000..c0440564a8 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/renderable/SimpleRenderable.java @@ -0,0 +1,168 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.model.renderable; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Matrix4f; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.resources.ResourceLocation; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * Implements a simple renderable consisting of a hierarchy of parts, where each part can contain a number of meshes. + * Each mesh pairs a texture, with a set of quads. + */ +public class SimpleRenderable implements IRenderable +{ + private final List parts = new ArrayList<>(); + + private SimpleRenderable() + { + } + + public static Builder builder() + { + return new Builder(); + } + + @Override + public void render(PoseStack poseStack, MultiBufferSource bufferSource, Function renderTypeFunction, int lightmapCoord, int overlayCoord, float partialTicks, MultipartTransforms renderValues) + { + for(var part : parts) + { + part.render(poseStack, bufferSource, renderTypeFunction, lightmapCoord, overlayCoord, renderValues); + } + } + + private static class Part + { + private final String name; + private final List parts = new ArrayList<>(); + private final List meshes = new ArrayList<>(); + + public Part(String name) + { + this.name = name; + } + + public void render(PoseStack poseStack, MultiBufferSource bufferSource, Function renderTypeFunction, int lightmapCoord, int overlayCoord, MultipartTransforms renderValues) + { + Matrix4f matrix = renderValues.getPartValues(name); + if (matrix != null) + { + poseStack.pushPose(); + poseStack.mulPoseMatrix(matrix); + } + + for(var part : parts) + { + part.render(poseStack, bufferSource, renderTypeFunction, lightmapCoord, overlayCoord, renderValues); + } + + for(var mesh : meshes) + { + mesh.render(poseStack, bufferSource, renderTypeFunction, lightmapCoord, overlayCoord, renderValues); + } + + if (matrix != null) + { + poseStack.popPose(); + } + } + } + + private static class Mesh + { + private final ResourceLocation texture; + private final List quads = new ArrayList<>(); + + public Mesh(ResourceLocation texture) + { + this.texture = texture; + } + + public void render(PoseStack poseStack, MultiBufferSource bufferSource, Function renderTypeFunction, int lightmapCoord, int overlayCoord, MultipartTransforms renderValues) + { + var consumer = bufferSource.getBuffer(renderTypeFunction.apply(texture)); + for(var quad : quads) + { + consumer.putBulkData(poseStack.last(), quad, 1, 1, 1, 1, lightmapCoord, overlayCoord, true); + } + } + } + + public static class Builder + { + private final SimpleRenderable renderable = new SimpleRenderable(); + + private Builder() + { + } + + public PartBuilder child(String name) + { + var child = new Part(name); + renderable.parts.add(child); + return new PartBuilder<>(this, child); + } + + public SimpleRenderable get() + { + return renderable; + } + } + + public static class PartBuilder + { + private final T parent; + private final Part part; + + private PartBuilder(T parent, Part part) + { + this.parent = parent; + this.part = part; + } + + public PartBuilder> child(String name) + { + var child = new Part(part.name + "/" + name); + this.part.parts.add(child); + return new PartBuilder<>(this, child); + } + + public PartBuilder addMesh(ResourceLocation texture, List quads) + { + var mesh = new Mesh(texture); + mesh.quads.addAll(quads); + part.meshes.add(mesh); + return this; + } + + public T end() + { + return parent; + } + } +} diff --git a/src/main/java/net/minecraftforge/client/textures/UnitSprite.java b/src/main/java/net/minecraftforge/client/textures/UnitSprite.java new file mode 100644 index 0000000000..32999a9a7b --- /dev/null +++ b/src/main/java/net/minecraftforge/client/textures/UnitSprite.java @@ -0,0 +1,59 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.textures; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.metadata.animation.AnimationMetadataSection; +import net.minecraft.client.resources.model.Material; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Function; + +/** + * A helper that lets you bake quads that won't be used with an atlas. + */ +public class UnitSprite extends TextureAtlasSprite +{ + public static final UnitSprite INSTANCE = new UnitSprite(); + public static final ResourceLocation LOCATION = new ResourceLocation("forge", "unit"); + public static final Function GETTER = (x) -> INSTANCE; + + private UnitSprite() + { + super(new TextureAtlas(LOCATION), + new Info(LOCATION, 1, 1, AnimationMetadataSection.EMPTY), + 0, 1, 1, + 0, 0, new NativeImage(1, 1, false)); + } + + @Override + public float getU(double u) + { + return (float) u / 16; + } + + @Override + public float getV(double v) + { + return (float) v / 16; + } +} diff --git a/src/test/java/net/minecraftforge/debug/client/rendering/RenderableTest.java b/src/test/java/net/minecraftforge/debug/client/rendering/RenderableTest.java new file mode 100644 index 0000000000..1d7861f695 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/client/rendering/RenderableTest.java @@ -0,0 +1,134 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.client.rendering; + +import com.google.common.collect.ImmutableMap; +import com.mojang.math.Matrix4f; +import com.mojang.math.Quaternion; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; +import net.minecraftforge.client.event.RenderLevelLastEvent; +import net.minecraftforge.client.model.StandaloneModelConfiguration; +import net.minecraftforge.client.model.obj.OBJLoader; +import net.minecraftforge.client.model.obj.OBJModel; +import net.minecraftforge.client.model.renderable.IRenderable; +import net.minecraftforge.client.model.renderable.MultipartTransforms; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLEnvironment; + +import java.util.Map; + +@Mod(RenderableTest.MODID) +public class RenderableTest +{ + public static final String MODID = "renderable_test"; + + public static final boolean ENABLED = false; + + public RenderableTest() + { + if (ENABLED) + { + if (FMLEnvironment.dist == Dist.CLIENT) + { + Client.init(); + } + } + } + + private static class Client + { + private static IRenderable renderable; + + public static void init() + { + var bus = FMLJavaModLoadingContext.get().getModEventBus(); + bus.addListener(Client::registerReloadListeners); + MinecraftForge.EVENT_BUS.addListener(Client::renderLast); + } + + public static void registerReloadListeners(RegisterClientReloadListenersEvent event) + { + event.registerReloadListener(new SimplePreparableReloadListener() + { + @Override + protected OBJModel prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) + { + var settings = new OBJModel.ModelSettings( + new ResourceLocation("new_model_loader_test:models/item/sugar_glider.obj"), + false, + true, + true, + false, + null + ); + return OBJLoader.INSTANCE.loadModel(settings); + } + + @Override + protected void apply(OBJModel model, ResourceManager resourceManager, ProfilerFiller profilerFiller) + { + var config = StandaloneModelConfiguration.create(Map.of( + "#qr", new ResourceLocation("minecraft:block/quartz_block_top") + )); + renderable = model.bakeRenderable(config); + } + }); + } + + private static double time; + + public static void renderLast(RenderLevelLastEvent event) + { + var poseStack = event.getPoseStack(); + var bufferSource = Minecraft.getInstance().renderBuffers().bufferSource(); + + time += Minecraft.getInstance().getDeltaFrameTime(); + + var map = ImmutableMap.builder(); + + var left = new Matrix4f(); + left.setIdentity(); + left.multiply(Quaternion.fromYXZ(0, 0, (float)Math.sin(time * 0.05) * 0.1f)); + map.put("object_1", left); + + var right = new Matrix4f(); + right.setIdentity(); + right.multiply(Quaternion.fromYXZ(0, 0, -(float)Math.sin(time * 0.05) * 0.1f)); + map.put("object_9", right); + + var transforms = MultipartTransforms.of(map.build()); + + renderable.render(poseStack, bufferSource, RenderType::entitySolid, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, event.getPartialTick(), transforms); + + bufferSource.endBatch(); + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 46ab655ce8..d305624ab0 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -178,6 +178,8 @@ license="LGPL v2.1" modId="duplicate_optional_tag_test" [[mods]] modId="custom_particle_type_test" +[[mods]] + modId="renderable_test" [[mods]] modId="ingredient_invalidation" [[mods]]