diff --git a/chunky/src/java/se/llbit/chunky/block/AbstractModelBlock.java b/chunky/src/java/se/llbit/chunky/block/AbstractModelBlock.java index 6c7341c91c..da65ea76b1 100644 --- a/chunky/src/java/se/llbit/chunky/block/AbstractModelBlock.java +++ b/chunky/src/java/se/llbit/chunky/block/AbstractModelBlock.java @@ -1,12 +1,16 @@ package se.llbit.chunky.block; import se.llbit.chunky.model.BlockModel; +import se.llbit.chunky.model.QuadModel; import se.llbit.chunky.plugin.PluginApi; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.resources.Texture; import se.llbit.math.Ray; +import se.llbit.math.Transform; import se.llbit.math.Vector3; +import se.llbit.math.primitive.Primitive; +import java.util.Collection; import java.util.Random; @PluginApi @@ -50,4 +54,9 @@ public boolean intersect(Ray ray, Scene scene) { public boolean isBiomeDependant() { return super.isBiomeDependant() || model.isBiomeDependant(); } + + @Override + public Collection getPrimitives(Transform transform) { + return getModel().getPrimitives(transform); + } } diff --git a/chunky/src/java/se/llbit/chunky/block/Block.java b/chunky/src/java/se/llbit/chunky/block/Block.java index f7b507f4f1..f040245e18 100644 --- a/chunky/src/java/se/llbit/chunky/block/Block.java +++ b/chunky/src/java/se/llbit/chunky/block/Block.java @@ -9,10 +9,14 @@ import se.llbit.json.JsonValue; import se.llbit.math.AABB; import se.llbit.math.Ray; +import se.llbit.math.Transform; import se.llbit.math.Vector3; +import se.llbit.math.primitive.Primitive; import se.llbit.nbt.CompoundTag; import se.llbit.nbt.Tag; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Random; @@ -153,4 +157,6 @@ public Tag getNewTagWithBlockEntity(Tag blockTag, CompoundTag entityTag) { public boolean isBiomeDependant() { return isWaterFilled(); } + + public abstract Collection getPrimitives(Transform transform); } diff --git a/chunky/src/java/se/llbit/chunky/block/MinecraftBlock.java b/chunky/src/java/se/llbit/chunky/block/MinecraftBlock.java index 951d0517fd..9e4b4c3bab 100644 --- a/chunky/src/java/se/llbit/chunky/block/MinecraftBlock.java +++ b/chunky/src/java/se/llbit/chunky/block/MinecraftBlock.java @@ -2,6 +2,14 @@ import se.llbit.chunky.resources.Texture; import se.llbit.chunky.world.Material; +import se.llbit.math.Transform; +import se.llbit.math.Vector3; +import se.llbit.math.Vector4; +import se.llbit.math.primitive.Box; +import se.llbit.math.primitive.Primitive; + +import java.util.Collection; +import java.util.LinkedList; /** * A simple opaque block with a single texture. @@ -14,4 +22,21 @@ public MinecraftBlock(String name, Texture texture) { opaque = true; solid = true; } + + @Override + public Collection getPrimitives(Transform transform) { + // TODO check uv mapping and flip/rotate textures if needed + // TODO apply this block's material properties to the primitives + + Collection primitives = new LinkedList<>(); + Box box = new Box(0, 1, 0, 1, 0, 1); + box.transform(transform); + box.addFrontFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addBackFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addLeftFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addRightFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addTopFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addBottomFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + return primitives; + } } diff --git a/chunky/src/java/se/llbit/chunky/block/SolidNonOpaqueBlock.java b/chunky/src/java/se/llbit/chunky/block/SolidNonOpaqueBlock.java index 1fab2c7711..c1a05aa93b 100644 --- a/chunky/src/java/se/llbit/chunky/block/SolidNonOpaqueBlock.java +++ b/chunky/src/java/se/llbit/chunky/block/SolidNonOpaqueBlock.java @@ -1,6 +1,13 @@ package se.llbit.chunky.block; import se.llbit.chunky.resources.Texture; +import se.llbit.math.Transform; +import se.llbit.math.Vector4; +import se.llbit.math.primitive.Box; +import se.llbit.math.primitive.Primitive; + +import java.util.Collection; +import java.util.LinkedList; public class SolidNonOpaqueBlock extends Block { @@ -9,4 +16,21 @@ public SolidNonOpaqueBlock(String name, Texture texture) { solid = true; opaque = false; } + + @Override + public Collection getPrimitives(Transform transform) { + // TODO check uv mapping and flip/rotate textures if needed + // TODO apply this block's material properties to the primitives + + Collection primitives = new LinkedList<>(); + Box box = new Box(0, 1, 0, 1, 0, 1); + box.transform(transform); + box.addFrontFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addBackFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addLeftFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addRightFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addTopFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addBottomFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + return primitives; + } } diff --git a/chunky/src/java/se/llbit/chunky/block/UntintedLeaves.java b/chunky/src/java/se/llbit/chunky/block/UntintedLeaves.java index 341d1870df..2b70c07eb1 100644 --- a/chunky/src/java/se/llbit/chunky/block/UntintedLeaves.java +++ b/chunky/src/java/se/llbit/chunky/block/UntintedLeaves.java @@ -1,6 +1,13 @@ package se.llbit.chunky.block; import se.llbit.chunky.resources.Texture; +import se.llbit.math.Transform; +import se.llbit.math.Vector4; +import se.llbit.math.primitive.Box; +import se.llbit.math.primitive.Primitive; + +import java.util.Collection; +import java.util.LinkedList; public class UntintedLeaves extends Block { @@ -9,4 +16,21 @@ public UntintedLeaves(String name, Texture texture) { solid = false; opaque = false; } + + @Override + public Collection getPrimitives(Transform transform) { + // TODO check uv mapping and flip/rotate textures if needed + // TODO apply this block's material properties to the primitives + + Collection primitives = new LinkedList<>(); + Box box = new Box(0, 1, 0, 1, 0, 1); + box.transform(transform); + box.addFrontFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addBackFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addLeftFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addRightFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addTopFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + box.addBottomFaces(primitives, texture, new Vector4(0, 1, 0, 1)); + return primitives; + } } diff --git a/chunky/src/java/se/llbit/chunky/block/legacy/UnfinalizedLegacyBlock.java b/chunky/src/java/se/llbit/chunky/block/legacy/UnfinalizedLegacyBlock.java index 49ebe10cac..d9d9da1bb7 100644 --- a/chunky/src/java/se/llbit/chunky/block/legacy/UnfinalizedLegacyBlock.java +++ b/chunky/src/java/se/llbit/chunky/block/legacy/UnfinalizedLegacyBlock.java @@ -6,8 +6,13 @@ import se.llbit.chunky.renderer.scene.Scene; import se.llbit.log.Log; import se.llbit.math.Ray; +import se.llbit.math.Transform; +import se.llbit.math.primitive.Primitive; import se.llbit.nbt.CompoundTag; +import java.util.Collection; +import java.util.Collections; + /** * A wrapper for legacy blocks (before the flattening in Minecraft 1.13) that have properties that * depend on surrounding blocks (e.g. doors, snow covered grass or fences). @@ -79,4 +84,9 @@ public Block getIncompleteBlock() { * @param state Current finalization state */ public abstract void finalizeBlock(FinalizationState state); + + @Override + public Collection getPrimitives(Transform transform) { + return Collections.emptyList(); + } } diff --git a/chunky/src/java/se/llbit/chunky/block/minecraft/SporeBlossom.java b/chunky/src/java/se/llbit/chunky/block/minecraft/SporeBlossom.java index c9a8ea0e0d..d3e6183356 100644 --- a/chunky/src/java/se/llbit/chunky/block/minecraft/SporeBlossom.java +++ b/chunky/src/java/se/llbit/chunky/block/minecraft/SporeBlossom.java @@ -20,10 +20,15 @@ import se.llbit.chunky.block.Block; import se.llbit.chunky.entity.Entity; +import se.llbit.chunky.model.minecraft.SporeBlossomModel; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.resources.Texture; import se.llbit.math.Ray; +import se.llbit.math.Transform; import se.llbit.math.Vector3; +import se.llbit.math.primitive.Primitive; + +import java.util.Collection; public class SporeBlossom extends Block { @@ -48,4 +53,9 @@ public boolean isEntity() { public Entity toEntity(Vector3 position) { return new se.llbit.chunky.entity.SporeBlossom(position); } + + @Override + public Collection getPrimitives(Transform transform) { + return SporeBlossomModel.primitives(transform); + } } diff --git a/chunky/src/java/se/llbit/chunky/entity/BlockDisplayEntity.java b/chunky/src/java/se/llbit/chunky/entity/BlockDisplayEntity.java new file mode 100644 index 0000000000..5008d39ca9 --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/entity/BlockDisplayEntity.java @@ -0,0 +1,71 @@ +package se.llbit.chunky.entity; + +import se.llbit.chunky.block.Block; +import se.llbit.json.JsonObject; +import se.llbit.json.JsonValue; +import se.llbit.math.Transform; +import se.llbit.math.Vector3; +import se.llbit.math.Vector4; +import se.llbit.math.primitive.Primitive; +import se.llbit.nbt.CompoundTag; +import se.llbit.nbt.Tag; + +import java.util.Collection; + +public class BlockDisplayEntity extends Entity { + private final Block block; + private final CompoundTag tag; + + public BlockDisplayEntity(Vector3 position, Block block, CompoundTag tag) { + super(position); + this.block = block; + this.tag = tag; + } + + @Override + public Collection primitives(Vector3 offset) { + // TODO add support for matrix form (ie. 16 values in row-major order) + + Transform transform = getTransform(tag.get("transformation")) + .translate(position) + .translate(offset); + + return block.getPrimitives(transform); + } + + private Transform getTransform(Tag transformation) { + return Transform.NONE + .rotateQuaternion( // TODO support axis-angle form (ie. angle of rotation and axis vector) + new Vector4( + transformation.get("right_rotation").get(1).floatValue(), + transformation.get("right_rotation").get(2).floatValue(), + -transformation.get("right_rotation").get(3).floatValue(), // fix rotation direction + -transformation.get("right_rotation").get(0).floatValue() // fix rotation direction + ) + ) + .scale( + transformation.get("scale").get(0).floatValue(1), + transformation.get("scale").get(1).floatValue(1), + transformation.get("scale").get(2).floatValue(1) + ) + .rotateQuaternion( // TODO support axis-angle form (ie. angle of rotation and axis vector) + new Vector4( + transformation.get("left_rotation").get(1).floatValue(), + transformation.get("left_rotation").get(2).floatValue(), + -transformation.get("left_rotation").get(3).floatValue(), // fix rotation direction + -transformation.get("left_rotation").get(0).floatValue() // fix rotation direction + ) + ) + .translate( + transformation.get("translation").get(0).floatValue(0), + transformation.get("translation").get(1).floatValue(0), + transformation.get("translation").get(2).floatValue(0) + ); + } + + @Override + public JsonValue toJson() { + // TODO can we even serialize this or do we put the tag into a block palette? + return new JsonObject(); + } +} diff --git a/chunky/src/java/se/llbit/chunky/model/AABBModel.java b/chunky/src/java/se/llbit/chunky/model/AABBModel.java index 90ca47f55e..da1463ca68 100644 --- a/chunky/src/java/se/llbit/chunky/model/AABBModel.java +++ b/chunky/src/java/se/llbit/chunky/model/AABBModel.java @@ -3,14 +3,11 @@ import se.llbit.chunky.plugin.PluginApi; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.resources.Texture; -import se.llbit.math.AABB; -import se.llbit.math.Ray; -import se.llbit.math.Vector3; +import se.llbit.math.*; +import se.llbit.math.primitive.Box; +import se.llbit.math.primitive.Primitive; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Random; +import java.util.*; /** * A block model that is made out of textured AABBs. @@ -186,7 +183,7 @@ private boolean intersectFace(Ray ray, Scene scene, Texture texture, UVMapping m @Override public boolean isBiomeDependant() { Tint[][] tints = getTints(); - if(tints == null) + if (tints == null) return false; return Arrays.stream(tints) @@ -194,4 +191,38 @@ public boolean isBiomeDependant() { .flatMap(Arrays::stream) .anyMatch(Tint::isBiomeTint); } + + @Override + public Collection getPrimitives(Transform transform) { + List primitives = new ArrayList<>(); + + AABB[] boxes = getBoxes(); + Texture[][] textures = getTextures(); + + for (int i = 0; i < boxes.length; ++i) { + AABB aabb = boxes[i]; + Box box = new Box(aabb.xmin, aabb.xmax, aabb.ymin, aabb.ymax, aabb.zmin, aabb.zmax); + box.transform(transform); + if (textures[i][0] != null) { + box.addFrontFaces(primitives, textures[i][0], new Vector4(0, 1, 0, 1)); // TODO + } + if (textures[i][1] != null) { + box.addBackFaces(primitives, textures[i][1], new Vector4(0, 1, 0, 1)); // TODO + } + if (textures[i][2] != null) { + box.addLeftFaces(primitives, textures[i][2], new Vector4(0, 1, 0, 1)); // TODO + } + if (textures[i][3] != null) { + box.addRightFaces(primitives, textures[i][3], new Vector4(0, 1, 0, 1)); // TODO + } + if (textures[i][4] != null) { + box.addTopFaces(primitives, textures[i][4], new Vector4(0, 1, 0, 1)); + } + if (textures[i][5] != null) { + box.addBottomFaces(primitives, textures[i][5], new Vector4(0, 1, 0, 1)); + } + } + // TODO apply correct texture index, tint and uv-mapping, material properties + return primitives; + } } diff --git a/chunky/src/java/se/llbit/chunky/model/BlockModel.java b/chunky/src/java/se/llbit/chunky/model/BlockModel.java index 5fd4786a83..454804d1bb 100644 --- a/chunky/src/java/se/llbit/chunky/model/BlockModel.java +++ b/chunky/src/java/se/llbit/chunky/model/BlockModel.java @@ -3,8 +3,11 @@ import se.llbit.chunky.plugin.PluginApi; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.math.Ray; +import se.llbit.math.Transform; import se.llbit.math.Vector3; +import se.llbit.math.primitive.Primitive; +import java.util.Collection; import java.util.List; import java.util.Random; @@ -20,4 +23,6 @@ public interface BlockModel { double faceSurfaceArea(int face); boolean isBiomeDependant(); + + Collection getPrimitives(Transform transform); } diff --git a/chunky/src/java/se/llbit/chunky/model/QuadModel.java b/chunky/src/java/se/llbit/chunky/model/QuadModel.java index e8bdcfb9c4..8ceb9c0abf 100644 --- a/chunky/src/java/se/llbit/chunky/model/QuadModel.java +++ b/chunky/src/java/se/llbit/chunky/model/QuadModel.java @@ -18,19 +18,14 @@ package se.llbit.chunky.model; -import se.llbit.chunky.model.BlockModel; -import se.llbit.chunky.model.Tint; import se.llbit.chunky.plugin.PluginApi; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.resources.Texture; -import se.llbit.math.Quad; -import se.llbit.math.Ray; -import se.llbit.math.Vector3; -import se.llbit.math.Vector4; +import se.llbit.chunky.world.material.TextureMaterial; +import se.llbit.math.*; +import se.llbit.math.primitive.Primitive; -import java.util.Arrays; -import java.util.Objects; -import java.util.Random; +import java.util.*; /** * A block model that is made out of textured quads. @@ -154,10 +149,23 @@ public boolean intersect(Ray ray, Scene scene) { @Override public boolean isBiomeDependant() { Tint[] tints = getTints(); - if(tints == null) + if (tints == null) return false; return Arrays.stream(tints) .anyMatch(Tint::isBiomeTint); } + + @Override + public Collection getPrimitives(Transform transform) { + List primitives = new ArrayList<>(); + Quad[] quads = getQuads(); + Texture[] textures = getTextures(); + for (int i = 0; i < quads.length; ++i) { + Quad quad = quads[i]; + quad.addTriangles(primitives, new TextureMaterial(textures[i]), transform); + } + // TODO material properties, tinting + return primitives; + } } diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/SceneEntities.java b/chunky/src/java/se/llbit/chunky/renderer/scene/SceneEntities.java index b36d3d6f82..e28a75b745 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/SceneEntities.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/SceneEntities.java @@ -1,12 +1,10 @@ package se.llbit.chunky.renderer.scene; import se.llbit.chunky.PersistentSettings; +import se.llbit.chunky.block.BlockSpec; import se.llbit.chunky.chunk.BlockPalette; import se.llbit.chunky.chunk.ChunkData; -import se.llbit.chunky.entity.ArmorStand; -import se.llbit.chunky.entity.Entity; -import se.llbit.chunky.entity.PaintingEntity; -import se.llbit.chunky.entity.PlayerEntity; +import se.llbit.chunky.entity.*; import se.llbit.chunky.world.Dimension; import se.llbit.json.JsonArray; import se.llbit.json.JsonObject; @@ -139,38 +137,54 @@ public void loadPlayers(TaskTracker.Task task, Dimension dimension) { public void loadEntitiesInChunk(Scene scene, ChunkData chunkData) { for (CompoundTag tag : chunkData.getEntities()) { - Tag posTag = tag.get("Pos"); - if (posTag.isList()) { - ListTag pos = posTag.asList(); - double x = pos.get(0).doubleValue(); - double y = pos.get(1).doubleValue(); - double z = pos.get(2).doubleValue(); - - if (y >= scene.yClipMin && y < scene.yClipMax) { - String id = tag.get("id").stringValue(""); - // Before 1.12 paintings had id=Painting. - // After 1.12 paintings had id=minecraft:painting. - if ((id.equals("minecraft:painting") || id.equals("Painting")) && entityLoadingPreferences.shouldLoadClass(PaintingEntity.class)) { - Tag paintingVariant = NbtUtil.getTagFromNames(tag, "Motive", "variant"); - int facing = (tag.get("facing").isError()) - ? tag.get("Facing").byteValue(0) // pre 1.17 - : tag.get("facing").byteValue(0); // 1.17+ - addEntity(new PaintingEntity( - new Vector3(x, y, z), - paintingVariant.stringValue(), - facing - )); - } else if (id.equals("minecraft:armor_stand") && entityLoadingPreferences.shouldLoadClass(ArmorStand.class)) { - addActor(new ArmorStand( - new Vector3(x, y, z), - tag - )); - } + this.loadEntity(scene, tag); + } + } + + private void loadEntity(Scene scene, CompoundTag tag) { + Tag posTag = tag.get("Pos"); + if (!posTag.isList()) { + return; + } + + ListTag pos = posTag.asList(); + double x = pos.get(0).doubleValue(); + double y = pos.get(1).doubleValue(); + double z = pos.get(2).doubleValue(); + + if (y >= scene.yClipMin && y < scene.yClipMax) { + String id = tag.get("id").stringValue(""); + // Before 1.12 paintings had id=Painting. + // After 1.12 paintings had id=minecraft:painting. + if ((id.equals("minecraft:painting") || id.equals("Painting")) && entityLoadingPreferences.shouldLoadClass(PaintingEntity.class)) { + Tag paintingVariant = NbtUtil.getTagFromNames(tag, "Motive", "variant"); + int facing = (tag.get("facing").isError()) + ? tag.get("Facing").byteValue(0) // pre 1.17 + : tag.get("facing").byteValue(0); // 1.17+ + addEntity(new PaintingEntity( + new Vector3(x, y, z), + paintingVariant.stringValue(), + facing + )); + } else if (id.equals("minecraft:armor_stand") && entityLoadingPreferences.shouldLoadClass(ArmorStand.class)) { + addActor(new ArmorStand( + new Vector3(x, y, z), + tag + )); + } else if (id.equals("minecraft:block_display")) { + addEntity(new BlockDisplayEntity( + new Vector3(x, y, z), + new BlockSpec(tag.get("block_state")).toBlock(), + tag + )); + for (Tag passenger : tag.get("Passengers").asList()) { + loadEntity(scene, passenger.asCompound()); } } } } + public boolean shouldLoad(Entity entity) { return entityLoadingPreferences.shouldLoad(entity); } diff --git a/chunky/src/java/se/llbit/math/Transform.java b/chunky/src/java/se/llbit/math/Transform.java index 4e4f0cab09..b2c0a108b6 100644 --- a/chunky/src/java/se/llbit/math/Transform.java +++ b/chunky/src/java/se/llbit/math/Transform.java @@ -125,6 +125,27 @@ public final Transform scale(final double scale) { }); } + /** + * Scales all coordinates. + */ + public final Transform scale(final double scaleX, final double scaleY, final double scaleZ) { + return chain(new Transform() { + @Override + public void apply(Vector3 v) { + v.x *= scaleX; + v.y *= scaleY; + v.z *= scaleZ; + } + + @Override + public void applyRotScale(Vector3 v) { + v.x *= scaleX; + v.y *= scaleY; + v.z *= scaleZ; + } + }); + } + /** * Rotation by 90 degrees around the Y axis */ @@ -239,6 +260,34 @@ public final Transform rotateNegZ() { }); } + public Transform rotateQuaternion(Vector4 quaternion) { + return chain(new Transform() { + @Override + public void apply(Vector3 o) { + Vector4 oAsVec4 = new Vector4(0, o); + Vector4 quaternionConj = new Vector4(quaternion.x, quaternion.y * -1, quaternion.z * -1, quaternion.w * -1); + Vector4 result = multiplyQuaternion(multiplyQuaternion(quaternion, oAsVec4), quaternionConj); + o.x = result.y; + o.y = result.z; + o.z = result.w; + } + + @Override + public void applyRotScale(Vector3 o) { + apply(o); + } + + private Vector4 multiplyQuaternion(Vector4 q, Vector4 r) { + return new Vector4( + r.x * q.x - r.y * q.y - r.z * q.z - r.w * q.w, + r.x * q.y + r.y * q.x - r.z * q.w + r.w * q.z, + r.x * q.z + r.y * q.w + r.z * q.x - r.w * q.y, + r.x * q.w - r.y * q.z + r.z * q.y + r.w * q.x + ); + } + }); + } + /** * Mirror in Y axis */ diff --git a/chunky/src/java/se/llbit/math/Vector4.java b/chunky/src/java/se/llbit/math/Vector4.java index 77919c99d8..356065a415 100644 --- a/chunky/src/java/se/llbit/math/Vector4.java +++ b/chunky/src/java/se/llbit/math/Vector4.java @@ -41,6 +41,8 @@ public Vector4(Vector4 v) { public Vector4(Vector3 v, double w) { this(v.x, v.y, v.z, w); } + public Vector4(double x, Vector3 v) { this(x, v.x, v.y, v.z); } + public Vector4(double i, double j, double k, double l) { x = i; y = j;