diff --git a/vtm-playground/src/org/oscim/test/ExtrusionsTest.java b/vtm-playground/src/org/oscim/test/ExtrusionsTest.java
new file mode 100644
index 00000000..14ab7009
--- /dev/null
+++ b/vtm-playground/src/org/oscim/test/ExtrusionsTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2019 Gustl22
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+ */
+package org.oscim.test;
+
+import org.oscim.core.MapElement;
+import org.oscim.core.Tag;
+import org.oscim.gdx.GdxMapApp;
+import org.oscim.layers.TileGridLayer;
+import org.oscim.layers.tile.buildings.BuildingLayer;
+import org.oscim.layers.tile.buildings.S3DBLayer;
+import org.oscim.layers.tile.vector.VectorTileLayer;
+import org.oscim.test.tiling.source.TestTileSource;
+import org.oscim.theme.VtmThemes;
+
+public class ExtrusionsTest extends GdxMapApp {
+
+ enum GroundShape {
+ HEXAGON, RECTANGLE, SHAPE_L, SHAPE_O,
+ }
+
+ private GroundShape mGroundShape = GroundShape.RECTANGLE;
+
+ private Tag[] mTags = {
+ new Tag(Tag.KEY_BUILDING, Tag.VALUE_YES),
+ new Tag(Tag.KEY_BUILDING_LEVELS, "2"),
+ new Tag(Tag.KEY_ROOF_LEVELS, "2"),
+ new Tag(Tag.KEY_ROOF_COLOR, "#99ffff"),
+ new Tag(Tag.KEY_BUILDING_COLOR, "white")
+ };
+
+ private void addExtrusions(TestTileSource tileSource) {
+ MapElement e = new MapElement();
+ e.startPolygon();
+ switch (mGroundShape) {
+ case HEXAGON:
+ hexagonGround(e);
+ break;
+ case RECTANGLE:
+ rectangleGround(e);
+ break;
+ case SHAPE_L:
+ shapeLGround(e);
+ break;
+ case SHAPE_O:
+ shapeOGround(e);
+ break;
+ }
+ e.tags.set(mTags);
+
+ MapElement building;
+
+ building = new MapElement(e);
+ building = building.translate(0, 0);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_FLAT));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(100, 0);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_GABLED));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(200, 0);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_PYRAMIDAL));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(300, 0);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_DOME));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(0, 100);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_GAMBREL));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(100, 100);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_HALF_HIPPED));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(200, 100);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_HIPPED));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(300, 100);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_MANSARD));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(0, 200);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_ONION));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(100, 200);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_ROUND));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(200, 200);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_SALTBOX));
+ tileSource.addMapElement(building);
+
+ building = new MapElement(e);
+ building = building.translate(300, 200);
+ building.tags.add(new Tag(Tag.KEY_ROOF_SHAPE, Tag.VALUE_SKILLION));
+ tileSource.addMapElement(building);
+ }
+
+ private void hexagonGround(MapElement e) {
+ hexagonGround(e, 10, 0, 0);
+ }
+
+ private void hexagonGround(MapElement e, float unit, float shiftX, float shiftY) {
+ float sqrt2 = unit * (float) Math.sqrt(2);
+
+ e.addPoint(shiftX + unit, shiftY + 0);
+ e.addPoint(shiftX + unit + sqrt2, shiftY + 0);
+ e.addPoint(shiftX + 2 * unit + sqrt2, shiftY + unit);
+ e.addPoint(shiftX + 2 * unit + sqrt2, shiftY + unit + sqrt2);
+ e.addPoint(shiftX + unit + sqrt2, shiftY + 2 * unit + sqrt2);
+ e.addPoint(shiftX + unit, shiftY + 2 * unit + sqrt2);
+ e.addPoint(shiftX + 0, shiftY + unit + sqrt2);
+ e.addPoint(shiftX + 0, shiftY + unit);
+ }
+
+ private void rectangleGround(MapElement e) {
+ e.addPoint(0, 0);
+ e.addPoint(25, 0);
+ e.addPoint(25, 20);
+ e.addPoint(0, 20);
+ }
+
+ private void shapeLGround(MapElement e) {
+ e.addPoint(0, 0);
+ e.addPoint(20, 0);
+ e.addPoint(20, 15);
+ e.addPoint(12, 15);
+ e.addPoint(12, 10);
+ e.addPoint(0, 10);
+ }
+
+ private void shapeOGround(MapElement e) {
+ hexagonGround(e);
+ e.reverse();
+ e.startHole();
+ hexagonGround(e, 5, 5, 5);
+ e.reverse();
+ }
+
+ @Override
+ public void createLayers() {
+ TestTileSource tts = new TestTileSource();
+
+ addExtrusions(tts);
+
+ VectorTileLayer vtl = mMap.setBaseMap(tts);
+ BuildingLayer buildingLayer = new S3DBLayer(mMap, vtl, true);
+ buildingLayer.getExtrusionRenderer().getSun().setProgress(0.1f);
+ buildingLayer.getExtrusionRenderer().getSun().updatePosition();
+
+ mMap.layers().add(buildingLayer);
+ mMap.layers().add(new TileGridLayer(mMap));
+
+ mMap.setTheme(VtmThemes.DEFAULT);
+
+ mMap.setMapPosition(0, 0, 1 << 17);
+ }
+
+ public static void main(String[] args) {
+ GdxMapApp.init();
+ GdxMapApp.run(new ExtrusionsTest());
+ }
+}
diff --git a/vtm-playground/src/org/oscim/test/tiling/source/TestTileSource.java b/vtm-playground/src/org/oscim/test/tiling/source/TestTileSource.java
new file mode 100644
index 00000000..3383a458
--- /dev/null
+++ b/vtm-playground/src/org/oscim/test/tiling/source/TestTileSource.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 Gustl22
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+ */
+package org.oscim.test.tiling.source;
+
+import org.oscim.core.MapElement;
+import org.oscim.layers.tile.MapTile;
+import org.oscim.tiling.ITileDataSink;
+import org.oscim.tiling.ITileDataSource;
+import org.oscim.tiling.TileSource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.oscim.tiling.QueryResult.SUCCESS;
+
+public class TestTileSource extends TileSource {
+
+ private TileDataSource mTileDataSource;
+
+ public TestTileSource() {
+ super(0, 17);
+ mTileDataSource = new TileDataSource();
+ }
+
+ public void addMapElement(MapElement e) {
+ mTileDataSource.mElems.add(e);
+ }
+
+ @Override
+ public ITileDataSource getDataSource() {
+ return mTileDataSource;
+ }
+
+ @Override
+ public OpenResult open() {
+ return OpenResult.SUCCESS;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ static class TileDataSource implements ITileDataSource {
+
+ final List mElems = new ArrayList<>();
+
+ TileDataSource() {
+ }
+
+ @Override
+ public void query(MapTile tile, ITileDataSink sink) {
+ for (MapElement e : mElems) {
+ sink.process(e);
+ }
+
+ sink.completed(SUCCESS);
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void cancel() {
+ }
+ }
+}