From ca9e32ae53ba39856e426422bb555dbb0dbd5ff2 Mon Sep 17 00:00:00 2001 From: paulevsGitch Date: Sat, 24 Jul 2021 18:20:29 +0300 Subject: [PATCH] OBJ model enhancements and builder --- .../ru/bclib/client/models/OBJBlockModel.java | 98 +++++++++++++++---- .../bclib/client/models/OBJModelBuilder.java | 95 ++++++++++++++++++ .../ru/bclib/client/models/UnbakedQuad.java | 29 +++++- 3 files changed, 199 insertions(+), 23 deletions(-) create mode 100644 src/main/java/ru/bclib/client/models/OBJModelBuilder.java diff --git a/src/main/java/ru/bclib/client/models/OBJBlockModel.java b/src/main/java/ru/bclib/client/models/OBJBlockModel.java index b4a8fd20..585040e9 100644 --- a/src/main/java/ru/bclib/client/models/OBJBlockModel.java +++ b/src/main/java/ru/bclib/client/models/OBJBlockModel.java @@ -1,7 +1,11 @@ package ru.bclib.client.models; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.mojang.datafixers.util.Pair; +import com.mojang.math.Vector3f; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.ItemOverrides; @@ -19,6 +23,8 @@ import net.minecraft.server.packs.resources.Resource; import net.minecraft.util.Mth; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; +import ru.bclib.util.BlocksHelper; +import ru.bclib.util.MHelper; import java.io.BufferedReader; import java.io.IOException; @@ -28,35 +34,42 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.Set; import java.util.function.Function; +@Environment(EnvType.CLIENT) public class OBJBlockModel implements UnbakedModel, BakedModel { - private static final byte[] QUAD_INDEXES = new byte[] {0, 1, 2, 0, 2, 3}; + private static final Vector3f[] POSITIONS = new Vector3f[] { new Vector3f(), new Vector3f(), new Vector3f() }; + + protected final Map> quadsUnbakedMap = Maps.newHashMap(); + protected final Map> quadsBakedMap = Maps.newHashMap(); + protected final List quadsUnbaked = Lists.newArrayList(); + protected final List quadsBaked = Lists.newArrayList(); protected TextureAtlasSprite[] sprites; - protected TextureAtlasSprite particles; protected ItemTransforms transforms; protected ItemOverrides overrides; - protected List quadsUnbaked; - protected Material particleMaterial; protected List materials; - protected List quads; + protected boolean useCulling; + protected boolean useShading; + protected byte particleIndex; - public OBJBlockModel(ResourceLocation location, ResourceLocation particleTextureID, ResourceLocation... textureIDs) { + public OBJBlockModel(ResourceLocation location, boolean useCulling, boolean useShading, byte particleIndex, ResourceLocation... textureIDs) { + for (Direction dir: BlocksHelper.DIRECTIONS) { + quadsUnbakedMap.put(dir, Lists.newArrayList()); + quadsBakedMap.put(dir, Lists.newArrayList()); + } transforms = ItemTransforms.NO_TRANSFORMS; overrides = ItemOverrides.EMPTY; - - quadsUnbaked = Lists.newArrayList(); - materials = Lists.newArrayList(); - - loadModel(location, textureIDs); - - quads = new ArrayList<>(quadsUnbaked.size()); - particleMaterial = new Material(TextureAtlas.LOCATION_BLOCKS, particleTextureID); + materials = new ArrayList<>(textureIDs.length + 1); sprites = new TextureAtlasSprite[materials.size()]; + this.particleIndex = particleIndex; + this.useCulling = useCulling; + this.useShading = useShading; + loadModel(location, textureIDs); } // UnbakedModel // @@ -77,9 +90,14 @@ public class OBJBlockModel implements UnbakedModel, BakedModel { for (int i = 0; i < sprites.length; i++) { sprites[i] = textureGetter.apply(materials.get(i)); } - particles = textureGetter.apply(particleMaterial); - quads.clear(); - quadsUnbaked.forEach(quad -> quads.add(quad.bake(sprites))); + quadsBaked.clear(); + quadsUnbaked.forEach(quad -> quadsBaked.add(quad.bake(sprites))); + for (Direction dir: BlocksHelper.DIRECTIONS) { + List unbaked = quadsUnbakedMap.get(dir); + List baked = quadsBakedMap.get(dir); + baked.clear(); + unbaked.forEach(quad -> baked.add(quad.bake(sprites))); + } return this; } @@ -87,7 +105,7 @@ public class OBJBlockModel implements UnbakedModel, BakedModel { @Override public List getQuads(@Nullable BlockState blockState, @Nullable Direction direction, Random random) { - return direction == null ? quads : Collections.emptyList(); + return direction == null ? quadsBaked : quadsBakedMap.get(direction); } @Override @@ -112,7 +130,7 @@ public class OBJBlockModel implements UnbakedModel, BakedModel { @Override public TextureAtlasSprite getParticleIcon() { - return particles; + return sprites[particleIndex]; } @Override @@ -219,7 +237,18 @@ public class OBJBlockModel implements UnbakedModel, BakedModel { } } quad.setSpriteIndex(materialIndex); - quadsUnbaked.add(quad); + if (useShading) { + Direction dir = getNormalDirection(quad); + quad.setDirection(dir); + quad.setShading(true); + } + if (useCulling) { + Direction dir = getCullingDirection(quad); + quadsUnbakedMap.get(dir).add(quad); + } + else { + quadsUnbaked.add(quad); + } } } @@ -238,4 +267,33 @@ public class OBJBlockModel implements UnbakedModel, BakedModel { materials.add(new Material(TextureAtlas.LOCATION_BLOCKS, textureIDs[index])); } } + + private Direction getNormalDirection(UnbakedQuad quad) { + Vector3f pos = quad.getPos(0, POSITIONS[0]); + Vector3f dirA = quad.getPos(1, POSITIONS[1]); + Vector3f dirB = quad.getPos(2, POSITIONS[2]); + dirA.sub(pos); + dirB.sub(pos); + pos = MHelper.cross(dirA, dirB); + return Direction.getNearest(pos.x(), pos.y(), pos.z()); + } + + @Nullable + private Direction getCullingDirection(UnbakedQuad quad) { + Direction dir = null; + for (int i = 0; i < 4; i++) { + Vector3f pos = quad.getPos(i, POSITIONS[0]); + if (pos.x() < 1 || pos.x() > 0 || pos.y() < 1 || pos.y() > 0 || pos.z() < 1 || pos.z() > 0) { + return null; + } + Direction newDir = Direction.getNearest(pos.x(), pos.y(), pos.z()); + if (dir == null) { + dir = newDir; + } + else if (newDir != dir) { + return null; + } + } + return dir; + } } diff --git a/src/main/java/ru/bclib/client/models/OBJModelBuilder.java b/src/main/java/ru/bclib/client/models/OBJModelBuilder.java new file mode 100644 index 00000000..99b7c8de --- /dev/null +++ b/src/main/java/ru/bclib/client/models/OBJModelBuilder.java @@ -0,0 +1,95 @@ +package ru.bclib.client.models; + +import com.google.common.collect.Lists; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.resources.ResourceLocation; + +import java.util.List; + +@Environment(EnvType.CLIENT) +public class OBJModelBuilder { + private static final OBJModelBuilder INSTANCE = new OBJModelBuilder(); + private final List textures = Lists.newArrayList(); + private ResourceLocation modelLocation; + private ResourceLocation particles; + private boolean useCulling; + private boolean useShading; + + private OBJModelBuilder() {} + + /** + * Start a new bodel building process, clears data of previous builder. + * @return {@link OBJModelBuilder} instance. + */ + public static OBJModelBuilder start(ResourceLocation modelLocation) { + INSTANCE.modelLocation = modelLocation; + INSTANCE.useCulling = true; + INSTANCE.useShading = true; + INSTANCE.particles = null; + INSTANCE.textures.clear(); + return INSTANCE; + } + + /** + * Add texture to the model. All textures have indexes with same order as in source OBJ model. + * @param texture {@link ResourceLocation} texture ID. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder addTexture(ResourceLocation texture) { + textures.add(texture); + return this; + } + + /** + * Culling used to remove block faces if they are on block faces or outside of the block to reduce faces count in rendering. + * Opaque blocks shoud have this as true to reduce geometry issues, block like plants should have this as false. + * Default value is {@code true}. + * @param useCulling {@link Boolean}. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder useCulling(boolean useCulling) { + this.useCulling = useCulling; + return this; + } + + /** + * Shading tints block faces in shades of gray to immitate volume in MC rendering. + * Blocks like plants don't have shading, most full opaque blocks - have. + * Default value is {@code true}. + * @param useShading {@link Boolean}. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder useShading(boolean useShading) { + this.useShading = useShading; + return this; + } + + /** + * Set particle texture for this model. + * Not required, if texture is not selected the first texture will be used instead of it. + * @param texture {@link ResourceLocation} texture ID. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder setParticlesTexture(ResourceLocation texture) { + this.particles = texture; + return this; + } + + /** + * Builds model from all required data. + * @return {@link OBJBlockModel}. + */ + public OBJBlockModel build() { + byte particleIndex = 0; + if (particles != null) { + particleIndex = (byte) textures.indexOf(particles); + if (particleIndex < 0) { + particleIndex = (byte) textures.size(); + textures.add(particles); + } + } + ResourceLocation[] sprites = textures.toArray(new ResourceLocation[textures.size()]); + return new OBJBlockModel(modelLocation, useCulling, useShading, particleIndex, sprites); + } +} diff --git a/src/main/java/ru/bclib/client/models/UnbakedQuad.java b/src/main/java/ru/bclib/client/models/UnbakedQuad.java index 60cd7561..7df9250c 100644 --- a/src/main/java/ru/bclib/client/models/UnbakedQuad.java +++ b/src/main/java/ru/bclib/client/models/UnbakedQuad.java @@ -1,12 +1,18 @@ package ru.bclib.client.models; +import com.mojang.math.Vector3f; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.Direction; +@Environment(EnvType.CLIENT) public class UnbakedQuad { - float[] data = new float[20]; // 4 points with 3 positions and 2 uvs, 4 * (3 + 2) - int spriteIndex; + private float[] data = new float[20]; // 4 points with 3 positions and 2 uvs, 4 * (3 + 2) + private Direction dir = Direction.UP; + private boolean useShading = false; + private int spriteIndex; public void addData(int index, float value) { data[index] = value; @@ -16,6 +22,23 @@ public class UnbakedQuad { spriteIndex = index; } + public void setDirection(Direction dir) { + this.dir = dir; + } + + public void setShading(boolean useShading) { + this.useShading = useShading; + } + + public Vector3f getPos(int index, Vector3f result) { + int dataIndex = index * 5; + float x = data[dataIndex++]; + float y = data[dataIndex++]; + float z = data[dataIndex]; + result.set(x, y, z); + return result; + } + public BakedQuad bake(TextureAtlasSprite[] sprites) { TextureAtlasSprite sprite = sprites[spriteIndex]; int[] vertexData = new int[32]; @@ -30,6 +53,6 @@ public class UnbakedQuad { vertexData[index | 5] = Float.floatToIntBits(sprite.getV(data[dataIndex])); // V } // vertices, tint index, direction, sprite, shade - return new BakedQuad(vertexData, 0, Direction.UP, sprites[spriteIndex], true); + return new BakedQuad(vertexData, 0, dir, sprites[spriteIndex], useShading); } }