diff --git a/chunky/src/java/se/llbit/chunky/block/HangingSign.java b/chunky/src/java/se/llbit/chunky/block/HangingSign.java new file mode 100644 index 0000000000..52e6498a42 --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/block/HangingSign.java @@ -0,0 +1,39 @@ +package se.llbit.chunky.block; + +import se.llbit.chunky.entity.Entity; +import se.llbit.chunky.entity.HangingSignEntity; +import se.llbit.chunky.renderer.scene.Scene; +import se.llbit.math.Ray; +import se.llbit.math.Vector3; +import se.llbit.nbt.CompoundTag; + +public class HangingSign extends MinecraftBlockTranslucent { + private final String material; + private final int rotation; + private final boolean attached; + + public HangingSign(String name, String material, int rotation, boolean attached) { + super(name, HangingSignEntity.textureFromMaterial(material)); + this.material = material; + this.rotation = rotation; + this.attached = attached; + invisible = true; + solid = false; + localIntersect = true; + } + + @Override + public boolean intersect(Ray ray, Scene scene) { + return false; + } + + @Override + public boolean isBlockEntity() { + return true; + } + + @Override + public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) { + return new HangingSignEntity(position, entityTag, rotation, attached, material); + } +} diff --git a/chunky/src/java/se/llbit/chunky/block/MinecraftBlockProvider.java b/chunky/src/java/se/llbit/chunky/block/MinecraftBlockProvider.java index c806c87cf0..32bdc5f0b5 100644 --- a/chunky/src/java/se/llbit/chunky/block/MinecraftBlockProvider.java +++ b/chunky/src/java/se/llbit/chunky/block/MinecraftBlockProvider.java @@ -1048,6 +1048,28 @@ private static void addBlocks(Texture texture, String... names) { addBlock("calibrated_sculk_sensor", (name, tag) -> new CalibratedSculkSensor( tag.get("Properties").get("sculk_sensor_phase").stringValue("cooldown"), tag.get("Properties").get("facing").stringValue("north"))); + addBlock("oak_hanging_sign", (name, tag) -> hangingSign(tag, "oak")); + addBlock("oak_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "oak")); + addBlock("spruce_hanging_sign", (name, tag) -> hangingSign(tag, "spruce")); + addBlock("spruce_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "spruce")); + addBlock("birch_hanging_sign", (name, tag) -> hangingSign(tag, "birch")); + addBlock("birch_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "birch")); + addBlock("jungle_hanging_sign", (name, tag) -> hangingSign(tag, "jungle")); + addBlock("jungle_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "jungle")); + addBlock("acacia_hanging_sign", (name, tag) -> hangingSign(tag, "acacia")); + addBlock("acacia_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "acacia")); + addBlock("dark_oak_hanging_sign", (name, tag) -> hangingSign(tag, "dark_oak")); + addBlock("dark_oak_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "dark_oak")); + addBlock("crimson_hanging_sign", (name, tag) -> hangingSign(tag, "crimson")); + addBlock("crimson_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "crimson")); + addBlock("warped_hanging_sign", (name, tag) -> hangingSign(tag, "warped")); + addBlock("warped_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "warped")); + addBlock("mangrove_hanging_sign", (name, tag) -> hangingSign(tag, "mangrove")); + addBlock("mangrove_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "mangrove")); + addBlock("bamboo_hanging_sign", (name, tag) -> hangingSign(tag, "bamboo")); + addBlock("bamboo_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "bamboo")); + addBlock("cherry_hanging_sign", (name, tag) -> hangingSign(tag, "cherry")); + addBlock("cherry_wall_hanging_sign", (name, tag) -> wallHangingSign(tag, "cherry")); } @Override @@ -3259,6 +3281,17 @@ private static Block sign(Tag tag, String material) { return new Sign(name, material, rotation); } + private static Block hangingSign(Tag tag, String material) { + String name = BlockProvider.blockName(tag); + int rotation = BlockProvider.stringToInt(tag.get("Properties").get("rotation"), 0); + boolean attached = tag.get("Properties").get("attached").stringValue("false").equals("true"); + return new HangingSign(name, material, rotation, attached); + } + + private static Block wallHangingSign(Tag tag, String material) { + return new WallHangingSign(BlockProvider.blockName(tag), material, BlockProvider.facing(tag)); + } + private static Block banner(Tag tag, Texture texture, int color) { String name = BlockProvider.blockName(tag); int rotation = BlockProvider.stringToInt(tag.get("Properties").get("rotation"), 0); diff --git a/chunky/src/java/se/llbit/chunky/block/WallHangingSign.java b/chunky/src/java/se/llbit/chunky/block/WallHangingSign.java new file mode 100644 index 0000000000..de7d01de4e --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/block/WallHangingSign.java @@ -0,0 +1,71 @@ +package se.llbit.chunky.block; + +import se.llbit.chunky.entity.Entity; +import se.llbit.chunky.entity.HangingSignEntity; +import se.llbit.chunky.entity.WallHangingSignEntity; +import se.llbit.chunky.renderer.scene.Scene; +import se.llbit.math.Ray; +import se.llbit.math.Vector3; +import se.llbit.nbt.CompoundTag; + +public class WallHangingSign extends MinecraftBlockTranslucent { + private final String material; + private final Facing facing; + + public WallHangingSign(String name, String material, String facing) { + super(name, HangingSignEntity.textureFromMaterial(material)); + this.material = material; + this.facing = Facing.fromString(facing); + invisible = true; + solid = false; + localIntersect = true; + } + + @Override + public boolean intersect(Ray ray, Scene scene) { + return false; + } + + @Override + public boolean isBlockEntity() { + return true; + } + + @Override + public Entity toBlockEntity(Vector3 position, CompoundTag entityTag) { + return new WallHangingSignEntity(position, entityTag, facing, material); + } + + public enum Facing { + NORTH, EAST, SOUTH, WEST; + + public static Facing fromString(String facing) { + switch (facing) { + case "east": + return EAST; + case "south": + return SOUTH; + case "west": + return WEST; + case "north": + default: + return NORTH; + } + } + + @Override + public String toString() { + switch (this) { + case EAST: + return "east"; + case SOUTH: + return "south"; + case WEST: + return "west"; + case NORTH: + default: + return "north"; + } + } + } +} diff --git a/chunky/src/java/se/llbit/chunky/entity/Entity.java b/chunky/src/java/se/llbit/chunky/entity/Entity.java index 21615c8817..eef4c594c4 100644 --- a/chunky/src/java/se/llbit/chunky/entity/Entity.java +++ b/chunky/src/java/se/llbit/chunky/entity/Entity.java @@ -114,6 +114,10 @@ public static Entity fromJson(JsonObject json) { return DecoratedPotModel.DecoratedPotSpoutEntity.fromJson(json); case "calibratedSculkSensorAmethyst": return CalibratedSculkSensorAmethyst.fromJson(json); + case "hangingSign": + return HangingSignEntity.fromJson(json); + case "wallHangingSign": + return WallHangingSignEntity.fromJson(json); } return null; } diff --git a/chunky/src/java/se/llbit/chunky/entity/HangingSignEntity.java b/chunky/src/java/se/llbit/chunky/entity/HangingSignEntity.java new file mode 100644 index 0000000000..ccd621f15f --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/entity/HangingSignEntity.java @@ -0,0 +1,362 @@ +package se.llbit.chunky.entity; + +import se.llbit.chunky.model.Model; +import se.llbit.chunky.resources.SignTexture; +import se.llbit.chunky.resources.Texture; +import se.llbit.chunky.world.material.TextureMaterial; +import se.llbit.json.JsonArray; +import se.llbit.json.JsonObject; +import se.llbit.json.JsonValue; +import se.llbit.math.Quad; +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 java.util.Collection; +import java.util.LinkedList; + +public class HangingSignEntity extends Entity { + private static final Quad[] quadsAttached = new Quad[]{ + // top + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector4(2 / 64., 16 / 64., 1 - 12 / 32., 1 - 14 / 32.) + ), + // bottom + new Quad( + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(16 / 64., 30 / 64., 1 - 12 / 32., 1 - 14 / 32.) + ), + // left + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(0 / 64., 2 / 64.0, 1 - 14 / 32., 1 - 24 / 32.) + ), + // right + new Quad( + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(16 / 64., 18 / 64.0, 1 - 14 / 32., 1 - 24 / 32.) + ), + // front + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(16 / 64., 2 / 64., 1 - 14 / 32., 1 - 24 / 32.) + ), + // back + new Quad( + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(32 / 64., 18 / 64., 1 - 14 / 32., 1 - 24 / 32.) + ), + // chains front + new Quad( + new Vector3(-6 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(6 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-6 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(13 / 64., 27 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + // chains back + new Quad( + new Vector3(6 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-6 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(6 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(13 / 64., 27 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }; + + private static final Quad[] quadsNotAttached = Model.join( + new Quad[]{ + // top + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector4(2 / 64., 16 / 64., 1 - 12 / 32., 1 - 14 / 32.) + ), + // bottom + new Quad( + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(16 / 64., 30 / 64., 1 - 12 / 32., 1 - 14 / 32.) + ), + // left + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(0 / 64., 2 / 64.0, 1 - 14 / 32., 1 - 24 / 32.) + ), + // right + new Quad( + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(16 / 64., 18 / 64.0, 1 - 14 / 32., 1 - 24 / 32.) + ), + // front + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(16 / 64., 2 / 64., 1 - 14 / 32., 1 - 24 / 32.) + ), + // back + new Quad( + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(32 / 64., 18 / 64., 1 - 14 / 32., 1 - 24 / 32.) + ), + }, + // chains + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(-45)) + .translate(-0.5, -0.5, -0.5) + .translate(-5 / 16.0, 0 / 16.0, 0 / 16.0) + ), + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(45)) + .translate(-0.5, -0.5, -0.5) + .translate(-5 / 16.0, 0 / 16.0, 0 / 16.0) + ), + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(-45)) + .translate(-0.5, -0.5, -0.5) + .translate(5 / 16.0, 0 / 16.0, 0 / 16.0) + ), + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 16 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(45)) + .translate(-0.5, -0.5, -0.5) + .translate(5 / 16.0, 0 / 16.0, 0 / 16.0) + ) + ); + + private static Quad[][] rotatedQuadsAttached = new Quad[16][]; + private static Quad[][] rotatedQuadsNotAttached = new Quad[16][]; + + private static Quad[] frontFaceWithText = new Quad[16]; + private static Quad[] backFaceWithText = new Quad[16]; + + static { + rotatedQuadsAttached[0] = Model.translate(quadsAttached, 0.5, 0, 0.5); + rotatedQuadsNotAttached[0] = Model.translate(quadsNotAttached, 0.5, 0, 0.5); + + for (int i = 1; i < 16; ++i) { + rotatedQuadsAttached[i] = Model.rotateY(rotatedQuadsAttached[0], Math.PI - i * Math.PI / 8); + rotatedQuadsNotAttached[i] = Model.rotateY(rotatedQuadsNotAttached[0], Math.PI - i * Math.PI / 8); + } + + frontFaceWithText[0] = new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(1, 0, 1, 0) + ); + frontFaceWithText[0] = frontFaceWithText[0].transform(Transform.NONE.translate(0.5, 0, 0.5)); + for (int i = 1; i < 16; ++i) { + frontFaceWithText[i] = frontFaceWithText[0].transform(Transform.NONE.rotateY(Math.PI - i * Math.PI / 8)); + } + + backFaceWithText[0] = new Quad( + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(1, 0, 1, 0) + ); + backFaceWithText[0] = backFaceWithText[0].transform(Transform.NONE.translate(0.5, 0, 0.5)); + for (int i = 1; i < 16; ++i) { + backFaceWithText[i] = backFaceWithText[0].transform(Transform.NONE.rotateY(Math.PI - i * Math.PI / 8)); + } + } + + private final JsonArray[] frontText; + private final JsonArray[] backText; + private final int angle; + private final boolean attached; + private final SignTexture frontTexture; + private final SignTexture backTexture; + private final Texture texture; + private final String material; + + public HangingSignEntity(Vector3 position, CompoundTag entityTag, int rotation, boolean attached, String material) { + this(position, SignEntity.getFrontTextLines(entityTag), SignEntity.getBackTextLines(entityTag), rotation, attached, material); + } + + public HangingSignEntity(Vector3 position, JsonArray[] frontText, JsonArray[] backText, int rotation, boolean attached, String material) { + super(position); + Texture signTexture = HangingSignEntity.textureFromMaterial(material); + this.frontText = frontText; + this.backText = backText; + this.angle = rotation; + this.attached = attached; + this.frontTexture = frontText != null ? new SignTexture(frontText, signTexture, 14, 10, 2 / 64., 1 - 24 / 32., 16 / 64., 1 - 14 / 32., 4.5, 3, 9) : null; + this.backTexture = backText != null ? new SignTexture(backText, signTexture, 14, 10, 18 / 64., 1 - 24 / 32., 32 / 64., 1 - 14 / 32., 4.5, 3, 9) : null; + this.texture = signTexture; + this.material = material; + } + + @Override + public Collection primitives(Vector3 offset) { + Collection primitives = new LinkedList<>(); + Transform transform = Transform.NONE.translate(position.x + offset.x, position.y + offset.y, position.z + offset.z); + Quad[] quads = attached ? rotatedQuadsAttached[angle] : rotatedQuadsNotAttached[angle]; + for (int i = 0; i < quads.length; ++i) { + Quad quad = quads[i]; + Texture tex = texture; + if (i == 4 && frontTexture != null) { + tex = frontTexture; + quad = frontFaceWithText[angle]; + } else if (i == 5 && backTexture != null) { + tex = backTexture; + quad = backFaceWithText[angle]; + } + quad.addTriangles(primitives, new TextureMaterial(tex), transform); + } + return primitives; + } + + @Override + public JsonValue toJson() { + JsonObject json = new JsonObject(); + json.add("kind", "hangingSign"); + json.add("position", position.toJson()); + if (frontText != null) { + json.add("text", SignEntity.textToJson(frontText)); + } + if (backText != null) { + json.add("backText", SignEntity.textToJson(backText)); + } + json.add("direction", angle); + json.add("attached", attached); + json.add("material", material); + return json; + } + + /** + * Unmarshalls a sign entity from JSON data. + */ + public static Entity fromJson(JsonObject json) { + Vector3 position = new Vector3(); + position.fromJson(json.get("position").object()); + JsonArray[] frontText = null; + if (json.get("text").isArray()) { + frontText = SignEntity.textFromJson(json.get("text")); + } + JsonArray[] backText = null; + if (json.get("backText").isArray()) { + backText = SignEntity.textFromJson(json.get("backText")); + } + int direction = json.get("direction").intValue(0); + boolean attached = json.get("attached").boolValue(false); + String material = json.get("material").stringValue("oak"); + return new HangingSignEntity(position, frontText, backText, direction, attached, material); + } + + public static Texture textureFromMaterial(String material) { + switch (material) { + case "oak": + return Texture.oakHangingSign; + case "spruce": + return Texture.spruceHangingSign; + case "birch": + return Texture.birchHangingSign; + case "jungle": + return Texture.jungleHangingSign; + case "acacia": + return Texture.acaciaHangingSign; + case "dark_oak": + return Texture.darkOakHangingSign; + case "crimson": + return Texture.crimsonHangingSign; + case "warped": + return Texture.warpedHangingSign; + case "mangrove": + return Texture.mangroveHangingSign; + case "bamboo": + return Texture.bambooHangingSign; + case "cherry": + return Texture.cherryHangingSign; + default: + throw new IllegalArgumentException("Unknown hanging sign material: " + material); + } + } +} + diff --git a/chunky/src/java/se/llbit/chunky/entity/SignEntity.java b/chunky/src/java/se/llbit/chunky/entity/SignEntity.java index 2c667801bc..e4915f1920 100644 --- a/chunky/src/java/se/llbit/chunky/entity/SignEntity.java +++ b/chunky/src/java/se/llbit/chunky/entity/SignEntity.java @@ -193,8 +193,8 @@ public SignEntity(Vector3 position, JsonArray[] frontText, JsonArray[] backText, this.frontText = frontText; this.backText = backText; this.angle = direction; - this.frontTexture = frontText != null ? new SignTexture(frontText, signTexture, false) : null; - this.backTexture = backText != null ? new SignTexture(backText, signTexture, true) : null; + this.frontTexture = frontText != null ? new SignTexture(frontText, signTexture, 24, 12, 2 / 64., 18 / 32., 26 / 64., 30 / 32., 4, 1, 10) : null; + this.backTexture = backText != null ? new SignTexture(backText, signTexture, 24, 12, 28 / 64., 18 / 32., 52 / 64., 30 / 32., 4, 1, 10) : null; this.texture = signTexture; this.material = material; } diff --git a/chunky/src/java/se/llbit/chunky/entity/WallHangingSignEntity.java b/chunky/src/java/se/llbit/chunky/entity/WallHangingSignEntity.java new file mode 100644 index 0000000000..104c53425e --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/entity/WallHangingSignEntity.java @@ -0,0 +1,309 @@ +package se.llbit.chunky.entity; + +import se.llbit.chunky.block.WallHangingSign; +import se.llbit.chunky.model.Model; +import se.llbit.chunky.resources.SignTexture; +import se.llbit.chunky.resources.Texture; +import se.llbit.chunky.world.material.TextureMaterial; +import se.llbit.json.JsonArray; +import se.llbit.json.JsonObject; +import se.llbit.json.JsonValue; +import se.llbit.math.Quad; +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 java.util.Collection; +import java.util.LinkedHashSet; + +public class WallHangingSignEntity extends Entity { + private static final Quad[] quads = Model.join( + new Quad[]{ + // sign top + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector4(2 / 64., 16 / 64., 1 - 12 / 32., 1 - 14 / 32.) + ), + // sign bottom + new Quad( + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(16 / 64., 30 / 64., 1 - 12 / 32., 1 - 14 / 32.) + ), + // sign left + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(0 / 64., 2 / 64.0, 1 - 14 / 32., 1 - 24 / 32.) + ), + // sign right + new Quad( + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(16 / 64., 18 / 64.0, 1 - 14 / 32., 1 - 24 / 32.) + ), + // sign front + new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(16 / 64., 2 / 64., 1 - 14 / 32., 1 - 24 / 32.) + ), + // sign back + new Quad( + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(32 / 64., 18 / 64., 1 - 14 / 32., 1 - 24 / 32.) + ) + }, + new Quad[]{ + // bar top + new Quad( + new Vector3(-8 / 16.0, 16 / 16.0, 2 / 16.0), + new Vector3(8 / 16.0, 16 / 16.0, 2 / 16.0), + new Vector3(-8 / 16.0, 16 / 16.0, -2 / 16.0), + new Vector4(4 / 64., 20 / 64., 1 - 0 / 32., 1 - 4 / 32.) + ), + // bar bottom + new Quad( + new Vector3(-8 / 16.0, 14 / 16.0, -2 / 16.0), + new Vector3(8 / 16.0, 14 / 16.0, -2 / 16.0), + new Vector3(-8 / 16.0, 14 / 16.0, 2 / 16.0), + new Vector4(20 / 64., 36 / 64., 1 - 0 / 32., 1 - 4 / 32.) + ), + // bar left + new Quad( + new Vector3(-8 / 16.0, 16 / 16.0, 2 / 16.0), + new Vector3(-8 / 16.0, 16 / 16.0, -2 / 16.0), + new Vector3(-8 / 16.0, 14 / 16.0, 2 / 16.0), + new Vector4(0 / 64., 4 / 64., 1 - 4 / 32., 1 - 6 / 32.) + ), + // bar right + new Quad( + new Vector3(8 / 16.0, 16 / 16.0, -2 / 16.0), + new Vector3(8 / 16.0, 16 / 16.0, 2 / 16.0), + new Vector3(8 / 16.0, 14 / 16.0, -2 / 16.0), + new Vector4(20 / 64., 24 / 64., 1 - 4 / 32., 1 - 6 / 32.) + ), + // bar front + new Quad( + new Vector3(-8 / 16.0, 16 / 16.0, -2 / 16.0), + new Vector3(8 / 16.0, 16 / 16.0, -2 / 16.0), + new Vector3(-8 / 16.0, 14 / 16.0, -2 / 16.0), + new Vector4(4 / 64., 20 / 64., 1 - 4 / 32., 1 - 6 / 32.) + ), + // bar back + new Quad( + new Vector3(8 / 16.0, 16 / 16.0, 2 / 16.0), + new Vector3(-8 / 16.0, 16 / 16.0, 2 / 16.0), + new Vector3(8 / 16.0, 14 / 16.0, 2 / 16.0), + new Vector4(24 / 64., 40 / 64., 1 - 4 / 32., 1 - 6 / 32.) + ) + }, + // chains + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(-45)) + .translate(-0.5, -0.5, -0.5) + .translate(-5 / 16.0, 6 / 16.0, 0 / 16.0) + ), + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(45)) + .translate(-0.5, -0.5, -0.5) + .translate(-5 / 16.0, 6 / 16.0, 0 / 16.0) + ), + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(6 / 64., 9 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(-45)) + .translate(-0.5, -0.5, -0.5) + .translate(5 / 16.0, 6 / 16.0, 0 / 16.0) + ), + Model.transform( + new Quad[]{ + new Quad( + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ), + new Quad( + new Vector3(1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(-1.5 / 16.0, 10 / 16.0, 0 / 16.0), + new Vector3(1.5 / 16.0, 4 / 16.0, 0 / 16.0), + new Vector4(0 / 64., 3 / 64., 1 - 6 / 32., 1 - 12 / 32.) + ) + }, + Transform.NONE + .translate(0.5, 0.5, 0.5) + .rotateY(Math.toRadians(45)) + .translate(-0.5, -0.5, -0.5) + .translate(5 / 16.0, 6 / 16.0, 0 / 16.0) + ) + ); + private static Quad[][] rotatedQuads = new Quad[4][]; + + private static Quad[] frontFaceWithText = new Quad[4]; + private static Quad[] backFaceWithText = new Quad[4]; + + static { + rotatedQuads[0] = Model.translate(quads, 0.5, 0, 0.5); + rotatedQuads[1] = Model.rotateY(rotatedQuads[0]); + rotatedQuads[2] = Model.rotateY(rotatedQuads[1]); + rotatedQuads[3] = Model.rotateY(rotatedQuads[2]); + + frontFaceWithText[0] = new Quad( + new Vector3(-7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(7 / 16.0, 10 / 16.0, -1 / 16.0), + new Vector3(-7 / 16.0, 0 / 16.0, -1 / 16.0), + new Vector4(1, 0, 1, 0) + ); + frontFaceWithText[0] = frontFaceWithText[0].transform(Transform.NONE.translate(0.5, 0, 0.5)); + frontFaceWithText[1] = frontFaceWithText[0].transform(Transform.NONE.rotateY()); + frontFaceWithText[2] = frontFaceWithText[1].transform(Transform.NONE.rotateY()); + frontFaceWithText[3] = frontFaceWithText[2].transform(Transform.NONE.rotateY()); + + backFaceWithText[0] = new Quad( + new Vector3(7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(-7 / 16.0, 10 / 16.0, 1 / 16.0), + new Vector3(7 / 16.0, 0 / 16.0, 1 / 16.0), + new Vector4(1, 0, 1, 0) + ); + backFaceWithText[0] = backFaceWithText[0].transform(Transform.NONE.translate(0.5, 0, 0.5)); + backFaceWithText[1] = backFaceWithText[0].transform(Transform.NONE.rotateY()); + backFaceWithText[2] = backFaceWithText[1].transform(Transform.NONE.rotateY()); + backFaceWithText[3] = backFaceWithText[2].transform(Transform.NONE.rotateY()); + } + + private final JsonArray[] frontText; + private final JsonArray[] backText; + private final WallHangingSign.Facing orientation; + private final SignTexture frontTexture; + private final SignTexture backTexture; + private final Texture texture; + private final String material; + + public WallHangingSignEntity(Vector3 position, CompoundTag entityTag, WallHangingSign.Facing direction, String material) { + this(position, SignEntity.getFrontTextLines(entityTag), SignEntity.getBackTextLines(entityTag), direction, material); + } + + public WallHangingSignEntity(Vector3 position, JsonArray[] frontText, JsonArray[] backText, WallHangingSign.Facing direction, String material) { + super(position); + Texture signTexture = HangingSignEntity.textureFromMaterial(material); + this.frontText = frontText; + this.backText = backText; + this.orientation = direction; + this.frontTexture = frontText != null ? new SignTexture(frontText, signTexture, 14, 10, 2 / 64., 1 - 24 / 32., 16 / 64., 1 - 14 / 32., 4.5, 3, 9) : null; + this.backTexture = backText != null ? new SignTexture(backText, signTexture, 14, 10, 18 / 64., 1 - 24 / 32., 32 / 64., 1 - 14 / 32., 4.5, 3, 9) : null; + this.texture = signTexture; + this.material = material; + } + + @Override + public Collection primitives(Vector3 offset) { + LinkedHashSet set = new LinkedHashSet<>(); + Quad[] quads = rotatedQuads[orientation.ordinal()]; + for (int i = 0; i < quads.length; ++i) { + Quad quad = quads[i]; + Texture tex = texture; + if (i == 4 && frontTexture != null) { + tex = frontTexture; + quad = frontFaceWithText[orientation.ordinal()]; + } else if (i == 5 && backTexture != null) { + tex = backTexture; + quad = backFaceWithText[orientation.ordinal()]; + } + quad.addTriangles(set, new TextureMaterial(tex), + Transform.NONE.translate(position.x + offset.x, position.y + offset.y, position.z + offset.z)); + } + return set; + } + + @Override + public JsonValue toJson() { + JsonObject json = new JsonObject(); + json.add("kind", "wallHangingSign"); + json.add("position", position.toJson()); + if (frontText != null) { + json.add("text", SignEntity.textToJson(frontText)); + } + if (backText != null) { + json.add("backText", SignEntity.textToJson(backText)); + } + json.add("direction", orientation.toString()); + json.add("material", material); + return json; + } + + /** + * Unmarshalls a sign entity from JSON data. + */ + public static Entity fromJson(JsonObject json) { + Vector3 position = new Vector3(); + position.fromJson(json.get("position").object()); + JsonArray[] frontText = null; + if (json.get("text").isArray()) { + frontText = SignEntity.textFromJson(json.get("text")); + } + JsonArray[] backText = null; + if (json.get("backText").isArray()) { + backText = SignEntity.textFromJson(json.get("backText")); + } + WallHangingSign.Facing direction = WallHangingSign.Facing.fromString(json.get("direction").stringValue("north")); + String material = json.get("material").stringValue("oak"); + return new WallHangingSignEntity(position, frontText, backText, direction, material); + } +} diff --git a/chunky/src/java/se/llbit/chunky/entity/WallSignEntity.java b/chunky/src/java/se/llbit/chunky/entity/WallSignEntity.java index cf97faa195..224799a123 100644 --- a/chunky/src/java/se/llbit/chunky/entity/WallSignEntity.java +++ b/chunky/src/java/se/llbit/chunky/entity/WallSignEntity.java @@ -108,7 +108,7 @@ public WallSignEntity(Vector3 position, JsonArray[] text, int direction, String Texture signTexture = SignEntity.textureFromMaterial(material); this.orientation = direction; this.text = text; - this.frontTexture = text != null ? new SignTexture(text, signTexture, false) : null; + this.frontTexture = text != null ? new SignTexture(text, signTexture, 24, 12, 2 / 64., 18 / 32., 26 / 64., 30 / 32., 4, 1, 10) : null; this.texture = signTexture; this.material = material; } diff --git a/chunky/src/java/se/llbit/chunky/model/Model.java b/chunky/src/java/se/llbit/chunky/model/Model.java index 29d4b2653e..32b22c0e7d 100644 --- a/chunky/src/java/se/llbit/chunky/model/Model.java +++ b/chunky/src/java/se/llbit/chunky/model/Model.java @@ -226,6 +226,18 @@ public static Quad[] scale(Quad[] src, double scale) { return out; } + /** + * @param src source quads + * @return Quads rotated about the Z axis by some angle + */ + public static Quad[] transform(Quad[] src, Transform transform) { + Quad[] rot = new Quad[src.length]; + for (int i = 0; i < src.length; ++i) { + rot[i] = src[i].transform(transform); + } + return rot; + } + /** * @param models source quads * @return All quads merged into a single array diff --git a/chunky/src/java/se/llbit/chunky/resources/SignTexture.java b/chunky/src/java/se/llbit/chunky/resources/SignTexture.java index 373792b5b5..f56a6bd3b4 100644 --- a/chunky/src/java/se/llbit/chunky/resources/SignTexture.java +++ b/chunky/src/java/se/llbit/chunky/resources/SignTexture.java @@ -25,77 +25,65 @@ import se.llbit.math.Vector4; public class SignTexture extends Texture { - - private static final double hh, v0; - private final double ww, u0; - - static { - // Set up texture coordinates. - v0 = 18 / 32.; - double v1 = 30 / 32.; - hh = v1 - v0; - } - + private final double hh, ww, u0, v0; private final Texture signTexture; private final PalettizedBitmapImage textColor; private final BinaryBitmapImage textMask; static private boolean hasVisibleCharacter(JsonArray line) { - for(JsonValue textItem : line) { - if(!textItem.object().get("text").stringValue("").trim().isEmpty()) { + for (JsonValue textItem : line) { + if (!textItem.object().get("text").stringValue("").trim().isEmpty()) { return true; } } return false; } - public SignTexture(JsonArray[] text, Texture signTexture, boolean isBackSide) { + public SignTexture(JsonArray[] text, Texture signTexture, int signWidth, int signHeight, double x0, double y0, double x1, double y1, double fontSize, int ymargin, int lineHeight) { this.signTexture = signTexture; - int ymargin = 1; - int lineHeight = 10; - int width = 96; - int height = 48; + int width = (int) Math.ceil(signWidth * fontSize); + int height = (int) Math.ceil(signHeight * fontSize); int ystart = ymargin; boolean allEmpty = true; - for(JsonArray line : text) { - if(hasVisibleCharacter(line)) { + for (JsonArray line : text) { + if (hasVisibleCharacter(line)) { allEmpty = false; break; } } - if(allEmpty) { + if (allEmpty) { textColor = null; textMask = null; } else { textColor = new PalettizedBitmapImage(width, height); textMask = new BinaryBitmapImage(width, height); - for(JsonArray line : text) { - if(line.isEmpty()) { + for (JsonArray line : text) { + if (line.isEmpty()) { ystart += lineHeight; continue; } int lineWidth = 0; - for(JsonValue textItem : line) { + for (JsonValue textItem : line) { String textLine = textItem.object().get("text").stringValue(""); - for(int c : textLine.codePoints().toArray()) { + for (int c : textLine.codePoints().toArray()) { Glyph glyph = Texture.fonts.getGlyph(c); lineWidth += glyph != null ? glyph.width : 0; } } int xstart = (int) Math.ceil((width - lineWidth) / 2.0); - for(JsonValue textItem : line) { + for (JsonValue textItem : line) { String textLine = textItem.object().get("text").stringValue(""); Color color = Color.get(textItem.object().get("color").intValue(0)); - for(int c : textLine.codePoints().toArray()) { + for (int c : textLine.codePoints().toArray()) { Glyph glyph = Texture.fonts.getGlyph(c); - if(glyph != null) { + if (glyph != null) { int y = ystart - glyph.ascent + lineHeight; - for(int py = 0; py < glyph.height; ++py) { + for (int py = 0; py < glyph.height; ++py) { int x = xstart; - for(int px = glyph.xmin; px <= glyph.xmax; ++px) { - if((glyph.lines[py] & (1 << px)) != 0) { + for (int px = glyph.xmin; px <= glyph.xmax; ++px) { + if ((glyph.lines[py] & (1 << px)) != 0) { textColor.setPixel(x, y, color.id); textMask.setPixel(x, y, true); } @@ -111,22 +99,17 @@ public SignTexture(JsonArray[] text, Texture signTexture, boolean isBackSide) { } } - if (isBackSide) { - u0 = 28 / 64.; - double u1 = 52 / 64.; - ww = u1 - u0; - } else { - u0 = 2 / 64.; - double u1 = 26 / 64.; - ww = u1 - u0; - } + ww = x1 - x0; + hh = y1 - y0; + u0 = x0; + v0 = y0; } @Override public float[] getColor(double u, double v) { - int x = (int)(u * 96 - Ray.EPSILON); - int y = (int) ((1 - v) * 48 - Ray.EPSILON); - if(textMask != null && textMask.getPixel(x, y)) { + int x = (int) (u * textColor.width - Ray.EPSILON); + int y = (int) ((1 - v) * textColor.height - Ray.EPSILON); + if (textMask != null && textMask.getPixel(x, y)) { Color characterColor = Color.get(textColor.getPixel(x, y)); return characterColor.linearColor; } else { diff --git a/chunky/src/java/se/llbit/chunky/resources/Texture.java b/chunky/src/java/se/llbit/chunky/resources/Texture.java index bcd0ce5f03..077210c71f 100644 --- a/chunky/src/java/se/llbit/chunky/resources/Texture.java +++ b/chunky/src/java/se/llbit/chunky/resources/Texture.java @@ -1317,6 +1317,28 @@ public class Texture { public static final Texture calibratedSculkSensorTop = new Texture(); @TexturePath("assets/minecraft/textures/block/calibrated_sculk_sensor_input_side") public static final Texture calibratedSculkSensorInputSide = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/oak") + public static final Texture oakHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/spruce") + public static final Texture spruceHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/birch") + public static final Texture birchHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/jungle") + public static final Texture jungleHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/acacia") + public static final Texture acaciaHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/dark_oak") + public static final Texture darkOakHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/crimson") + public static final Texture crimsonHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/warped") + public static final Texture warpedHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/mangrove") + public static final Texture mangroveHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/bamboo") + public static final Texture bambooHangingSign = new Texture(); + @TexturePath("assets/minecraft/textures/entity/signs/hanging/cherry") + public static final Texture cherryHangingSign = new Texture(); /** Banner base texture. */ public static final Texture bannerBase = new Texture(); diff --git a/chunky/src/java/se/llbit/math/Quad.java b/chunky/src/java/se/llbit/math/Quad.java index 97f41a4b3c..336ed7a489 100644 --- a/chunky/src/java/se/llbit/math/Quad.java +++ b/chunky/src/java/se/llbit/math/Quad.java @@ -200,8 +200,7 @@ public Quad getScaled(double scale) { return new Quad(this, transform); } - public void addTriangles(Collection primitives, Material material, - Transform transform) { + public void addTriangles(Collection primitives, Material material, Transform transform) { Vector3 c0 = new Vector3(o); Vector3 c1 = new Vector3(); Vector3 c2 = new Vector3();