This commit is contained in:
runette committed 2024-04-22 15:52:51 +01:00
commit e0afef912e
1285 files changed
+83219

No files matched your search

@@ -0,0 +1,413 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using System;
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Smooth Height for Buildings Modifier")]
public class ChamferHeightModifier : MeshModifier
{
[SerializeField]
[Tooltip("Flatten top polygons to prevent unwanted slanted roofs because of the bumpy terrain")]
private bool _flatTops;
[SerializeField]
[Tooltip("Fixed height value for ForceHeight option")]
private float _height;
[SerializeField]
[Tooltip("Fix all features to certain height, suggested to be used for pushing roads above terrain level to prevent z-fighting.")]
private bool _forceHeight;
[SerializeField]
[Range(0.1f,2)]
[Tooltip("Chamfer width value")]
private float _offset = 0.2f;
public override ModifierType Type { get { return ModifierType.Preprocess; } }
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
if (md.Vertices.Count == 0 || feature == null || feature.Points.Count < 1)
return;
var minHeight = 0f;
float hf = _height;
if (!_forceHeight)
{
GetHeightData(feature, tile.TileScale, ref minHeight, ref hf);
}
var max = md.Vertices[0].y;
var min = md.Vertices[0].y;
if (_flatTops)
{
FlattenTops(md, minHeight, ref hf, ref max, ref min);
}
else
{
for (int i = 0; i < md.Vertices.Count; i++)
{
md.Vertices[i] = new Vector3(md.Vertices[i].x, md.Vertices[i].y + minHeight + hf, md.Vertices[i].z);
}
}
var originalVertexCount = md.Vertices.Count;
Chamfer(feature, md, tile);
Sides(feature, md, hf, originalVertexCount);
}
private void Sides(VectorFeatureUnity feature, MeshData meshData, float hf, int originalVertexCount)
{
float d = 0f;
Vector3 v1;
Vector3 v2 = Mapbox.Unity.Constants.Math.Vector3Zero;
int ind = 0;
var wallTri = new List<int>();
var wallUv = new List<Vector2>();
meshData.Vertices.Add(new Vector3(meshData.Vertices[originalVertexCount - 1].x, meshData.Vertices[originalVertexCount - 1].y - hf, meshData.Vertices[originalVertexCount - 1].z));
meshData.Tangents.Add(meshData.Tangents[originalVertexCount - 1]);
wallUv.Add(new Vector2(0, -hf));
meshData.Normals.Add(meshData.Normals[originalVertexCount - 1]);
for (int i = 0; i < meshData.Edges.Count; i += 2)
{
v1 = meshData.Vertices[meshData.Edges[i]];
v2 = meshData.Vertices[meshData.Edges[i + 1]];
ind = meshData.Vertices.Count;
meshData.Vertices.Add(v1);
meshData.Vertices.Add(v2);
meshData.Vertices.Add(new Vector3(v1.x, v1.y - hf, v1.z));
meshData.Vertices.Add(new Vector3(v2.x, v2.y - hf, v2.z));
meshData.Normals.Add(meshData.Normals[meshData.Edges[i]]);
meshData.Normals.Add(meshData.Normals[meshData.Edges[i + 1]]);
meshData.Normals.Add(meshData.Normals[meshData.Edges[i]]);
meshData.Normals.Add(meshData.Normals[meshData.Edges[i + 1]]);
meshData.Tangents.Add(v2 - v1.normalized);
meshData.Tangents.Add(v2 - v1.normalized);
meshData.Tangents.Add(v2 - v1.normalized);
meshData.Tangents.Add(v2 - v1.normalized);
d = (v2 - v1).magnitude;
wallUv.Add(new Vector2(0, 0));
wallUv.Add(new Vector2(d, 0));
wallUv.Add(new Vector2(0, -hf));
wallUv.Add(new Vector2(d, -hf));
wallTri.Add(ind);
wallTri.Add(ind + 1);
wallTri.Add(ind + 2);
wallTri.Add(ind + 1);
wallTri.Add(ind + 3);
wallTri.Add(ind + 2);
}
meshData.Triangles.Add(wallTri);
meshData.UV[0].AddRange(wallUv);
}
private static void FlattenTops(MeshData meshData, float minHeight, ref float hf, ref float max, ref float min)
{
for (int i = 0; i < meshData.Vertices.Count; i++)
{
if (meshData.Vertices[i].y > max)
max = meshData.Vertices[i].y;
else if (meshData.Vertices[i].y < min)
min = meshData.Vertices[i].y;
}
for (int i = 0; i < meshData.Vertices.Count; i++)
{
meshData.Vertices[i] = new Vector3(meshData.Vertices[i].x, max + minHeight + hf, meshData.Vertices[i].z);
}
hf += max - min;
}
private static void GetHeightData(VectorFeatureUnity feature, float scale, ref float minHeight, ref float hf)
{
if (feature.Properties.ContainsKey("height"))
{
hf = Convert.ToSingle(feature.Properties["height"]);
hf *= scale;
if (feature.Properties.ContainsKey("min_height"))
{
minHeight = Convert.ToSingle(feature.Properties["min_height"]) * scale;
hf -= minHeight;
}
}
if (feature.Properties.ContainsKey("ele"))
{
hf = Convert.ToSingle(feature.Properties["ele"]);
hf *= scale;
}
}
public void Chamfer(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
if (md.Vertices.Count == 0 || feature.Points.Count < 1)
return;
List<Vector3> newVertices = new List<Vector3>();
List<Vector2> newUV = new List<Vector2>();
md.Normals.Clear();
md.Edges.Clear();
md.Tangents.Clear();
for (int t = 0; t < md.Triangles[0].Count; t++)
{
md.Triangles[0][t] *= 3;
}
var next = 0; var current = 0; var prev = 0;
Vector3 v1, v2, n1, n2, pij1, pij2, pjk1, pjk2;
Vector3 poi, close1, close2;
var start = 0;
for (int i = 0; i < feature.Points.Count; i++)
{
var count = feature.Points[i].Count;
var cst = newVertices.Count;
for (int j = 0; j < count; j++)
{
if (j == count - 1)
{
newVertices.Add(newVertices[cst]);
newVertices.Add(newVertices[cst + 1]);
newVertices.Add(newVertices[cst + 2]);
newUV.Add(newUV[cst]);
newUV.Add(newUV[cst + 1]);
newUV.Add(newUV[cst + 2]);
md.Normals.Add(md.Normals[cst]);
md.Normals.Add(md.Normals[cst + 1]);
md.Normals.Add(md.Normals[cst + 2]);
md.Tangents.Add(md.Tangents[cst]);
md.Tangents.Add(md.Tangents[cst + 1]);
md.Tangents.Add(md.Tangents[cst + 2]);
continue;
}
current = start + j;
if (j > 0)
next = start + j - 1;
else
next = start + j - 1 + count - 1; //another -1 as last item equals first
prev = start + j + 1;
v1 = new Vector3(
md.Vertices[current].x - md.Vertices[next].x, 0,
md.Vertices[current].z - md.Vertices[next].z);
v1.Normalize();
v1 *= -_offset;
n1 = new Vector3(-v1.z, 0, v1.x);
pij1 = new Vector3(
(float)(md.Vertices[next].x + n1.x), 0,
(float)(md.Vertices[next].z + n1.z));
pij2 = new Vector3(
(float)(md.Vertices[current].x + n1.x), 0,
(float)(md.Vertices[current].z + n1.z));
v2 = new Vector3(
md.Vertices[prev].x - md.Vertices[current].x, 0,
md.Vertices[prev].z - md.Vertices[current].z);
v2.Normalize();
v2 *= -_offset;
n2 = new Vector3(-v2.z, 0, v2.x);
pjk1 = new Vector3(
(float)(md.Vertices[current].x + n2.x), 0,
(float)(md.Vertices[current].z + n2.z));
pjk2 = new Vector3(
(float)(md.Vertices[prev].x + n2.x), 0,
(float)(md.Vertices[prev].z + n2.z));
// See where the shifted lines ij and jk intersect.
bool lines_intersect, segments_intersect;
FindIntersection(pij1, pij2, pjk1, pjk2,
out lines_intersect, out segments_intersect,
out poi, out close1, out close2);
var d = Vector3.Distance(poi, pij2);
if (d > 10 * _offset)
{
poi = new Vector3((md.Vertices[current].x + (poi - (-v1 - v2)).normalized.x), 0,
(md.Vertices[current].z + (poi - (-v1 - v2)).normalized.z));
}
newVertices.Add(new Vector3(poi.x, poi.y + _offset + md.Vertices[current].y, poi.z));
newVertices.Add(md.Vertices[current] + v1);
newVertices.Add(md.Vertices[current] - v2);
md.Normals.Add(Constants.Math.Vector3Up);
md.Normals.Add(-n1);
md.Normals.Add(-n2);
md.Tangents.Add(v1 - v2);
md.Tangents.Add(v1 - v2);
md.Tangents.Add(v1 - v2);
newUV.Add(md.UV[0][current]);
newUV.Add(md.UV[0][current]);
newUV.Add(md.UV[0][current]);
md.Triangles[0].Add(3 * current);
md.Triangles[0].Add(3 * current + 1);
md.Triangles[0].Add(3 * current + 2);
md.Edges.Add(3 * current + 2);
md.Edges.Add(3 * current + 1);
md.Triangles[0].Add(3 * prev);
md.Triangles[0].Add(3 * current + 2);
md.Triangles[0].Add(3 * prev + 1);
md.Triangles[0].Add(3 * current);
md.Triangles[0].Add(3 * current + 2);
md.Triangles[0].Add(3 * prev);
md.Edges.Add(3 * prev + 1);
md.Edges.Add(3 * current + 2);
}
start += count;
}
md.Vertices = newVertices;
md.UV[0] = newUV;
}
private List<Vector3> GetEnlargedPolygon(List<Vector3> old_points, float offset)
{
List<Vector3> enlarged_points = new List<Vector3>();
int num_points = old_points.Count;
for (int j = 0; j < num_points; j++)
{
// Find the new location for point j.
// Find the points before and after j.
int i = (j - 1);
if (i < 0) i += num_points;
int k = (j + 1) % num_points;
// Move the points by the offset.
Vector3 v1 = new Vector3(
old_points[j].x - old_points[i].x, 0,
old_points[j].z - old_points[i].z);
v1.Normalize();
v1 *= offset;
Vector3 n1 = new Vector3(-v1.z, 0, v1.x);
Vector3 pij1 = new Vector3(
(float)(old_points[i].x + n1.x), 0,
(float)(old_points[i].z + n1.z));
Vector3 pij2 = new Vector3(
(float)(old_points[j].x + n1.x), 0,
(float)(old_points[j].z + n1.z));
Vector3 v2 = new Vector3(
old_points[k].x - old_points[j].x, 0,
old_points[k].z - old_points[j].z);
v2.Normalize();
v2 *= offset;
Vector3 n2 = new Vector3(-v2.z, 0, v2.x);
Vector3 pjk1 = new Vector3(
(float)(old_points[j].x + n2.x), 0,
(float)(old_points[j].z + n2.z));
Vector3 pjk2 = new Vector3(
(float)(old_points[k].x + n2.x), 0,
(float)(old_points[k].z + n2.z));
// See where the shifted lines ij and jk intersect.
bool lines_intersect, segments_intersect;
Vector3 poi, close1, close2;
FindIntersection(pij1, pij2, pjk1, pjk2,
out lines_intersect, out segments_intersect,
out poi, out close1, out close2);
Debug.Assert(lines_intersect,
"Edges " + i + "-->" + j + " and " +
j + "-->" + k + " are parallel");
enlarged_points.Add(poi);
}
return enlarged_points;
}
// Find the point of intersection between
// the lines p1 --> p2 and p3 --> p4.
private void FindIntersection(
Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4,
out bool lines_intersect, out bool segments_intersect,
out Vector3 intersection,
out Vector3 close_p1, out Vector3 close_p2)
{
// Get the segments' parameters.
float dx12 = p2.x - p1.x;
float dy12 = p2.z - p1.z;
float dx34 = p4.x - p3.x;
float dy34 = p4.z - p3.z;
// Solve for t1 and t2
float denominator = (dy12 * dx34 - dx12 * dy34);
float t1 =
((p1.x - p3.x) * dy34 + (p3.z - p1.z) * dx34)
/ denominator;
if (float.IsInfinity(t1))
{
// The lines are parallel (or close enough to it).
lines_intersect = false;
segments_intersect = false;
intersection = new Vector3(float.NaN, 0, float.NaN);
close_p1 = new Vector3(float.NaN, 0, float.NaN);
close_p2 = new Vector3(float.NaN, 0, float.NaN);
return;
}
lines_intersect = true;
float t2 =
((p3.x - p1.x) * dy12 + (p1.z - p3.z) * dx12)
/ -denominator;
// Find the point of intersection.
intersection = new Vector3(p1.x + dx12 * t1, 0, p1.z + dy12 * t1);
// The segments intersect if t1 and t2 are between 0 and 1.
segments_intersect =
((t1 >= 0) && (t1 <= 1) &&
(t2 >= 0) && (t2 <= 1));
// Find the closest points on the segments.
if (t1 < 0)
{
t1 = 0;
}
else if (t1 > 1)
{
t1 = 1;
}
if (t2 < 0)
{
t2 = 0;
}
else if (t2 > 1)
{
t2 = 1;
}
close_p1 = new Vector3(p1.x + dx12 * t1, 0, p1.z + dy12 * t1);
close_p2 = new Vector3(p3.x + dx34 * t2, 0, p3.z + dy34 * t2);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 232ffe870b9f2df408d25ad59dbfe77c
timeCreated: 1478553093
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,766 @@
using System;
using System.Collections.Generic;
using Mapbox.VectorTile.Geometry;
using UnityEngine;
namespace Assets.Mapbox.Unity.MeshGeneration.Modifiers.MeshModifiers
{
public static class EarcutLibrary
{
public static List<int> Earcut(List<float> data, List<int> holeIndices, int dim)
{
dim = Math.Max(dim, 2);
var hasHoles = holeIndices.Count;
var outerLen = hasHoles > 0 ? holeIndices[0] * dim : data.Count;
var outerNode = linkedList(data, 0, outerLen, dim, true);
var triangles = new List<int>((int)(outerNode.i * 1.5));
if (outerNode == null) return triangles;
var minX = 0f;
var minY = 0f;
var maxX = 0f;
var maxY = 0f;
var x = 0f;
var y = 0f;
var size = 0f;
if (hasHoles > 0) outerNode = EliminateHoles(data, holeIndices, outerNode, dim);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if (data.Count > 80 * dim)
{
minX = maxX = data[0];
minY = maxY = data[1];
for (var i = dim; i < outerLen; i += dim)
{
x = data[i];
y = data[i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
// minX, minY and size are later used to transform coords into integers for z-order calculation
size = Math.Max(maxX - minX, maxY - minY);
}
earcutLinked(outerNode, triangles, dim, minX, minY, size);
return triangles;
}
private static void earcutLinked(Node ear, List<int> triangles, int dim, float minX, float minY, float size, int pass = 0)
{
if (ear == null) return;
// interlink polygon nodes in z-order
if (pass == 0 && size > 0) indexCurve(ear, minX, minY, size);
var stop = ear;
Node prev;
Node next;
// iterate through ears, slicing them one by one
while (ear.prev != ear.next)
{
prev = ear.prev;
next = ear.next;
if (size > 0 ? isEarHashed(ear, minX, minY, size) : isEar(ear))
{
// cut off the triangle
triangles.Add(prev.i / dim);
triangles.Add(next.i / dim);
triangles.Add(ear.i / dim);
removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear == stop)
{
// try filtering points and slicing again
if (pass == 0)
{
earcutLinked(FilterPoints(ear, null), triangles, dim, minX, minY, size, 1);
// if this didn't work, try curing all small self-intersections locally
}
else if (pass == 1)
{
ear = cureLocalIntersections(ear, triangles, dim);
earcutLinked(ear, triangles, dim, minX, minY, size, 2);
// as a last resort, try splitting the remaining polygon into two
}
else if (pass == 2)
{
splitEarcut(ear, triangles, dim, minX, minY, size);
}
break;
}
}
}
private static bool isEarHashed(Node ear, float minX, float minY, float size)
{
var a = ear.prev;
var b = ear;
var c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x);
var minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y);
var maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x);
var maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
// z-order range for the current triangle bbox;
var minZ = zOrder(minTX, minTY, minX, minY, size);
var maxZ = zOrder(maxTX, maxTY, minX, minY, size);
// first look for points inside the triangle in increasing z-order
var p = ear.nextZ;
while (p != null && p.mZOrder <= maxZ)
{
if (p != ear.prev && p != ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.nextZ;
}
// then look for points in decreasing z-order
p = ear.prevZ;
while (p != null && p.mZOrder >= minZ)
{
if (p != ear.prev && p != ear.next &&
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
}
return true;
}
private static int zOrder(float x, float y, float minX, float minY, float size)
{
//TODO casting here might be wrong
x = 32767 * (x - minX) / size;
y = 32767 * (y - minY) / size;
x = ((int)x | ((int)x << 8)) & 0x00FF00FF;
x = ((int)x | ((int)x << 4)) & 0x0F0F0F0F;
x = ((int)x | ((int)x << 2)) & 0x33333333;
x = ((int)x | ((int)x << 1)) & 0x55555555;
y = ((int)y | ((int)y << 8)) & 0x00FF00FF;
y = ((int)y | ((int)y << 4)) & 0x0F0F0F0F;
y = ((int)y | ((int)y << 2)) & 0x33333333;
y = ((int)y | ((int)y << 1)) & 0x55555555;
return (int)x | ((int)y << 1);
}
private static void splitEarcut(Node start, List<int> triangles, int dim, float minX, float minY, float size)
{
var a = start;
do
{
var b = a.next.next;
while (b != a.prev)
{
if (a.i != b.i && isValidDiagonal(a, b))
{
// split the polygon in two by the diagonal
var c = SplitPolygon(a, b);
// filter colinear points around the cuts
a = FilterPoints(a, a.next);
c = FilterPoints(c, c.next);
// run earcut on each half
earcutLinked(a, triangles, dim, minX, minY, size);
earcutLinked(c, triangles, dim, minX, minY, size);
return;
}
b = b.next;
}
a = a.next;
} while (a != start);
}
private static bool isValidDiagonal(Node a, Node b)
{
return a.next.i != b.i && a.prev.i != b.i && !intersectsPolygon(a, b) &&
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
}
private static bool middleInside(Node a, Node b)
{
var p = a;
var inside = false;
var px = (a.x + b.x) / 2;
var py = (a.y + b.y) / 2;
do
{
if (((p.y > py) != (p.next.y > py)) && p.next.y != p.y &&
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
inside = !inside;
p = p.next;
} while (p != a);
return inside;
}
private static bool intersectsPolygon(Node a, Node b)
{
var p = a;
do
{
if (p.i != a.i && p.next.i != a.i && p.i != b.i && p.next.i != b.i &&
intersects(p, p.next, a, b)) return true;
p = p.next;
} while (p != a);
return false;
}
private static Node cureLocalIntersections(Node start, List<int> triangles, int dim)
{
var p = start;
do
{
var a = p.prev;
var b = p.next.next;
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a))
{
triangles.Add(a.i / dim);
triangles.Add(p.i / dim);
triangles.Add(b.i / dim);
// remove two nodes involved
removeNode(p);
removeNode(p.next);
p = start = b;
}
p = p.next;
} while (p != start);
return p;
}
private static bool intersects(Node p1, Node q1, Node p2, Node q2)
{
if ((equals(p1, q1) && equals(p2, q2)) ||
(equals(p1, q2) && equals(p2, q1))) return true;
return area(p1, q1, p2) > 0 != area(p1, q1, q2) > 0 &&
area(p2, q2, p1) > 0 != area(p2, q2, q1) > 0;
}
private static bool isEar(Node ear)
{
var a = ear.prev;
var b = ear;
var c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
var p = ear.next.next;
while (p != ear.prev)
{
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.next;
}
return true;
}
private static void indexCurve(Node start, float minX, float minY, float size)
{
var p = start;
do
{
if (p.mZOrder == 0) p.mZOrder = zOrder(p.x, p.y, minX, minY, size);
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
} while (p != start);
p.prevZ.nextZ = null;
p.prevZ = null;
sortLinked(p);
}
private static Node sortLinked(Node list)
{
var i = 0;
Node p;
Node q;
Node e;
Node tail;
var numMerges = 0; ;
var pSize = 0;
var qSize = 0;
var inSize = 1;
do
{
p = list;
list = null;
tail = null;
numMerges = 0;
while (p != null)
{
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++)
{
pSize++;
q = q.nextZ;
if (q == null) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q != null))
{
if (pSize != 0 && (qSize == 0 || q == null || p.mZOrder <= q.mZOrder))
{
e = p;
p = p.nextZ;
pSize--;
}
else
{
e = q;
q = q.nextZ;
qSize--;
}
if (tail != null) tail.nextZ = e;
else list = e;
e.prevZ = tail;
tail = e;
}
p = q;
}
tail.nextZ = null;
inSize *= 2;
} while (numMerges > 1);
return list;
}
private static Node EliminateHoles(List<float> data, List<int> holeIndices, Node outerNode, int dim)
{
var i = 0;
var len = holeIndices.Count;
var start = 0;
var end = 0;
Node list = null;
var queue = new List<Node>(len);
for (i = 0; i < len; i++)
{
start = holeIndices[i] * dim;
end = i < len - 1 ? holeIndices[i + 1] * dim : data.Count;
list = linkedList(data, start, end, dim, false);
if (list == list.next) list.steiner = true;
queue.Add(getLeftmost(list));
}
queue.Sort(delegate (Node a, Node b)
{
return (int)Math.Ceiling(a.x - b.x);
});
// process holes from left to right
for (i = 0; i < queue.Count; i++)
{
EliminateHole(queue[i], outerNode);
outerNode = FilterPoints(outerNode, outerNode.next);
}
return outerNode;
}
private static void EliminateHole(Node hole, Node outerNode)
{
outerNode = FindHoleBridge(hole, outerNode);
if (outerNode != null)
{
var b = SplitPolygon(outerNode, hole);
FilterPoints(b, b.next);
}
}
private static Node FilterPoints(Node start, Node end)
{
if (start == null) return start;
if (end == null) end = start;
var p = start;
bool again = true;
do
{
again = false;
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) == 0))
{
removeNode(p);
p = end = p.prev;
if (p == p.next) return null;
again = true;
}
else
{
p = p.next;
}
} while (again || p != end);
return end;
}
private static Node SplitPolygon(Node a, Node b)
{
var a2 = new Node(a.i, a.x, a.y);
var b2 = new Node(b.i, b.x, b.y);
var an = a.next;
var bp = b.prev;
a.next = b;
b.prev = a;
a2.next = an;
an.prev = a2;
b2.next = a2;
a2.prev = b2;
bp.next = b2;
b2.prev = bp;
return b2;
}
private static Node FindHoleBridge(Node hole, Node outerNode)
{
var p = outerNode;
var hx = hole.x;
var hy = hole.y;
var qx = float.MinValue;
Node m = null;
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
do
{
if (hy <= p.y && hy >= p.next.y && p.next.y != p.y)
{
var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
if (x <= hx && x > qx)
{
qx = x;
if (x == hx)
{
if (hy == p.y) return p;
if (hy == p.next.y) return p.next;
}
m = p.x < p.next.x ? p : p.next;
}
}
p = p.next;
} while (p != outerNode);
if (m == null) return null;
if (hx == qx) return m.prev; // hole touches outer segment; pick lower endpoint
// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the point of the minimum angle with the ray as connection point
var stop = m;
var mx = m.x;
var my = m.y;
var tanMin = float.MaxValue;
float tan = 0f;
p = m.next;
while (p != stop)
{
if (hx >= p.x && p.x >= mx && hx != p.x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y))
{
tan = Math.Abs(hy - p.y) / (hx - p.x); // tangential
if ((tan < tanMin || (tan == tanMin && p.x > m.x)) && locallyInside(p, hole))
{
m = p;
tanMin = tan;
}
}
p = p.next;
}
return m;
}
private static bool locallyInside(Node a, Node b)
{
return area(a.prev, a, a.next) < 0 ?
area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}
private static float area(Node p, Node q, Node r)
{
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}
private static bool pointInTriangle(float ax, float ay, float bx, float by, float cx, float cy, float px, float py)
{
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}
private static Node getLeftmost(Node start)
{
var p = start;
var leftmost = start;
do
{
if (p.x < leftmost.x) leftmost = p;
p = p.next;
} while (p != start);
return leftmost;
}
// create a circular doubly linked list from polygon points in the specified winding order
private static Node linkedList(List<float> data, int start, int end, int dim, bool clockwise)
{
var i = 0;
Node last = null;
if (clockwise == (signedArea(data, start, end, dim) > 0))
{
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
}
else
{
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
}
if (last != null && equals(last, last.next))
{
removeNode(last);
last = last.next;
}
return last;
}
private static void removeNode(Node p)
{
p.next.prev = p.prev;
p.prev.next = p.next;
if (p.prevZ != null) p.prevZ.nextZ = p.nextZ;
if (p.nextZ != null) p.nextZ.prevZ = p.prevZ;
}
private static bool equals(Node p1, Node p2)
{
return p1.x == p2.x && p1.y == p2.y;
}
private static float signedArea(List<float> data, int start, int end, int dim)
{
var sum = 0f;
var j = end - dim;
for (var i = start; i < end; i += dim)
{
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
j = i;
}
return sum;
}
private static Node insertNode(int i, float x, float y, Node last)
{
var p = new Node(i, x, y);
if (last == null)
{
p.prev = p;
p.next = p;
}
else
{
p.next = last.next;
p.prev = last;
last.next.prev = p;
last.next = p;
}
return p;
}
public static Data Flatten(List<List<Vector3>> data)
{
var dataCount = data.Count;
var totalVertCount = 0;
for (int i = 0; i < dataCount; i++)
{
totalVertCount += data[i].Count;
}
var result = new Data() { Dim = 2 };
result.Vertices = new List<float>(totalVertCount * 2);
var holeIndex = 0;
for (var i = 0; i < dataCount; i++)
{
var subCount = data[i].Count;
for (var j = 0; j < subCount; j++)
{
result.Vertices.Add(data[i][j][0]);
result.Vertices.Add(data[i][j][2]);
}
if (i > 0)
{
holeIndex += data[i - 1].Count;
result.Holes.Add(holeIndex);
}
}
return result;
}
}
public class Data
{
public List<float> Vertices;
public List<int> Holes;
public int Dim;
public Data()
{
Holes = new List<int>();
Dim = 2;
}
}
public class Node
{
/* Member Variables. */
public int i;
public float x;
public float y;
public int mZOrder;
public Node prev;
public Node next;
public Node prevZ;
public Node nextZ;
public bool steiner;
public Node(int ind, float pX, float pY)
{
/* Initialize Member Variables. */
this.i = ind;
this.x = pX;
this.y = pY;
this.mZOrder = 0;
this.prev = null;
this.next = null;
this.prevZ = null;
this.nextZ = null;
}
protected void setPreviousNode(Node pNode)
{
this.prev = pNode;
}
protected Node getPreviousNode()
{
return this.prev;
}
protected void setNextNode(Node pNode)
{
this.next = pNode;
}
protected Node getNextNode()
{
return this.next;
}
protected void setZOrder(int pZOrder)
{
this.mZOrder = pZOrder;
}
protected int getZOrder()
{
return this.mZOrder;
}
protected void setPreviousZNode(Node pNode)
{
this.prevZ = pNode;
}
protected Node getPreviousZNode()
{
return this.prevZ;
}
protected void setNextZNode(Node pNode)
{
this.nextZ = pNode;
}
protected Node getNextZNode()
{
return this.nextZ;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e7b04bd1ddccfc64aa5e47acd098a3ef
timeCreated: 1499344109
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,288 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using System;
using Mapbox.Unity.Map;
public class MinMaxPair
{
public float min, max;
public static MinMaxPair GetMinMaxHeight(List<Vector3> vertices)
{
int counter = vertices.Count;
MinMaxPair returnValue = new MinMaxPair
{
max = float.MinValue,
min = float.MaxValue
};
for (int i = 0; i < counter; i++)
{
if (vertices[i].y > returnValue.max)
returnValue.max = vertices[i].y;
else if (vertices[i].y < returnValue.min)
returnValue.min = vertices[i].y;
}
return returnValue;
}
}
/// <summary>
/// Height Modifier is responsible for the y axis placement of the feature. It pushes the original vertices upwards by "height" value and creates side walls around that new polygon down to "min_height" value.
/// It also checkes for "ele" (elevation) value used for contour lines in Mapbox Terrain data.
/// Height Modifier also creates a continuous UV mapping for side walls.
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Height Modifier")]
public class HeightModifier : MeshModifier
{
private float _scale = 1;
GeometryExtrusionOptions _options;
[SerializeField]
[Tooltip("Create side walls as separate submesh.")]
private bool _separateSubmesh = true;
public override ModifierType Type { get { return ModifierType.Preprocess; } }
private int _counter = 0;
float height = 0.0f;
public override void SetProperties(ModifierProperties properties)
{
_options = (GeometryExtrusionOptions)properties;
_options.PropertyHasChanged += UpdateModifier;
}
public override void UnbindProperties()
{
_options.PropertyHasChanged -= UpdateModifier;
}
public override void Run(VectorFeatureUnity feature, MeshData md, float scale)
{
_scale = scale;
Run(feature, md);
}
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
_counter = 0;
if (md.Vertices.Count == 0 || feature == null || feature.Points.Count < 1)
return;
if (tile != null)
_scale = tile.TileScale;
float maxHeight = 1.0f;
float minHeight = 0.0f;
QueryHeight(feature, md, tile, out maxHeight, out minHeight);
maxHeight = maxHeight * _options.extrusionScaleFactor * _scale;
minHeight = minHeight * _options.extrusionScaleFactor * _scale;
height = (maxHeight - minHeight);
//Set roof height
GenerateRoofMesh(md, minHeight, maxHeight);
if (_options.extrusionGeometryType == ExtrusionGeometryType.SideOnly)
{
md.Triangles[0].Clear();
}
GenerateWallMesh(md);
}
protected virtual void GenerateWallMesh(MeshData md)
{
md.Vertices.Capacity = _counter + md.Edges.Count * 2;
float d = 0f;
Vector3 v1;
Vector3 v2;
int ind = 0;
Vector3 wallDir;
if (_options.extrusionGeometryType != ExtrusionGeometryType.RoofOnly)
{
_counter = md.Edges.Count;
var wallTri = new List<int>(_counter * 3);
var wallUv = new List<Vector2>(_counter * 2);
Vector3 norm = Constants.Math.Vector3Zero;
md.Vertices.Capacity = md.Vertices.Count + _counter * 2;
md.Normals.Capacity = md.Normals.Count + _counter * 2;
for (int i = 0; i < _counter; i += 2)
{
v1 = md.Vertices[md.Edges[i]];
v2 = md.Vertices[md.Edges[i + 1]];
ind = md.Vertices.Count;
md.Vertices.Add(v1);
md.Vertices.Add(v2);
md.Vertices.Add(new Vector3(v1.x, v1.y - height, v1.z));
md.Vertices.Add(new Vector3(v2.x, v2.y - height, v2.z));
d = (v2 - v1).magnitude;
norm = Vector3.Normalize(Vector3.Cross(v2 - v1, md.Vertices[ind + 2] - v1));
md.Normals.Add(norm);
md.Normals.Add(norm);
md.Normals.Add(norm);
md.Normals.Add(norm);
wallDir = (v2 - v1).normalized;
md.Tangents.Add(wallDir);
md.Tangents.Add(wallDir);
md.Tangents.Add(wallDir);
md.Tangents.Add(wallDir);
wallUv.Add(new Vector2(0, 0));
wallUv.Add(new Vector2(d, 0));
wallUv.Add(new Vector2(0, -height));
wallUv.Add(new Vector2(d, -height));
wallTri.Add(ind);
wallTri.Add(ind + 1);
wallTri.Add(ind + 2);
wallTri.Add(ind + 1);
wallTri.Add(ind + 3);
wallTri.Add(ind + 2);
}
// TODO: Do we really need this?
if (_separateSubmesh)
{
md.Triangles.Add(wallTri);
}
else
{
md.Triangles.Capacity = md.Triangles.Count + wallTri.Count;
md.Triangles[0].AddRange(wallTri);
}
md.UV[0].AddRange(wallUv);
}
}
protected virtual void GenerateRoofMesh(MeshData md, float minHeight, float maxHeight)
{
_counter = md.Vertices.Count;
switch (_options.extrusionType)
{
case ExtrusionType.None:
break;
case ExtrusionType.PropertyHeight:
for (int i = 0; i < _counter; i++)
{
md.Vertices[i] = new Vector3(md.Vertices[i].x, md.Vertices[i].y + maxHeight, md.Vertices[i].z);
}
break;
case ExtrusionType.MinHeight:
{
var minmax = MinMaxPair.GetMinMaxHeight(md.Vertices);
for (int i = 0; i < _counter; i++)
{
md.Vertices[i] = new Vector3(md.Vertices[i].x, minmax.min + maxHeight, md.Vertices[i].z);
}
}
break;
case ExtrusionType.MaxHeight:
{
var minmax = MinMaxPair.GetMinMaxHeight(md.Vertices);
for (int i = 0; i < _counter; i++)
{
md.Vertices[i] = new Vector3(md.Vertices[i].x, minmax.max + maxHeight, md.Vertices[i].z);
}
height += (minmax.max - minmax.min);
}
break;
case ExtrusionType.RangeHeight:
for (int i = 0; i < _counter; i++)
{
md.Vertices[i] = new Vector3(md.Vertices[i].x, md.Vertices[i].y + maxHeight, md.Vertices[i].z);
}
break;
case ExtrusionType.AbsoluteHeight:
for (int i = 0; i < _counter; i++)
{
md.Vertices[i] = new Vector3(md.Vertices[i].x, md.Vertices[i].y + maxHeight, md.Vertices[i].z);
}
break;
default:
break;
}
}
protected virtual void QueryHeight(VectorFeatureUnity feature, MeshData md, UnityTile tile, out float maxHeight, out float minHeight)
{
minHeight = 0.0f;
maxHeight = 0.0f;
switch (_options.extrusionType)
{
case ExtrusionType.None:
break;
case ExtrusionType.PropertyHeight:
case ExtrusionType.MinHeight:
case ExtrusionType.MaxHeight:
if (feature.Properties.ContainsKey(_options.propertyName))
{
try
{
maxHeight = Convert.ToSingle(feature.Properties[_options.propertyName]);
}
catch (Exception)
{
Debug.LogError("Property: '" + _options.propertyName + "' must contain a numerical value for extrusion.");
return;
}
if (feature.Properties.ContainsKey("min_height"))
{
minHeight = Convert.ToSingle(feature.Properties["min_height"]);
}
}
break;
case ExtrusionType.RangeHeight:
if (feature.Properties.ContainsKey(_options.propertyName))
{
if (_options.minimumHeight > _options.maximumHeight)
{
Debug.LogError("Maximum Height less than Minimum Height.Swapping values for extrusion.");
var temp = _options.minimumHeight;
_options.minimumHeight = _options.maximumHeight;
_options.maximumHeight = temp;
}
float featureHeight;
try
{
featureHeight = Convert.ToSingle(feature.Properties[_options.propertyName]);
}
catch (Exception)
{
Debug.LogError("Property: '" + _options.propertyName + "' must contain a numerical value for extrusion.");
return;
}
maxHeight = Math.Min(Math.Max(_options.minimumHeight, featureHeight), _options.maximumHeight);
if (feature.Properties.ContainsKey("min_height"))
{
var featureMinHeight = Convert.ToSingle(feature.Properties["min_height"]);
minHeight = Math.Min(featureMinHeight, _options.maximumHeight);
}
}
break;
case ExtrusionType.AbsoluteHeight:
maxHeight = _options.maximumHeight;
break;
default:
break;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a7dfb85d33966074ebf2acd2de263474
timeCreated: 1478553093
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,497 @@
using System;
using Mapbox.Unity.Utilities;
using System.Collections.Generic;
using Mapbox.Unity.Map;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
/// <summary>
/// Line Mesh Modifier creates line polygons from a list of vertices. It offsets the original vertices to both sides using Width parameter and triangulates them manually.
/// It also creates tiled UV mapping using the line length.
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Line Mesh Modifier")]
public class LineMeshModifier : MeshModifier
{
#region Constants
private readonly float _cosHalfSharpCorner = Mathf.Cos(75f / 2f * (Mathf.PI / 180f));
private readonly float _sharpCornerOffset = 15f;
#endregion
#region Line Parameters
private LineGeometryOptions _options;
private float _scaledWidth;
#endregion
#region Mesh Generation Fields
private List<Vector3> _vertexList;
private List<Vector3> _normalList;
private List<int> _triangleList;
private List<Vector2> _uvList;
private List<Vector4> _tangentList;
//triangle indices
private int _index1 = -1;
private int _index2 = -1;
private int _index3 = -1;
private float _cornerOffsetA;
private float _cornerOffsetB;
private bool _startOfLine = true;
private Vector3 _prevVertex;
private Vector3 _currentVertex;
private Vector3 _nextVertex;
private Vector3 _prevNormal;
private Vector3 _nextNormal;
private float _distance = 0f;
#endregion
#region Modifier Overrides
public override ModifierType Type
{
get { return ModifierType.Preprocess; }
}
public override void Initialize()
{
_scaledWidth = _options.Width;
_vertexList = new List<Vector3>();
_normalList = new List<Vector3>();
_triangleList = new List<int>();
_uvList = new List<Vector2>();
_tangentList = new List<Vector4>();
}
public override void SetProperties(ModifierProperties properties)
{
_options = (LineGeometryOptions) properties;
properties.PropertyHasChanged += UpdateModifier;
}
public override void Run(VectorFeatureUnity feature, MeshData md, float scale)
{
_scaledWidth = _options.Width * scale;
ExtrudeLine(feature, md);
}
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
_scaledWidth = tile != null ? _options.Width * tile.TileScale : _options.Width;
ExtrudeLine(feature, md);
}
#endregion
#region Mesh Generations
private void ExtrudeLine(VectorFeatureUnity feature, MeshData md)
{
if (feature.Points.Count < 1)
{
return;
}
foreach (var roadSegment in feature.Points)
{
if (roadSegment.Count < 2)
continue;
ResetFields();
var roadSegmentCount = roadSegment.Count;
for (int i = 0; i < roadSegmentCount; i++)
{
_nextVertex = i != (roadSegmentCount - 1) ? roadSegment[i + 1] : Constants.Math.Vector3Unused;
if (_nextNormal != Constants.Math.Vector3Unused)
{
_prevNormal = _nextNormal;
}
if (_currentVertex != Constants.Math.Vector3Unused)
{
_prevVertex = _currentVertex;
}
_currentVertex = roadSegment[i];
_nextNormal = (_nextVertex != Constants.Math.Vector3Unused)
? (_nextVertex - _currentVertex).normalized.Perpendicular()
: _prevNormal;
if (_prevNormal == Constants.Math.Vector3Unused)
{
_prevNormal = _nextNormal;
}
var joinNormal = (_prevNormal + _nextNormal).normalized;
/* joinNormal prevNormal
* ↖ ↑
* .________. prevVertex
* |
* nextNormal ← | currentVertex
* |
* nextVertex !
*
*/
var cosHalfAngle = joinNormal.x * _nextNormal.x + joinNormal.z * _nextNormal.z;
var miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle : float.PositiveInfinity;
var isSharpCorner = cosHalfAngle < _cosHalfSharpCorner && _prevVertex != Constants.Math.Vector3Unused && _nextVertex != Constants.Math.Vector3Unused;
if (isSharpCorner && i > 0)
{
var prevSegmentLength = Vector3.Distance(_currentVertex, _prevVertex);
if (prevSegmentLength > 2 * _sharpCornerOffset)
{
var dir = (_currentVertex - _prevVertex);
var newPrevVertex = _currentVertex - (dir * (_sharpCornerOffset / prevSegmentLength));
_distance += Vector3.Distance(newPrevVertex, _prevVertex);
AddCurrentVertex(newPrevVertex, _distance, _prevNormal, md);
_prevVertex = newPrevVertex;
}
}
var middleVertex = _prevVertex != Constants.Math.Vector3Unused && _nextVertex != Constants.Math.Vector3Unused;
var currentJoin = middleVertex ? _options.JoinType : _options.CapType;
if (middleVertex && currentJoin == JoinType.Round)
{
if (miterLength < _options.RoundLimit)
{
currentJoin = JoinType.Miter;
}
else if (miterLength <= 2)
{
currentJoin = JoinType.Fakeround;
}
}
if (currentJoin == JoinType.Miter && miterLength > _options.MiterLimit)
{
currentJoin = JoinType.Bevel;
}
if (currentJoin == JoinType.Bevel)
{
// The maximum extrude length is 128 / 63 = 2 times the width of the line
// so if miterLength >= 2 we need to draw a different type of bevel here.
if (miterLength > 2)
{
currentJoin = JoinType.Flipbevel;
}
// If the miterLength is really small and the line bevel wouldn't be visible,
// just draw a miter join to save a triangle.
if (miterLength < _options.MiterLimit)
{
currentJoin = JoinType.Miter;
}
}
if (_prevVertex != Constants.Math.Vector3Unused)
{
_distance += Vector3.Distance(_currentVertex, _prevVertex);
}
if (currentJoin == JoinType.Miter)
{
joinNormal *= miterLength;
AddCurrentVertex(_currentVertex, _distance, joinNormal, md);
}
else if (currentJoin == JoinType.Flipbevel)
{
// miter is too big, flip the direction to make a beveled join
if (miterLength > 100)
{
// Almost parallel lines
joinNormal = _nextNormal * -1;
}
else
{
var direction = (_prevNormal.x * _nextNormal.z - _prevNormal.z * _nextNormal.x) > 0 ? -1 : 1;
var bevelLength = miterLength * (_prevNormal + _nextNormal).magnitude / (_prevNormal - _nextNormal).magnitude;
joinNormal = joinNormal.Perpendicular() * (bevelLength * direction);
}
AddCurrentVertex(_currentVertex, _distance, joinNormal, md, 0, 0);
AddCurrentVertex(_currentVertex, _distance, joinNormal * -1, md, 0, 0);
}
else if (currentJoin == JoinType.Bevel || currentJoin == JoinType.Fakeround)
{
var lineTurnsLeft = (_prevNormal.x * _nextNormal.z - _prevNormal.z * _nextNormal.x) > 0;
var offset = (float) -Math.Sqrt(miterLength * miterLength - 1);
if (lineTurnsLeft)
{
_cornerOffsetB = 0;
_cornerOffsetA = offset;
}
else
{
_cornerOffsetA = 0;
_cornerOffsetB = offset;
}
// Close previous segment with a bevel
if (!_startOfLine)
{
AddCurrentVertex(_currentVertex, _distance, _prevNormal, md, _cornerOffsetA, _cornerOffsetB);
}
if (currentJoin == JoinType.Fakeround)
{
// The join angle is sharp enough that a round join would be visible.
// Bevel joins fill the gap between segments with a single pie slice triangle.
// Create a round join by adding multiple pie slices. The join isn't actually round, but
// it looks like it is at the sizes we render lines at.
// Add more triangles for sharper angles.
// This math is just a good enough approximation. It isn't "correct".
var n = Mathf.Floor((0.5f - (cosHalfAngle - 0.5f)) * 8);
Vector3 approxFractionalJoinNormal;
for (var m = 0f; m < n; m++)
{
approxFractionalJoinNormal = (_nextNormal * ((m + 1f) / (n + 1f)) + (_prevNormal)).normalized;
AddPieSliceVertex(_currentVertex, _distance, approxFractionalJoinNormal, lineTurnsLeft, md);
}
AddPieSliceVertex(_currentVertex, _distance, joinNormal, lineTurnsLeft, md);
//change it to go -1, not sure if it's a good idea but it adds the last vertex in the corner,
//as duplicate of next road segment start
for (var k = n - 1; k >= -1; k--)
{
approxFractionalJoinNormal = (_prevNormal * ((k + 1) / (n + 1)) + (_nextNormal)).normalized;
AddPieSliceVertex(_currentVertex, _distance, approxFractionalJoinNormal, lineTurnsLeft, md);
}
//ending corner
_index1 = -1;
_index2 = -1;
}
if (_nextVertex != Constants.Math.Vector3Unused)
{
AddCurrentVertex(_currentVertex, _distance, _nextNormal, md, -_cornerOffsetA, -_cornerOffsetB);
}
}
else if (currentJoin == JoinType.Butt)
{
if (!_startOfLine)
{
// Close previous segment with a butt
AddCurrentVertex(_currentVertex, _distance, _prevNormal, md, 0, 0);
}
// Start next segment with a butt
if (_nextVertex != Constants.Math.Vector3Unused)
{
AddCurrentVertex(_currentVertex, _distance, _nextNormal, md, 0, 0);
}
}
else if (currentJoin == JoinType.Square)
{
if (!_startOfLine)
{
// Close previous segment with a square cap
AddCurrentVertex(_currentVertex, _distance, _prevNormal, md, 1, 1);
// The segment is done. Unset vertices to disconnect segments.
_index1 = _index2 = -1;
}
// Start next segment
if (_nextVertex != Constants.Math.Vector3Unused)
{
AddCurrentVertex(_currentVertex, _distance, _nextNormal, md, -1, -1);
}
}
else if (currentJoin == JoinType.Round)
{
if (_startOfLine)
{
AddCurrentVertex(_currentVertex, _distance, _prevNormal * 0.33f, md, -2f, -2f);
AddCurrentVertex(_currentVertex, _distance, _prevNormal * 0.66f, md, -.7f, -.7f);
AddCurrentVertex(_currentVertex, _distance, _prevNormal, md, 0, 0);
}
else if (_nextVertex == Constants.Math.Vector3Unused)
{
AddCurrentVertex(_currentVertex, _distance, _prevNormal, md, 0, 0);
AddCurrentVertex(_currentVertex, _distance, _prevNormal * 0.66f, md, .7f, .7f);
AddCurrentVertex(_currentVertex, _distance, _prevNormal * 0.33f, md, 2f, 2f);
_index1 = -1;
_index2 = -1;
}
else
{
AddCurrentVertex(_currentVertex, _distance, _prevNormal, md, 0, 0);
AddCurrentVertex(_currentVertex, _distance, _nextNormal, md, 0, 0);
}
}
if (isSharpCorner && i < roadSegmentCount - 1)
{
var nextSegmentLength = Vector3.Distance(_currentVertex, _nextVertex);
if (nextSegmentLength > 2 * _sharpCornerOffset)
{
var newCurrentVertex = _currentVertex + ((_nextVertex - _currentVertex) * (_sharpCornerOffset / nextSegmentLength)); //._round()
_distance += Vector3.Distance(newCurrentVertex, _currentVertex);
AddCurrentVertex(newCurrentVertex, _distance, _nextNormal, md);
_currentVertex = newCurrentVertex;
}
}
_startOfLine = false;
}
md.Edges.Add(md.Vertices.Count);
md.Edges.Add(md.Vertices.Count + 1);
md.Edges.Add(md.Vertices.Count + _vertexList.Count - 1);
md.Edges.Add(md.Vertices.Count + _vertexList.Count - 2);
md.Vertices.AddRange(_vertexList);
md.Normals.AddRange(_normalList);
if (md.Triangles.Count == 0)
{
md.Triangles.Add(new List<int>());
}
md.Triangles[0].AddRange(_triangleList);
md.Tangents.AddRange(_tangentList);
md.UV[0].AddRange(_uvList);
}
}
private void ResetFields()
{
_index1 = -1;
_index2 = -1;
_index3 = -1;
_startOfLine = true;
_cornerOffsetA = 0f;
_cornerOffsetB = 0f;
_vertexList.Clear();
_normalList.Clear();
_uvList.Clear();
_tangentList.Clear();
_triangleList.Clear();
_prevVertex = Constants.Math.Vector3Unused;
_currentVertex = Constants.Math.Vector3Unused;
_nextVertex = Constants.Math.Vector3Unused;
_prevNormal = Constants.Math.Vector3Unused;
_nextNormal = Constants.Math.Vector3Unused;
_distance = 0f;
}
private void AddPieSliceVertex(Vector3 vertexPosition, float dist, Vector3 normal, bool lineTurnsLeft, MeshData md)
{
var triIndexStart = md.Vertices.Count;
var extrude = normal * (lineTurnsLeft ? -1 : 1);
_vertexList.Add(vertexPosition + extrude * _scaledWidth);
_normalList.Add(Constants.Math.Vector3Up);
_uvList.Add(new Vector2(1, dist));
_tangentList.Add(normal.Perpendicular() * -1);
_index3 = triIndexStart + _vertexList.Count - 1;
if (_index1 >= 0 && _index2 >= 0)
{
_triangleList.Add(_index1);
_triangleList.Add(_index3);
_triangleList.Add(_index2);
if (!lineTurnsLeft)
{
md.Edges.Add(_index3);
md.Edges.Add(_index1);
}
else
{
md.Edges.Add(_index2);
md.Edges.Add(_index3);
}
}
if (lineTurnsLeft)
{
_index2 = _index3;
}
else
{
_index1 = _index3;
}
}
private void AddCurrentVertex(Vector3 vertexPosition, float dist, Vector3 normal, MeshData md, float endLeft = 0, float endRight = 0)
{
var triIndexStart = md.Vertices.Count;
var extrude = normal;
if (endLeft != 0)
{
extrude -= (normal.Perpendicular() * endLeft);
}
var vert = vertexPosition + extrude * _scaledWidth;
_vertexList.Add(vert);
_normalList.Add(Constants.Math.Vector3Up);
_uvList.Add(new Vector2(1, dist));
_tangentList.Add(normal.Perpendicular() * -1);
_index3 = triIndexStart + _vertexList.Count - 1;
if (_index1 >= triIndexStart && _index2 >= triIndexStart)
{
_triangleList.Add(_index1);
_triangleList.Add(_index3);
_triangleList.Add(_index2);
md.Edges.Add(triIndexStart + _vertexList.Count - 1);
md.Edges.Add(triIndexStart + _vertexList.Count - 3);
}
_index1 = _index2;
_index2 = _index3;
extrude = normal * -1;
if (endRight != 0)
{
extrude -= normal.Perpendicular() * endRight;
}
_vertexList.Add(vertexPosition + extrude * _scaledWidth);
_normalList.Add(Constants.Math.Vector3Up);
_uvList.Add(new Vector2(0, dist));
_tangentList.Add(normal.Perpendicular() * -1);
_index3 = triIndexStart + _vertexList.Count - 1;
if (_index1 >= triIndexStart && _index2 >= triIndexStart)
{
_triangleList.Add(_index1);
_triangleList.Add(_index2);
_triangleList.Add(_index3);
md.Edges.Add(triIndexStart + _vertexList.Count - 3);
md.Edges.Add(triIndexStart + _vertexList.Count - 1);
}
_index1 = _index2;
_index2 = _index3;
}
#endregion
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9e1e9c060b6589e4e8b990a9c709bcdd
timeCreated: 1478553093
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,172 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using Assets.Mapbox.Unity.MeshGeneration.Modifiers.MeshModifiers;
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Loft Modifier")]
public class LoftModifier : MeshModifier
{
public override ModifierType Type { get { return ModifierType.Preprocess; } }
public GameObject Slice;
public bool _closeEdges = false;
public float SliceScaleMultiplier = 1;
private int _counter = 0;
private List<Vector3> _slice;
private int _sliceCount;
private float _sliceTotalMagnitude;
private Vector2[] _sliceUvs;
public override void Initialize()
{
base.Initialize();
_slice = new List<Vector3>();
foreach (Transform tr in Slice.transform)
{
_slice.Add(tr.position);
}
_sliceCount = _slice.Count;
_sliceTotalMagnitude = 0;
for (int i = 0; i < _sliceCount - 1; i++)
{
_sliceTotalMagnitude += (_slice[i + 1] - _slice[i]).magnitude;
}
_sliceUvs = new Vector2[_sliceCount];
_sliceUvs[0] = new Vector2(0, 0);
for (int i = 0; i < _sliceCount - 1; i++)
{
_sliceUvs[i + 1] = new Vector2(0, _sliceUvs[i].y + (_slice[i + 1] - _slice[i]).magnitude / _sliceTotalMagnitude);
}
}
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
if (feature.Points.Count < 1)
return;
foreach (var roadSegment in feature.Points)
{
_counter = roadSegment.Count;
if (_counter <= 1)
continue;
var vl = new List<Vector3>(_sliceCount * _counter);
var edges = new List<Vector3>(_counter);
int co = 0;
for (int j = 0; j < _counter; j++)
{
var current = Constants.Math.Vector3Zero;
current = roadSegment[j];
Vector3 dirCurrent, dir1, dir2;
if (j > 0 && j < (_counter - 1))
{
dir1 = (roadSegment[j] - roadSegment[j - 1]).normalized;
dir2 = (roadSegment[j + 1] - roadSegment[j]).normalized;
dirCurrent = (dir2 + dir1).normalized;
}
else if (j == 0) //first
{
dirCurrent = (roadSegment[j + 1] - roadSegment[j]).normalized;
}
else //last
{
dirCurrent = (roadSegment[j] - roadSegment[j - 1]).normalized;
}
var q = Quaternion.LookRotation(dirCurrent);
co = _slice.Count;
for (int i = 0; i < co; i++)
{
var p = (q * _slice[i]) * SliceScaleMultiplier;
vl.Add(p + current);
if (i == co - 1) //last item capped
{
edges.Add(p + current);
}
}
}
if (md.Triangles.Count == 0)
{
md.Triangles.Add(new List<int>());
}
md.Vertices.Capacity = md.Vertices.Count + (vl.Count - _sliceCount) * 4;
md.Normals.Capacity = md.Normals.Count + (vl.Count - _sliceCount) * 4;
md.Triangles.Capacity = md.Triangles.Count + (vl.Count - _sliceCount) * 6;
var uvDist = 0f;
float edMag = 0f, h = 0f;
co = 0;
Vector3 norm;
for (int i = 0; i < _counter - 1; i++)
{
for (int j = 0; j < _sliceCount - 1; j++)
{
var ind = i * _sliceCount + j;
var ed = vl[ind + _sliceCount] - vl[ind];
edMag = ed.magnitude;
co = md.Vertices.Count;
norm = Vector3.Cross(vl[ind] - vl[ind + 1], vl[ind + _sliceCount] - vl[ind]).normalized;
md.Vertices.Add(vl[ind]);
md.Vertices.Add(vl[ind + 1]);
md.Vertices.Add(vl[ind + _sliceCount]);
md.Vertices.Add(vl[ind + _sliceCount + 1]);
h = (float)j / _sliceCount;
md.UV[0].Add(new Vector2(uvDist, ((float)j - 1) / _sliceCount));
md.UV[0].Add(new Vector2(uvDist, h));
md.UV[0].Add(new Vector2(uvDist + edMag, ((float)j - 1) / _sliceCount));
md.UV[0].Add(new Vector2(uvDist + edMag, h));
md.Tangents.Add(new Vector4(ed.normalized.x, ed.normalized.y, ed.normalized.z, 1));
md.Tangents.Add(new Vector4(ed.normalized.x, ed.normalized.y, ed.normalized.z, 1));
md.Tangents.Add(new Vector4(ed.normalized.x, ed.normalized.y, ed.normalized.z, 1));
md.Tangents.Add(new Vector4(ed.normalized.x, ed.normalized.y, ed.normalized.z, 1));
md.Normals.Add(norm);
md.Normals.Add(norm);
md.Normals.Add(norm);
md.Normals.Add(norm);
md.Triangles[0].Add(co);
md.Triangles[0].Add(co + 2);
md.Triangles[0].Add(co + 1);
md.Triangles[0].Add(co + 1);
md.Triangles[0].Add(co + 2);
md.Triangles[0].Add(co + 3);
}
uvDist += edMag;
}
if (_closeEdges && edges.Count > 2)
{
if (md.Triangles.Count < 2)
{
md.Triangles.Add(new List<int>());
}
var flatData = EarcutLibrary.Flatten(new List<List<Vector3>>() { edges });
var result = EarcutLibrary.Earcut(flatData.Vertices, flatData.Holes, flatData.Dim);
md.Triangles[1].AddRange(result.Select(x => md.Vertices.Count + x).ToList());
for (int i = 0; i < edges.Count; i++)
{
md.Vertices.Add(edges[i]);
md.Normals.Add(Constants.Math.Vector3Up);
md.UV[0].Add(new Vector2(edges[i].x, edges[i].z));
md.Tangents.Add(new Vector4(1,0,0,1));
}
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 634e53b543236f246833fc88fea03aa0
timeCreated: 1507750860
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,213 @@
using Mapbox.Unity.Map;
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using Assets.Mapbox.Unity.MeshGeneration.Modifiers.MeshModifiers;
using System;
/// <summary>
/// Polygon modifier creates the polygon (vertex&triangles) using the original vertex list.
/// Currently uses Triangle.Net for triangulation, which occasionally adds extra vertices to maintain a good triangulation so output vertex list might not be exactly same as the original vertex list.
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Polygon Mesh Modifier")]
public class PolygonMeshModifier : MeshGenerationBase
{
public override ModifierType Type
{
get { return ModifierType.Preprocess; }
}
private UVModifierOptions _options;
private Vector3 _v1, _v2;
#region Atlas Fields
private Vector3 _vert;
private AtlasEntity _currentFacade;
private Quaternion _textureDirection;
private Vector2[] _textureUvCoordinates;
private Vector3 _vertexRelativePos;
private Vector3 _firstVert;
private float minx;
private float miny;
private float maxx;
private float maxy;
#endregion
public override void SetProperties(ModifierProperties properties)
{
_options = (UVModifierOptions) properties;
_options.PropertyHasChanged += UpdateModifier;
}
public override void UnbindProperties()
{
_options.PropertyHasChanged -= UpdateModifier;
}
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
if (Criteria != null && Criteria.Count > 0)
{
foreach (var criterion in Criteria)
{
if (criterion.ShouldReplaceFeature(feature))
{
return;
}
}
}
var _counter = feature.Points.Count;
var subset = new List<List<Vector3>>(_counter);
Data flatData = null;
List<int> result = null;
var currentIndex = 0;
int vertCount = 0, polygonVertexCount = 0;
List<int> triList = null;
List<Vector3> sub = null;
for (int i = 0; i < _counter; i++)
{
sub = feature.Points[i];
//earcut is built to handle one polygon with multiple holes
//point data can contain multiple polygons though, so we're handling them separately here
vertCount = md.Vertices.Count;
if (IsClockwise(sub) && vertCount > 0)
{
flatData = EarcutLibrary.Flatten(subset);
result = EarcutLibrary.Earcut(flatData.Vertices, flatData.Holes, flatData.Dim);
polygonVertexCount = result.Count;
if (triList == null)
{
triList = new List<int>(polygonVertexCount);
}
else
{
triList.Capacity = triList.Count + polygonVertexCount;
}
for (int j = 0; j < polygonVertexCount; j++)
{
triList.Add(result[j] + currentIndex);
}
currentIndex = vertCount;
subset.Clear();
}
subset.Add(sub);
polygonVertexCount = sub.Count;
md.Vertices.Capacity = md.Vertices.Count + polygonVertexCount;
md.Normals.Capacity = md.Normals.Count + polygonVertexCount;
md.Edges.Capacity = md.Edges.Count + polygonVertexCount * 2;
var _size = md.TileRect.Size;
for (int j = 0; j < polygonVertexCount; j++)
{
md.Edges.Add(vertCount + ((j + 1) % polygonVertexCount));
md.Edges.Add(vertCount + j);
md.Vertices.Add(sub[j]);
md.Tangents.Add(Constants.Math.Vector3Forward);
md.Normals.Add(Constants.Math.Vector3Up);
if (_options.style == StyleTypes.Satellite)
{
var fromBottomLeft = new Vector2(
(float) (((sub[j].x + md.PositionInTile.x) / tile.TileScale + _size.x / 2) / _size.x),
(float) (((sub[j].z + md.PositionInTile.z) / tile.TileScale + _size.x / 2) / _size.x));
md.UV[0].Add(fromBottomLeft);
}
else if (_options.texturingType == UvMapType.Tiled)
{
md.UV[0].Add(new Vector2(sub[j].x, sub[j].z));
}
}
}
flatData = EarcutLibrary.Flatten(subset);
result = EarcutLibrary.Earcut(flatData.Vertices, flatData.Holes, flatData.Dim);
polygonVertexCount = result.Count;
if (_options.texturingType == UvMapType.Atlas || _options.texturingType == UvMapType.AtlasWithColorPalette)
{
_currentFacade = _options.atlasInfo.Roofs[UnityEngine.Random.Range(0, _options.atlasInfo.Roofs.Count)];
minx = float.MaxValue;
miny = float.MaxValue;
maxx = float.MinValue;
maxy = float.MinValue;
_textureUvCoordinates = new Vector2[md.Vertices.Count];
_textureDirection = Quaternion.FromToRotation((md.Vertices[0] - md.Vertices[1]), Mapbox.Unity.Constants.Math.Vector3Right);
_textureUvCoordinates[0] = new Vector2(0, 0);
_firstVert = md.Vertices[0];
for (int i = 1; i < md.Vertices.Count; i++)
{
_vert = md.Vertices[i];
_vertexRelativePos = _vert - _firstVert;
_vertexRelativePos = _textureDirection * _vertexRelativePos;
_textureUvCoordinates[i] = new Vector2(_vertexRelativePos.x, _vertexRelativePos.z);
if (_vertexRelativePos.x < minx)
minx = _vertexRelativePos.x;
if (_vertexRelativePos.x > maxx)
maxx = _vertexRelativePos.x;
if (_vertexRelativePos.z < miny)
miny = _vertexRelativePos.z;
if (_vertexRelativePos.z > maxy)
maxy = _vertexRelativePos.z;
}
var width = maxx - minx;
var height = maxy - miny;
for (int i = 0; i < md.Vertices.Count; i++)
{
md.UV[0].Add(new Vector2(
(((_textureUvCoordinates[i].x - minx) / width) * _currentFacade.TextureRect.width) + _currentFacade.TextureRect.x,
(((_textureUvCoordinates[i].y - miny) / height) * _currentFacade.TextureRect.height) + _currentFacade.TextureRect.y));
}
}
if (triList == null)
{
triList = new List<int>(polygonVertexCount);
}
else
{
triList.Capacity = triList.Count + polygonVertexCount;
}
for (int i = 0; i < polygonVertexCount; i++)
{
triList.Add(result[i] + currentIndex);
}
md.Triangles.Add(triList);
}
private bool IsClockwise(IList<Vector3> vertices)
{
double sum = 0.0;
var _counter = vertices.Count;
for (int i = 0; i < _counter; i++)
{
_v1 = vertices[i];
_v2 = vertices[(i + 1) % _counter];
sum += (_v2.x - _v1.x) * (_v2.z + _v1.z);
}
return sum > 0.0;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6c7ba03c743d46c4a99f4971c74f7cbd
timeCreated: 1478553093
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,147 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using UnityEngine;
using System.Collections.Generic;
using Mapbox.Unity.MeshGeneration.Data;
using Mapbox.Unity.Utilities;
using Mapbox.Unity.Map;
using System;
[System.Serializable]
public class FeatureBundle
{
//public name param will be displayed in inspector list ui instead of element x...
[HideInInspector] public string Name;
public bool active;
public GameObject prefab;
public bool scaleDownWithWorld = true;
[Geocode] public List<string> _prefabLocations = new List<string>();
public List<string> _explicitlyBlockedFeatureIds = new List<string>();
}
/// <summary>
/// ReplaceFeatureCollectionModifier aggregates multiple ReplaceFeatureModifier objects into one modifier.
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Replace Feature Collection Modifier")]
public class ReplaceFeatureCollectionModifier : GameObjectModifier, IReplacementCriteria
{
public List<FeatureBundle> features = new List<FeatureBundle>();
private List<ReplaceFeatureModifier> _replaceFeatureModifiers;
//update all names to make inspector look better...
private void OnValidate()
{
for (int i = 0; i < features.Count; i++)
{
features[i].Name = (features[i].prefab == null) ? "Feature" : features[i].prefab.name;
}
}
public override void Initialize()
{
base.Initialize();
if (_replaceFeatureModifiers != null && _replaceFeatureModifiers.Count > 0)
{
foreach (var replaceFeatureModifier in _replaceFeatureModifiers)
{
if (replaceFeatureModifier != null)
{
replaceFeatureModifier.Clear();
}
}
}
_replaceFeatureModifiers = new List<ReplaceFeatureModifier>();
foreach (FeatureBundle feature in features)
{
ReplaceFeatureModifier replaceFeatureModifier = ScriptableObject.CreateInstance<ReplaceFeatureModifier>();
replaceFeatureModifier.Active = feature.active;
replaceFeatureModifier.SpawnPrefabOptions = new SpawnPrefabOptions()
{
prefab = feature.prefab,
scaleDownWithWorld = feature.scaleDownWithWorld
};
replaceFeatureModifier.PrefabLocations = new List<string>(feature._prefabLocations);
replaceFeatureModifier.BlockedIds = new List<string>(feature._explicitlyBlockedFeatureIds);
replaceFeatureModifier.Initialize();
_replaceFeatureModifiers.Add(replaceFeatureModifier);
}
}
public override void FeaturePreProcess(VectorFeatureUnity feature)
{
foreach (ReplaceFeatureModifier modifier in _replaceFeatureModifiers)
{
if (modifier == null)
{
continue;
}
modifier.FeaturePreProcess(feature);
}
}
public override void SetProperties(ModifierProperties properties)
{
foreach (ReplaceFeatureModifier modifier in _replaceFeatureModifiers)
{
if (modifier == null)
{
continue;
}
modifier.SetProperties(properties);
}
}
public bool ShouldReplaceFeature(VectorFeatureUnity feature)
{
foreach (ReplaceFeatureModifier modifier in _replaceFeatureModifiers)
{
if (modifier == null)
{
continue;
}
if (modifier.ShouldReplaceFeature(feature))
{
return true;
}
}
return false;
}
public override void Run(VectorEntity ve, UnityTile tile)
{
foreach (ReplaceFeatureModifier modifier in _replaceFeatureModifiers)
{
modifier.Run(ve, tile);
}
}
public override void OnPoolItem(VectorEntity vectorEntity)
{
foreach (ReplaceFeatureModifier modifier in _replaceFeatureModifiers)
{
modifier.OnPoolItem(vectorEntity);
}
}
public override void Clear()
{
foreach (var subModules in _replaceFeatureModifiers)
{
subModules.Clear();
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 13ade7ea2a146442b9575619cf724737
timeCreated: 1530570387
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,331 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using System;
using Mapbox.Unity.Map;
using Mapbox.Utils;
using Mapbox.Unity.Utilities;
using Mapbox.VectorTile.Geometry;
using Mapbox.Unity.MeshGeneration.Interfaces;
/// <summary>
/// ReplaceBuildingFeatureModifier takes in POIs and checks if the feature layer has those points and deletes them
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Replace Feature Modifier")]
public class ReplaceFeatureModifier : GameObjectModifier, IReplacementCriteria
{
private List<Vector2d> _latLonToSpawn;
private Dictionary<ulong, GameObject> _objects;
private Dictionary<ulong, Vector2d> _objectPosition;
private static GameObject _poolGameObject;
[SerializeField]
private SpawnPrefabOptions _options;
private List<GameObject> _prefabList = new List<GameObject>();
[SerializeField]
[Geocode]
private List<string> _prefabLocations;
[SerializeField]
private List<string> _explicitlyBlockedFeatureIds;
//maximum distance to trigger feature replacement ( in tile space )
private const float _maxDistanceToBlockFeature_tilespace = 1000f;
/// <summary>
/// List of featureIds to test against.
/// We need a list of featureIds per location.
/// A list is required since buildings on tile boundary will have multiple id's for the same feature.
/// </summary>
private List<List<string>> _featureId;
private string _tempFeatureId;
private static AbstractMap _abstractMap;
public SpawnPrefabOptions SpawnPrefabOptions
{
set
{
_options = value;
}
}
public List<string> PrefabLocations
{
set
{
_prefabLocations = value;
}
}
public List<string> BlockedIds
{
set
{
_explicitlyBlockedFeatureIds = value;
}
}
public override void Initialize()
{
base.Initialize();
//duplicate the list of lat/lons to track which coordinates have already been spawned
_featureId = new List<List<string>>();
for (int i = 0; i < _prefabLocations.Count; i++)
{
_featureId.Add(new List<string>());
}
if (_objects == null)
{
_objects = new Dictionary<ulong, GameObject>();
_objectPosition = new Dictionary<ulong, Vector2d>();
if(_poolGameObject == null)
{
_poolGameObject = new GameObject("_inactive_prefabs_pool");
}
if(_abstractMap == null)
{
_abstractMap = FindObjectOfType<AbstractMap>();
}
if(_abstractMap != null)
{
_poolGameObject.transform.SetParent(_abstractMap.transform, true);
}
}
_latLonToSpawn = new List<Vector2d>();
foreach (var loc in _prefabLocations)
{
_latLonToSpawn.Add(Conversions.StringToLatLon(loc));
}
}
public override void SetProperties(ModifierProperties properties)
{
_options = (SpawnPrefabOptions)properties;
}
public override void FeaturePreProcess(VectorFeatureUnity feature)
{
int index = -1;
foreach (var point in _prefabLocations)
{
try
{
index++;
var coord = Conversions.StringToLatLon(point);
if (feature.ContainsLatLon(coord) && (feature.Data.Id != 0))
{
_featureId[index] = (_featureId[index] == null) ? new List<string>() : _featureId[index];
_tempFeatureId = feature.Data.Id.ToString();
string idCandidate = (_tempFeatureId.Length <= 3) ? _tempFeatureId : _tempFeatureId.Substring(0, _tempFeatureId.Length - 3);
_featureId[index].Add(idCandidate);
}
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
/// <summary>
/// Check the feature against the list of lat/lons in the modifier
/// </summary>
/// <returns><c>true</c>, if the feature overlaps with a lat/lon in the modifier <c>false</c> otherwise.</returns>
/// <param name="feature">Feature.</param>
public bool ShouldReplaceFeature(VectorFeatureUnity feature)
{
int index = -1;
//preventing spawning of explicitly blocked features
foreach (var blockedId in _explicitlyBlockedFeatureIds)
{
if (feature.Data.Id.ToString() == blockedId)
{
return true;
}
}
foreach (var point in _prefabLocations)
{
try
{
index++;
if (_featureId[index] != null)
{
foreach (var featureId in _featureId[index])
{
var latlngVector = Conversions.StringToLatLon(point);
var from = Conversions.LatLonToMeters(latlngVector.x, latlngVector.y);
var to = new Vector2d((feature.Points[0][0].x / feature.Tile.TileScale) + feature.Tile.Rect.Center.x, (feature.Points[0][0].z / feature.Tile.TileScale) + feature.Tile.Rect.Center.y);
var dist = Vector2d.Distance(from, to);
if (dist > 500)
{
return false;
}
if (feature.Data.Id.ToString().StartsWith(featureId, StringComparison.CurrentCulture))
{
return true;
}
}
}
}
catch (Exception e)
{
Debug.LogException(e);
}
}
return false;
}
public override void Run(VectorEntity ve, UnityTile tile)
{
//replace the feature only once per lat/lon
Vector2d latLong = Vector2d.zero;
if (ShouldSpawnFeature(ve.Feature, out latLong))
{
SpawnPrefab(ve, tile, latLong);
}
}
private void SpawnPrefab(VectorEntity ve, UnityTile tile, Vector2d latLong)
{
GameObject go;
var featureId = ve.Feature.Data.Id;
if (_objects.ContainsKey(featureId))
{
go = _objects[featureId];
go.SetActive(true);
go.transform.SetParent(ve.GameObject.transform, false);
}
else
{
go = Instantiate(_options.prefab);
_prefabList.Add(go);
_objects.Add(featureId, go);
_objectPosition.Add(featureId, latLong);
go.transform.SetParent(ve.GameObject.transform, false);
}
PositionScaleRectTransform(ve, tile, go, latLong);
if (_options.AllPrefabsInstatiated != null)
{
_options.AllPrefabsInstatiated(_prefabList);
}
}
public void PositionScaleRectTransform(VectorEntity ve, UnityTile tile, GameObject go, Vector2d latLong)
{
go.transform.localScale = _options.prefab.transform.localScale;
RectTransform goRectTransform;
IFeaturePropertySettable settable = null;
var latLongPosition = new Vector3();
var centroidVector = new Vector3();
foreach (var point in ve.Feature.Points[0])
{
centroidVector += point;
}
centroidVector = centroidVector / ve.Feature.Points[0].Count;
latLongPosition = Conversions.LatitudeLongitudeToUnityTilePosition(latLong, tile.CurrentZoom, tile.TileScale, 4096).ToVector3xz();
latLongPosition.y = centroidVector.y;
go.name = ve.Feature.Data.Id.ToString();
goRectTransform = go.GetComponent<RectTransform>();
if (goRectTransform == null)
{
go.transform.localPosition = centroidVector;
}
else
{
goRectTransform.anchoredPosition3D = centroidVector;
}
settable = go.GetComponent<IFeaturePropertySettable>();
if (settable != null)
{
settable.Set(ve.Feature.Properties);
}
if (_options.scaleDownWithWorld)
{
go.transform.localScale = (go.transform.localScale * (tile.TileScale));
}
}
/// <summary>
/// Checks if the feature should be used to spawn a prefab, once per lat/lon
/// </summary>
/// <returns><c>true</c>, if the feature should be spawned <c>false</c> otherwise.</returns>
/// <param name="feature">Feature.</param>
private bool ShouldSpawnFeature(VectorFeatureUnity feature, out Vector2d latLong)
{
latLong = Vector2d.zero;
if (feature == null)
{
return false;
}
if (_objects.ContainsKey(feature.Data.Id))
{
_objectPosition.TryGetValue(feature.Data.Id, out latLong);
_latLonToSpawn.Remove(latLong);
return true;
}
foreach (var point in _latLonToSpawn)
{
if (feature.ContainsLatLon(point))
{
_latLonToSpawn.Remove(point);
latLong = point;
return true;
}
}
return false;
}
public override void OnPoolItem(VectorEntity vectorEntity)
{
base.OnPoolItem(vectorEntity);
var featureId = vectorEntity.Feature.Data.Id;
if (!_objects.ContainsKey(featureId))
{
return;
}
var go = _objects[featureId];
if (go == null || _poolGameObject == null)
{
return;
}
go.SetActive(false);
go.transform.SetParent(_poolGameObject.transform, false);
}
public override void Clear()
{
foreach (var gameObject in _objects.Values)
{
gameObject.Destroy();
}
_objects.Clear();
_objectPosition.Clear();
_poolGameObject.Destroy();
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f386309a8f86f48aaabbbd02b9f95059
timeCreated: 1525817443
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Smooth Line Modifier")]
public class SmoothLineModifier : MeshModifier
{
public override ModifierType Type { get { return ModifierType.Preprocess; } }
public int _maxEdgeSectionCount = 40;
public int _preferredEdgeSectionLength = 10;
private int _counter, _counter2;
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
_counter = feature.Points.Count;
for (int i = 0; i < _counter; i++)
{
var nl = new List<Vector3>();
_counter2 = feature.Points[i].Count;
for (int j = 1; j < _counter2; j++)
{
nl.Add(feature.Points[i][j - 1]);
var dist = Vector3.Distance(feature.Points[i][j - 1], feature.Points[i][j]);
var step = Math.Min(_maxEdgeSectionCount, dist / _preferredEdgeSectionLength);
if (step > 1)
{
var counter = 1;
while (counter < step)
{
var nv = Vector3.Lerp(feature.Points[i][j - 1], feature.Points[i][j], Mathf.Min(1, counter / step));
nl.Add(nv);
counter++;
}
}
nl.Add(feature.Points[i][j]);
}
feature.Points[i] = nl;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9558f79dbf9b00d488e061a68f8394aa
timeCreated: 1492731089
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Snap Terrain Modifier")]
public class SnapTerrainModifier : MeshModifier
{
public override ModifierType Type { get { return ModifierType.Preprocess; } }
private double scaledX;
private double scaledY;
private int _counter;
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
scaledX = tile.Rect.Size.x * tile.TileScale;
scaledY = tile.Rect.Size.y * tile.TileScale;
_counter = md.Vertices.Count;
if (_counter > 0)
{
for (int i = 0; i < _counter; i++)
{
var h = tile.QueryHeightData(
(float)((md.Vertices[i].x + md.PositionInTile.x + scaledX / 2) / scaledX),
(float)((md.Vertices[i].z + md.PositionInTile.z + scaledY / 2) / scaledY));
md.Vertices[i] += new Vector3(0, h, 0);
}
}
else
{
foreach (var sub in feature.Points)
{
_counter = sub.Count;
for (int i = 0; i < _counter; i++)
{
var h = tile.QueryHeightData(
(float)((sub[i].x + md.PositionInTile.x + scaledX / 2) / scaledX),
(float)((sub[i].z + md.PositionInTile.z + scaledY / 2) / scaledY));
sub[i] += new Vector3(0, h, 0);
}
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 40bee3110109c0848bb86c9b5c436039
timeCreated: 1492732222
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using Mapbox.Unity.Map;
using Mapbox.Unity.MeshGeneration.Data;
using UnityEngine;
[CreateAssetMenu(menuName = "Mapbox/Modifiers/Snap Terrain Raycast Modifier")]
public class SnapTerrainRaycastModifier : MeshModifier
{
private const int RAY_LENGTH = 50;
[SerializeField]
private LayerMask _terrainMask;
private double scaledX;
private double scaledY;
public override ModifierType Type
{
get { return ModifierType.Preprocess; }
}
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
scaledX = tile.Rect.Size.x * tile.TileScale;
scaledY = tile.Rect.Size.y * tile.TileScale;
foreach (var sub in feature.Points)
{
for (int i = 0; i < sub.Count; i++)
{
var h = tile.QueryHeightData((float)((sub[i].x + md.PositionInTile.x + scaledX / 2) / scaledX), (float)((sub[i].z + md.PositionInTile.z + scaledY / 2) / scaledY));
RaycastHit hit;
Vector3 rayCenter =
new Vector3(sub[i].x + md.PositionInTile.x + tile.transform.position.x,
h + RAY_LENGTH / 2,
sub[i].z + md.PositionInTile.z + tile.transform.position.z);
if (Physics.Raycast(rayCenter, Vector3.down, out hit, RAY_LENGTH * 5, _terrainMask))
{
sub[i] += new Vector3(0, hit.point.y + md.PositionInTile.y - tile.transform.position.y, 0);
}
else
{
// Raycasting sometimes fails at terrain boundaries, fallback to tile height data.
sub[i] += new Vector3(0, h, 0);
}
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 41aa014e797344c2c99c979bad017646
timeCreated: 1492732222
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,121 @@
namespace Mapbox.Unity.MeshGeneration.Modifiers
{
using System.Collections.Generic;
using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using System;
using Mapbox.Unity.Map;
using Mapbox.Utils;
/// <summary>
/// UV Modifier works only with (and right after) Polygon Modifier and not with Line Mesh Modifier.
/// If UseSatelliteRoof parameter is false, it creates a tiled UV map, otherwise it creates a stretched UV map.
/// </summary>
[CreateAssetMenu(menuName = "Mapbox/Modifiers/UV Modifier")]
public class UvModifier : MeshModifier
{
UVModifierOptions _options;
public override ModifierType Type { get { return ModifierType.Preprocess; } }
private int _mdVertexCount;
private Vector2d _size;
private Vector3 _vert;
private List<Vector2> _uv = new List<Vector2>();
#region Atlas Fields
private AtlasEntity _currentFacade;
private Quaternion _textureDirection;
private Vector2[] _textureUvCoordinates;
private Vector3 _vertexRelativePos;
private Vector3 _firstVert;
private float minx;
private float miny;
private float maxx;
private float maxy;
#endregion
public override void SetProperties(ModifierProperties properties)
{
_options = (UVModifierOptions)properties;
_options.PropertyHasChanged += UpdateModifier;
}
public override void UnbindProperties()
{
_options.PropertyHasChanged -= UpdateModifier;
}
public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
{
if (md.Vertices.Count == 0 || feature == null || feature.Points.Count < 1)
{
return;
}
_uv.Clear();
_mdVertexCount = md.Vertices.Count;
_size = md.TileRect.Size;
if (_options.texturingType != UvMapType.Atlas && _options.texturingType != UvMapType.AtlasWithColorPalette)
{
for (int i = 0; i < _mdVertexCount; i++)
{
_vert = md.Vertices[i];
if (_options.style == StyleTypes.Satellite)
{
var fromBottomLeft = new Vector2((float)(((_vert.x + md.PositionInTile.x) / tile.TileScale + _size.x / 2) / _size.x),
(float)(((_vert.z + md.PositionInTile.z) / tile.TileScale + _size.x / 2) / _size.x));
_uv.Add(fromBottomLeft);
}
else if (_options.texturingType == UvMapType.Tiled)
{
_uv.Add(new Vector2(_vert.x, _vert.z));
}
}
}
else if (_options.texturingType == UvMapType.Atlas || _options.texturingType == UvMapType.AtlasWithColorPalette)
{
_currentFacade = _options.atlasInfo.Roofs[UnityEngine.Random.Range(0, _options.atlasInfo.Roofs.Count)];
minx = float.MaxValue;
miny = float.MaxValue;
maxx = float.MinValue;
maxy = float.MinValue;
_textureUvCoordinates = new Vector2[_mdVertexCount];
_textureDirection = Quaternion.FromToRotation((md.Vertices[0] - md.Vertices[1]), Mapbox.Unity.Constants.Math.Vector3Right);
_textureUvCoordinates[0] = new Vector2(0, 0);
_firstVert = md.Vertices[0];
for (int i = 1; i < _mdVertexCount; i++)
{
_vert = md.Vertices[i];
_vertexRelativePos = _vert - _firstVert;
_vertexRelativePos = _textureDirection * _vertexRelativePos;
_textureUvCoordinates[i] = new Vector2(_vertexRelativePos.x, _vertexRelativePos.z);
if (_vertexRelativePos.x < minx)
minx = _vertexRelativePos.x;
if (_vertexRelativePos.x > maxx)
maxx = _vertexRelativePos.x;
if (_vertexRelativePos.z < miny)
miny = _vertexRelativePos.z;
if (_vertexRelativePos.z > maxy)
maxy = _vertexRelativePos.z;
}
var width = maxx - minx;
var height = maxy - miny;
for (int i = 0; i < _mdVertexCount; i++)
{
_uv.Add(new Vector2(
(((_textureUvCoordinates[i].x - minx) / width) * _currentFacade.TextureRect.width) + _currentFacade.TextureRect.x,
(((_textureUvCoordinates[i].y - miny) / height) * _currentFacade.TextureRect.height) + _currentFacade.TextureRect.y));
}
}
md.UV[0].AddRange(_uv);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 059a0278fa9c44be087aad07dbf4fdf9
timeCreated: 1544732618
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: