org.oscim.layers.tile.buildings.S3DBUtils Maven / Gradle / Ivy
Show all versions of vtm Show documentation
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2017-2019 Gustl22
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* 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.layers.tile.buildings;
import org.oscim.backend.canvas.Color;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.Tag;
import org.oscim.utils.ColorsCSS;
import org.oscim.utils.Tessellator;
import org.oscim.utils.geom.GeometryUtils;
import org.oscim.utils.math.MathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* Provides utils for S3DB layers.
*/
public final class S3DBUtils {
private static final Logger log = LoggerFactory.getLogger(S3DBUtils.class);
// Toggle this to debug and improve ridge calculation, you can see the faults in map then.
private static final boolean IMPROVE_RIDGE_CALCULATION = false;
private static final int SNAP_THRESHOLD = 70; // Threshold for ridge snap calculation (maybe should depend on map scale)
private static final float[][] PROFILE_DOME = new float[][]{
{1, 0},
{0.825f, 0.5f},
{0.5f, 0.825f},
{0, 1}};
private static final float[][] PROFILE_HIPPED = new float[][]{
{1, 0},
{0, 1}};
private static final float[][] PROFILE_MANSARD = new float[][]{
{1, 0},
{0.75f, 0.75f},
{0, 1}};
private static final float[][] PROFILE_ONION = new float[][]{
{1, 0},
{0.2f, 0.01f},
{0.875f, 0.1875f},
{1, 0.375f},
{0.875f, 0.5625f},
{0.5f, 0.75f},
{0.2f, 0.8125f},
{0, 1}};
private static final float[][] PROFILE_SALTBOX = new float[][]{
{1, 0},
{0.5f, 1},
{0, 1}};
/**
* Adds point to ridgePoints and snaps it to a point which is in radius of SNAP_THRESHOLD.
*/
private static void addSnapRidgePoint(int id, float[] point, TreeMap ridgePoints) {
// Simplify ridgePoints
if (point == null) return;
for (float[] ridPoint : ridgePoints.values()) {
if (ridPoint == null) {
log.debug("Ridge point not found!");
continue;
}
if (GeometryUtils.distance2D(ridPoint, point) < SNAP_THRESHOLD) {
ridgePoints.put(id, ridPoint);
return;
}
}
ridgePoints.put(id, point);
}
/**
* Calculates a circle mesh of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @return true if calculation succeeded, false otherwise
*/
public static boolean calcCircleMesh(GeometryBuffer element, float minHeight, float maxHeight, String roofShape) {
float[] points = element.points;
int[] index = element.index;
boolean outerProcessed = false;
for (int i = 0, pointPos = 0; i < index.length && !outerProcessed; i++) {
if (index[i] < 0) {
break;
}
int numSections = index[i] / 2;
if (numSections < 0) continue;
outerProcessed = true;
// Init mesh
GeometryBuffer mesh;
mesh = initCircleMesh(getProfile(roofShape), numSections);
// Calculate center and load points
float centerX = 0;
float centerY = 0;
float radius = 0;
List point3Fs = new ArrayList<>();
for (int j = 0; j < (numSections * 2); j += 2, pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
point3Fs.add(new float[]{x, y, minHeight});
centerX += x;
centerY += y;
}
centerX = centerX / numSections;
centerY = centerY / numSections;
// Calc max radius
for (float[] point3F : point3Fs) {
float difX = point3F[0] - centerX;
float difY = point3F[1] - centerY;
float tmpR = (float) Math.sqrt(difX * difX + difY * difY);
if (tmpR > radius) {
radius = tmpR;
}
}
element.points = mesh.points;
// Calc radius and adjust angle
int numPointsPerSection = (element.points.length / (3 * numSections));
float heightRange = maxHeight - minHeight;
for (int k = 0, j = 0; k < numSections; k++) {
float px = point3Fs.get(k)[0] - centerX;
float py = point3Fs.get(k)[1] - centerY;
float phi = (float) Math.atan2(py, px);
int sectionLimit = (numPointsPerSection + numPointsPerSection * k) * 3;
for (boolean first = true; j < sectionLimit; j = j + 3) {
float r = element.points[j + 0] * radius; // Set radius to initial x (always positive) stretched with individual element radius
px = (float) (r * Math.cos(phi));
py = (float) (r * Math.sin(phi));
if (!first) {
element.points[j + 0] = centerX + px;
element.points[j + 1] = centerY + py;
element.points[j + 2] = minHeight + element.points[j + 2] * heightRange;
} else {
// Set lowest points to outline points.
first = false;
element.points[j + 0] = point3Fs.get(k)[0];
element.points[j + 1] = point3Fs.get(k)[1];
element.points[j + 2] = minHeight;
}
}
}
element.index = mesh.index;
element.pointNextPos = element.points.length;
}
element.type = GeometryBuffer.GeometryType.TRIS;
return true;
}
/**
* Calculates a flat mesh of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @return true if calculation succeeded, false otherwise
*/
public static boolean calcFlatMesh(GeometryBuffer element, float maxHeight) {
if (Tessellator.tessellate(element, element) == 0) return false;
float[] points = element.points;
List point3Fs = new ArrayList<>();
// Calculate center and load points
for (int pointPos = 0; pointPos < points.length; pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
point3Fs.add(new float[]{x, y, maxHeight});
}
points = new float[3 * point3Fs.size()];
for (int i = 0; i < point3Fs.size(); i++) {
int pPos = 3 * i;
float[] point3D = point3Fs.get(i);
points[pPos + 0] = point3D[0];
points[pPos + 1] = point3D[1];
points[pPos + 2] = point3D[2];
}
element.points = points;
element.pointNextPos = element.points.length;
element.type = GeometryBuffer.GeometryType.TRIS;
return true;
}
/**
* Calculates a mesh for the outlines of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @return true if calculation succeeded, false otherwise
*/
public static boolean calcOutlines(GeometryBuffer element, float minHeight, float maxHeight) {
float[] points = element.points;
int[] index = element.index;
element.points = null;
element.index = null;
for (int i = 0, pointPos = 0; i < index.length; i++) {
if (index[i] < 0) {
break;
}
int numPoints = index[i] / 2;
if (numPoints < 0) continue;
List point3Fs = new ArrayList<>();
// load points
for (int j = 0; j < numPoints; j++, pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
point3Fs.add(new float[]{x, y, minHeight});
point3Fs.add(new float[]{x, y, maxHeight});
}
// Write index: index gives the first point of triangle mesh (divided 3)
int[] meshIndex = new int[numPoints * 6]; // 3 vertices and each side needs 2 triangles
for (int j = 0; j < point3Fs.size(); j = j + 2) {
int pos = 3 * j; // triangle mesh
meshIndex[pos + 2] = j;
meshIndex[pos + 1] = (j + 1) % point3Fs.size();
meshIndex[pos + 0] = (j + 3) % point3Fs.size();
meshIndex[pos + 5] = (j + 3) % point3Fs.size();
meshIndex[pos + 4] = (j + 2) % point3Fs.size();
meshIndex[pos + 3] = (j);
}
// Write points
float[] meshPoints = new float[point3Fs.size() * 3];
for (int j = 0; j < point3Fs.size(); j++) {
int pos = 3 * j;
meshPoints[pos + 0] = point3Fs.get(j)[0];
meshPoints[pos + 1] = point3Fs.get(j)[1];
meshPoints[pos + 2] = point3Fs.get(j)[2];
}
// Init points and indices or add more polygons (e.g. inner rings)
if (element.points == null) {
element.points = meshPoints;
} else {
float[] tmpPoints = element.points;
element.points = new float[tmpPoints.length + meshPoints.length];
System.arraycopy(tmpPoints, 0, element.points, 0, tmpPoints.length);
System.arraycopy(meshPoints, 0, element.points, tmpPoints.length, meshPoints.length);
}
if (element.index == null) {
element.index = meshIndex;
} else {
int[] tmpIndex = element.index;
element.index = new int[tmpIndex.length + meshIndex.length];
System.arraycopy(tmpIndex, 0, element.index, 0, tmpIndex.length);
// Shift all indices with previous element.points.length
for (int k = 0; k < meshIndex.length; k++) {
element.index[k + tmpIndex.length] = meshIndex[k] + (element.pointNextPos / 3);
}
}
element.pointNextPos = element.points.length;
}
if (element.points == null) {
return false;
}
//element.indexCurrentPos = 0;
element.type = GeometryBuffer.GeometryType.TRIS;
return true;
}
/**
* Calculates a pyramidal mesh of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @return true if calculation succeeded, false otherwise
*/
public static boolean calcPyramidalMesh(GeometryBuffer element, float minHeight, float maxHeight) {
float[] points = element.points;
int[] index = element.index;
float[] topPoint = new float[3];
topPoint[2] = maxHeight;
for (int i = 0, pointPos = 0; i < index.length; i++) {
if (index[i] < 0) {
break;
}
if (i > 0) break; // May add inner rings
int numPoints = index[i] / 2;
if (numPoints < 0) continue;
// Init top of roof (attention with pointPos)
GeometryUtils.center(points, pointPos, numPoints << 1, topPoint);
List point3Fs = new ArrayList<>();
// Load points
for (int j = 0; j < (numPoints * 2); j += 2, pointPos += 2)
point3Fs.add(new float[]{points[pointPos], points[pointPos + 1], minHeight});
// Write index: index gives the first point of triangle mesh (divided 3)
int[] meshIndex = new int[numPoints * 3];
for (int j = 0; j < point3Fs.size(); j++) {
int pos = 3 * j; // triangle mesh
meshIndex[pos + 0] = j;
meshIndex[pos + 1] = (j + 1) % point3Fs.size();
meshIndex[pos + 2] = point3Fs.size();
}
// Write points
point3Fs.add(topPoint);
float[] meshPoints = new float[point3Fs.size() * 3];
for (int j = 0; j < point3Fs.size(); j++) {
int pos = 3 * j;
float[] point3D = point3Fs.get(j);
meshPoints[pos + 0] = point3D[0];
meshPoints[pos + 1] = point3D[1];
meshPoints[pos + 2] = point3D[2];
}
element.points = meshPoints;
element.index = meshIndex;
//element.indexCurrentPos = 0;
element.pointNextPos = meshPoints.length;
}
element.type = GeometryBuffer.GeometryType.TRIS;
return true;
}
/**
* Calculates a ridge mesh of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @param minHeight the minimum height
* @param maxHeight the maximum height
* @param orientationAcross indicates if ridge is parallel to short side
* @param roofShape the roof shape
* @param specialParts element to add missing parts of underlying element
* @return true if calculation succeeded, false otherwise
*/
public static boolean calcRidgeMesh(GeometryBuffer element, float minHeight, float maxHeight, boolean orientationAcross, String roofShape, GeometryBuffer specialParts) {
float[] points = element.points;
int[] index = element.index;
boolean isGabled = isGabled(roofShape);
for (int i = 0, pointPos = 0; i < index.length; i++) {
if (index[i] < 0) {
break;
}
if (i > 0) break; // Handle only first polygon
int numPoints = index[i] / 2;
if (numPoints < 0) continue;
if (numPoints < 4 || (!isGabled && orientationAcross)) {
calcPyramidalMesh(element, minHeight, maxHeight);
return true;
}
List point3Fs = new ArrayList<>();
// Calculate center and load points
for (int j = 0; j < (numPoints * 2); j += 2, pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
point3Fs.add(new float[]{x, y, minHeight});
}
// Number of ground points
int groundSize = point3Fs.size();
// Calc vectors
List lengths = new ArrayList<>();
List normVectors = GeometryUtils.normalizedVectors2D(point3Fs, lengths);
List simpleAngles = getSimpleAngles(normVectors);
Integer indexStart = getIndexStart(simpleAngles, lengths, orientationAcross);
int countConcavAngles = 0;
for (Byte simpleAngle : simpleAngles) {
if (simpleAngle < -1)
countConcavAngles++;
}
// Calc different mesh, if roof has no nearly right angle
if (indexStart == null) {
if (isGabled)
return calcSimpleGabledMesh(element, minHeight, maxHeight, orientationAcross, specialParts);
else
return calcPyramidalMesh(element, minHeight, maxHeight);
}
List bisections = getBisections(normVectors);
List intersections = new ArrayList<>();
// Calc intersection of bisection
for (int k = 0; k < groundSize; k++) {
int nextTurn = getIndexNextTurn(k, simpleAngles);
float[] pA = point3Fs.get(nextTurn);
float[] pB = point3Fs.get(k);
intersections.add(GeometryUtils.intersectionLines2D(pA, bisections.get(nextTurn), pB, bisections.get(k)));
}
// Calc ridge points
TreeMap ridgePoints = new TreeMap<>();
TreeMap ridgeLines = new TreeMap<>();
HashSet gablePoints = new HashSet<>(); // Only used if gabled
Integer currentRidgeInd = null;
boolean isOdd = false;
for (int k = 0; k < groundSize; k++) {
int shift = (k + indexStart) % groundSize;
byte direction = simpleAngles.get(shift);
if (direction == 0) {
continue; // direction is similar to last one
} else if (direction < 0) {
// If shape turns left (concave)
float[] positionRidgeA = null;
float[] positionRidgeB = null;
// Check two previous corners
Integer indexPrevious = getIndexPreviousConvexTurn(shift, simpleAngles);
Integer indexPrevious2 = getIndexPreviousConvexTurn(indexPrevious == null ? shift - 1 : indexPrevious, simpleAngles);
if (indexPrevious != null && indexPrevious2 != null) {
// Write two previous
if (!ridgeLines.containsKey(indexPrevious2)) {
ridgeLines.put(indexPrevious2, normVectors.get(indexPrevious));
}
positionRidgeA = intersections.get(indexPrevious2);
currentRidgeInd = indexPrevious2;
if (isGabled) {
positionRidgeA = GeometryUtils.intersectionLines2D(positionRidgeA, ridgeLines.get(indexPrevious2), point3Fs.get(indexPrevious2), normVectors.get(indexPrevious2));
gablePoints.add(indexPrevious2);
}
ridgePoints.put(indexPrevious2, positionRidgeA);
// Remove previous ridge, if exists
gablePoints.remove(indexPrevious);
ridgePoints.remove(indexPrevious);
ridgeLines.remove(indexPrevious);
}
// Check two next corners
Integer indexNext = getIndexNextConvexTurn(shift, simpleAngles);
Integer indexNext2 = getIndexNextConvexTurn(indexNext == null ? shift + 1 : indexNext, simpleAngles);
if (indexNext != null && indexNext2 != null) {
if (ridgePoints.get(indexNext) == null) {
// Write both next
if (!ridgeLines.containsKey(indexNext)) {
ridgeLines.put(indexNext, normVectors.get(indexNext2));
}
positionRidgeB = intersections.get(indexNext);
if (isGabled) {
positionRidgeB = GeometryUtils.intersectionLines2D(positionRidgeB, ridgeLines.get(indexNext), point3Fs.get(indexNext), normVectors.get(indexNext));
gablePoints.add(indexNext);
}
ridgePoints.put(indexNext, positionRidgeB);
} else {
positionRidgeB = ridgePoints.get(indexNext);
}
}
// Handle multiple concaves
if (positionRidgeA == null || positionRidgeB == null) {
if (positionRidgeA == null && positionRidgeB == null && currentRidgeInd != null) {
positionRidgeA = ridgePoints.get(currentRidgeInd);
}
if (positionRidgeA != null && positionRidgeB == null) { // Next index is concave
positionRidgeA = GeometryUtils.intersectionLines2D(positionRidgeA, ridgeLines.get(currentRidgeInd), point3Fs.get(shift), bisections.get(shift));
currentRidgeInd = shift;
addSnapRidgePoint(shift, positionRidgeA, ridgePoints);
ridgeLines.put(shift, normVectors.get(shift)); // Add ridgeLine, if concave
isOdd = false;
continue;
} else if (positionRidgeA == null && positionRidgeB != null) { // Previous index is concave
positionRidgeA = GeometryUtils.intersectionLines2D(positionRidgeB, ridgeLines.get(indexNext), point3Fs.get(shift), bisections.get(shift));
addSnapRidgePoint(shift, positionRidgeA, ridgePoints);
currentRidgeInd = null;
isOdd = false;
continue;
} else {
log.debug("Should never happen, because positionRidge wouldn't be null then");
currentRidgeInd = null;
continue;
}
}
// Calc actual concave
if (currentRidgeInd == null || indexNext == null || ridgeLines.get(currentRidgeInd) == null || ridgeLines.get(indexNext) == null) {
log.debug("Concave shape not calculated correctly: " + element.toString());
currentRidgeInd = null;
continue;
}
float[] intersection = GeometryUtils.intersectionLines2D(positionRidgeA, ridgeLines.get(currentRidgeInd), positionRidgeB, ridgeLines.get(indexNext));
addSnapRidgePoint(shift, intersection, ridgePoints);
// Set opposite ridge, if only one concave corner
if (countConcavAngles == 1) {
Integer opposite = getIndexNextConvexTurn(indexNext2, simpleAngles);
if (opposite != null) {
if (isGabled)
gablePoints.remove(opposite);
ridgePoints.put(opposite, intersection);
}
}
// Reset ridges
currentRidgeInd = null;
isOdd = false;
continue;
}
// Regular right angle (convex turn)
if (isOdd) {
isOdd = false;
continue;
}
if (simpleAngles.get(shift) > 1) {
isOdd = true;
}
if (ridgePoints.containsKey(shift) && ridgeLines.containsKey(shift)) {
currentRidgeInd = shift;
continue;
}
if (currentRidgeInd != null) {
float[] intersection;
// If is gabled, then use the normal line as intersection instead of bisection, but if the angle is not right, this is usually not a gable point
if (isGabled && direction > 1) {
if (ridgePoints.get(currentRidgeInd) == null) {
log.debug("Gabled intersection calc failed");
currentRidgeInd = null;
continue;
}
intersection = GeometryUtils.intersectionLines2D(ridgePoints.get(currentRidgeInd), ridgeLines.get(currentRidgeInd), point3Fs.get(shift), normVectors.get(shift));
if (intersection == null) {
log.debug("Gabled intersection calc failed");
currentRidgeInd = null;
continue;
}
gablePoints.add(shift);
ridgePoints.put(shift, intersection);
} else {
intersection = GeometryUtils.intersectionLines2D(ridgePoints.get(currentRidgeInd), ridgeLines.get(currentRidgeInd), point3Fs.get(shift), bisections.get(shift));
addSnapRidgePoint(shift, intersection, ridgePoints);
}
if (isOdd) {
currentRidgeInd = null;
} else {
ridgeLines.put(shift, normVectors.get(shift));
currentRidgeInd = shift;
}
} else {
Integer indexNext = getIndexNextConvexTurn(shift, simpleAngles);
if (indexNext == null) continue;
if (!ridgeLines.containsKey(shift)) {
ridgeLines.put(shift, normVectors.get(indexNext));
}
currentRidgeInd = shift;
float[] ridgePos = intersections.get(shift);
if (isGabled) {
ridgePos = GeometryUtils.intersectionLines2D(ridgePos, ridgeLines.get(currentRidgeInd), point3Fs.get(shift), normVectors.get(shift));
gablePoints.add(shift);
}
addSnapRidgePoint(shift, ridgePos, ridgePoints);
}
}
if (ridgePoints.isEmpty()) {
calcPyramidalMesh(element, minHeight, maxHeight);
return true;
}
Iterator> ridgeIt = ridgePoints.entrySet().iterator();
while (ridgeIt.hasNext()) {
Map.Entry ridgeEntry = ridgeIt.next();
Integer key = ridgeEntry.getKey();
if (ridgeEntry.getValue() == null) {
log.debug("Ridge calculation failed at point " + key);
ridgeIt.remove();
continue;
}
// Only remove ridgePoint at concave corners
if (!isGabled || simpleAngles.get(key) < 0) {
boolean isIn = GeometryUtils.pointInPoly(ridgeEntry.getValue()[0], ridgeEntry.getValue()[1], points, points.length, 0);
if (!isIn) {
// FIXME can improve shapes with concaves that intersect each other and remove shapes which have ridgepoints outside the outline
if (!IMPROVE_RIDGE_CALCULATION) {
if (isGabled) {
return calcSimpleGabledMesh(element, minHeight, maxHeight, orientationAcross, specialParts);
} else return calcFlatMesh(element, minHeight);
}
}
}
}
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
int ridgePointSize = ridgePoints.size();
float[] meshPoints = new float[(groundSize * profileSizePlus + ridgePointSize) * 3]; //(ridgePoints * 3 = 6)
List meshVarIndex = new ArrayList<>();
// Add special building parts
List meshPartVarIndex = null;
if (isGabled && specialParts != null) {
meshPartVarIndex = new ArrayList<>();
}
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
float[] p = point3Fs.get(l);
int ridgePointIndex1 = l;
while (!ridgePoints.containsKey(ridgePointIndex1)) {
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
boolean isGable = false;
if (meshPartVarIndex != null && gablePoints.contains(ridgePointIndex1) && getIndexNextTurn(ridgePointIndex1, simpleAngles).equals(getIndexNextTurn(l, simpleAngles))) {
isGable = true;
// Add missing parts to building
meshPartVarIndex.add(k + profileSize);
meshPartVarIndex.add((k + profileSizePlus + profileSize) % grRsSize);
meshPartVarIndex.add(ridgeIndex1 + grRsSize);
} else {
// Add first roof face
meshVarIndex.add(k + profileSize);
meshVarIndex.add((k + profileSizePlus + profileSize) % grRsSize);
meshVarIndex.add(ridgeIndex1 + grRsSize);
}
// Add second face, if necessary
int ridgePointIndex2 = (l + 1) % groundSize;
while (!ridgePoints.containsKey(ridgePointIndex2)) {
ridgePointIndex2 = (ridgePointIndex2 + groundSize - 1) % groundSize; // Decrease ridgePointIndex
}
if (ridgePointIndex2 != ridgePointIndex1) {
int ridgeIndex2 = ridgePoints.headMap(ridgePointIndex2).size(); // Set ridgeIndex to position in ridgePoints
meshVarIndex.add(ridgeIndex1 + grRsSize);
meshVarIndex.add((k + profileSizePlus + profileSize) % grRsSize);
meshVarIndex.add(ridgeIndex2 + grRsSize);
}
// Write ground points
int offset = 3 * k;
meshPoints[offset + 0] = p[0];
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][1];
}
}
// Tessellate top, if necessary (can be used to improve wrong rendered roofs)
if (ridgePointSize > 2) {
HashSet ridgeSkipFaceIndex = new HashSet<>();
boolean isTessellateAble = true;
for (int k = 0; k < groundSize; k++) {
if (!isTessellateAble || ridgePoints.get(k) == null) continue;
Integer middle = null;
for (int m = k + 1; m <= k + groundSize; m++) {
int secIndex = m % groundSize;
if (ridgePoints.get(secIndex) == null) continue;
if (middle == null) {
middle = secIndex;
} else {
float isClockwise = GeometryUtils.isTrisClockwise(ridgePoints.get(k), ridgePoints.get(middle), ridgePoints.get(secIndex));
if (Math.abs(isClockwise) < 0.001) {
ridgeSkipFaceIndex.add(middle);
if (Arrays.equals(ridgePoints.get(k), ridgePoints.get(secIndex)))
ridgeSkipFaceIndex.add(k);
}
if (isClockwise > 0 && IMPROVE_RIDGE_CALCULATION) {
// TODO Improve handling of counter clockwise faces and support multiple faces
isTessellateAble = false;
break;
//return calcSimpleGabledMesh(element, minHeight, maxHeight, orientationAcross, specialParts);
}
break;
}
}
}
int faceLength = ridgePointSize - ridgeSkipFaceIndex.size();
if (isTessellateAble && faceLength > 0) {
float[] gbPoints = new float[2 * faceLength];
int k = 0;
List faceIndex = new ArrayList<>(); // Store used indices
for (int m = 0; m < groundSize; m++) {
float[] point = ridgePoints.get(m);
if (ridgeSkipFaceIndex.contains(m) || point == null) {
continue;
}
faceIndex.add(m);
gbPoints[2 * k] = point[0];
gbPoints[2 * k + 1] = point[1];
k++;
}
GeometryBuffer buffer = new GeometryBuffer(gbPoints, new int[]{2 * faceLength});
if (Tessellator.tessellate(buffer, buffer) != 0) {
for (int ind : buffer.index) {
// Get position in ridgePoints, considering skipped points
meshVarIndex.add(ridgePoints.headMap(faceIndex.get(ind)).size() + grRsSize);
}
} else {
// TODO Improve wrong or not tessellated faces
if (!IMPROVE_RIDGE_CALCULATION) {
if (isGabled) {
return calcSimpleGabledMesh(element, minHeight, maxHeight, orientationAcross, specialParts);
} else return calcFlatMesh(element, minHeight);
}
}
}
}
// Replace in Java 8 / min API 24
int[] meshIndex = new int[meshVarIndex.size()]; // new int[(groundSize + ridgePointSize) * 3]; // 3 vertices per point + 6 vertices for left and right roof
for (int k = 0; k < meshIndex.length; k++)
meshIndex[k] = meshVarIndex.get(k);
for (int k = 0, l = 0; k < groundSize; k++) {
// Add ridge points
float[] tmp = ridgePoints.get(k);
if (tmp != null) {
int ppos = 3 * (l + grRsSize);
meshPoints[ppos + 0] = tmp[0];
meshPoints[ppos + 1] = tmp[1];
meshPoints[ppos + 2] = maxHeight;
l++;
}
}
// Add special parts e.g. for gabled roofs
if (specialParts != null && meshPartVarIndex != null) {
// Replace in Java 8 / min API 24
int[] meshPartsIndex = new int[meshPartVarIndex.size()];
for (int k = 0; k < meshPartsIndex.length; k++)
meshPartsIndex[k] = meshPartVarIndex.get(k);
specialParts.points = meshPoints;
specialParts.index = meshPartsIndex;
specialParts.pointNextPos = meshPoints.length;
specialParts.type = GeometryBuffer.GeometryType.TRIS;
}
element.points = meshPoints;
element.index = meshIndex;
element.pointNextPos = meshPoints.length;
element.type = GeometryBuffer.GeometryType.TRIS;
}
return element.isTris();
}
/**
* Calculates a simple gabled mesh of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @return true if calculation succeeded, false otherwise
*/
private static boolean calcSimpleGabledMesh(GeometryBuffer element, float minHeight, float maxHeight, boolean orientationAcross, GeometryBuffer specialParts) {
float[] points = element.points;
int[] index = element.index;
for (int i = 0, pointPos = 0; i < index.length; i++) {
if (index[i] < 0) {
break;
}
if (i > 0) break; // Handle only first polygon
int numPoints = index[i] / 2;
if (numPoints < 0) continue;
if (numPoints < 4) {
calcPyramidalMesh(element, minHeight, maxHeight);
return true;
}
List point3Fs = new ArrayList<>();
// Calculate center and load points
for (int j = 0; j < (numPoints * 2); j += 2, pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
point3Fs.add(new float[]{x, y, minHeight});
}
// Calc vectors
int groundSize = point3Fs.size();
List lengths = new ArrayList<>();
List normVectors = GeometryUtils.normalizedVectors2D(point3Fs, lengths);
List simpleAngles = getSimpleAngles(normVectors);
int indexStart = getIndicesLongestSide(simpleAngles, lengths, null)[0];
if (orientationAcross) {
Integer tmp = getIndexPreviousConvexTurn(indexStart, simpleAngles);
if (tmp == null) {
tmp = getIndexNextTurn(indexStart, simpleAngles);
}
indexStart = tmp;
}
float[] vL = normVectors.get(indexStart);
float[] pL = point3Fs.get(indexStart);
float[] splitLinePoint = null;
float maxDist = 0;
for (float[] point : point3Fs) {
float curDist = GeometryUtils.distancePointLine2D(point, pL, vL);
if (curDist > maxDist) {
maxDist = curDist;
splitLinePoint = point; // Farthest point from line
}
}
// Scale of normal vec
maxDist = Math.signum(GeometryUtils.isTrisClockwise(
pL,
GeometryUtils.sumVec(pL, vL),
splitLinePoint)) * (maxDist / 2);
float[] normL = new float[]{-vL[1], vL[0]}; // Normal vec to line
normL = GeometryUtils.scale(normL, (float) (maxDist / Math.sqrt(GeometryUtils.dotProduct(normL, normL)))); // normalize vec
splitLinePoint = GeometryUtils.sumVec(pL, normL);
float degreeNormL = (float) Math.atan2(normL[0], -normL[1]) * MathUtils.radiansToDegrees;
// Split polygon
int sideChange = 0;
List elementPoints1 = new ArrayList<>();
List elementPoints2 = new ArrayList<>();
float[] secSplitPoint = GeometryUtils.sumVec(splitLinePoint, vL);
float sideLastPoint = Math.signum(GeometryUtils.isTrisClockwise(splitLinePoint, secSplitPoint, point3Fs.get(groundSize - 1)));
degreeNormL = sideLastPoint > 0 ? degreeNormL : (degreeNormL + 180f) % 360; // Correct angle
List intersection1 = new ArrayList<>(), intersection2 = new ArrayList<>();
for (int k = 0; k < groundSize; k++) {
// If point is not on the same side as the previous point, the split line intersect and can calc split point
float sideCurPoint = Math.signum(GeometryUtils.isTrisClockwise(splitLinePoint, secSplitPoint, point3Fs.get(k)));
if (sideCurPoint != sideLastPoint) {
if (sideChange > 2 && !IMPROVE_RIDGE_CALCULATION)
return calcFlatMesh(element, minHeight); // TODO Improve multiple side changes
int indexPrev = (k + groundSize - 1) % groundSize;
float[] intersection = GeometryUtils.intersectionLines2D(splitLinePoint, vL, point3Fs.get(indexPrev), normVectors.get(indexPrev));
elementPoints1.add(intersection);
elementPoints2.add(intersection);
intersection1.add(elementPoints1.size() - 1);
intersection2.add(elementPoints2.size() - 1);
sideChange++;
}
if (sideChange % 2 == 0) {
elementPoints1.add(point3Fs.get(k));
} else {
elementPoints2.add(point3Fs.get(k));
}
sideLastPoint = sideCurPoint;
}
GeometryBuffer geoEle1 = new GeometryBuffer(elementPoints1.size(), 1);
for (int k = 0; k < elementPoints1.size(); k++) {
geoEle1.points[2 * k] = elementPoints1.get(k)[0];
geoEle1.points[2 * k + 1] = elementPoints1.get(k)[1];
}
geoEle1.index[0] = geoEle1.points.length;
geoEle1.pointNextPos = geoEle1.points.length;
GeometryBuffer geoEle2 = new GeometryBuffer(elementPoints2.size(), 1);
for (int k = 0; k < elementPoints2.size(); k++) {
geoEle2.points[2 * k] = elementPoints2.get(k)[0];
geoEle2.points[2 * k + 1] = elementPoints2.get(k)[1];
}
geoEle2.index[0] = geoEle2.points.length;
geoEle2.pointNextPos = geoEle2.points.length;
GeometryBuffer specialParts1 = new GeometryBuffer(geoEle1);
GeometryBuffer specialParts2 = new GeometryBuffer(geoEle2);
if (!(calcSkillionMesh(geoEle1, minHeight, maxHeight, degreeNormL, specialParts1)
&& calcSkillionMesh(geoEle2, minHeight, maxHeight,
degreeNormL + 180, specialParts2))) {
return false;
}
// Adapt gable intersections to max height
for (Integer integer : intersection1) {
geoEle1.points[integer * 3 + 2] = maxHeight;
specialParts1.points[6 * integer + 5] = maxHeight;
}
for (Integer integer : intersection2) {
geoEle2.points[integer * 3 + 2] = maxHeight;
specialParts2.points[6 * integer + 5] = maxHeight;
}
// Merge buffers
mergeMeshGeometryBuffer(geoEle1, geoEle2, element);
mergeMeshGeometryBuffer(specialParts1, specialParts2, specialParts);
return true;
}
return false;
}
/**
* Calculates a skillion mesh of a Poly-GeometryBuffer.
*
* @param element the GeometryBuffer which is used to write the 3D mesh
* @param roofDegree the direction of slope
* @param specialParts the GeometryBuffer which is used to write the additional building parts
* @return true if calculation succeeded, false otherwise
*/
public static boolean calcSkillionMesh(GeometryBuffer element, float minHeight, float maxHeight, float roofDegree, GeometryBuffer specialParts) {
float[] points = element.points;
int[] index = element.index;
for (int i = 0, pointPos = 0; i < index.length; i++) {
if (index[i] < 0) {
break;
}
if (i > 0) break; // May add inner rings
int numPoints = index[i] / 2;
if (numPoints < 0) continue;
List point3Fs = new ArrayList<>();
for (int j = 0; j < (numPoints * 2); j += 2, pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
point3Fs.add(new float[]{x, y, minHeight});
}
boolean hasOutlines = calcOutlines(specialParts, minHeight, maxHeight);
// Use 3 points that match the angle the best and use as plane
float[] min1 = null, min2 = null, max1 = null, max2 = null;
float minDif1, minDif2, maxDif1, maxDif2;
minDif1 = minDif2 = Float.MAX_VALUE;
maxDif1 = maxDif2 = 0;
if (calcFlatMesh(element, maxHeight)) {
// To positive radian
roofDegree = ((MathUtils.degreesToRadians * roofDegree) + MathUtils.PI2) % MathUtils.PI2;
float[] vRidge = new float[2];
vRidge[0] = (float) Math.sin(roofDegree);
vRidge[1] = (float) -Math.cos(roofDegree);
vRidge = GeometryUtils.scale(vRidge, 100000000); // Use very large value, so the distances are nearly parallel
for (int k = 0; k < point3Fs.size(); k++) {
float[] point = point3Fs.get(k);
float vx = vRidge[0] - point[0];
float vy = vRidge[1] - point[1];
float currentDiff = (float) Math.sqrt(vx * vx + vy * vy);
if (max1 == null || currentDiff > maxDif1) {
if (max1 != null) {
max2 = max1;
maxDif2 = maxDif1;
}
max1 = point;
maxDif1 = currentDiff;
} else if (max2 == null || currentDiff > maxDif2) {
max2 = point;
maxDif2 = currentDiff;
}
if (min1 == null || currentDiff < minDif1) {
if (min1 != null) {
min2 = min1;
minDif2 = minDif1;
}
min1 = point;
minDif1 = currentDiff;
} else if (min2 == null || currentDiff < minDif2) {
min2 = point;
minDif2 = currentDiff;
}
}
if (min1 == max1) return false;
float[] normal, zVector = new float[]{0, 0, 1}; // height intersection
min1[2] = minHeight;
max1[2] = maxHeight;
// Use lower two points if they promise better results (e.g. at triangles)
if (Math.abs(minDif2 - minDif1) < Math.abs(maxDif2 - maxDif1)) {
min2[2] = minHeight; // Note: min2 == max2 is possible
normal = GeometryUtils.normalOfPlane(min1, max1, min2);
} else {
max2[2] = maxHeight;
normal = GeometryUtils.normalOfPlane(min1, max1, max2);
}
// Calc intersection points of ground points with plane
for (int k = 0; k < point3Fs.size(); k++) {
float[] intersection = GeometryUtils.intersectionLinePlane(point3Fs.get(k), zVector, min1, normal);
if (intersection == null) return false;
intersection[2] = intersection[2] > (2 * maxHeight) ? maxHeight : (intersection[2] < minHeight ? minHeight : intersection[2]);
element.points[3 * k + 2] = intersection[2];
if (hasOutlines) {
specialParts.points[6 * k + 5] = intersection[2]; // Every sixth point is height of k
}
}
return true;
}
}
return false;
}
/**
* @return the bisections of vectors
*/
private static List getBisections(List normVectors) {
int size = normVectors.size();
List bisections = new ArrayList<>();
// Calc bisections
for (int k = 0; k < size; k++) {
float[] vBC = normVectors.get((k + size - 1) % size);
float[] vBA = normVectors.get(k);
// Change direction to get correct angle
vBC = Arrays.copyOf(vBC, vBC.length);
vBC[0] = -vBC[0];
vBC[1] = -vBC[1];
// Calc bisection
bisections.add(GeometryUtils.bisectionNorm2D(vBC, vBA));
}
return bisections;
}
/**
* @param color the color as string (see http://wiki.openstreetmap.org/wiki/Key:colour)
* @param hsv the HSV color values to modify given color
* @param relative declare if colors are modified relative to their values
* @return the color as integer (8 bit each a, r, g, b)
*/
public static int getColor(String color, Color.HSV hsv, boolean relative) {
if ("transparent".equals(color))
return Color.get(0, 1, 1, 1);
int c;
if (color.charAt(0) == '#')
c = Color.parseColor(color, Color.CYAN);
else {
Integer css = ColorsCSS.get(color);
if (css == null) {
log.debug("unknown color:{}", color);
c = Color.CYAN;
} else
c = css;
}
return hsv.mod(c, relative);
}
/**
* @return the index of convex turn after specified index or null, if it's concave.
*/
private static Integer getIndexNextConvexTurn(int index, List simpleAngles) {
for (int i = index + 1; i < simpleAngles.size() + index; i++) {
int iMod = i % simpleAngles.size();
if (simpleAngles.get(iMod) > 0) {
return iMod;
} else if (simpleAngles.get(iMod) < 0) {
return null;
}
}
return (index + 1) % simpleAngles.size();
}
/**
* @return the index of next turn after specified index
*/
private static Integer getIndexNextTurn(int index, List simpleAngles) {
for (int i = index + 1; i < simpleAngles.size() + index; i++) {
int iMod = i % simpleAngles.size();
if (simpleAngles.get(iMod) != 0) {
return iMod;
}
}
return (index + 1) % simpleAngles.size();
}
/**
* @return the index of previous convex turn at specified index
*/
private static Integer getIndexPreviousConvexTurn(int index, List simpleAngles) {
for (int i = simpleAngles.size() + index - 1; i >= 0; i--) {
int iMod = i % simpleAngles.size();
if (simpleAngles.get(iMod) > 0) {
return iMod;
} else if (simpleAngles.get(iMod) < 0) {
return null;
}
}
return (simpleAngles.size() + index - 1) % simpleAngles.size();
}
/**
* @return the best index to begin a calculation
*/
private static Integer getIndexStart(List simpleAngles, List lengths, boolean directionAcross) {
int size = simpleAngles.size();
Integer indexStart = null;
Integer concaveStart = null;
for (int i = 0; i < size; i++) {
if (indexStart != null && concaveStart != null) break;
if (indexStart == null && simpleAngles.get(i) > 1) {
// Use first angle as start index;
indexStart = i;
} else if (concaveStart == null && simpleAngles.get(i) < -1) {
// A real concave corner
concaveStart = i;
}
}
if (indexStart == null) {
return null;
}
if (concaveStart != null) {
// look for next convex shape (point)
for (int i = concaveStart; i < size + indexStart; i++) {
if (simpleAngles.get(i % size) < 0) {
return i % size;
}
}
}
// Calculate longest side with right angle next to it.
int[] iLongSide = getIndicesLongestSide(simpleAngles, lengths, indexStart);
if (simpleAngles.get(iLongSide[1]) < 2) {
// If angle is not good to start a ridge use previous
indexStart = getIndexPreviousConvexTurn(iLongSide[0], simpleAngles);
} else {
indexStart = iLongSide[1]; // Get side next to longest one
}
// Direction across only possible if no concave shapes are present
if (directionAcross) {
return iLongSide[0];
}
return indexStart;
}
/**
* @param indexStart the start index, if already calculated (can be null)
* @return int[0] = start index, int[1] = end index
*/
private static int[] getIndicesLongestSide(List simpleAngles, List lengths, Integer indexStart) {
int[] iLongSide = new int[2];
int size = simpleAngles.size();
if (indexStart == null) {
for (int i = 0; i < size; i++) {
if (simpleAngles.get(i) > 0) {
// Use first convex angle as start index;
indexStart = i;
break;
}
}
}
float longestSideLength = 0, currentLength = 0;
int indexCurrentSide = indexStart;
int loopSize = size + indexStart;
for (int i = indexStart; i < loopSize; i++) {
if (i >= size) {
i -= size;
loopSize -= size;
}
if (simpleAngles.get(i) != 0) {
// Right angle
currentLength = lengths.get(i);
indexCurrentSide = i;
} else {
currentLength += lengths.get(i);
}
if (currentLength > longestSideLength) {
longestSideLength = currentLength;
iLongSide[0] = indexCurrentSide;
iLongSide[1] = (i + 1) % size;
}
}
return iLongSide;
}
/**
* @param material the material as string (see http://wiki.openstreetmap.org/wiki/Key:material and following pages)
* @param hsv the HSV color values to modify given material
* @return the color as integer (8 bit each a, r, g, b)
*/
public static int getMaterialColor(String material, Color.HSV hsv, boolean relative) {
int c;
if (material.charAt(0) == '#') {
c = Color.parseColor(material, Color.CYAN);
} else {
switch (material) {
case "roof_tiles":
c = Color.get(216, 167, 111);
break;
case "tile":
c = Color.get(216, 167, 111);
break;
case "concrete":
case "cement_block":
c = Color.get(210, 212, 212);
break;
case "metal":
c = 0xFFC0C0C0;
break;
case "tar_paper":
c = 0xFF969998;
break;
case "eternit":
c = Color.get(216, 167, 111);
break;
case "tin":
c = 0xFFC0C0C0;
break;
case "asbestos":
c = Color.get(160, 152, 141);
break;
case "glass":
c = Color.fade(Color.get(130, 224, 255), 0.6f);
break;
case "slate":
c = 0xFF605960;
break;
case "zink":
c = Color.get(180, 180, 180);
break;
case "gravel":
c = Color.get(170, 130, 80);
break;
case "copper":
// same as roof color:green
c = Color.get(150, 200, 130);
break;
case "wood":
c = Color.get(170, 130, 80);
break;
case "grass":
c = 0xFF50AA50;
break;
case "stone":
c = Color.get(206, 207, 181);
break;
case "plaster":
c = Color.get(236, 237, 181);
break;
case "brick":
c = Color.get(255, 217, 191);
break;
case "stainless_steel":
c = Color.get(153, 157, 160);
break;
case "gold":
c = 0xFFFFD700;
break;
default:
c = Color.CYAN;
log.debug("unknown material:{}", material);
break;
}
}
return hsv.mod(c, relative);
}
/**
* Get the profile (half cross section) of roof shape.
* profile[i][0]: x-coordinate
* profile[i][1]: z-coordinate
*
* @param roofShape the roof shape value
* @return the profile (read-only!)
*/
public static float[][] getProfile(String roofShape) {
switch (roofShape) {
case Tag.VALUE_ONION:
return PROFILE_ONION;
case Tag.VALUE_ROUND:
case Tag.VALUE_DOME:
return PROFILE_DOME;
case Tag.VALUE_SALTBOX:
return PROFILE_SALTBOX;
case Tag.VALUE_MANSARD:
case Tag.VALUE_GAMBREL:
return PROFILE_MANSARD;
case Tag.VALUE_GABLED:
case Tag.VALUE_HIPPED:
default:
return PROFILE_HIPPED;
}
}
/**
* @param normVectors the normalized vectors
* @return a list of simple angles:
* 0 straight
* (+/-) 1 (convex/concave) obtuse angle
* (+/-) 2 (convex/concave) right angle (or acute angle)
*
* Note lhs coordinate system.
* convex: turns right
* concave: turns left
*/
private static List getSimpleAngles(List normVectors) {
int size = normVectors.size();
// List angles = new ArrayList<>();
List simpAngls = new ArrayList<>();
float tmpAnlgeSum = 0;
float threshold = MathUtils.PI / 12;
for (int k = 0; k < size; k++) {
// Check angle between next and this vector
float[] v2 = normVectors.get(k);
float[] v1 = normVectors.get((k - 1 + size) % size);
float val = v1[0] * v2[0] + v1[1] * v2[1];
float angle = (float) Math.acos(Math.abs(val) > 1 ? Math.signum(val) : val);
// angles.add(angle);
// Positive turns right (convex), negative turns left (concave)
byte simpAngle = (byte) Math.signum(v1[0] * v2[1] - v1[1] * v2[0]);
if (angle > (MathUtils.PI / 2) - threshold) {
// Right angle
simpAngle *= 2;
tmpAnlgeSum = 0;
} else if (angle < threshold) {
tmpAnlgeSum += simpAngle * angle; // Many small angles, indicate a corner
if (Math.abs(tmpAnlgeSum) > threshold) {
// Can improve sum of concave/convex shapes
simpAngle = (byte) Math.signum(tmpAnlgeSum);
tmpAnlgeSum = 0;
} else
simpAngle = 0;
} else {
// Angle which is not right, and not straight
tmpAnlgeSum = 0;
}
simpAngls.add(simpAngle);
}
return simpAngls;
}
private static GeometryBuffer initCircleMesh(float[][] profile, int numSections) {
int indexSize = numSections * (profile.length - 1) * 2 * 3; // * 2 faces * 3 vertices
int[] meshIndex = new int[indexSize];
int meshSize = numSections * profile.length;
float[] meshPoints = new float[meshSize * 3];
for (int i = 0; i < numSections; i++) {
for (int j = 0; j < profile.length; j++) {
// Write point mesh
int pPos = 3 * (i * profile.length + j);
meshPoints[pPos + 0] = profile[j][0];
meshPoints[pPos + 1] = 0f;
meshPoints[pPos + 2] = profile[j][1];
// Write point indices
if (j != profile.length - 1) {
int iPos = 6 * (i * (profile.length - 1) + j); // 6 = 2 * Mesh * 3PointsPerMesh
pPos = pPos / 3;
meshIndex[iPos + 2] = pPos + 0;
meshIndex[iPos + 1] = pPos + 1;
meshIndex[iPos + 0] = (pPos + profile.length) % meshSize;
// FIXME if is last point, only one tris is needed, if top shape is closed
meshIndex[iPos + 5] = pPos + 1;
meshIndex[iPos + 4] = (pPos + profile.length + 1) % meshSize;
meshIndex[iPos + 3] = (pPos + profile.length) % meshSize;
}
}
}
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) {
if (!(gb1.isTris() && gb2.isTris())) return;
int gb1PointSize = gb1.points.length;
float[] mergedPoints = new float[gb1PointSize + gb2.points.length];
System.arraycopy(gb1.points, 0, mergedPoints, 0, gb1PointSize);
System.arraycopy(gb2.points, 0, mergedPoints, gb1PointSize, gb2.points.length);
out.points = mergedPoints;
out.pointNextPos = mergedPoints.length;
int gb1IndexSize = gb1.index.length;
int[] mergedIndices = new int[gb1IndexSize + gb2.index.length];
System.arraycopy(gb1.index, 0, mergedIndices, 0, gb1IndexSize);
gb1PointSize /= 3; // (x,y,z)
for (int k = 0; k < gb2.index.length; k++) {
mergedIndices[gb1IndexSize + k] = gb2.index[k] + gb1PointSize;
}
out.index = mergedIndices;
out.type = gb1.type;
}
private S3DBUtils() {
}
}