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;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
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;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue