--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/map_templates_tool/main.cpp Fri Nov 29 22:29:58 2024 +0100
@@ -0,0 +1,19 @@
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ const QUrl url(u"qrc:/map_templates_tool/main.qml"_qs);
+ QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
+ &app, [url](QObject *obj, const QUrl &objUrl) {
+ if (!obj && url == objUrl)
+ QCoreApplication::exit(-1);
+ }, Qt::QueuedConnection);
+ engine.load(url);
+
+ return app.exec();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/map_templates_tool/main.qml Fri Nov 29 22:29:58 2024 +0100
@@ -0,0 +1,248 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Shapes
+
+Window {
+ id: control
+
+ width: 1024
+ height: 768
+ visible: true
+ title: qsTr("Map Templates")
+
+ property bool hasError: false
+
+ Page {
+ id: page
+ anchors.fill: parent
+
+ Rectangle {
+ id: mapContainer
+
+ property int spaceForCode: Math.max(200, parent.height / 2)
+ property int mapWidth: 2048
+ property int mapHeight: 1024
+ property real aspectRatio: mapWidth / mapHeight
+ property bool fitWidth: aspectRatio > (parent.width / (parent.height - spaceForCode))
+
+ implicitWidth: fitWidth ? parent.width : (parent.height - spaceForCode) * aspectRatio
+ implicitHeight: fitWidth ? parent.width / aspectRatio : (parent.height - spaceForCode)
+
+ x: (parent.width - width) / 2
+
+ border.width: 2
+ border.color: hasError ? "red" : "black"
+ }
+
+ Shape {
+ id: shape
+
+ anchors.fill: mapContainer
+ }
+
+ Rectangle {
+ anchors.fill: codeInput
+ color: "gray"
+ }
+
+ TextEdit {
+ id: codeInput
+
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: parent.height - mapContainer.height
+
+ text: " -
+ width: 3072
+ height: 1424
+ can_flip: false
+ can_invert: false
+ can_mirror: true
+ is_negative: false
+ put_girders: true
+ max_hedgehogs: 18
+ outline_points:
+ -
+ - {x: 748, y: 1424, w: 1, h: 1}
+ - {x: 636, y: 1252, w: 208, h: 72}
+ - {x: 898, y: 1110, w: 308, h: 60}
+ - {x: 1128, y: 1252, w: 434, h: 40}
+ - {x: 1574, y: 1112, w: 332, h: 40}
+ - {x: 1802, y: 1238, w: 226, h: 36}
+ - {x: 1930, y: 1424, w: 1, h: 1}
+ -
+ - {x: 2060, y: 898, w: 111, h: 111}
+ - {x: 1670, y: 876, w: 34, h: 102}
+ - {x: 1082, y: 814, w: 284, h: 132}
+ - {x: 630, y: 728, w: 126, h: 168}
+ - {x: 810, y: 574, w: 114, h: 100}
+ - {x: 1190, y: 572, w: 352, h: 120}
+ - {x: 1674, y: 528, w: 60, h: 240}
+ - {x: 1834, y: 622, w: 254, h: 116}
+ fill_points:
+ - {x: 1423, y: 0}
+"
+
+ onTextChanged: {
+ const template = parseInput()
+
+ if (template) {
+ mapContainer.mapWidth = Number(template.width)
+ mapContainer.mapHeight = Number(template.height)
+
+ shape.data = renderTemplate(template)
+ }
+ }
+ }
+ }
+
+ function parseInput() {
+ let code = codeInput.text.split('\n')
+
+ if(code[0] !== " -") {
+ hasError = true
+ return
+ }
+
+ code = code.slice(1)
+ code.push("")
+
+ let parsed = ({})
+ let polygonAccumulator = []
+ let pointAccumulator = []
+ let key = ""
+ code.forEach(line => {
+ let newKey
+ if (line === " outline_points:") {
+ newKey = "outline_points"
+ }
+
+ if (line === " holes:") {
+ newKey = "holes"
+ }
+
+ if (line === " fill_points:") {
+ newKey = "fill_points"
+ }
+
+ if (line === "") {
+ newKey = "_"
+ }
+
+ if (key === "fill_points" && line.startsWith(" - {")) {
+ // ignore
+ return
+ }
+
+ if (newKey) {
+ if (key.length > 0) {
+ polygonAccumulator.push(pointAccumulator)
+ parsed[key] = polygonAccumulator
+ }
+
+ key = newKey
+ polygonAccumulator = []
+ pointAccumulator = []
+
+ return
+ }
+
+ if (line === " -") {
+ if (pointAccumulator.length > 0) {
+ polygonAccumulator.push(pointAccumulator)
+ pointAccumulator = []
+ }
+
+ return
+ }
+
+ const matchValue = line.match(/^\s{4}(\w+):\s(.+)$/);
+
+ if (matchValue) {
+ parsed[matchValue[1]] = matchValue[2]
+ return
+ }
+
+ const matchPoint = line.match(/^\s{8}-\s\{([^}]+)\}$/);
+
+ if (matchPoint) {
+ const point = matchPoint[1].split(", ").reduce((obj, pair) => {
+ const [key, value] = pair.split(": ");
+ obj[key] = isNaN(value) ? value : parseInt(value);
+ return obj;
+ }, {})
+ pointAccumulator.push(point)
+ return
+ }
+
+ console.log("Unrecognized: " + JSON.stringify(line))
+ hasError = true
+ throw ""
+ })
+
+ hasError = false
+
+ return parsed
+ }
+
+ Component {
+ id: shapePathComponent
+
+ ShapePath {
+ fillColor: "transparent"
+ scale: Qt.size(mapContainer.width / mapContainer.mapWidth, mapContainer.height / mapContainer.mapHeight)
+ strokeWidth: 3
+ }
+ }
+
+ Component {
+ id: pathLineComponent
+ PathLine {
+ }
+ }
+
+ function polygons2shapes(polygons, lineColor, rectColor) {
+ if (!Array.isArray(polygons)) {
+ return []
+ }
+
+ let rectangles = []
+
+ polygons.forEach(polygon => polygon.forEach(r => {
+ let shapePath = shapePathComponent.createObject(shape)
+ shapePath.strokeWidth = 1
+ shapePath.strokeColor = rectColor
+
+ shapePath.startX = r.x
+ shapePath.startY = r.y
+ shapePath.pathElements = [
+ pathLineComponent.createObject(shapePath, {x: r.x, y: r.y + r.h}),
+ pathLineComponent.createObject(shapePath, {x: r.x + r.w, y: r.y + r.h}),
+ pathLineComponent.createObject(shapePath, {x: r.x + r.w, y: r.y}),
+ pathLineComponent.createObject(shapePath, {x: r.x, y: r.y})
+ ]
+
+ rectangles.push(shapePath)
+ }))
+ let polygonShapes = polygons.map(polygon => {
+ let points = polygon.map(r => ({x: r.x + r.w / 2, y: r.y + r.h / 2}))
+ let shapePath = shapePathComponent.createObject(shape)
+ let start = points[points.length - 1]
+
+ shapePath.strokeColor = lineColor
+ shapePath.startX = start.x
+ shapePath.startY = start.y
+ shapePath.pathElements = points.map(p => pathLineComponent.createObject(shapePath, p))
+
+ return shapePath
+ })
+
+ return rectangles.concat(polygonShapes)
+ }
+
+ function renderTemplate(template) {
+ return polygons2shapes(template.outline_points, "red", "black").concat(polygons2shapes(template.holes, "gray", "gray"))
+ }
+}