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; package ru.bclib.client.models;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mojang.datafixers.util.Pair; 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.Minecraft;
import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemOverrides; 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.util.Mth;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import ru.bclib.util.BlocksHelper;
import ru.bclib.util.MHelper;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -28,35 +34,42 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
@Environment(EnvType.CLIENT)
public class OBJBlockModel implements UnbakedModel, BakedModel { 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[] sprites;
protected TextureAtlasSprite particles;
protected ItemTransforms transforms; protected ItemTransforms transforms;
protected ItemOverrides overrides; protected ItemOverrides overrides;
protected List<UnbakedQuad> quadsUnbaked;
protected Material particleMaterial;
protected List<Material> materials; 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; transforms = ItemTransforms.NO_TRANSFORMS;
overrides = ItemOverrides.EMPTY; overrides = ItemOverrides.EMPTY;
materials = new ArrayList<>(textureIDs.length + 1);
quadsUnbaked = Lists.newArrayList();
materials = Lists.newArrayList();
loadModel(location, textureIDs);
quads = new ArrayList<>(quadsUnbaked.size());
particleMaterial = new Material(TextureAtlas.LOCATION_BLOCKS, particleTextureID);
sprites = new TextureAtlasSprite[materials.size()]; sprites = new TextureAtlasSprite[materials.size()];
this.particleIndex = particleIndex;
this.useCulling = useCulling;
this.useShading = useShading;
loadModel(location, textureIDs);
} }
// UnbakedModel // // UnbakedModel //
@ -77,9 +90,14 @@ public class OBJBlockModel implements UnbakedModel, BakedModel {
for (int i = 0; i < sprites.length; i++) { for (int i = 0; i < sprites.length; i++) {
sprites[i] = textureGetter.apply(materials.get(i)); sprites[i] = textureGetter.apply(materials.get(i));
} }
particles = textureGetter.apply(particleMaterial); quadsBaked.clear();
quads.clear(); quadsUnbaked.forEach(quad -> quadsBaked.add(quad.bake(sprites)));
quadsUnbaked.forEach(quad -> quads.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; return this;
} }
@ -87,7 +105,7 @@ public class OBJBlockModel implements UnbakedModel, BakedModel {
@Override @Override
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction direction, Random random) { 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 @Override
@ -112,7 +130,7 @@ public class OBJBlockModel implements UnbakedModel, BakedModel {
@Override @Override
public TextureAtlasSprite getParticleIcon() { public TextureAtlasSprite getParticleIcon() {
return particles; return sprites[particleIndex];
} }
@Override @Override
@ -219,7 +237,18 @@ public class OBJBlockModel implements UnbakedModel, BakedModel {
} }
} }
quad.setSpriteIndex(materialIndex); 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])); 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; 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.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
@Environment(EnvType.CLIENT)
public class UnbakedQuad { public class UnbakedQuad {
float[] data = new float[20]; // 4 points with 3 positions and 2 uvs, 4 * (3 + 2) private float[] data = new float[20]; // 4 points with 3 positions and 2 uvs, 4 * (3 + 2)
int spriteIndex; private Direction dir = Direction.UP;
private boolean useShading = false;
private int spriteIndex;
public void addData(int index, float value) { public void addData(int index, float value) {
data[index] = value; data[index] = value;
@ -16,6 +22,23 @@ public class UnbakedQuad {
spriteIndex = index; 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) { public BakedQuad bake(TextureAtlasSprite[] sprites) {
TextureAtlasSprite sprite = sprites[spriteIndex]; TextureAtlasSprite sprite = sprites[spriteIndex];
int[] vertexData = new int[32]; int[] vertexData = new int[32];
@ -30,6 +53,6 @@ public class UnbakedQuad {
vertexData[index | 5] = Float.floatToIntBits(sprite.getV(data[dataIndex])); // V vertexData[index | 5] = Float.floatToIntBits(sprite.getV(data[dataIndex])); // V
} }
// vertices, tint index, direction, sprite, shade // 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);
} }
} }