(
var normalizeTile, getMasterRect, tileDrawFunc;

// provide scaled coordinate for the tiles
normalizeTile = {arg child, parent;
	var parentXMin, parentYMin, parentWidth, parentHeight, p1, p2, p3, p4;
	parentXMin = minItem(parent.slice(nil, 0));
	parentYMin = minItem(parent.slice(nil, 1));
	parentWidth = (maxItem(parent.slice(nil, 0)) - parentXMin);
	parentHeight = (maxItem(parent.slice(nil, 1)) - parentYMin);
	p1 = [(minItem(child.slice(nil, 0)) - parentXMin) / parentWidth, (minItem(child.slice(nil, 1)) - parentYMin) / parentHeight];
	p2 = [(maxItem(child.slice(nil, 0)) - parentXMin) / parentWidth, p1[1]];
	p3 = [p1[0], (maxItem(child.slice(nil, 1)) - parentYMin) / parentHeight];
	p4 = [p2[0], p3[1]];
	[p1, p2, p3, p4]};

// get the master rectangle for the tiling based on orientation
getMasterRect = {arg tileWin, rotationState;
	var rectWidth, rectHeight;
	case({(rotationState % 2 == 0) && (tileWin.bounds.width <= (tileWin.bounds.height * 2))},{
			rectWidth =  tileWin.bounds.width - 20;
			rectHeight = rectWidth / 2},
		{(rotationState % 2 == 0) && (tileWin.bounds.width > (tileWin.bounds.height * 2))},{
			rectHeight = tileWin.bounds.height - 20;
			rectWidth =  rectHeight * 2},
		{(rotationState % 2 == 1) && (tileWin.bounds.height <= (tileWin.bounds.width * 2))},{
			rectWidth = tileWin.bounds.height - 20;
			rectHeight =  rectWidth / 2},
		{(rotationState % 2 == 1) && (tileWin.bounds.height > (tileWin.bounds.width * 2))},{
			rectWidth = tileWin.bounds.width - 20;
			rectHeight =  rectWidth / 2});
	Rect.new((tileWin.bounds.width - rectWidth) / 2, (tileWin.bounds.height - rectHeight) / 2, rectWidth, rectHeight)};

// draw the tiling
~tileDrawFunc = {arg tileWin, tileMap, transform, layoutState;
	var rotationState = transform[4], mirrorState = transform[5], invertState = transform[6],
	masterRect, parentRects, depthLevels;

	masterRect = getMasterRect.value(tileWin, rotationState);
	parentRects = [[masterRect], [], []];

	Pen.width = (masterRect.width + masterRect.width) / 8000;
	Pen.scale(abs(mirrorState - 1) * 2 - 1, 1);
	Pen.translate(mirrorState * -1 * tileWin.bounds.width, 0);
	Pen.rotate(0.5 * rotationState * pi, tileWin.bounds.width / 2, tileWin.bounds.height / 2);

	depthLevels = switch(layoutState, 0, {[6]}, 1, {[5]}, 2, {[4]}, 3, {[4, 5, 6]});
	depthLevels.do({arg depth, dIndex;
		tileMap[depth].do({arg childTile, cIndex;
			var childTileNorm, parentRect, p1, p2, childRect;

			childTileNorm = if(dIndex > 0, {normalizeTile.value(childTile, tileMap[depth - 1][(cIndex / 4).trunc])}, {childTile});
			parentRect = if(dIndex > 0, {parentRects[dIndex][(cIndex / 4).trunc]}, {parentRects[0][0]});
			if((layoutState == 3) && (dIndex > 0), {parentRect = parentRect.insetBy(parentRect.width * 0.04 , parentRect.height * 0.04)});

			p1 = Point.new(childTileNorm[0][0] * parentRect.width / if(dIndex > 0, {1}, {2}), childTileNorm[0][1] * parentRect.height);
			p2 = Point.new(childTileNorm[3][0] * parentRect.width / if(dIndex > 0, {1}, {2}), childTileNorm[3][1] * parentRect.height);

			childRect = Rect.fromPoints(p1, p2).moveBy(parentRect.left, parentRect.top);
			if((layoutState == 3) && (depth < 6), {parentRects[dIndex + 1] = parentRects[dIndex + 1].add(childRect)});

			if(if(invertState == 0, {abs(p1.x - p2.x) > abs(p1.y - p2.y)}, {abs(p1.x - p2.x) < abs(p1.y - p2.y)}),
				{Pen.color = Color.white; Pen.fillRect(childRect); Pen.color = Color.black; Pen.strokeRect(childRect)},
				{Pen.color = Color.black; Pen.fillRect(childRect); Pen.color = Color.white; Pen.strokeRect(childRect)});
	})})};

// launch the visualization
~launchTileVisualizer = {
	arg border = true, isLaunch = true;
	var tileWin;
	~tileWin = Window("tile", if(isLaunch, {Rect(200, 500, 1000, 500)}, {~tileWin.bounds}), true, border);
	~tileWin.view.background_(Color.gray(0.2));
	~tileWin.drawFunc = {arg win; ~tileDrawFunc.value(win, ~tileMap, ~transform, ~layoutState)}.inEnvir;
	~tileWin.front;
	~tileWin.refresh}
)