Skip to content

Instantly share code, notes, and snippets.

@mitchcurtis
Last active April 12, 2017 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mitchcurtis/11bf1a079b7ad84cd55801e11b3bb359 to your computer and use it in GitHub Desktop.
Save mitchcurtis/11bf1a079b7ad84cd55801e11b3bb359 to your computer and use it in GitHub Desktop.
Isometric map rendering in QML (in the hackiest possible way)
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