Last active
April 12, 2017 16:46
-
-
Save mitchcurtis/11bf1a079b7ad84cd55801e11b3bb359 to your computer and use it in GitHub Desktop.
Isometric map rendering in QML (in the hackiest possible way)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import QtQuick 2.9 | |
import QtQuick.Controls 2.2 | |
// A small demo app for "correct" isometric rendering according to the following explanation: | |
// https://github.com/clintbellanger/flare/issues/461 | |
// The code is hacky. | |
ApplicationWindow { | |
id: window | |
width: 1400 | |
height: 480 | |
visible: true | |
property int tilesWide: 0 | |
property int tilesHigh: 0 | |
property int tileWidth: 32 | |
property int tileHeight: 32 | |
property int startX: (tilesWide * tileWidth) / 2 | |
function indexToMapPos(index) { | |
return Qt.point( | |
(index % tilesWide), | |
Math.floor(index / tilesWide)); | |
} | |
// http://clintbellanger.net/articles/isometric_math/ | |
function mapToScreen(mapPos) { | |
return Qt.point( | |
(mapPos.x - mapPos.y) * (tileWidth / 2), | |
(mapPos.x + mapPos.y) * (tileHeight / 2)); | |
} | |
function addTiles(model) { | |
var tileCount = tilesWide * tilesHigh; | |
if (tileCount == 0) | |
return; | |
var i = 0; | |
var mapPos = indexToMapPos(i); | |
var screenPos = mapToScreen(mapPos); | |
model.append({ actualIndex: 0, tileX: screenPos.x, tileY: screenPos.y }); | |
var tilesCreated = 1; | |
var z = 1; | |
// This loop takes us down to the widest point of the "diamond". | |
// For example, with a 15 x 10 map, this loop will create items | |
// up to and including the row starting with index 135 (marked (a)): | |
// | |
// 0 | |
// 15 1 | |
// 30 16 2 | |
// .. .. .. .. | |
// 135 ... 9 (a) | |
// 136 122 ... 24 10 | |
// 137 123 ... 25 11 | |
// .. ... .. | |
// 140 126 ... 28 14 (b) | |
// .. ... .. | |
// .. ... .. | |
// 148 134 | |
// 149 | |
// | |
// The process is similar for a differently sized map; | |
// we swap some checks around to check for height instead of width, for example. | |
// Start off assuming that one is bigger than the other to save some code. | |
var smallerDimension = tilesHigh; | |
var largerDimension = tilesWide; | |
var widthLarger = true; | |
// Check that our assumption is true. | |
if (tilesWide < tilesHigh) { | |
smallerDimension = tilesWide; | |
largerDimension = tilesHigh; | |
widthLarger = false; | |
} | |
while (z < smallerDimension) { | |
// * tilesWide because we always start from the left | |
// and move to the right. | |
i = z * tilesWide; | |
do { | |
mapPos = indexToMapPos(i); | |
screenPos = mapToScreen(mapPos); | |
model.append({ actualIndex: i, tileX: screenPos.x, tileY: screenPos.y }); | |
++tilesCreated; | |
i -= tilesWide - 1; | |
} while (i > 0) | |
++z; | |
} | |
var rowEndIndex = widthLarger ? 0 : (tilesWide * 2) - 1; | |
while (z < largerDimension) { | |
// We've reached the "widest" point of the "diamond" | |
// ( (a) on the diagram above). | |
// From now on we'll be completing the next part. | |
// If the map is square, this loop will be skipped. | |
// However, for the 15 x 10 example we've been using, | |
// The loop will continue until we've created the last row | |
// of the "widest" part ( (b) on the diagram above). | |
// | |
// z will be 10 the first time this code is hit, so: | |
// z - (tilesHigh - 1) | |
// 10 - ( 10 - 1) | |
// 10 - ( 9 ) | |
// 1 | |
// The first part of the calculation gets us to the first index | |
// of the last "row" we were on: | |
// (tilesHigh - 1) * tilesWide | |
// (10 - 1) * 15 | |
// 9 * 15 | |
// 135 | |
// 135 + 1 = 136. The index then increases by 1 from then on. | |
if (tilesWide > tilesHigh) | |
i = ((tilesHigh - 1) * tilesWide) + (z - (tilesHigh - 1)); | |
else | |
i = (tilesWide * z); | |
do { | |
mapPos = indexToMapPos(i); | |
screenPos = mapToScreen(mapPos); | |
model.append({ actualIndex: i, tileX: screenPos.x, tileY: screenPos.y }); | |
++tilesCreated; | |
i -= tilesWide - 1; | |
} while (widthLarger ? i > 0 : i >= rowEndIndex) | |
++z; | |
rowEndIndex += tilesWide; | |
} | |
// Go back to the index of the last tile that we created. | |
i += tilesWide - 1; | |
var xyz = 0; | |
if (widthLarger) { | |
rowEndIndex = (tilesWide * 2) - 1; | |
} | |
while (tilesCreated < tileCount) { | |
i = ((tilesHigh - 1) * tilesWide) + (z - (tilesHigh - 1)); | |
do { | |
mapPos = indexToMapPos(i); | |
screenPos = mapToScreen(mapPos); | |
model.append({ actualIndex: i, tileX: screenPos.x, tileY: screenPos.y }); | |
++tilesCreated; | |
i -= tilesWide - 1; | |
} while (i >= rowEndIndex) | |
++z; | |
rowEndIndex += tilesWide; | |
} | |
} | |
Column { | |
Row { | |
Slider { | |
id: widthSlider | |
from: 0 | |
to: 10 | |
live: false // comment out if error | |
onValueChanged: repeater.recreate() | |
} | |
Label { | |
text: "Tiles wide: " + widthSlider.value.toFixed(0) | |
anchors.verticalCenter: parent.verticalCenter | |
} | |
} | |
Row { | |
Slider { | |
id: heightSlider | |
from: 0 | |
to: 10 | |
live: false // comment out if error | |
onValueChanged: repeater.recreate() | |
} | |
Label { | |
text: "Tiles high: " + heightSlider.value.toFixed(0) | |
anchors.verticalCenter: parent.verticalCenter | |
} | |
} | |
} | |
Repeater { | |
id: repeater | |
model: ListModel { | |
// | |
} | |
Component.onCompleted: recreate() | |
function recreate() { | |
model.clear(); | |
tilesWide = widthSlider.value; | |
tilesHigh = heightSlider.value; | |
addTiles(model) | |
} | |
delegate: Rectangle { | |
x: 650 + tileX | |
y: tileY | |
border.width: 1 | |
width: tileWidth | |
height: tileHeight | |
readonly property int idx: index | |
Text { | |
text: actualIndex | |
color: Qt.rgba(index / repeater.count, 0, 0, 1) | |
anchors.centerIn: parent | |
font.pixelSize: 14 | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment