OBJ model enhancements and builder

This commit is contained in:
paulevsGitch 2021-07-24 18:20:29 +03:00
parent 099ecf68b6
commit ca9e32ae53
3 changed files with 199 additions and 23 deletions

View file

@ -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<Direction, List<UnbakedQuad>> quadsUnbakedMap = Maps.newHashMap();
protected final Map<Direction, List<BakedQuad>> quadsBakedMap = Maps.newHashMap();
protected final List<UnbakedQuad> quadsUnbaked = Lists.newArrayList();
protected final List<BakedQuad> quadsBaked = Lists.newArrayList();
protected TextureAtlasSprite[] sprites;
protected TextureAtlasSprite particles;
protected ItemTransforms transforms;
protected ItemOverrides overrides;
protected List<UnbakedQuad> quadsUnbaked;
protected Material particleMaterial;
protected List<Material> materials;
protected List<BakedQuad> 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<UnbakedQuad> unbaked = quadsUnbakedMap.get(dir);
List<BakedQuad> 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<BakedQuad> 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,9 +237,20 @@ public class OBJBlockModel implements UnbakedModel, BakedModel {
}
}
quad.setSpriteIndex(materialIndex);
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);
}
}
}
reader.close();
streamReader.close();
@ -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;
}
}

View file

@ -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<ResourceLocation> 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);
}
}

View file

@ -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);
}
}