diff --git a/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorComplex.java b/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorComplex.java new file mode 100644 index 0000000000..86946a0793 --- /dev/null +++ b/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorComplex.java @@ -0,0 +1,114 @@ +//package fi.dy.masa.litematica.gui; +// +//import fi.dy.masa.litematica.data.DataManager; +//import fi.dy.masa.litematica.gui.widgets.WidgetListSelectionSubRegions; +//import fi.dy.masa.litematica.selection.AreaSelection; +//import fi.dy.masa.litematica.selection.Box; +//import fi.dy.masa.litematica.util.PositionUtils.Corner; +//import fi.dy.masa.malilib.MaLiLib; +//import fi.dy.masa.malilib.util.StringUtils; +//import net.minecraft.util.math.BlockPos; +// +//public class GuiAreaSelectionEditorComplex extends GuiAreaSelectionEditorNormal +//{ +// protected static String defaultBoxName = "DefaultComplexBox"; +// +// public GuiAreaSelectionEditorComplex(AreaSelection selection) +// { +// super(selection); +// +// if (DataManager.getSchematicProjectsManager().hasProjectOpen()) +// { +// this.title = StringUtils.translate("litematica.gui.title.area_editor_normal_schematic_projects"); +// } +// else +// { +// this.title = StringUtils.translate("litematica.gui.title.area_editor_complex"); +// } +// } +// +// @Override +// public void initGui() +// { +// super.initGui(); +// +// if (this.selection != null) +// { +// this.createSelectionEditFields(); +// this.addSubRegionFields(this.xOrigin, this.yNext); +// this.updateCheckBoxes(); +// } +// else +// { +// this.addLabel(20, 30, 120, 12, 0xFFFFAA00, StringUtils.translate("litematica.error.area_editor.no_selection")); +// } +// } +// +// +// @Override +// protected int addSubRegionFields(int x, int y) +// { +// x = 12; +// y += 20; +// +// int width = 68; +// +// int nextY = 0; +// this.createCoordinateInputs(x, y, width, Corner.CORNER_1); +// x += width + 42; +// nextY = this.createCoordinateInputs(x, y, width, Corner.CORNER_2); +// this.createButton(x + 10, nextY, -1, ButtonListener.Type.ANALYZE_AREA); +// x += width + 42; +// +// x = this.createButton(22, nextY, -1, ButtonListener.Type.CREATE_SCHEMATIC) + 26; +// +// x = this.createButton(22, nextY + 22, -1, ButtonListener.Type.TOGGLE_GENERATE_CIRCLE); +// nextY += 22; +// this.createButtonOnOff(22, nextY + 22, -1, circleMode, ButtonListener.Type.TOGGLE_CIRCLE_ENABLED); +// +// this.addRenderingDisabledWarning(250, 48); +// +// return y; +// } +// +// @Override +// protected Box getBox() +// { +// // 设置默认位置和名称 +// Box selBox = this.selection.getSelectedSubRegionBox(); +// this.selection.removeSelectedSubRegionBox(); +// if (selBox != null) { +// selBox.setName(defaultBoxName); +// } else if (this.mc.player != null) { +// BlockPos pos = fi.dy.masa.malilib.util.PositionUtils.getEntityBlockPos(this.mc.player); +// selBox = new Box(pos, pos, defaultBoxName); +// } else { +// MaLiLib.logger.error("can not getSelectedSubRegionBox"); +// selBox = new Box(); +// selBox.setName(defaultBoxName); +// } +// this.selection.addSubRegionBox(selBox,false); +// this.selection.setSelectedSubRegionBox(defaultBoxName); +// return selBox; +// } +// +// @Override +// protected WidgetListSelectionSubRegions getListWidget() +// { +// return this.createListWidget(8, 116); +// } +// +// @Override +// protected void reCreateListWidget() +// { +//// super.createListWidget(8,116); +// // NO-OP +// } +// +// @Override +// protected WidgetListSelectionSubRegions createListWidget(int listX, int listY) +// { +// return new WidgetListSelectionSubRegions(listX, listY, +// this.getBrowserWidth(), this.getBrowserHeight(), this.selection, this); +// } +//} diff --git a/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorNormal.java b/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorNormal.java index c1bf24444e..1672adbe9f 100644 --- a/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorNormal.java +++ b/src/main/java/fi/dy/masa/litematica/gui/GuiAreaSelectionEditorNormal.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; + +import fi.dy.masa.malilib.MaLiLib; import net.minecraft.util.math.BlockPos; import fi.dy.masa.litematica.config.Configs; import fi.dy.masa.litematica.config.Hotkeys; @@ -35,6 +37,7 @@ import fi.dy.masa.malilib.interfaces.IStringConsumerFeedback; import fi.dy.masa.malilib.util.PositionUtils.CoordinateType; import fi.dy.masa.malilib.util.StringUtils; +import net.minecraft.util.math.Vec3i; public class GuiAreaSelectionEditorNormal extends GuiListBase implements ISelectionListener @@ -48,7 +51,11 @@ public class GuiAreaSelectionEditorNormal extends GuiListBase posList = new ArrayList<>(); + posList.add(new double[]{startX, startZ}); + // 1/4圆 + while (startZ > c1z) { + double[][] nextPos = {{startX + 1, startZ}, {startX + 1, startZ - 1}, {startX, startZ - 1}}; + double minDisSquare = Double.MAX_VALUE; + for (int i = 0; i < 3; i++) { + double disSquare = Math.abs(calculateDistance2(nextPos[i][0], nextPos[i][1], c1x, c1z) - dis2); + if (disSquare < minDisSquare) { + minDisSquare = disSquare; + startX = nextPos[i][0]; + startZ = nextPos[i][1]; + } + } + posList.add(new double[]{startX, startZ}); + } + //关于x轴对称 1/2圆 + int len = posList.size(); + for (int i = 0; i < len; ++i) { + double[] pos = posList.get(i); + double syncPosX = pos[0]; + double syncPosZ = 2 * c1z - pos[1]; + posList.add(new double[]{syncPosX, syncPosZ}); + } + //关于z轴对称 整个圆 + Set seenPosLongSet = new HashSet<>(); + List boxList = new ArrayList<>(); + for (double[] pos : posList) { + double curX = (int) pos[0]; + double curZ = (int) pos[1]; + double syncPosX = (int) (2 * c1x - pos[0]); + double syncPosZ = (int) (pos[1]); + + int c1Y = (int) c1y; + int c2Y = (int) c2y; + + BlockPos pos1 = new BlockPos((int) curX, c1Y, (int) curZ); + BlockPos pos2 = new BlockPos((int) syncPosX, c2Y, (int) syncPosZ); + // 添加到多选区域列表中 + if (onCircle) {//仅创建圆上节点,考虑y坐标 + BlockPos pos11 = new BlockPos(pos1.getX(), c1Y, pos1.getZ()); + BlockPos pos12 = new BlockPos(pos1.getX(), c2Y, pos1.getZ()); + boxList.add(new Box(pos11, pos12, makeKey(pos1.getX(), pos1.getZ()))); +// addOneBox(pos11, pos12, makeKey(pos1.getX(), pos1.getZ())); + + BlockPos pos21 = new BlockPos(pos2.getX(), c1Y, pos2.getZ()); + BlockPos pos22 = new BlockPos(pos2.getX(), c2Y, pos2.getZ()); + boxList.add(new Box(pos21, pos22, makeKey(pos2.getX(), pos2.getZ()))); +// addOneBox(pos21, pos22, makeKey(pos2.getX(), pos2.getZ())); + if (expand) { + seenPosLongSet.add(pos1.asLong()); + seenPosLongSet.add(pos2.asLong()); + expandPos(boxList, pos1, c1Y, c2Y,seenPosLongSet); + expandPos(boxList,pos2, c1Y, c2Y,seenPosLongSet); + } + } else { + String name = "z_" + (int) curZ;// 按照z轴坐标去重 + boxList.add(new Box(pos1, pos2, name)); +// addOneBox(pos1, pos2, name); + } +// MaLiLib.logger.error(String.format("(%s, %s, %s)|(%s, %s, %s)", curX, cor1.getY(), curZ, syncPosX, cor1.getY(), syncPosZ)); + } + seenPosLongSet.clear(); + MaLiLib.logger.error(String.format("createNewSubRegionBoxCircle boxList size: %s", boxList.size())); + Map boxMap = mergeBoxList(boxList,true); + Map boxMap2 = mergeBoxList(new ArrayList<>(boxMap.values()),false); + MaLiLib.logger.error(String.format("createNewSubRegionBoxCircle merged size: %s", boxMap.size())); + if (!onCircle) {// 按照z轴两个点去重, + Map newBoxMap = new HashMap<>(); + for (Box box : boxMap2.values()) { + newBoxMap.put(makeKey(box.getPos1().getZ(), box.getPos2().getZ()), box); + } + this.subRegionBoxes.putAll(newBoxMap); + } else { // 圆圈不存在两个box相同不需要去重 + this.subRegionBoxes.putAll(boxMap2); + } + MaLiLib.logger.error(String.format("createNewSubRegionBoxCircle subRegionBoxes size: %s", this.subRegionBoxes.size())); + } catch (Exception e) { + MaLiLib.logger.error("createNewSubRegionBoxCircle ", e); + } + return "createNewSubRegionBoxCircle"; + } + + protected boolean ifAnyNull(Box box) { + return box.getPos1() == null || box.getPos2() == null; + } + + protected boolean isBoxAdjX(Box box1, Box box2) { + if (Math.abs(box2.getPos1().getX() - box1.getPos2().getX()) > 1 && + Math.abs(box1.getPos1().getX() - box2.getPos2().getX()) > 1 + ) { + return false; + } + if (box1.getPos1().getZ() != box2.getPos1().getZ()) { + return false; + } + if (box1.getPos2().getZ() != box2.getPos2().getZ()) { + return false; + } + return true; + } + + protected boolean isBoxAdjZ(Box box1, Box box2) { + if (Math.abs(box2.getPos1().getZ() - box1.getPos2().getZ()) > 1 && + Math.abs(box1.getPos1().getZ() - box2.getPos2().getZ()) > 1 + ) { + return false; + } + if (box1.getPos1().getX() != box2.getPos1().getX()) { + return false; + } + if (box1.getPos2().getX() != box2.getPos2().getX()) { + return false; + } + return true; + } + public int multipleMax(int ... args) { + int ret = Integer.MIN_VALUE; + for (int x: args) { + ret = Math.max(ret, x); + } + return ret; + } + + public int multipleMin(int ... args) { + int ret = Integer.MAX_VALUE; + for (int x: args) { + ret = Math.min(ret, x); + } + return ret; + } + public List getAllCoordinatesByType(CoordinateType type, Box ...boxes) { + List values = new ArrayList<>(); + for (Box box: boxes) { + values.add(box.getCoordinate(Corner.CORNER_1,type)); + values.add(box.getCoordinate(Corner.CORNER_2,type)); + } + return values; + } + + protected Box mergeTwoBoxes(Box box1, Box box2) { + Box box = new Box(); + /* + * |------|12 |-------|22 |------|12 + * | | | | | | + * 11|------| 21|-------| 11|------| + */ + int p1x = multipleMin(getAllCoordinatesByType(CoordinateType.X, box1, box2).stream().mapToInt(o -> o).toArray()); + int p1y = multipleMin(getAllCoordinatesByType(CoordinateType.Y, box1, box2).stream().mapToInt(o -> o).toArray()); + int p1z = multipleMin(getAllCoordinatesByType(CoordinateType.Z, box1, box2).stream().mapToInt(o -> o).toArray()); + + int p2x = multipleMax(getAllCoordinatesByType(CoordinateType.X, box1, box2).stream().mapToInt(o -> o).toArray()); + int p2y = multipleMax(getAllCoordinatesByType(CoordinateType.Y, box1, box2).stream().mapToInt(o -> o).toArray()); + int p2z = multipleMax(getAllCoordinatesByType(CoordinateType.Z, box1, box2).stream().mapToInt(o -> o).toArray()); + + box.setPos1(new BlockPos(p1x, p1y, p1z)); + box.setPos2(new BlockPos(p2x, p2y, p2z)); + return box; + } + + protected Map mergeBoxList(List boxList,boolean xFirst) { + Map boxMap = new HashMap<>(); + if (boxList == null || boxList.isEmpty()) { + return boxMap; + } + Comparator xFirstFunction = (o1, o2) -> { + if (ifAnyNull(o1) || ifAnyNull(o2)) { + return 0; + } + if (o1.getPos1().getZ() != o2.getPos1().getZ()) { + return o1.getPos1().getZ() - o2.getPos1().getZ(); + } + return o1.getPos1().getX() - o2.getPos1().getX(); + }; + Comparator zFirstFunction = (o1, o2) -> { + if (ifAnyNull(o1) || ifAnyNull(o2)) { + return 0; + } + if (o1.getPos1().getX() != o2.getPos1().getX()) { + return o1.getPos1().getX() - o2.getPos1().getX(); + } + return o1.getPos1().getZ() - o2.getPos1().getZ(); + }; + Queue priorityQueue = new PriorityQueue<>(xFirst ? xFirstFunction : zFirstFunction); + // 坐标归一 + List newBoxList = boxList.stream().map(box -> { + BlockPos pos1 = box.getPos1(); + BlockPos pos2 = box.getPos1(); + if (pos1.getX() > pos2.getX() || pos1.getZ() > pos2.getZ()) { + return new Box(pos2, pos1, box.getName()); + } + return box; + }).toList(); + priorityQueue.addAll(newBoxList);// 堆排序 + String keyFmt = "merge_%s"; + int ind = 1; + while (priorityQueue.size() >= 2) { + Box curBox = priorityQueue.remove();// one box + Box nextBox = priorityQueue.remove();// next box + // x axis, first x equal + if (isBoxAdjX(curBox, nextBox)) { + Box merged = mergeTwoBoxes(curBox, nextBox); + priorityQueue.add(merged); + } else if (isBoxAdjZ(curBox, nextBox)) { // z axis, second z equal + Box merged = mergeTwoBoxes(curBox, nextBox); + priorityQueue.add(merged); + } else { + priorityQueue.add(nextBox); + boxMap.put(keyFmt.formatted(ind), curBox); + ++ind; + } + } + while (!priorityQueue.isEmpty()) { + Box box = priorityQueue.remove(); + boxMap.put(keyFmt.formatted(ind), box); + ++ind; + } + + return boxMap; + + } + + protected int[][] DIRECTIONS = new int[][]{{0,1},{1,0},{0,-1},{-1,0}}; + + protected void expandPos(List boxList, BlockPos pos, int c1Y, int c2Y,Set seenPosLongSet) { + for (int[] dir : DIRECTIONS) { + BlockPos nextPos = new BlockPos(pos.getX() + dir[0], pos.getY(), pos.getZ() + dir[1]); + if (seenPosLongSet.contains(nextPos.asLong())) { + continue; + } + seenPosLongSet.add(nextPos.asLong()); + BlockPos pos1 = new BlockPos(nextPos.getX(),c1Y, nextPos.getZ()); + BlockPos pos2 = new BlockPos(nextPos.getX(),c2Y, nextPos.getZ()); + boxList.add(new Box(pos1, pos2,makeKey(nextPos.getX(), nextPos.getZ()))); + //addOneBox(pos1, pos2,makeKey(nextPos.getX(), nextPos.getZ())); + } + } + protected void addOneBox(BlockPos pos1, BlockPos pos2,String name) { + Box box = new Box(); + box.setSelectedCorner(Corner.CORNER_1); + box.setName(name); + this.setSubRegionCornerPos(box, Corner.CORNER_1, pos1); + this.setSubRegionCornerPos(box, Corner.CORNER_2, pos2); + this.subRegionBoxes.put(name, box); + } + + + protected String makeKey(int x, int y, int z) { + return String.format("%s_%s_%s", x, y, z); + } + + protected String makeKey(int x,int z) { + return String.format("%s_%s", x, z); + } + + public void removeAllSubRegion() { + Box currentSelected = this.getSelectedSubRegionBox(); + this.subRegionBoxes.clear(); + this.addSubRegionBox(currentSelected, false); + } + + public static void main(String[] args) { + BlockPos pos1 = new BlockPos(0,0,0); + BlockPos pos2 = new BlockPos(0,0,500); + new AreaSelection().createNewSubRegionBoxCircle(pos1, pos2, false, true); + } + public void clearCurrentSelectedCorner() { this.setCurrentSelectedCorner(Corner.NONE); diff --git a/src/main/java/fi/dy/masa/litematica/util/InventoryUtils.java b/src/main/java/fi/dy/masa/litematica/util/InventoryUtils.java index 451ff65f7b..900658d360 100644 --- a/src/main/java/fi/dy/masa/litematica/util/InventoryUtils.java +++ b/src/main/java/fi/dy/masa/litematica/util/InventoryUtils.java @@ -4,6 +4,7 @@ import java.util.List; import javax.annotation.Nullable; +import fi.dy.masa.malilib.MaLiLib; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; @@ -113,11 +114,12 @@ public static void setPickedItemToHand(int sourceSlot, ItemStack stack, Minecraf public static void schematicWorldPickBlock(ItemStack stack, BlockPos pos, World schematicWorld, MinecraftClient mc) { + MaLiLib.logger.error("schematicWorldPickBlock stack.isEmpty() {} ", stack.isEmpty()); if (stack.isEmpty() == false) { PlayerInventory inv = mc.player.getInventory(); stack = stack.copy(); - + MaLiLib.logger.error("schematicWorldPickBlock EntityUtils.isCreativeMode(mc.player) {} ", EntityUtils.isCreativeMode(mc.player)); if (EntityUtils.isCreativeMode(mc.player)) { BlockEntity te = schematicWorld.getBlockEntity(pos); @@ -138,7 +140,9 @@ public static void schematicWorldPickBlock(ItemStack stack, BlockPos pos, } else { + MaLiLib.logger.error("schematicWorldPickBlock getSlotWithStack invoking {} {}", stack.toString(), pos.toString()); int slot = inv.getSlotWithStack(stack); + MaLiLib.logger.error("schematicWorldPickBlock getSlotWithStack ret slot {} selected slot {}", slot, inv.selectedSlot); boolean shouldPick = inv.selectedSlot != slot; if (shouldPick && slot != -1) @@ -147,8 +151,9 @@ public static void schematicWorldPickBlock(ItemStack stack, BlockPos pos, } else if (slot == -1 && Configs.Generic.PICK_BLOCK_SHULKERS.getBooleanValue()) { - slot = findSlotWithBoxWithItem(mc.player.playerScreenHandler, stack, false); + slot = findSlotWithBoxWithItem(mc.player.playerScreenHandler, stack, false); + MaLiLib.logger.error("schematicWorldPickBlock findSlotWithBoxWithItem ret slot {}", slot); if (slot != -1) { ItemStack boxStack = mc.player.playerScreenHandler.slots.get(slot).getStack(); diff --git a/src/main/resources/assets/litematica/lang/en_us.json b/src/main/resources/assets/litematica/lang/en_us.json index 30f131ac57..7ad9896dd6 100644 --- a/src/main/resources/assets/litematica/lang/en_us.json +++ b/src/main/resources/assets/litematica/lang/en_us.json @@ -533,6 +533,11 @@ "litematica.gui.button.area_editor.create_schematic": "Save Schematic", "litematica.gui.button.area_editor.create_sub_region": "New sub-region", "litematica.gui.button.area_editor.origin_enabled": "Manual Origin: %s", + "litematica.gui.button.area_editor.generate_circle": "Generate Circle", + "litematica.gui.button.area_editor.generate_round": "Generate Round", + "litematica.gui.button.area_editor.remove_all_region": "Remove All SubRegion", + "litematica.gui.button.area_editor.special_enabled": "Special Mode: %s", + "litematica.gui.button.area_editor.expand_circle_enabled": "Expand Circle: %s", "litematica.gui.button.area_editor.set_box_name": "Set", "litematica.gui.button.area_editor.set_selection_name": "Set", @@ -673,6 +678,7 @@ "litematica.gui.label.area_selection_box_count": "Boxes: %d", "litematica.gui.label.area_selection_origin": "Origin: %s", "litematica.gui.label.area_selection.mode.normal": "Normal", + "litematica.gui.label.area_selection.mode.complex": "Complex", "litematica.gui.label.area_selection.mode.simple": "Simple", "litematica.gui.label.block_info.state_client": "Client World Block State", diff --git a/src/main/resources/assets/litematica/lang/zh_cn.json b/src/main/resources/assets/litematica/lang/zh_cn.json index feaa065334..195399190a 100644 --- a/src/main/resources/assets/litematica/lang/zh_cn.json +++ b/src/main/resources/assets/litematica/lang/zh_cn.json @@ -533,6 +533,11 @@ "litematica.gui.button.area_editor.create_schematic": "保存原理图", "litematica.gui.button.area_editor.create_sub_region": "新建子区域", "litematica.gui.button.area_editor.origin_enabled": "手动来源:%s", + "litematica.gui.button.area_editor.generate_circle": "生成圆圈", + "litematica.gui.button.area_editor.generate_round": "生成圆面", + "litematica.gui.button.area_editor.remove_all_region": "删除所有子区域", + "litematica.gui.button.area_editor.special_enabled": "特殊模式:%s", + "litematica.gui.button.area_editor.expand_circle_enabled": "圆圈扩展:%s", "litematica.gui.button.area_editor.set_box_name": "set", "litematica.gui.button.area_editor.set_selection_name": "set",