Texture rotation/uvlock
This commit is contained in:
parent
c073a71527
commit
30a7fae3cb
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
Template blocks can be placed in the world, then right-clicked with a full-size block to set the textures for the template. Template blocks will inherit light and redstone values from the blocks they're given, or they can have light or redstone output added to any given block by right-clicking the template with glowstone dust or a redstone torch, respectively.
|
Template blocks can be placed in the world, then right-clicked with a full-size block to set the textures for the template. Template blocks will inherit light and redstone values from the blocks they're given, or they can have light or redstone output added to any given block by right-clicking the template with glowstone dust or a redstone torch, respectively.
|
||||||
|
|
||||||
While Templates itself adds a handful of common shapes, it's not too hard for other mods to interface with Templates and add their own templatable blocks.
|
While Templates itself adds a handful of common shapes, it's also not too hard for other mods to interface with Templates and add their own templatable blocks.
|
||||||
|
|
||||||
# quat was here
|
# quat was here
|
||||||
|
|
||||||
@ -18,7 +18,6 @@ Todo move this into the main readme section
|
|||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
* `uvlock` in a blockstate will not work for `RetexturedMeshTemplateUnbakedModel`s. Can it be fixed?
|
|
||||||
* More templates !!
|
* More templates !!
|
||||||
|
|
||||||
# For addon developers
|
# For addon developers
|
||||||
@ -81,6 +80,6 @@ That's all you need in order to construct a `RetexturedMeshUnbakedModel`, so to
|
|||||||
* If your base model lives at `yourmod:block/awesome_template`, something like `yourmod:awesome_template_special` would do.
|
* If your base model lives at `yourmod:block/awesome_template`, something like `yourmod:awesome_template_special` would do.
|
||||||
2. Register it using `TemplatesClient.provider.addTemplateModel`.
|
2. Register it using `TemplatesClient.provider.addTemplateModel`.
|
||||||
3. Create a blockstate json for your block, and point it at the ID you decided for your special model in 1).
|
3. Create a blockstate json for your block, and point it at the ID you decided for your special model in 1).
|
||||||
* You may rotate the blockmodel with the `x` and `y` properties.
|
* You may rotate the blockmodel with the `x` and `y` properties. You can also toggle `uvlock`. Things should work as expected.
|
||||||
|
|
||||||
You may create a regular item model, or use ours by calling `TemplatesClient.provider.assignItemModel`, passing the ID of the special model & the items you want to assign it to. (The reason you have to do this instead of simply creating a regular item model and setting its `parent`, is that `JsonUnbakedModel`s can't have non-`JsonUnbakedModel`s as their `parent`, and even a trivial item model with only the `parent` field set counts as a `JsonUnbakedModel`. This isn't a problem for block models because blockstates are a layer of indirection before model loading.)
|
You may create a regular item model, or use ours by calling `TemplatesClient.provider.assignItemModel`, passing the ID of the special model & the items you want to assign it to. (The reason you have to do this instead of simply creating a regular item model and setting its `parent`, is that `JsonUnbakedModel`s can't have non-`JsonUnbakedModel`s as their `parent`, and even a trivial item model with only the `parent` field set counts as a `JsonUnbakedModel`. This isn't a problem for block models because blockstates are a layer of indirection before model loading.)
|
@ -5,6 +5,8 @@ import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
|||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||||
import net.minecraft.util.math.AffineTransformation;
|
import net.minecraft.util.math.AffineTransformation;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.util.math.Direction;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
@ -14,16 +16,14 @@ import org.joml.Vector4f;
|
|||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
public class MeshTransformUtil {
|
||||||
* Transforms the position of each vertex in a `Mesh`.
|
public static Mesh aroundCenter(Mesh oldMesh, ModelBakeSettings settings) {
|
||||||
* The transformation's origin is bumped to (0.5, 0.5, 0.5) just because it's more convenient for me lol.
|
return aroundCenter(oldMesh, settings.getRotation().getMatrix());
|
||||||
*/
|
|
||||||
public class MatrixTransformer {
|
|
||||||
public static Mesh meshAroundCenter(AffineTransformation aff, Mesh oldMesh) {
|
|
||||||
return meshAroundCenter(aff.getMatrix(), oldMesh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Mesh meshAroundCenter(Matrix4f mat, Mesh oldMesh) {
|
public static Mesh aroundCenter(Mesh oldMesh, Matrix4f mat) {
|
||||||
|
Map<Direction, Direction> facePermutation = facePermutation(mat);
|
||||||
|
|
||||||
Renderer r = TemplatesClient.getFabricRenderer();
|
Renderer r = TemplatesClient.getFabricRenderer();
|
||||||
MeshBuilder newMesh = r.meshBuilder();
|
MeshBuilder newMesh = r.meshBuilder();
|
||||||
QuadEmitter emitter = newMesh.getEmitter();
|
QuadEmitter emitter = newMesh.getEmitter();
|
||||||
@ -52,6 +52,11 @@ public class MatrixTransformer {
|
|||||||
emitter.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
|
emitter.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitter.nominalFace(facePermutation.get(emitter.lightFace()));
|
||||||
|
|
||||||
|
Direction cull = emitter.cullFace();
|
||||||
|
if(cull != null) emitter.cullFace(facePermutation.get(cull));
|
||||||
|
|
||||||
//Output the quad
|
//Output the quad
|
||||||
emitter.emit();
|
emitter.emit();
|
||||||
});
|
});
|
||||||
@ -68,12 +73,28 @@ public class MatrixTransformer {
|
|||||||
//
|
//
|
||||||
//This seems to work, but I'm kinda surprised I don't need to invert the transformation here, which is a clue that
|
//This seems to work, but I'm kinda surprised I don't need to invert the transformation here, which is a clue that
|
||||||
//I don't really understand all the math, loool
|
//I don't really understand all the math, loool
|
||||||
public static Map<Direction, Direction> facePermutation(AffineTransformation aff) {
|
public static Map<Direction, Direction> facePermutation(ModelBakeSettings aff) {
|
||||||
|
return facePermutation(aff.getRotation().getMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<Direction, Direction> facePermutation(Matrix4f mat) {
|
||||||
Map<Direction, Direction> facePermutation = new EnumMap<>(Direction.class);
|
Map<Direction, Direction> facePermutation = new EnumMap<>(Direction.class);
|
||||||
for(Direction input : Direction.values()) {
|
for(Direction input : Direction.values()) {
|
||||||
Direction output = Direction.transform(aff.getMatrix(), input);
|
Direction output = Direction.transform(mat, input);
|
||||||
facePermutation.put(input, output);
|
facePermutation.put(input, output);
|
||||||
}
|
}
|
||||||
return facePermutation;
|
return facePermutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Mesh pretransformMesh(Mesh mesh, RenderContext.QuadTransform transform) {
|
||||||
|
MeshBuilder builder = TemplatesClient.getFabricRenderer().meshBuilder();
|
||||||
|
QuadEmitter emitter = builder.getEmitter();
|
||||||
|
|
||||||
|
mesh.forEach(quad -> {
|
||||||
|
emitter.copyFrom(quad);
|
||||||
|
if(transform.transform(emitter)) emitter.emit();
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
}
|
}
|
@ -50,7 +50,6 @@ public class RetexturedJsonModelBakedModel extends ForwardingBakedModel {
|
|||||||
private final Sprite[] specialSprites = new Sprite[DIRECTIONS.length];
|
private final Sprite[] specialSprites = new Sprite[DIRECTIONS.length];
|
||||||
private final BlockState itemModelState;
|
private final BlockState itemModelState;
|
||||||
|
|
||||||
//TODO: Check that TemplateAppearance equals() behavior is what i want, and also that it's fast
|
|
||||||
private record CacheKey(BlockState state, TemplateAppearance appearance) {}
|
private record CacheKey(BlockState state, TemplateAppearance appearance) {}
|
||||||
private final ConcurrentHashMap<CacheKey, Mesh> meshCache = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<CacheKey, Mesh> meshCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@ -103,7 +102,12 @@ public class RetexturedJsonModelBakedModel extends ForwardingBakedModel {
|
|||||||
QuadUvBounds bounds = QuadUvBounds.read(emitter);
|
QuadUvBounds bounds = QuadUvBounds.read(emitter);
|
||||||
for(int i = 0; i < specialSprites.length; i++) {
|
for(int i = 0; i < specialSprites.length; i++) {
|
||||||
if(bounds.displaysSprite(specialSprites[i])) {
|
if(bounds.displaysSprite(specialSprites[i])) {
|
||||||
bounds.remap(emitter, specialSprites[i], key.appearance().getSprite(DIRECTIONS[i]));
|
bounds.remap(
|
||||||
|
emitter,
|
||||||
|
specialSprites[i],
|
||||||
|
key.appearance().getSprite(DIRECTIONS[i]),
|
||||||
|
key.appearance().getBakeFlags(DIRECTIONS[i])
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,22 +135,31 @@ public class RetexturedJsonModelBakedModel extends ForwardingBakedModel {
|
|||||||
return sprite.getMinU() <= minU && sprite.getMaxU() >= maxU && sprite.getMinV() <= minV && sprite.getMaxV() >= maxV;
|
return sprite.getMinU() <= minU && sprite.getMaxU() >= maxU && sprite.getMinV() <= minV && sprite.getMaxV() >= maxV;
|
||||||
}
|
}
|
||||||
|
|
||||||
void remap(MutableQuadView quad, Sprite specialSprite, Sprite newSprite) {
|
void remap(MutableQuadView quad, Sprite specialSprite, Sprite newSprite, int bakeFlags) {
|
||||||
float remappedMinU = rangeRemap(minU, specialSprite.getMinU(), specialSprite.getMaxU(), newSprite.getMinU(), newSprite.getMaxU());
|
//move the UVs into 0..1 range
|
||||||
float remappedMaxU = rangeRemap(maxU, specialSprite.getMinU(), specialSprite.getMaxU(), newSprite.getMinU(), newSprite.getMaxU());
|
float remappedMinU = norm(minU, specialSprite.getMinU(), specialSprite.getMaxU());
|
||||||
float remappedMinV = rangeRemap(minV, specialSprite.getMinV(), specialSprite.getMaxV(), newSprite.getMinV(), newSprite.getMaxV());
|
float remappedMaxU = norm(maxU, specialSprite.getMinU(), specialSprite.getMaxU());
|
||||||
float remappedMaxV = rangeRemap(maxV, specialSprite.getMinV(), specialSprite.getMaxV(), newSprite.getMinV(), newSprite.getMaxV());
|
float remappedMinV = norm(minV, specialSprite.getMinV(), specialSprite.getMaxV());
|
||||||
|
float remappedMaxV = norm(maxV, specialSprite.getMinV(), specialSprite.getMaxV());
|
||||||
quad.uv(0, MathHelper.approximatelyEquals(quad.u(0), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(0), minV) ? remappedMinV : remappedMaxV);
|
quad.uv(0, MathHelper.approximatelyEquals(quad.u(0), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(0), minV) ? remappedMinV : remappedMaxV);
|
||||||
quad.uv(1, MathHelper.approximatelyEquals(quad.u(1), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(1), minV) ? remappedMinV : remappedMaxV);
|
quad.uv(1, MathHelper.approximatelyEquals(quad.u(1), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(1), minV) ? remappedMinV : remappedMaxV);
|
||||||
quad.uv(2, MathHelper.approximatelyEquals(quad.u(2), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(2), minV) ? remappedMinV : remappedMaxV);
|
quad.uv(2, MathHelper.approximatelyEquals(quad.u(2), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(2), minV) ? remappedMinV : remappedMaxV);
|
||||||
quad.uv(3, MathHelper.approximatelyEquals(quad.u(3), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(3), minV) ? remappedMinV : remappedMaxV);
|
quad.uv(3, MathHelper.approximatelyEquals(quad.u(3), minU) ? remappedMinU : remappedMaxU, MathHelper.approximatelyEquals(quad.v(3), minV) ? remappedMinV : remappedMaxV);
|
||||||
|
|
||||||
|
//call spriteBake
|
||||||
|
//done this way (instead of directly setting UV coordinates to their final values) so that I can use the convenient bakeFlags option
|
||||||
|
quad.spriteBake(newSprite, MutableQuadView.BAKE_NORMALIZED | bakeFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float rangeRemap(float value, float low1, float high1, float low2, float high2) {
|
static float norm(float value, float low, float high) {
|
||||||
float value2 = MathHelper.clamp(value, low1, high1);
|
float value2 = MathHelper.clamp(value, low, high);
|
||||||
return low2 + (value2 - low1) * (high2 - low2) / (high1 - low1);
|
return (value2 - low) / (high - low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//static float rangeRemap(float value, float low1, float high1, float low2, float high2) {
|
||||||
|
// float value2 = MathHelper.clamp(value, low1, high1);
|
||||||
|
// return low2 + (value2 - low1) * (high2 - low2) / (high1 - low1);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Direction[] DIRECTIONS = Direction.values();
|
private static final Direction[] DIRECTIONS = Direction.values();
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
package io.github.cottonmc.templates.model;
|
package io.github.cottonmc.templates.model;
|
||||||
|
|
||||||
import io.github.cottonmc.templates.TemplatesClient;
|
|
||||||
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
|
||||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||||
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
|
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachedBlockView;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.client.color.block.BlockColorProvider;
|
import net.minecraft.client.color.block.BlockColorProvider;
|
||||||
import net.minecraft.client.render.model.BakedModel;
|
import net.minecraft.client.render.model.BakedModel;
|
||||||
|
import net.minecraft.client.render.model.ModelBakeSettings;
|
||||||
import net.minecraft.client.texture.Sprite;
|
import net.minecraft.client.texture.Sprite;
|
||||||
import net.minecraft.item.BlockItem;
|
import net.minecraft.item.BlockItem;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.nbt.NbtCompound;
|
import net.minecraft.nbt.NbtCompound;
|
||||||
import net.minecraft.nbt.NbtHelper;
|
import net.minecraft.nbt.NbtHelper;
|
||||||
import net.minecraft.registry.Registries;
|
import net.minecraft.registry.Registries;
|
||||||
import net.minecraft.util.math.AffineTransformation;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.util.math.Direction;
|
||||||
import net.minecraft.util.math.random.Random;
|
import net.minecraft.util.math.random.Random;
|
||||||
@ -29,18 +26,19 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class RetexturedMeshBakedModel extends ForwardingBakedModel {
|
public class RetexturedMeshBakedModel extends ForwardingBakedModel {
|
||||||
public RetexturedMeshBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, AffineTransformation aff, Mesh baseMesh) {
|
public RetexturedMeshBakedModel(BakedModel baseModel, TemplateAppearanceManager tam, ModelBakeSettings settings, Mesh baseMesh) {
|
||||||
this.wrapped = baseModel;
|
this.wrapped = baseModel;
|
||||||
this.tam = tam;
|
this.tam = tam;
|
||||||
this.baseMesh = MatrixTransformer.meshAroundCenter(aff, baseMesh);
|
this.baseMesh = MeshTransformUtil.aroundCenter(baseMesh, settings);
|
||||||
this.facePermutation = MatrixTransformer.facePermutation(aff);
|
this.facePermutation = MeshTransformUtil.facePermutation(settings);
|
||||||
|
this.uvLock = settings.isUvLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final TemplateAppearanceManager tam;
|
private final TemplateAppearanceManager tam;
|
||||||
private final Mesh baseMesh;
|
private final Mesh baseMesh;
|
||||||
private final Map<Direction, Direction> facePermutation;
|
private final Map<Direction, Direction> facePermutation;
|
||||||
|
private final boolean uvLock;
|
||||||
|
|
||||||
//TODO: Check that TemplateAppearance equals() behavior is what i want, and also that it's fast
|
|
||||||
private final ConcurrentHashMap<TemplateAppearance, Mesh> meshCache = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<TemplateAppearance, Mesh> meshCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -69,7 +67,7 @@ public class RetexturedMeshBakedModel extends ForwardingBakedModel {
|
|||||||
//is likely unnecessary and will fill the cache with a ton of single-use meshes with only slighly different colors.
|
//is likely unnecessary and will fill the cache with a ton of single-use meshes with only slighly different colors.
|
||||||
//We'd also have to percolate that tint color into the cache key, which is an allocation, blah blah blah.
|
//We'd also have to percolate that tint color into the cache key, which is an allocation, blah blah blah.
|
||||||
//Let's fall back to a quad transform. In practice this is still nice and quick.
|
//Let's fall back to a quad transform. In practice this is still nice and quick.
|
||||||
context.pushTransform(new RetexturingTransformer(ta, tint, facePermutation));
|
context.pushTransform(new RetexturingTransformer(ta, tint, facePermutation, uvLock));
|
||||||
context.meshConsumer().accept(baseMesh);
|
context.meshConsumer().accept(baseMesh);
|
||||||
context.popTransform();
|
context.popTransform();
|
||||||
}
|
}
|
||||||
@ -99,10 +97,10 @@ public class RetexturedMeshBakedModel extends ForwardingBakedModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Mesh makeUntintedMesh(TemplateAppearance appearance) {
|
protected Mesh makeUntintedMesh(TemplateAppearance appearance) {
|
||||||
return new RetexturingTransformer(appearance, 0xFFFFFF, facePermutation).applyTo(baseMesh);
|
return MeshTransformUtil.pretransformMesh(baseMesh, new RetexturingTransformer(appearance, 0xFFFFFFFF, facePermutation, uvLock));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static record RetexturingTransformer(TemplateAppearance appearance, int color, Map<Direction, Direction> facePermutation) implements RenderContext.QuadTransform {
|
public static record RetexturingTransformer(TemplateAppearance appearance, int color, Map<Direction, Direction> facePermutation, boolean uvLock) implements RenderContext.QuadTransform {
|
||||||
private static final Direction[] DIRECTIONS = Direction.values();
|
private static final Direction[] DIRECTIONS = Direction.values();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -116,22 +114,14 @@ public class RetexturedMeshBakedModel extends ForwardingBakedModel {
|
|||||||
if(appearance.hasColor(dir)) quad.color(color, color, color, color);
|
if(appearance.hasColor(dir)) quad.color(color, color, color, color);
|
||||||
|
|
||||||
Sprite sprite = appearance.getSprite(dir);
|
Sprite sprite = appearance.getSprite(dir);
|
||||||
quad.spriteBake(sprite, MutableQuadView.BAKE_NORMALIZED);
|
|
||||||
|
int flags = MutableQuadView.BAKE_NORMALIZED;
|
||||||
|
flags |= appearance.getBakeFlags(dir);
|
||||||
|
if(uvLock) flags |= MutableQuadView.BAKE_LOCK_UV;
|
||||||
|
|
||||||
|
quad.spriteBake(sprite, flags);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Pass a Mesh through a QuadTransform all at once, instead of at render time
|
|
||||||
private Mesh applyTo(Mesh original) {
|
|
||||||
MeshBuilder builder = TemplatesClient.getFabricRenderer().meshBuilder();
|
|
||||||
QuadEmitter emitter = builder.getEmitter();
|
|
||||||
|
|
||||||
original.forEach(quad -> {
|
|
||||||
emitter.copyFrom(quad);
|
|
||||||
if(transform(emitter)) emitter.emit();
|
|
||||||
});
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public class RetexturedMeshUnbakedModel implements UnbakedModel {
|
|||||||
return new RetexturedMeshBakedModel(
|
return new RetexturedMeshBakedModel(
|
||||||
baker.bake(parent, modelBakeSettings),
|
baker.bake(parent, modelBakeSettings),
|
||||||
TemplatesClient.provider.getOrCreateTemplateApperanceManager(spriteLookup),
|
TemplatesClient.provider.getOrCreateTemplateApperanceManager(spriteLookup),
|
||||||
modelBakeSettings.getRotation(),
|
modelBakeSettings,
|
||||||
baseMeshFactory.get()
|
baseMeshFactory.get()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -26,27 +26,29 @@ public class SlopeBaseMesh {
|
|||||||
qu.tag(TAG_SLOPE)
|
qu.tag(TAG_SLOPE)
|
||||||
.pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 1f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 0f)
|
.pos(0, 0f, 0f, 0f).pos(1, 0f, 1f, 1f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 0f)
|
||||||
.color(-1, -1, -1, -1)
|
.color(-1, -1, -1, -1)
|
||||||
.uv(0, 1f, 1f).uv(1, 1f, 0f).uv(2, 0f, 0f).uv(3, 0f, 1f)
|
.uv(0, 0f, 0f).uv(1, 0f, 1f).uv(2, 1f, 1f).uv(3, 1f, 0f)
|
||||||
.emit()
|
.emit()
|
||||||
.tag(TAG_LEFT)
|
.tag(TAG_LEFT)
|
||||||
.pos(0, 1f, 0f, 0f).pos(1, 1f, 0.5f, 0.5f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 1f)
|
.pos(0, 1f, 0f, 0f).pos(1, 1f, 0.5f, 0.5f).pos(2, 1f, 1f, 1f).pos(3, 1f, 0f, 1f)
|
||||||
.color(-1, -1, -1, -1)
|
.color(-1, -1, -1, -1)
|
||||||
.uv(0, 1f, 1f).uv(1, 0.5f, 0.5f).uv(2, 0f, 0f).uv(3, 0f, 1f)
|
.uv(0, 1f, 1f).uv(1, 0.5f, 0.5f).uv(2, 0f, 0f).uv(3, 0f, 1f)
|
||||||
|
.cullFace(Direction.EAST)
|
||||||
.emit()
|
.emit()
|
||||||
.tag(TAG_RIGHT)
|
.tag(TAG_RIGHT)
|
||||||
.pos(0, 0f, 0f, 0f).pos(1, 0f, 0f, 1f).pos(2, 0f, 1f, 1f).pos(3, 0f, 0.5f, 0.5f)
|
.pos(0, 0f, 0.5f, 0.5f).pos(1, 0, 0f, 0f).pos(2, 0f, 0f, 1f).pos(3, 0f, 1f, 1f)
|
||||||
.color(-1, -1, -1, -1)
|
.color(-1, -1, -1, -1)
|
||||||
.uv(0, 0f, 1f).uv(1, 1f, 1f).uv(2, 1f, 0f).uv(3, 0.5f, 0.5f)
|
.uv(0, 0.5f, 0.5f).uv(1, 0f, 1f).uv(2, 1f, 1f).uv(3, 1f, 0f)
|
||||||
|
.cullFace(Direction.WEST)
|
||||||
.emit()
|
.emit()
|
||||||
.tag(TAG_BACK)
|
.tag(TAG_BACK)
|
||||||
.pos(0, 0f, 0f, 1f).pos(1, 1f, 0f, 1f).pos(2, 1f, 1f, 1f).pos(3, 0f, 1f, 1f)
|
.square(Direction.SOUTH, 0, 0, 1, 1, 0) //sets pos & cullFace
|
||||||
.color(-1, -1, -1, -1)
|
.color(-1, -1, -1, -1)
|
||||||
.uv(0, 0f, 1f).uv(1, 1f, 1f).uv(2, 1f, 0f).uv(3, 0f, 0f)
|
.uvUnitSquare()
|
||||||
.emit()
|
.emit()
|
||||||
.tag(TAG_BOTTOM)
|
.tag(TAG_BOTTOM)
|
||||||
.pos(0, 0f, 0f, 0f).pos(1, 1f, 0f, 0f).pos(2, 1f, 0f, 1f).pos(3, 0f, 0f, 1f)
|
.square(Direction.DOWN, 0, 0, 1, 1, 0) //sets pos & cullFace
|
||||||
.color(-1, -1, -1, -1)
|
.color(-1, -1, -1, -1)
|
||||||
.uv(0, 0f, 1f).uv(1, 1f, 1f).uv(2, 1f, 0f).uv(3, 0f, 0f)
|
.uvUnitSquare()
|
||||||
.emit();
|
.emit();
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,6 @@ public interface TemplateAppearance {
|
|||||||
|
|
||||||
@NotNull RenderMaterial getRenderMaterial();
|
@NotNull RenderMaterial getRenderMaterial();
|
||||||
@NotNull Sprite getSprite(Direction dir);
|
@NotNull Sprite getSprite(Direction dir);
|
||||||
|
int getBakeFlags(Direction dir);
|
||||||
boolean hasColor(Direction dir);
|
boolean hasColor(Direction dir);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import io.github.cottonmc.templates.TemplatesClient;
|
|||||||
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
|
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
|
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||||
|
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
|
||||||
import net.fabricmc.fabric.api.util.TriState;
|
import net.fabricmc.fabric.api.util.TriState;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
@ -15,6 +17,7 @@ import net.minecraft.client.util.SpriteIdentifier;
|
|||||||
import net.minecraft.screen.PlayerScreenHandler;
|
import net.minecraft.screen.PlayerScreenHandler;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.math.Direction;
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
import net.minecraft.util.math.random.Random;
|
import net.minecraft.util.math.random.Random;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -60,7 +63,12 @@ public class TemplateAppearanceManager {
|
|||||||
Random rand = Random.create();
|
Random rand = Random.create();
|
||||||
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state);
|
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state);
|
||||||
|
|
||||||
|
//Only for parsing vanilla quads:
|
||||||
|
QuadEmitter emitter = TemplatesClient.getFabricRenderer().meshBuilder().getEmitter();
|
||||||
|
RenderMaterial defaultMat = TemplatesClient.getFabricRenderer().materialFinder().clear().find();
|
||||||
|
|
||||||
Sprite[] sprites = new Sprite[7];
|
Sprite[] sprites = new Sprite[7];
|
||||||
|
int[] bakeFlags = new int[6];
|
||||||
byte hasColorMask = 0b000000;
|
byte hasColorMask = 0b000000;
|
||||||
|
|
||||||
//Read quads off the model by their `cullface`
|
//Read quads off the model by their `cullface`
|
||||||
@ -75,8 +83,41 @@ public class TemplateAppearanceManager {
|
|||||||
|
|
||||||
Sprite sprite = arbitraryQuad.getSprite();
|
Sprite sprite = arbitraryQuad.getSprite();
|
||||||
if(sprite == null) continue;
|
if(sprite == null) continue;
|
||||||
|
|
||||||
sprites[dir.ordinal()] = sprite;
|
sprites[dir.ordinal()] = sprite;
|
||||||
|
|
||||||
|
//GOAL: Reconstruct the `"rotation": 90` stuff that a json model might provide, so we can bake our texture on the same way
|
||||||
|
emitter.fromVanilla(arbitraryQuad, defaultMat, dir);
|
||||||
|
int lowHighSignature = 0;
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
//For some reason the uvs stored on the Sprite have less ?precision? than the ones retrieved from the QuadEmitter.
|
||||||
|
// [STDOUT]: emitter u 0.14065552, sprite min u 0.140625
|
||||||
|
//?precision? in question marks cause it could be float noise. It's way higher than the epsilon in MathHelper.approximatelyEquals.
|
||||||
|
//So im gonna guesstimate using "is it closer to the sprite's min or max u", rather than doing an approximately-equals check
|
||||||
|
|
||||||
|
float diffMinU = Math.abs(emitter.u(i) - sprite.getMinU());
|
||||||
|
float diffMaxU = Math.abs(emitter.u(i) - sprite.getMaxU());
|
||||||
|
boolean minU = diffMinU < diffMaxU;
|
||||||
|
|
||||||
|
float diffMinV = Math.abs(emitter.v(i) - sprite.getMinV());
|
||||||
|
float diffMaxV = Math.abs(emitter.v(i) - sprite.getMaxV());
|
||||||
|
boolean minV = diffMinV < diffMaxV;
|
||||||
|
|
||||||
|
lowHighSignature <<= 2;
|
||||||
|
lowHighSignature |= (minU ? 2 : 0) | (minV ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lowHighSignature == 0b11100001) {
|
||||||
|
bakeFlags[dir.ordinal()] = 0;
|
||||||
|
} else if(lowHighSignature == 0b10000111) {
|
||||||
|
bakeFlags[dir.ordinal()] = MutableQuadView.BAKE_ROTATE_90;
|
||||||
|
} else if(lowHighSignature == 0b00011110) {
|
||||||
|
bakeFlags[dir.ordinal()] = MutableQuadView.BAKE_ROTATE_180;
|
||||||
|
} else if(lowHighSignature == 0b01111000) {
|
||||||
|
bakeFlags[dir.ordinal()] = MutableQuadView.BAKE_ROTATE_270;
|
||||||
|
} else {
|
||||||
|
//Its not critical error or anything, the texture will show rotated or flipped
|
||||||
|
//System.out.println("unknown sig " + Integer.toString(lowHighSignature, 2) + ", state: " + state + ", sprite: " + sprite.getContents().getId() + ", side: " + dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Just for space-usage purposes, we store the particle in sprites[6] instead of using another field.
|
//Just for space-usage purposes, we store the particle in sprites[6] instead of using another field.
|
||||||
@ -89,6 +130,7 @@ public class TemplateAppearanceManager {
|
|||||||
|
|
||||||
return new ComputedApperance(
|
return new ComputedApperance(
|
||||||
sprites,
|
sprites,
|
||||||
|
bakeFlags,
|
||||||
hasColorMask,
|
hasColorMask,
|
||||||
blockMaterials.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state))),
|
blockMaterials.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state))),
|
||||||
serialNumber.getAndIncrement()
|
serialNumber.getAndIncrement()
|
||||||
@ -98,12 +140,14 @@ public class TemplateAppearanceManager {
|
|||||||
@SuppressWarnings("ClassCanBeRecord")
|
@SuppressWarnings("ClassCanBeRecord")
|
||||||
private static final class ComputedApperance implements TemplateAppearance {
|
private static final class ComputedApperance implements TemplateAppearance {
|
||||||
private final Sprite @NotNull[] sprites;
|
private final Sprite @NotNull[] sprites;
|
||||||
|
private final int @NotNull[] bakeFlags;
|
||||||
private final byte hasColorMask;
|
private final byte hasColorMask;
|
||||||
private final RenderMaterial mat;
|
private final RenderMaterial mat;
|
||||||
private final int id;
|
private final int id;
|
||||||
|
|
||||||
private ComputedApperance(@NotNull Sprite @NotNull[] sprites, byte hasColorMask, RenderMaterial mat, int id) {
|
private ComputedApperance(@NotNull Sprite @NotNull[] sprites, int @NotNull[] bakeFlags, byte hasColorMask, RenderMaterial mat, int id) {
|
||||||
this.sprites = sprites;
|
this.sprites = sprites;
|
||||||
|
this.bakeFlags = bakeFlags;
|
||||||
this.hasColorMask = hasColorMask;
|
this.hasColorMask = hasColorMask;
|
||||||
this.mat = mat;
|
this.mat = mat;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -124,6 +168,11 @@ public class TemplateAppearanceManager {
|
|||||||
return sprites[dir.ordinal()];
|
return sprites[dir.ordinal()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBakeFlags(Direction dir) {
|
||||||
|
return bakeFlags[dir.ordinal()];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasColor(Direction dir) {
|
public boolean hasColor(Direction dir) {
|
||||||
return (hasColorMask & (1 << dir.ordinal())) != 0;
|
return (hasColorMask & (1 << dir.ordinal())) != 0;
|
||||||
@ -144,7 +193,7 @@ public class TemplateAppearanceManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ComputedApperance[sprites=%s, hasColorMask=%s, mat=%s, id=%d]".formatted(Arrays.toString(sprites), hasColorMask, mat, id);
|
return "ComputedApperance{sprites=%s, bakeFlags=%s, hasColorMask=%s, mat=%s, id=%d}".formatted(Arrays.toString(sprites), Arrays.toString(bakeFlags), hasColorMask, mat, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +224,11 @@ public class TemplateAppearanceManager {
|
|||||||
return defaultSprite;
|
return defaultSprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBakeFlags(Direction dir) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasColor(Direction dir) {
|
public boolean hasColor(Direction dir) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
"variants": {
|
"variants": {
|
||||||
"facing=east,half=bottom": {
|
"facing=east,half=bottom": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"y": 270
|
"y": 270
|
||||||
},
|
},
|
||||||
"facing=north,half=bottom": {
|
"facing=north,half=bottom": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"y": 180
|
"y": 180
|
||||||
},
|
},
|
||||||
"facing=south,half=bottom": {
|
"facing=south,half=bottom": {
|
||||||
@ -13,24 +15,29 @@
|
|||||||
},
|
},
|
||||||
"facing=west,half=bottom": {
|
"facing=west,half=bottom": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"y": 90
|
"y": 90
|
||||||
},
|
},
|
||||||
"facing=east,half=top": {
|
"facing=east,half=top": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"x": 180,
|
"x": 180,
|
||||||
"y": 90
|
"y": 90
|
||||||
},
|
},
|
||||||
"facing=north,half=top": {
|
"facing=north,half=top": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"x": 180
|
"x": 180
|
||||||
},
|
},
|
||||||
"facing=south,half=top": {
|
"facing=south,half=top": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"x": 180,
|
"x": 180,
|
||||||
"y": 180
|
"y": 180
|
||||||
},
|
},
|
||||||
"facing=west,half=top": {
|
"facing=west,half=top": {
|
||||||
"model": "templates:slope_special",
|
"model": "templates:slope_special",
|
||||||
|
"uvlock": true,
|
||||||
"x": 180,
|
"x": 180,
|
||||||
"y": 270
|
"y": 270
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user