diff --git a/src/main/java/cam72cam/mod/render/BakedScaledModel.java b/src/main/java/cam72cam/mod/render/BakedScaledModel.java index 99b7ba400..3ad9efa58 100644 --- a/src/main/java/cam72cam/mod/render/BakedScaledModel.java +++ b/src/main/java/cam72cam/mod/render/BakedScaledModel.java @@ -1,5 +1,8 @@ package cam72cam.mod.render; +import cam72cam.mod.render.cutter.MeshPlaneCutter; +import cam72cam.mod.render.cutter.Plane; +import cam72cam.mod.render.cutter.adapter.BakedQuadAdapter; import com.mojang.math.Transformation; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.ItemOverrides; @@ -14,6 +17,7 @@ import org.joml.Matrix4f; import util.Matrix4; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,6 +30,7 @@ class BakedScaledModel implements BakedModel { // I know this is evil and I love it :D + private final boolean isCut; private final Matrix4 transform; private final BakedModel source; private final Map> quadCache = new HashMap<>(); @@ -33,15 +38,72 @@ class BakedScaledModel implements BakedModel { public BakedScaledModel(BakedModel source, Matrix4 transform) { this.source = source; this.transform = transform; + this.isCut = false; } public BakedScaledModel(BakedModel source, float height) { this.source = source; transform = new Matrix4().scale(1, height, 1); + isCut = false; + } + + public BakedScaledModel(BakedModel source, Matrix4 transform, Plane plane) { + + this.source = source; + this.transform = transform; + this.isCut = true; + + quadCache.put(null, new ArrayList<>()); + + for (Direction dir : Direction.values()) { + quadCache.put(dir, new ArrayList<>()); + } + + RandomSource rand = RandomSource.create(); + + List all = new ArrayList<>(); + + all.addAll(source.getQuads(null, null, rand)); + + for (Direction dir : Direction.values()) { + all.addAll(source.getQuads(null, dir, rand)); + } + + all = MeshPlaneCutter.cut( + all, + plane, + new BakedQuadAdapter() + ); + + Matrix4f mat = transform.convertToMoj(); + + IQuadTransformer qt = + QuadTransformers.applying( + new Transformation(mat)); + + all = qt.process(all); + + for (BakedQuad quad : all) { + + quadCache.get(null).add(quad); + + Direction dir = quad.getDirection(); + + if (dir != null) { + quadCache.get(dir).add(quad); + } + } } @Override public List getQuads(BlockState state, Direction side, RandomSource rand) { + if(isCut) { + return quadCache.getOrDefault( + side, + List.of() + ); + } + if (quadCache.get(side) == null) { Matrix4f mat = transform.convertToMoj(); IQuadTransformer qt = QuadTransformers.applying(new Transformation(mat)); diff --git a/src/main/java/cam72cam/mod/render/StandardModel.java b/src/main/java/cam72cam/mod/render/StandardModel.java index 35174b49e..21dd90568 100644 --- a/src/main/java/cam72cam/mod/render/StandardModel.java +++ b/src/main/java/cam72cam/mod/render/StandardModel.java @@ -2,6 +2,7 @@ import cam72cam.mod.item.Fuzzy; import cam72cam.mod.item.ItemStack; +import cam72cam.mod.render.cutter.Plane; import cam72cam.mod.render.opengl.RenderContext; import cam72cam.mod.render.opengl.RenderState; import cam72cam.mod.render.opengl.Texture; @@ -26,8 +27,9 @@ import util.Matrix4; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.*; +import java.util.Map; /** A model that can render both standard MC constructs and custom OpenGL */ public class StandardModel { @@ -47,7 +49,7 @@ private static BlockState itemToBlockState(cam72cam.mod.item.ItemStack stack) { } /** Add a block with a solid color */ - public StandardModel addColorBlock(Color color, Matrix4 transform) { + public StandardModel addColorBlock(Color color, Matrix4 transform, Plane plane) { BlockState state = Fuzzy.CONCRETE.enumerate() .stream() .map(x -> Block.byItem(x.internal().getItem())) @@ -56,29 +58,31 @@ public StandardModel addColorBlock(Color color, Matrix4 transform) { .findFirst().get(); BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModelShaper().getBlockModel(state); - Pair pair = Pair.of(state, new BakedScaledModel(model, transform)); + Pair pair = Pair.of(state, new BakedScaledModel(model, transform, plane)); +// Pair pair = Pair.of(state, new BakedScaledModel(model, transform)); models.add(pair); inGuiBlock.put(pair, getRenderFunc(new net.minecraft.world.item.ItemStack(state.getBlock().asItem()), transform)); return this; } /** Add snow layers */ - public StandardModel addSnow(int layers, Matrix4 transform) { + public StandardModel addSnow(int layers, Matrix4 transform, Plane plane) { layers = Math.max(1, Math.min(8, layers)); BlockState state = Blocks.SNOW.defaultBlockState().setValue(SnowLayerBlock.LAYERS, layers); BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModelShaper().getBlockModel(state); - models.add(Pair.of(state, new BakedScaledModel(model, transform))); + models.add(Pair.of(state, new BakedScaledModel(model, transform, plane))); +// models.add(Pair.of(state, new BakedScaledModel(model, transform))); return this; } /** Add item as a block (best effort) */ - public StandardModel addItemBlock(ItemStack bed, Matrix4 transform) { + public StandardModel addItemBlock(ItemStack bed, Matrix4 transform, Plane plane) { BlockState state = itemToBlockState(bed); BakedModel model = Minecraft.getInstance().getBlockRenderer().getBlockModelShaper().getBlockModel(state); if (model instanceof WeightedBakedModel weightedBakedModel) { //TODO Modify result to make it not dynamic } - Pair pair = Pair.of(state, new BakedScaledModel(model, transform)); + Pair pair = Pair.of(state, new BakedScaledModel(model, transform, plane)); models.add(pair); inGuiBlock.put(pair, getRenderFunc(bed.internal(), transform)); return this; diff --git a/src/main/java/cam72cam/mod/render/cutter/CapBuilder.java b/src/main/java/cam72cam/mod/render/cutter/CapBuilder.java new file mode 100644 index 000000000..a0bea1bf7 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/CapBuilder.java @@ -0,0 +1,68 @@ +package cam72cam.mod.render.cutter; + +import cam72cam.mod.math.Vec3d; + +import java.util.Comparator; +import java.util.List; + +public final class CapBuilder { + + private CapBuilder() {} + + public static Polygon build( + List intersections, + Plane plane) { + + List vertices = + VertexDeduplicator.deduplicate( + intersections); + + if (vertices.size() < 3) { + return null; + } + + Vec3d center = computeCenter(vertices); + + PlaneBasis basis = + PlaneBasis.fromPlane(plane); + + vertices.sort( + Comparator.comparingDouble(v -> { + + Vec3d offset = v.pos.subtract(center); + + double x = offset.dotProduct(basis.u); + double y = offset.dotProduct(basis.v); + + return Math.atan2(y, x); + }) + ); + + Polygon polygon = new Polygon(); + polygon.vertices.addAll(vertices); + + return polygon; + } + + private static Vec3d computeCenter( + List vertices) { + + double x = 0; + double y = 0; + double z = 0; + + for (ClipVertex v : vertices) { + x += v.pos.x; + y += v.pos.y; + z += v.pos.z; + } + + double inv = 1.0 / vertices.size(); + + return new Vec3d( + x * inv, + y * inv, + z * inv + ); + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/CapUVGenerator.java b/src/main/java/cam72cam/mod/render/cutter/CapUVGenerator.java new file mode 100644 index 000000000..7ae382bf0 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/CapUVGenerator.java @@ -0,0 +1,75 @@ +package cam72cam.mod.render.cutter; + +import cam72cam.mod.math.Vec3d; + +import java.util.Collections; + +public final class CapUVGenerator { + + private CapUVGenerator() {} + + public static void generate(Polygon polygon, Plane plane) { + + generateUV( + polygon, + PlaneBasis + .fromPlane(plane) + .rotateCCW() + ); + + reverseWinding(polygon); + } + + private static void reverseWinding( + Polygon polygon) { + + Collections.reverse( + polygon.vertices + ); + } + + private static void generateUV( + Polygon polygon, + PlaneBasis basis) { + + double minU = Double.POSITIVE_INFINITY; + double maxU = Double.NEGATIVE_INFINITY; + + double minV = Double.POSITIVE_INFINITY; + double maxV = Double.NEGATIVE_INFINITY; + + for (ClipVertex vertex : polygon.vertices) { + + Vec3d p = vertex.pos; + + double u = p.dotProduct(basis.u); + double v = p.dotProduct(basis.v); + + minU = Math.min(minU, u); + maxU = Math.max(maxU, u); + + minV = Math.min(minV, v); + maxV = Math.max(maxV, v); + } + + double du = maxU - minU; + double dv = maxV - minV; + + if (du < 1E-6) du = 1; + if (dv < 1E-6) dv = 1; + + for (ClipVertex vertex : polygon.vertices) { + + Vec3d p = vertex.pos; + + double lu = p.dotProduct(basis.u); + double lv = p.dotProduct(basis.v); + + float u = (float)((lu - minU) / du); + float v = (float)((lv - minV) / dv); + + vertex.u = u; + vertex.v = v; + } + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/ClipResult.java b/src/main/java/cam72cam/mod/render/cutter/ClipResult.java new file mode 100644 index 000000000..9a2c97b97 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/ClipResult.java @@ -0,0 +1,11 @@ +package cam72cam.mod.render.cutter; + +import java.util.ArrayList; +import java.util.List; + +public class ClipResult { + + public final Polygon polygon = new Polygon(); + + public final List intersections = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/ClipVertex.java b/src/main/java/cam72cam/mod/render/cutter/ClipVertex.java new file mode 100644 index 000000000..faae72453 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/ClipVertex.java @@ -0,0 +1,180 @@ +package cam72cam.mod.render.cutter; + +import cam72cam.mod.math.Vec3d; + +public class ClipVertex { + + public Vec3d pos; + + public float u; + public float v; + + public int color; + public int light; + + public byte nx; + public byte ny; + public byte nz; + + public ClipVertex( + Vec3d pos, + float u, + float v, + int color, + int light, + byte nx, + byte ny, + byte nz) { + + this.pos = pos; + + this.u = u; + this.v = v; + + this.color = color; + this.light = light; + + this.nx = nx; + this.ny = ny; + this.nz = nz; + } + + public ClipVertex copy() { + return new ClipVertex( + pos, + u, + v, + color, + light, + nx, + ny, + nz + ); + } + + public ClipVertex lerp(ClipVertex other, double t) { + + Vec3d p = pos.add(other.pos.subtract(pos).scale(t)); + + float nu = (float) (u + (other.u - u) * t); + float nv = (float) (v + (other.v - v) * t); + + int c = lerpColor(color, other.color, t); + + int l = lerpLight(light, other.light, t); + + byte nnx = (byte) Math.round(nx + (other.nx - nx) * t); + byte nny = (byte) Math.round(ny + (other.ny - ny) * t); + byte nnz = (byte) Math.round(nz + (other.nz - nz) * t); + + return new ClipVertex( + p, + nu, + nv, + c, + l, + nnx, + nny, + nnz + ); + } + + private static int lerpColor(int a, int b, double t) { + + int aa = (a >>> 24) & 255; + int ar = (a >>> 16) & 255; + int ag = (a >>> 8) & 255; + int ab = a & 255; + + int ba = (b >>> 24) & 255; + int br = (b >>> 16) & 255; + int bg = (b >>> 8) & 255; + int bb = b & 255; + + int ca = (int)Math.round(aa + (ba - aa) * t); + int cr = (int)Math.round(ar + (br - ar) * t); + int cg = (int)Math.round(ag + (bg - ag) * t); + int cb = (int)Math.round(ab + (bb - ab) * t); + + return (ca << 24) + | (cr << 16) + | (cg << 8) + | cb; + } + + /** + * UV2(lightmap)format: + * low16 = block light + * high16 = sky light + */ + private static int lerpLight(int a, int b, double t) { + + int ablock = a & 0xffff; + int asky = (a >>> 16) & 0xffff; + + int bblock = b & 0xffff; + int bsky = (b >>> 16) & 0xffff; + + int block = (int)Math.round(ablock + (bblock - ablock) * t); + int sky = (int)Math.round(asky + (bsky - asky) * t); + + return (sky << 16) | block; + } + + public ClipVertex(Vec3d pos) { + this( + pos, + 0, + 0, + -1, + 0, + (byte)0, + (byte)0, + (byte)0 + ); + } + + public ClipVertex(Vec3d pos, float u, float v) { + this( + pos, + u, + v, + -1, + 0, + (byte)0, + (byte)0, + (byte)0 + ); + } + + public ClipVertex( + Vec3d pos, + float u, + float v, + int color, + int light) { + + this( + pos, + u, + v, + color, + light, + (byte)0, + (byte)0, + (byte)0 + ); + } + + public static ClipVertex of(Vec3d pos) { + return new ClipVertex(pos); + } + + public static ClipVertex of( + Vec3d pos, + float u, + float v) { + + return new ClipVertex(pos, u, v); + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/MeshPlaneCutter.java b/src/main/java/cam72cam/mod/render/cutter/MeshPlaneCutter.java new file mode 100644 index 000000000..b6738289a --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/MeshPlaneCutter.java @@ -0,0 +1,81 @@ +package cam72cam.mod.render.cutter; + +import cam72cam.mod.render.cutter.adapter.PrimitiveAdapter; + +import java.util.ArrayList; +import java.util.List; +public class MeshPlaneCutter { + + public static List cut( + List primitives, + Plane plane, + PrimitiveAdapter adapter) { + + List result = new ArrayList<>(); + + List intersections = new ArrayList<>(); + + for (T primitive : primitives) { + + Polygon polygon = + adapter.toPolygon(primitive); + + ClipResult clipped = + PolygonClipper.clip( + polygon, + plane + ); + + intersections.addAll( + clipped.intersections + ); + + if (clipped.polygon.vertices.size() >= 3) { + + result.addAll( + adapter.fromPrimitive( + clipped.polygon, + primitive + ) + ); + } + } + + Polygon cap = + CapBuilder.build( + intersections, + plane + ); + + if (cap != null) { + + Template template = + adapter.createTemplate( + result, + plane + ); + + if (template != null) { + + adapter.prepareCap( + cap, + plane, + template + ); + + result.addAll( + adapter.fromTemplate( + cap, + template + ) + ); + } + } + + return result; + } +} + + + + diff --git a/src/main/java/cam72cam/mod/render/cutter/Plane.java b/src/main/java/cam72cam/mod/render/cutter/Plane.java new file mode 100644 index 000000000..2d6d6abdc --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/Plane.java @@ -0,0 +1,23 @@ +package cam72cam.mod.render.cutter; + +import cam72cam.mod.math.Vec3d; + +public class Plane { + + public final Vec3d normal; + public final double d; + + public Plane(Vec3d normal, double d) { + this.normal = normal.normalize(); + this.d = d; + } + + public Plane(Vec3d point, Vec3d normal) { + this.normal = normal.normalize(); + this.d = -this.normal.dotProduct(point); + } + + public double distance(Vec3d p) { + return normal.dotProduct(p) + d; + } +} diff --git a/src/main/java/cam72cam/mod/render/cutter/PlaneBasis.java b/src/main/java/cam72cam/mod/render/cutter/PlaneBasis.java new file mode 100644 index 000000000..dc7652f72 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/PlaneBasis.java @@ -0,0 +1,79 @@ +package cam72cam.mod.render.cutter; + +import cam72cam.mod.math.Vec3d; + +public final class PlaneBasis { + + public final Vec3d u; + public final Vec3d v; + + public PlaneBasis(Vec3d u, Vec3d v) { + this.u = u; + this.v = v; + } + + public static PlaneBasis fromPlane(Plane plane) { + + Vec3d n = plane.normal.normalize(); + + Vec3d helper; + + if (Math.abs(n.x) <= Math.abs(n.y) + && Math.abs(n.x) <= Math.abs(n.z)) { + + helper = new Vec3d(1,0,0); + + } else if (Math.abs(n.y) <= Math.abs(n.z)) { + + helper = new Vec3d(0,1,0); + + } else { + + helper = new Vec3d(0,0,1); + } + + // u = helper × normal + Vec3d u = helper.crossProduct(n).normalize(); + + // v = normal × u + Vec3d v = n.crossProduct(u).normalize(); + + return new PlaneBasis(u, v); + } + + public PlaneBasis rotateCW() { + return new PlaneBasis( + v, + u.scale(-1) + ); + } + + public PlaneBasis rotateCCW() { + return new PlaneBasis( + v.scale(-1), + u + ); + } + + public PlaneBasis flipU() { + return new PlaneBasis( + u.scale(-1), + v + ); + } + + public PlaneBasis flipV() { + return new PlaneBasis( + u, + v.scale(-1) + ); + } + + public PlaneBasis flip() { + + return new PlaneBasis( + u.scale(-1), + v.scale(-1) + ); + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/Polygon.java b/src/main/java/cam72cam/mod/render/cutter/Polygon.java new file mode 100644 index 000000000..0a03e71bf --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/Polygon.java @@ -0,0 +1,27 @@ +package cam72cam.mod.render.cutter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Polygon { + + public final List vertices = new ArrayList<>();//max = 6 for now? + + public Polygon() {} + + public Polygon(Collection vertices) { + this.vertices.addAll(vertices); + } + + public Polygon copy() { + + Polygon polygon = new Polygon(); + + for (ClipVertex vertex : vertices) { + polygon.vertices.add(vertex.copy()); + } + + return polygon; + } +} diff --git a/src/main/java/cam72cam/mod/render/cutter/PolygonClipper.java b/src/main/java/cam72cam/mod/render/cutter/PolygonClipper.java new file mode 100644 index 000000000..05c496fe6 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/PolygonClipper.java @@ -0,0 +1,83 @@ +package cam72cam.mod.render.cutter; + +import java.util.List; + +public final class PolygonClipper { + + private static final double EPS = 1E-6; + + private PolygonClipper() {} + + /** + * Keep the positive side of the plane. + */ + public static ClipResult clip( + Polygon polygon, + Plane plane) { + ClipResult result = new ClipResult(); + + List vertices = polygon.vertices; + + if (vertices.isEmpty()) { + return result; + } + + int size = vertices.size(); + + for (int i = 0; i < size; i++) { + + ClipVertex current = vertices.get(i); + ClipVertex next = vertices.get((i + 1) % size); + + double dc = plane.distance(current.pos); + double dn = plane.distance(next.pos); + + boolean currentInside = dc >= -EPS; + boolean nextInside = dn >= -EPS; + + if (currentInside && nextInside) { + + // inside -> inside + result.polygon.vertices.add(next); + + } else if (currentInside) { + + // inside -> outside + ClipVertex inter = + intersection(current, next, dc, dn); + + result.polygon.vertices.add(inter); + + result.intersections.add(inter.copy()); + + } else if (nextInside) { + + // outside -> inside + ClipVertex inter = + intersection(current, next, dc, dn); + + result.polygon.vertices.add(inter); + + result.intersections.add(inter.copy()); + + result.polygon.vertices.add(next); + + } + // outside -> outside + } + + return result; + } + + private static ClipVertex intersection( + ClipVertex a, + ClipVertex b, + double da, + double db) { + + double t = da / (da - db); + + return a.lerp(b, t); + } + +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/PolygonQuadBuilder.java b/src/main/java/cam72cam/mod/render/cutter/PolygonQuadBuilder.java new file mode 100644 index 000000000..c9df3eb6c --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/PolygonQuadBuilder.java @@ -0,0 +1,64 @@ +package cam72cam.mod.render.cutter; + +import java.util.ArrayList; +import java.util.List; + +public final class PolygonQuadBuilder { + + private PolygonQuadBuilder() { + } + + /** + * Split an arbitrary polygon into quads. + * + * Result polygons always contain exactly 4 vertices. + * Triangles are represented by duplicating the last vertex. + */ + public static List build(Polygon polygon) { + + List result = new ArrayList<>(); + + List verts = new ArrayList<>(polygon.vertices); + + while (verts.size() > 4) { + + Polygon quad = new Polygon(); + + quad.vertices.add(verts.get(0)); + quad.vertices.add(verts.get(1)); + quad.vertices.add(verts.get(2)); + quad.vertices.add(verts.get(3)); + + result.add(quad); + + if ((verts.size() & 1) == 0) { + // even + verts.remove(2); + verts.remove(1); + } else { + // odd + verts.remove(1); + } + } + + if (verts.size() == 4) { + + Polygon quad = new Polygon(); + quad.vertices.addAll(verts); + result.add(quad); + + } else if (verts.size() == 3) { + + Polygon quad = new Polygon(); + + quad.vertices.add(verts.get(0)); + quad.vertices.add(verts.get(1)); + quad.vertices.add(verts.get(2)); + quad.vertices.add(verts.get(2)); + + result.add(quad); + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/VertexDeduplicator.java b/src/main/java/cam72cam/mod/render/cutter/VertexDeduplicator.java new file mode 100644 index 000000000..e6363121a --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/VertexDeduplicator.java @@ -0,0 +1,45 @@ +package cam72cam.mod.render.cutter; + +import java.util.ArrayList; +import java.util.List; + +public final class VertexDeduplicator { + + private static final double EPS = 1E-5; + + private VertexDeduplicator() {} + + public static List deduplicate( + List input) { + + List result = new ArrayList<>(); + + for (ClipVertex v : input) { + + boolean exists = false; + + for (ClipVertex old : result) { + + if (samePosition(v, old)) { + exists = true; + break; + } + } + + if (!exists) { + result.add(v.copy()); + } + } + + return result; + } + + private static boolean samePosition( + ClipVertex a, + ClipVertex b) { + + return Math.abs(a.pos.x - b.pos.x) < EPS + && Math.abs(a.pos.y - b.pos.y) < EPS + && Math.abs(a.pos.z - b.pos.z) < EPS; + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/adapter/BakedQuadAdapter.java b/src/main/java/cam72cam/mod/render/cutter/adapter/BakedQuadAdapter.java new file mode 100644 index 000000000..5709cc207 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/adapter/BakedQuadAdapter.java @@ -0,0 +1,234 @@ +package cam72cam.mod.render.cutter.adapter; + +import cam72cam.mod.math.Vec3d; +import cam72cam.mod.render.cutter.*; +import cam72cam.mod.util.BlockDirectionUtil; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +import java.util.ArrayList; +import java.util.List; + +public class BakedQuadAdapter + implements PrimitiveAdapter { + + private static final int STRIDE = DefaultVertexFormat.BLOCK.getVertexSize() / 4; + + @Override + public QuadTemplate createTemplate( + List quads, + Plane plane) { + + if (quads.isEmpty()) { + return null; + } + + BakedQuad quad = quads.get(0); + + return new QuadTemplate( + quad.getSprite(), + BlockDirectionUtil.fromNormal( + plane.normal.scale(-1) + ), + quad.getTintIndex(), + quad.isShade(), + quad.hasAmbientOcclusion() + ); + } + + @Override + public Polygon toPolygon(BakedQuad quad) { + + Polygon polygon = new Polygon(); + + int[] data = quad.getVertices(); + + for (int i = 0; i < 4; i++) { + polygon.vertices.add( + readVertex(data, i) + ); + } + + return polygon; + } + + @Override + public List fromPrimitive( + Polygon polygon, + BakedQuad primitive) { + + List result = new ArrayList<>(); + + if (polygon.vertices.size() < 3) { + return result; + } + + for (Polygon quad : PolygonQuadBuilder.build(polygon)) { + + int[] data = primitive.getVertices().clone(); + + writeVertex(data, 0, quad.vertices.get(0)); + writeVertex(data, 1, quad.vertices.get(1)); + writeVertex(data, 2, quad.vertices.get(2)); + writeVertex(data, 3, quad.vertices.get(3)); + + result.add(new BakedQuad( + data, + primitive.getTintIndex(), + primitive.getDirection(), + primitive.getSprite(), + primitive.isShade(), + primitive.hasAmbientOcclusion() + )); + } + + return result; + } + + @Override + public List fromTemplate( + Polygon polygon, + QuadTemplate template) { + + applySpriteUV( + polygon, + template.sprite + ); + + applyNormal( + polygon, + template.direction + ); + + List result = new ArrayList<>(); + + if (polygon.vertices.size() < 3) { + return result; + } + + for (Polygon quad : PolygonQuadBuilder.build(polygon)) { + + int[] data = new int[STRIDE * 4]; + + writeVertex(data, 0, quad.vertices.get(0)); + writeVertex(data, 1, quad.vertices.get(1)); + writeVertex(data, 2, quad.vertices.get(2)); + writeVertex(data, 3, quad.vertices.get(3)); + + result.add(new BakedQuad( + data, + template.tintIndex, + template.direction, + template.sprite, + template.shade, + template.ambientOcclusion + )); + } + + return result; + } + + @Override + public void prepareCap( + Polygon polygon, + Plane plane, + QuadTemplate template) { + + CapUVGenerator.generate( + polygon, + plane + ); + } + + private static ClipVertex readVertex( + int[] data, + int index) { + + int base = index * STRIDE; + + float x = Float.intBitsToFloat(data[base]); + float y = Float.intBitsToFloat(data[base + 1]); + float z = Float.intBitsToFloat(data[base + 2]); + + int color = data[base + 3]; + + float u = Float.intBitsToFloat(data[base + 4]); + float v = Float.intBitsToFloat(data[base + 5]); + + int light = data[base + 6]; + + int packed = data[base + 7]; + + byte nx = (byte) packed; + byte ny = (byte) (packed >> 8); + byte nz = (byte) (packed >> 16); + + return new ClipVertex( + new Vec3d(x, y, z), + u, + v, + color, + light, + nx, + ny, + nz + ); + } + + private static void writeVertex( + int[] data, + int index, + ClipVertex v) { + + int base = index * STRIDE; + + data[base] = + Float.floatToRawIntBits((float) v.pos.x); + data[base + 1] = + Float.floatToRawIntBits((float) v.pos.y); + data[base + 2] = + Float.floatToRawIntBits((float) v.pos.z); + + data[base + 3] = v.color; + + data[base + 4] = + Float.floatToRawIntBits(v.u); + data[base + 5] = + Float.floatToRawIntBits(v.v); + + data[base + 6] = v.light; + + data[base + 7] = + (v.nx & 0xff) + | ((v.ny & 0xff) << 8) + | ((v.nz & 0xff) << 16); + } + + private static void applySpriteUV( + Polygon polygon, + TextureAtlasSprite sprite) { + + for (ClipVertex vertex : polygon.vertices) { + + vertex.u = sprite.getU(vertex.u); + vertex.v = sprite.getV(vertex.v); + } + } + + private static void applyNormal( + Polygon polygon, + Direction direction) { + + byte nx = (byte) (direction.getStepX() * 127); + byte ny = (byte) (direction.getStepY() * 127); + byte nz = (byte) (direction.getStepZ() * 127); + + for (ClipVertex vertex : polygon.vertices) { + vertex.nx = nx; + vertex.ny = ny; + vertex.nz = nz; + } + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/adapter/PrimitiveAdapter.java b/src/main/java/cam72cam/mod/render/cutter/adapter/PrimitiveAdapter.java new file mode 100644 index 000000000..f6ffd05e4 --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/adapter/PrimitiveAdapter.java @@ -0,0 +1,28 @@ +package cam72cam.mod.render.cutter.adapter; + +import cam72cam.mod.render.cutter.Plane; +import cam72cam.mod.render.cutter.Polygon; + +import java.util.List; + +public interface PrimitiveAdapter { + + Polygon toPolygon(T primitive); + + List fromPrimitive( + Polygon polygon, + T primitive); + + List fromTemplate( + Polygon polygon, + Template template); + + Template createTemplate( + List primitives, + Plane plane); + + void prepareCap( + Polygon polygon, + Plane plane, + Template template); +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/render/cutter/adapter/QuadTemplate.java b/src/main/java/cam72cam/mod/render/cutter/adapter/QuadTemplate.java new file mode 100644 index 000000000..a27c0e9df --- /dev/null +++ b/src/main/java/cam72cam/mod/render/cutter/adapter/QuadTemplate.java @@ -0,0 +1,31 @@ +package cam72cam.mod.render.cutter.adapter; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +public class QuadTemplate { + + public final TextureAtlasSprite sprite; + + public final Direction direction; + + public final int tintIndex; + + public final boolean shade; + + public final boolean ambientOcclusion; + + public QuadTemplate( + TextureAtlasSprite sprite, + Direction direction, + int tintIndex, + boolean shade, + boolean ambientOcclusion) { + + this.sprite = sprite; + this.direction = direction; + this.tintIndex = tintIndex; + this.shade = shade; + this.ambientOcclusion = ambientOcclusion; + } +} \ No newline at end of file diff --git a/src/main/java/cam72cam/mod/util/BlockDirectionUtil.java b/src/main/java/cam72cam/mod/util/BlockDirectionUtil.java new file mode 100644 index 000000000..1c4f0883d --- /dev/null +++ b/src/main/java/cam72cam/mod/util/BlockDirectionUtil.java @@ -0,0 +1,35 @@ +package cam72cam.mod.util; + +import cam72cam.mod.math.Vec3d; +import net.minecraft.core.Direction; + +public final class BlockDirectionUtil { + + private BlockDirectionUtil() {} + + /** + * This convert Vec3d normal to minecraft block direction + * */ + public static Direction fromNormal(Vec3d normal) { + + double ax = Math.abs(normal.x); + double ay = Math.abs(normal.y); + double az = Math.abs(normal.z); + + if (ax >= ay && ax >= az) { + return normal.x >= 0 + ? Direction.EAST + : Direction.WEST; + } + + if (ay >= ax && ay >= az) { + return normal.y >= 0 + ? Direction.UP + : Direction.DOWN; + } + + return normal.z >= 0 + ? Direction.SOUTH + : Direction.NORTH; + } +} \ No newline at end of file