OBJ model enhancements and builder
This commit is contained in:
parent
099ecf68b6
commit
ca9e32ae53
3 changed files with 199 additions and 23 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
95
src/main/java/ru/bclib/client/models/OBJModelBuilder.java
Normal file
95
src/main/java/ru/bclib/client/models/OBJModelBuilder.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue