Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

Commit

Permalink
Merge pull request #9 from Amsterdam/bugfix/threshold-vertex-merging
Browse files Browse the repository at this point in the history
Bugfix/threshold vertex merging
  • Loading branch information
sambaas authored Feb 3, 2022
2 parents e44ab02 + e041fda commit 876487d
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 211 deletions.
1 change: 0 additions & 1 deletion TileBakeLibrary/BinaryMeshData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ public void ExportData(Tile tile, bool exportUVs = false)
{
mesh.vertices.Add(ConvertVertex(subobject.vertices[i]));
mesh.normals.Add(ConvertNormaltoBinary(subobject.normals[i]));

}

if (exportUVs)
Expand Down
166 changes: 91 additions & 75 deletions TileBakeLibrary/CityJSONToTileConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ public class CityJSONToTileConverter

private bool replaceExistingIDs = true;

private float maxNormalAngle = 5.0f;

private bool addToExistingTiles = false;

private bool exportUVCoordinates = false;
Expand All @@ -69,6 +67,16 @@ public class CityJSONToTileConverter
private float spikeCeiling = 0;
private float spikeFloor = 0;

/// <summary>
/// Sets the normal angle threshold for vertex+normal combinations to be considered the same
/// </summary>
/// <param name="mergeVerticesBelowNormalAngle">Angle in degrees.</param>
public void SetVertexMergeAngleThreshold(float mergeVerticesBelowNormalAngle)
{
Console.WriteLine($"Merging vertices with normal angle threshold: {mergeVerticesBelowNormalAngle}");
VertexNormalCombination.normalAngleComparisonThreshold = mergeVerticesBelowNormalAngle;
}

public void SetClipSpikes(bool setFunction, float ceiling, float floor)
{
clipSpikes = setFunction;
Expand Down Expand Up @@ -136,6 +144,11 @@ public void SetReplace(bool replace)
this.replaceExistingIDs = replace;
}

/// <summary>
/// Sets the output of UV coordinates.
/// CityJSON input should contain UV texture coordinates.
/// </summary>
/// <param name="exportUV"></param>
public void SetExportUV(bool exportUV)
{
this.exportUVCoordinates = exportUV;
Expand Down Expand Up @@ -394,89 +407,90 @@ private List<SubObject> CityJSONParseProcess(CityJSON cityJson)
int parsing = 0;
int simplifying = 0;
int tiling = 0;
var filterobjectsBucket = new ConcurrentBag<SubObject>();
var filterObjectsBucket = new ConcurrentBag<SubObject>();
int[] indices = Enumerable.Range(0, cityObjectCount).ToArray(); ;
//Turn cityobjects (and their children) into SubObject mesh data
var partitioner = Partitioner.Create(indices, EnumerablePartitionerOptions.NoBuffering);
Parallel.ForEach(partitioner, i =>
{
Interlocked.Increment(ref parsing);
CityObject cityObject = cityJson.LoadCityObjectByIndex(i, lod);
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");
var subObject = ToSubObjectMeshData(cityObject);
//cityJson.ClearCityObject(cityObject.keyName);
cityObject = null;
Interlocked.Decrement(ref parsing);

cityObject = null;
if (subObject == null)
{
Interlocked.Increment(ref done);
Interlocked.Increment(ref skipped);
return;
}
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");

if (subObject.maxVerticesPerSquareMeter > 0)
{
Interlocked.Increment(ref simplifying);
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");
subObject.SimplifyMesh();
Interlocked.Decrement(ref simplifying);
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");
}
else
{
if (maxNormalAngle != 0)
{

subObject.MergeSimilarVertices(maxNormalAngle);
}
}
if (clipSpikes)
{
subObject.ClipSpikes(spikeCeiling, spikeFloor);
}

if (TilingMethod == "TILED")
{
Interlocked.Increment(ref tiling);
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");
var newSubobjects = subObject.clipSubobject(new Vector2(tileSize, tileSize));
if (newSubobjects.Count == 0)
{
subObject.calculateNormals();
filterobjectsBucket.Add(subObject);
}
else
{
foreach (var newsubObject in newSubobjects)
{
if (newsubObject != null)
{
filterobjectsBucket.Add(newsubObject);
}
}
}
Interlocked.Decrement(ref tiling);
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");
}
else
{
filterobjectsBucket.Add(subObject);
}

Interlocked.Increment(ref done);
Console.Write("\r" + done + " done; " + skipped + " skipped ; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling ");
}
{
Interlocked.Increment(ref parsing);
CityObject cityObject = cityJson.LoadCityObjectByIndex(i, lod);
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);
var subObject = ToSubObjectMeshData(cityObject);
//cityJson.ClearCityObject(cityObject.keyName);
cityObject = null;
Interlocked.Decrement(ref parsing);

cityObject = null;
if (subObject == null)
{
Interlocked.Increment(ref done);
Interlocked.Increment(ref skipped);
return;
}
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);

if (subObject.maxVerticesPerSquareMeter > 0)
{
Interlocked.Increment(ref simplifying);
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);
subObject.SimplifyMesh();
Interlocked.Decrement(ref simplifying);
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);
}
else
{
//Always merge based on VertexNormalCombination.normalAngleComparisonThreshold
subObject.MergeSimilarVertices();
}
if (clipSpikes)
{
subObject.ClipSpikes(spikeCeiling, spikeFloor);
}

if (TilingMethod == "TILED")
{
Interlocked.Increment(ref tiling);
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);
var newSubobjects = subObject.ClipSubobject(new Vector2(tileSize, tileSize));
if (newSubobjects.Count == 0)
{
subObject.CalculateNormals();
filterObjectsBucket.Add(subObject);
}
else
{
foreach (var newsubObject in newSubobjects)
{
if (newsubObject != null)
{
filterObjectsBucket.Add(newsubObject);
}
}
}
Interlocked.Decrement(ref tiling);
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);
}
else
{
filterObjectsBucket.Add(subObject);
}

Interlocked.Increment(ref done);
WriteStatusToConsole(skipped, done, parsing, simplifying, tiling);
}
);

return filterobjectsBucket.ToList();
return filterObjectsBucket.ToList();
}

private static void WriteStatusToConsole(int skipped, int done, int parsing, int simplifying, int tiling)
{
Console.Write("\r" + done + " done; " + skipped + " skipped; " + parsing + " parsing; " + simplifying + " simplifying; " + tiling + " tiling;");
}

private SubObject ToSubObjectMeshData(CityObject cityObject)
{
List<SubObject> subObjects = new List<SubObject>();
var subObject = new SubObject();
subObject.vertices = new List<Vector3Double>();
subObject.normals = new List<Vector3>();
Expand Down Expand Up @@ -513,6 +527,7 @@ private SubObject ToSubObjectMeshData(CityObject cityObject)
}
}
}

subObject.parentSubmeshIndex = submeshindex;
if (submeshindex == -1)
{
Expand All @@ -537,6 +552,7 @@ private SubObject ToSubObjectMeshData(CityObject cityObject)
{
calculateNormals = true;
}

AppendCityObjectGeometry(cityObject, subObject, calculateNormals);
//Append all child geometry too
for (int i = 0; i < cityObject.children.Count; i++)
Expand Down
6 changes: 5 additions & 1 deletion TileBakeLibrary/Config/ConfigFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
public class ConfigFile
{
//Mandatory settings. The application should not start without them specified by the user:
public string sourceFolder { get; set; }
public string outputFolder { get; set; }
public bool replaceExistingObjects { get; set; }
Expand All @@ -26,12 +27,15 @@ public class ConfigFile
public float lod { get; set; }
public string tilingMethod { get; set; }

//Optional settings with default values

//Optional settings with predefined default values:
public int tileSize { get; set; } = 1000;
public float mergeVerticesBelowAngle { get; set; } = 5;
public bool brotliCompression { get; set; } = false;
public bool removeSpikes { get; set; } = false;
public float removeSpikesAbove { get; set; } = 25;
public float removeSpikesBelow { get; set; } = -10;

public CityObjectFilter[] cityObjectFilters { get; set; }
}

Expand Down
4 changes: 2 additions & 2 deletions TileBakeLibrary/Coordinates/Vector3Double.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ public static explicit operator Vector3(Vector3Double v)
}

public static double Distance(Vector3Double left, Vector3Double right){
return Magnitude(left - right);
return Math.Abs(Magnitude(left - right));
}

public static double Magnitude(Vector3Double left)
{
return Math.Sqrt(left.X * left.X + left.Y*left.Y + left.Z*left.Z);
return Math.Sqrt(left.X * left.X + left.Y * left.Y + left.Z * left.Z);
}

public bool Equals(Vector3Double other)
Expand Down
91 changes: 91 additions & 0 deletions TileBakeLibrary/Geometry/VertexNormalCombination.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (C) X Gemeente
* X Amsterdam
* X Economic Services Departments
*
* Licensed under the EUPL, Version 1.2 or later (the "License");
* You may not use this work except in compliance with the License.
* You may obtain a copy of the License at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Numerics;
using TileBakeLibrary.Coordinates;

namespace TileBakeLibrary
{
public struct VertexNormalCombination : IEquatable<VertexNormalCombination>
{
/// <summary>
/// Angle in degrees
/// </summary>
public static float vertexDistanceComparisonThreshold = 0.01f; //1mm
public static float normalAngleComparisonThreshold = 5.0f;

public Vector3 normal;

public Vector3Double vertex;
public VertexNormalCombination(Vector3Double vertex, Vector3 normal)
{
this.vertex = vertex;
this.normal = normal;
}

/// <summary>
/// Compares vert+normal with thresholds determining if they are the same
/// </summary>
/// <returns></returns>
public bool Equals(VertexNormalCombination other)
{
if (Vector3Double.Distance(other.vertex,vertex) < vertexDistanceComparisonThreshold)
{
if (AngleBetweenNormals(normal, other.normal) < normalAngleComparisonThreshold)
{
return true;
}
}
return false;
}

/// <summary>
/// Forces comparison using Equals
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return 0;
}

/// <summary>
/// Normal in degrees
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="radiansInsteadOfDegrees">Return the angle in radians</param>
/// <returns></returns>
public static double AngleBetweenNormals(Vector3 a, Vector3 b, bool radiansInsteadOfDegrees = false)
{
Vector3 normalA = Vector3.Normalize(a);
Vector3 normalB = Vector3.Normalize(b);

var radians = 2.0d * Math.Atan((normalA - normalB).Length() / (normalA + normalB).Length());
if(radiansInsteadOfDegrees)
{
return radians;
}
return (180.0f / Math.PI) * radians;
}

public override string ToString()
{
return $"v({vertex.X},{vertex.X},{vertex.X}), n({normal.X},{normal.Y},{normal.Z})";
}
}
}
7 changes: 7 additions & 0 deletions TileBakeLibrary/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"profiles": {
"TileBakeLibrary": {
"commandName": "Project"
}
}
}
Loading

0 comments on commit 876487d

Please sign in to comment.