S3DB: roof shape profiles (#744)

This commit is contained in:
Gustl22
2019-10-02 16:37:00 +02:00
committed by Emux
parent c20922b5f5
commit 14c485914c
3 changed files with 189 additions and 76 deletions

View File

@@ -28,7 +28,7 @@ import org.oscim.theme.VtmThemes;
public class ExtrusionsTest extends GdxMapApp { public class ExtrusionsTest extends GdxMapApp {
enum GroundShape { enum GroundShape {
HEXAGON, RECTANGLE, SHAPE_L, SHAPE_O, SHAPE_T, SHAPE_U, SHAPE_V, SHAPE_X, SHAPE_Z, TEST HEXAGON, RECTANGLE, SHAPE_L, SHAPE_M, SHAPE_O, SHAPE_T, SHAPE_U, SHAPE_V, SHAPE_X, SHAPE_Z, TEST
} }
/** /**
@@ -123,6 +123,9 @@ public class ExtrusionsTest extends GdxMapApp {
case SHAPE_L: case SHAPE_L:
shapeLGround(e); shapeLGround(e);
break; break;
case SHAPE_M:
shapeMGround(e);
break;
case SHAPE_O: case SHAPE_O:
shapeOGround(e); shapeOGround(e);
break; break;
@@ -181,6 +184,19 @@ public class ExtrusionsTest extends GdxMapApp {
e.addPoint(0, 10); e.addPoint(0, 10);
} }
private void shapeMGround(MapElement e) {
e.addPoint(0, 0);
e.addPoint(10, 0);
e.addPoint(10, 5);
e.addPoint(20, 5);
e.addPoint(20, 20);
e.addPoint(37, 20);
e.addPoint(37, 25);
e.addPoint(12, 25);
e.addPoint(12, 15);
e.addPoint(0, 15);
}
private void shapeOGround(MapElement e) { private void shapeOGround(MapElement e) {
hexagonGround(e); hexagonGround(e);
e.reverse(); e.reverse();

View File

@@ -16,12 +16,7 @@
package org.oscim.layers.tile.buildings; package org.oscim.layers.tile.buildings;
import org.oscim.backend.canvas.Color; import org.oscim.backend.canvas.Color;
import org.oscim.core.Box; import org.oscim.core.*;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapElement;
import org.oscim.core.Tag;
import org.oscim.core.TagSet;
import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile; import org.oscim.layers.tile.MapTile;
import org.oscim.layers.tile.vector.VectorTileLayer; import org.oscim.layers.tile.vector.VectorTileLayer;
import org.oscim.map.Map; import org.oscim.map.Map;
@@ -302,12 +297,12 @@ public class S3DBLayer extends BuildingLayer {
case Tag.VALUE_GABLED: case Tag.VALUE_GABLED:
case Tag.VALUE_GAMBREL: case Tag.VALUE_GAMBREL:
specialParts = new GeometryBuffer(0, 0); // No data in GeometryBuffer needed specialParts = new GeometryBuffer(0, 0); // No data in GeometryBuffer needed
success = S3DBUtils.calcRidgeMesh(gElement, minHeight, maxHeight, roofOrientationAcross, true, specialParts); success = S3DBUtils.calcRidgeMesh(gElement, minHeight, maxHeight, roofOrientationAcross, v, specialParts);
break; break;
case Tag.VALUE_MANSARD: case Tag.VALUE_MANSARD:
case Tag.VALUE_HALF_HIPPED: case Tag.VALUE_HALF_HIPPED:
case Tag.VALUE_HIPPED: case Tag.VALUE_HIPPED:
success = S3DBUtils.calcRidgeMesh(gElement, minHeight, maxHeight, roofOrientationAcross, false, null); success = S3DBUtils.calcRidgeMesh(gElement, minHeight, maxHeight, roofOrientationAcross, v, null);
break; break;
case Tag.VALUE_SKILLION: case Tag.VALUE_SKILLION:
// ROOF_SLOPE_DIRECTION is not supported yet // ROOF_SLOPE_DIRECTION is not supported yet

View File

@@ -64,7 +64,7 @@ public final class S3DBUtils {
* @param element the GeometryBuffer which is used to write the 3D mesh * @param element the GeometryBuffer which is used to write the 3D mesh
* @return true if calculation succeeded, false otherwise * @return true if calculation succeeded, false otherwise
*/ */
public static boolean calcCircleMesh(GeometryBuffer element, float minHeight, float maxHeight, String type) { public static boolean calcCircleMesh(GeometryBuffer element, float minHeight, float maxHeight, String roofShape) {
float[] points = element.points; float[] points = element.points;
int[] index = element.index; int[] index = element.index;
@@ -81,30 +81,7 @@ public final class S3DBUtils {
// Init mesh // Init mesh
GeometryBuffer mesh; GeometryBuffer mesh;
switch (type) { mesh = initCircleMesh(getProfile(roofShape), numSections);
case Tag.VALUE_ONION:
float[][] onionShape = new float[][]{
{1, 0, 0},
{0.2f, 0, 0.01f},
{0.875f, 0, 0.1875f},
{1, 0, 0.375f},
{0.875f, 0, 0.5625f},
{0.5f, 0, 0.75f},
{0.2f, 0, 0.8125f},
{0, 0, 1}};
mesh = initCircleMesh(onionShape, numSections);
break;
case Tag.VALUE_DOME:
default:
float[][] domeShape = new float[][]{
{1, 0, 0},
{0.825f, 0, 0.5f},
{0.5f, 0, 0.825f},
{0, 0, 1}};
mesh = initCircleMesh(domeShape, numSections);
break;
}
// Calculate center and load points // Calculate center and load points
float centerX = 0; float centerX = 0;
@@ -363,14 +340,16 @@ public final class S3DBUtils {
* @param minHeight the minimum height * @param minHeight the minimum height
* @param maxHeight the maximum height * @param maxHeight the maximum height
* @param orientationAcross indicates if ridge is parallel to short side * @param orientationAcross indicates if ridge is parallel to short side
* @param isGabled indicates if should calculate a gable * @param roofShape the roof shape
* @param specialParts element to add missing parts of underlying element * @param specialParts element to add missing parts of underlying element
* @return true if calculation succeeded, false otherwise * @return true if calculation succeeded, false otherwise
*/ */
public static boolean calcRidgeMesh(GeometryBuffer element, float minHeight, float maxHeight, boolean orientationAcross, boolean isGabled, GeometryBuffer specialParts) { public static boolean calcRidgeMesh(GeometryBuffer element, float minHeight, float maxHeight, boolean orientationAcross, String roofShape, GeometryBuffer specialParts) {
float[] points = element.points; float[] points = element.points;
int[] index = element.index; int[] index = element.index;
boolean isGabled = isGabled(roofShape);
for (int i = 0, pointPos = 0; i < index.length; i++) { for (int i = 0, pointPos = 0; i < index.length; i++) {
if (index[i] < 0) { if (index[i] < 0) {
break; break;
@@ -395,9 +374,10 @@ public final class S3DBUtils {
point3Fs.add(new float[]{x, y, minHeight}); point3Fs.add(new float[]{x, y, minHeight});
} }
// Calc vectors // Number of ground points
int groundSize = point3Fs.size(); int groundSize = point3Fs.size();
// Calc vectors
List<Float> lengths = new ArrayList<>(); List<Float> lengths = new ArrayList<>();
List<float[]> normVectors = GeometryUtils.normalizedVectors2D(point3Fs, lengths); List<float[]> normVectors = GeometryUtils.normalizedVectors2D(point3Fs, lengths);
@@ -627,9 +607,13 @@ public final class S3DBUtils {
} }
} }
float[][] profile = getProfile(roofShape);
int profileSize = profile.length - 2;
int profileSizePlus = profile.length - 1; // profile roof shape size + ground size (+1)
// Allocate the indices to the points // Allocate the indices to the points
int ridgePointSize = ridgePoints.size(); int ridgePointSize = ridgePoints.size();
float[] meshPoints = new float[(groundSize + ridgePointSize) * 3]; //(ridgePoints * 3 = 6) float[] meshPoints = new float[(groundSize * profileSizePlus + ridgePointSize) * 3]; //(ridgePoints * 3 = 6)
List<Integer> meshVarIndex = new ArrayList<>(); List<Integer> meshVarIndex = new ArrayList<>();
// Add special building parts // Add special building parts
@@ -638,41 +622,93 @@ public final class S3DBUtils {
meshPartVarIndex = new ArrayList<>(); meshPartVarIndex = new ArrayList<>();
} }
for (int k = 0; k < groundSize; k++) { float heightRange = maxHeight - minHeight;
// Number of 3D-points for the roof (groundSize * inner profile + groundSize)
int grRsSize = groundSize * profileSizePlus;
// WRITE MESH
for (int l = 0; l < groundSize; l++) {
// l: #ground points
// k: #(shape points + ground points)
int k = l * profileSizePlus;
// Add first face // Add first face
float[] p = point3Fs.get(k); float[] p = point3Fs.get(l);
int ridgePointIndex1 = k; int ridgePointIndex1 = l;
while (!ridgePoints.containsKey(ridgePointIndex1)) { while (!ridgePoints.containsKey(ridgePointIndex1)) {
ridgePointIndex1 = (ridgePointIndex1 + groundSize - 1) % groundSize; // Decrease ridgePointIndex ridgePointIndex1 = (ridgePointIndex1 + groundSize - 1) % groundSize; // Decrease ridgePointIndex until a ridge point is found for the k point.
} }
int ridgeIndex1 = ridgePoints.headMap(ridgePointIndex1).size(); // set ridgeIndex to shift in ridgePoints int ridgeIndex1 = ridgePoints.headMap(ridgePointIndex1).size(); // set ridgeIndex to shift in ridgePoints
if (meshPartVarIndex != null && gablePoints.contains(ridgePointIndex1) && getIndexNextTurn(ridgePointIndex1, simpleAngles).equals(getIndexNextTurn(k, simpleAngles))) { boolean isGable = false;
meshPartVarIndex.add(k); if (meshPartVarIndex != null && gablePoints.contains(ridgePointIndex1) && getIndexNextTurn(ridgePointIndex1, simpleAngles).equals(getIndexNextTurn(l, simpleAngles))) {
meshPartVarIndex.add((k + 1) % groundSize); isGable = true;
meshPartVarIndex.add(ridgeIndex1 + groundSize); // Add missing parts to building
meshPartVarIndex.add(k + profileSize);
meshPartVarIndex.add((k + profileSizePlus + profileSize) % grRsSize);
meshPartVarIndex.add(ridgeIndex1 + grRsSize);
} else { } else {
meshVarIndex.add(k); // Add first roof face
meshVarIndex.add((k + 1) % groundSize); meshVarIndex.add(k + profileSize);
meshVarIndex.add(ridgeIndex1 + groundSize); meshVarIndex.add((k + profileSizePlus + profileSize) % grRsSize);
meshVarIndex.add(ridgeIndex1 + grRsSize);
} }
// Add second face, if necessary // Add second face, if necessary
int ridgePointIndex2 = (k + 1) % groundSize; int ridgePointIndex2 = (l + 1) % groundSize;
while (!ridgePoints.containsKey(ridgePointIndex2)) { while (!ridgePoints.containsKey(ridgePointIndex2)) {
ridgePointIndex2 = (ridgePointIndex2 + groundSize - 1) % groundSize; // Decrease ridgePointIndex ridgePointIndex2 = (ridgePointIndex2 + groundSize - 1) % groundSize; // Decrease ridgePointIndex
} }
if (ridgePointIndex2 != ridgePointIndex1) { if (ridgePointIndex2 != ridgePointIndex1) {
int ridgeIndex2 = ridgePoints.headMap(ridgePointIndex2).size(); // Set ridgeIndex to position in ridgePoints int ridgeIndex2 = ridgePoints.headMap(ridgePointIndex2).size(); // Set ridgeIndex to position in ridgePoints
meshVarIndex.add(ridgeIndex1 + groundSize); meshVarIndex.add(ridgeIndex1 + grRsSize);
meshVarIndex.add((k + 1) % groundSize); meshVarIndex.add((k + profileSizePlus + profileSize) % grRsSize);
meshVarIndex.add(ridgeIndex2 + groundSize); meshVarIndex.add(ridgeIndex2 + grRsSize);
} }
// Write points // Write ground points
meshPoints[3 * k + 0] = p[0]; int offset = 3 * k;
meshPoints[3 * k + 1] = p[1]; meshPoints[offset + 0] = p[0];
meshPoints[3 * k + 2] = p[2]; meshPoints[offset + 1] = p[1];
meshPoints[offset + 2] = p[2];
// Write profile roof shape points, skip ground points and ridge points of profile
float[] rp1 = ridgePoints.get(ridgePointIndex1);
float[] dif = GeometryUtils.diffVec(p, rp1); // Vector from ridge point to ground point
float phi = (float) Math.atan2(dif[0], dif[1]); // direction of diff
float r = (float) GeometryUtils.length(dif);
for (int m = 1; m < profileSizePlus; m++) {
offset = 3 * (k + m); // (+ 1 - 1 = 0)
int o = k + m - 1; // the ground + actual point of profile (m = 0 is the ground point)
// Add profile roof faces (2 per side and profile point)
if (isGable) {
// Add missing parts to building
meshPartVarIndex.add(o); // ground
meshPartVarIndex.add((o + profileSizePlus) % grRsSize); // ground
meshPartVarIndex.add((o + 1) % grRsSize); // profile
meshPartVarIndex.add((o + profileSizePlus) % grRsSize); // ground + 1
meshPartVarIndex.add((o + 1 + profileSizePlus) % grRsSize); // profile
meshPartVarIndex.add((o + 1) % grRsSize); // profile
} else {
meshVarIndex.add(o); // ground
meshVarIndex.add((o + profileSizePlus) % grRsSize); // ground + 1
meshVarIndex.add((o + 1) % grRsSize); // profile
meshVarIndex.add((o + profileSizePlus) % grRsSize); // ground + 1
meshVarIndex.add((o + 1 + profileSizePlus) % grRsSize); // profile + 1
meshVarIndex.add((o + 1) % grRsSize); // profile
}
// Calculate position of profile point.
// profile[m][0] is same length for x and y
meshPoints[offset + 0] = rp1[0] + (float) (r * profile[m][0] * Math.sin(phi));
meshPoints[offset + 1] = rp1[1] + (float) (r * profile[m][0] * Math.cos(phi));
meshPoints[offset + 2] = p[2] + heightRange * profile[m][2];
}
} }
// Tessellate top, if necessary (can be used to improve wrong rendered roofs) // Tessellate top, if necessary (can be used to improve wrong rendered roofs)
@@ -723,7 +759,7 @@ public final class S3DBUtils {
if (Tessellator.tessellate(buffer, buffer) != 0) { if (Tessellator.tessellate(buffer, buffer) != 0) {
for (int ind : buffer.index) { for (int ind : buffer.index) {
// Get position in ridgePoints, considering skipped points // Get position in ridgePoints, considering skipped points
meshVarIndex.add(ridgePoints.headMap(faceIndex.get(ind)).size() + groundSize); meshVarIndex.add(ridgePoints.headMap(faceIndex.get(ind)).size() + grRsSize);
} }
} else { } else {
// TODO Improve wrong or not tessellated faces // TODO Improve wrong or not tessellated faces
@@ -746,11 +782,10 @@ public final class S3DBUtils {
// Add ridge points // Add ridge points
float[] tmp = ridgePoints.get(k); float[] tmp = ridgePoints.get(k);
if (tmp != null) { if (tmp != null) {
float[] p = new float[]{tmp[0], tmp[1], maxHeight}; int ppos = 3 * (l + grRsSize);
int ppos = 3 * (l + groundSize); meshPoints[ppos + 0] = tmp[0];
meshPoints[ppos + 0] = p[0]; meshPoints[ppos + 1] = tmp[1];
meshPoints[ppos + 1] = p[1]; meshPoints[ppos + 2] = maxHeight;
meshPoints[ppos + 2] = p[2];
l++; l++;
} }
} }
@@ -1292,6 +1327,58 @@ public final class S3DBUtils {
return hsv.mod(c, relative); return hsv.mod(c, relative);
} }
/**
* Get the profile (half cross section) of roof shape.
*
* @param roofShape the roof shape value
* @return the profile as 2D array
*/
public static float[][] getProfile(String roofShape) {
float[][] shape;
switch (roofShape) {
case Tag.VALUE_ONION:
shape = new float[][]{
{1, 0, 0},
{0.2f, 0, 0.01f},
{0.875f, 0, 0.1875f},
{1, 0, 0.375f},
{0.875f, 0, 0.5625f},
{0.5f, 0, 0.75f},
{0.2f, 0, 0.8125f},
{0, 0, 1}};
break;
case Tag.VALUE_ROUND:
case Tag.VALUE_DOME:
shape = new float[][]{
{1, 0, 0},
{0.825f, 0, 0.5f},
{0.5f, 0, 0.825f},
{0, 0, 1}};
break;
case Tag.VALUE_SALTBOX:
shape = new float[][]{
{1, 0, 0},
{0.5f, 0, 1},
{0, 0, 1}};
break;
case Tag.VALUE_MANSARD:
case Tag.VALUE_GAMBREL:
shape = new float[][]{
{1, 0, 0},
{0.75f, 0, 0.75f},
{0, 0, 1}};
break;
case Tag.VALUE_GABLED:
case Tag.VALUE_HIPPED:
default:
shape = new float[][]{
{1, 0, 0},
{0, 0, 1}};
break;
}
return shape;
}
/** /**
* @param normVectors the normalized vectors * @param normVectors the normalized vectors
* @return a list of simple angles: * @return a list of simple angles:
@@ -1342,32 +1429,32 @@ public final class S3DBUtils {
return simpAngls; return simpAngls;
} }
private static GeometryBuffer initCircleMesh(float[][] circleShape, int numSections) { private static GeometryBuffer initCircleMesh(float[][] profile, int numSections) {
int indexSize = numSections * (circleShape.length - 1) * 2 * 3; // * 2 faces * 3 vertices int indexSize = numSections * (profile.length - 1) * 2 * 3; // * 2 faces * 3 vertices
int[] meshIndex = new int[indexSize]; int[] meshIndex = new int[indexSize];
int meshSize = numSections * circleShape.length; int meshSize = numSections * profile.length;
float[] meshPoints = new float[meshSize * 3]; float[] meshPoints = new float[meshSize * 3];
for (int i = 0; i < numSections; i++) { for (int i = 0; i < numSections; i++) {
for (int j = 0; j < circleShape.length; j++) { for (int j = 0; j < profile.length; j++) {
// Write point mesh // Write point mesh
int pPos = 3 * (i * circleShape.length + j); int pPos = 3 * (i * profile.length + j);
meshPoints[pPos + 0] = circleShape[j][0]; meshPoints[pPos + 0] = profile[j][0];
meshPoints[pPos + 1] = circleShape[j][1]; meshPoints[pPos + 1] = profile[j][1];
meshPoints[pPos + 2] = circleShape[j][2]; meshPoints[pPos + 2] = profile[j][2];
// Write point indices // Write point indices
if (j != circleShape.length - 1) { if (j != profile.length - 1) {
int iPos = 6 * (i * (circleShape.length - 1) + j); // 6 = 2 * Mesh * 3PointsPerMesh int iPos = 6 * (i * (profile.length - 1) + j); // 6 = 2 * Mesh * 3PointsPerMesh
pPos = pPos / 3; pPos = pPos / 3;
meshIndex[iPos + 2] = pPos + 0; meshIndex[iPos + 2] = pPos + 0;
meshIndex[iPos + 1] = pPos + 1; meshIndex[iPos + 1] = pPos + 1;
meshIndex[iPos + 0] = (pPos + circleShape.length) % meshSize; meshIndex[iPos + 0] = (pPos + profile.length) % meshSize;
// FIXME if is last point, only one tris is needed, if top shape is closed // FIXME if is last point, only one tris is needed, if top shape is closed
meshIndex[iPos + 5] = pPos + 1; meshIndex[iPos + 5] = pPos + 1;
meshIndex[iPos + 4] = (pPos + circleShape.length + 1) % meshSize; meshIndex[iPos + 4] = (pPos + profile.length + 1) % meshSize;
meshIndex[iPos + 3] = (pPos + circleShape.length) % meshSize; meshIndex[iPos + 3] = (pPos + profile.length) % meshSize;
} }
} }
@@ -1376,6 +1463,21 @@ public final class S3DBUtils {
return new GeometryBuffer(meshPoints, meshIndex); return new GeometryBuffer(meshPoints, meshIndex);
} }
private static boolean isGabled(String roofShape) {
switch (roofShape) {
case Tag.VALUE_ROUND:
case Tag.VALUE_SALTBOX:
case Tag.VALUE_GABLED:
case Tag.VALUE_GAMBREL:
return true;
case Tag.VALUE_MANSARD:
case Tag.VALUE_HALF_HIPPED:
case Tag.VALUE_HIPPED:
default:
return false;
}
}
private static void mergeMeshGeometryBuffer(GeometryBuffer gb1, GeometryBuffer gb2, GeometryBuffer out) { private static void mergeMeshGeometryBuffer(GeometryBuffer gb1, GeometryBuffer gb2, GeometryBuffer out) {
if (!(gb1.isTris() && gb2.isTris())) return; if (!(gb1.isTris() && gb2.isTris())) return;
int gb1PointSize = gb1.points.length; int gb1PointSize = gb1.points.length;