[TASK] Initial commit with basic product setup
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Mapbox.Unity.MeshGeneration.Data
|
||||
{
|
||||
[Serializable]
|
||||
public class AtlasEntity
|
||||
{
|
||||
public Rect TextureRect;
|
||||
public int MidFloorCount;
|
||||
public float ColumnCount;
|
||||
|
||||
public float TopSectionRatio;
|
||||
public float BottomSectionRatio;
|
||||
|
||||
public int PreferredEdgeSectionLength = 10;
|
||||
public float FloorHeight;
|
||||
public float FirstFloorHeight;
|
||||
public float TopFloorHeight;
|
||||
|
||||
[HideInInspector] public float bottomOfTopUv;
|
||||
[HideInInspector] public float topOfMidUv;
|
||||
[HideInInspector] public float topOfBottomUv;
|
||||
[HideInInspector] public float midUvHeight;
|
||||
[HideInInspector] public float WallToFloorRatio;
|
||||
|
||||
public void CalculateParameters()
|
||||
{
|
||||
bottomOfTopUv = TextureRect.yMax - (TextureRect.size.y * TopSectionRatio); //not doing that scaling thing for y axis and floors yet
|
||||
topOfMidUv = TextureRect.yMax - (TextureRect.height * TopSectionRatio);
|
||||
topOfBottomUv = TextureRect.yMin + (TextureRect.size.y * BottomSectionRatio); // * (Mathf.Max(1, (float)Math.Floor(tby * textureSection.TopSectionFloorCount)) / textureSection.TopSectionFloorCount);
|
||||
midUvHeight = TextureRect.height * (1 - TopSectionRatio - BottomSectionRatio);
|
||||
WallToFloorRatio = (1 - TopSectionRatio - BottomSectionRatio) * (TextureRect.height / TextureRect.width);
|
||||
}
|
||||
}
|
||||
|
||||
public enum AtlasEntityType
|
||||
{
|
||||
TextureBased,
|
||||
MeshGenBased
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "Mapbox/AtlasInfo")]
|
||||
public class AtlasInfo : ScriptableObject
|
||||
{
|
||||
public List<AtlasEntity> Textures;
|
||||
public List<AtlasEntity> Roofs;
|
||||
|
||||
private UnityEvent m_OnValidate = new UnityEvent();
|
||||
|
||||
public AtlasEntityType AtlasEntityType;
|
||||
|
||||
public void AddOnValidateEvent(UnityAction action)
|
||||
{
|
||||
m_OnValidate.AddListener(action);
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
if(m_OnValidate != null)
|
||||
{
|
||||
m_OnValidate.Invoke();
|
||||
}
|
||||
|
||||
if(AtlasEntityType == AtlasEntityType.TextureBased)
|
||||
{
|
||||
foreach (var tex in Textures)
|
||||
{
|
||||
|
||||
tex.FirstFloorHeight = tex.PreferredEdgeSectionLength * ((tex.TextureRect.height * tex.BottomSectionRatio) / tex.TextureRect.width);
|
||||
tex.TopFloorHeight = tex.PreferredEdgeSectionLength * ((tex.TextureRect.height * tex.TopSectionRatio) / tex.TextureRect.width);
|
||||
tex.FloorHeight = tex.PreferredEdgeSectionLength * ((1 - tex.TopSectionRatio - tex.BottomSectionRatio) * (tex.TextureRect.height / tex.TextureRect.width));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var tex in Textures)
|
||||
{
|
||||
tex.BottomSectionRatio = (tex.FirstFloorHeight / tex.PreferredEdgeSectionLength) * tex.TextureRect.width / tex.TextureRect.height;
|
||||
tex.TopSectionRatio = (tex.TopFloorHeight / tex.PreferredEdgeSectionLength) * tex.TextureRect.width / tex.TextureRect.height;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48879146f3ce9fb4abc113a9a2bb8e0a
|
||||
timeCreated: 1515082410
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Mapbox.Unity.MeshGeneration
|
||||
{
|
||||
using Mapbox.Unity.MeshGeneration.Data;
|
||||
using UnityEngine;
|
||||
|
||||
public class FeatureCollectionBase : ScriptableObject
|
||||
{
|
||||
public virtual void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void AddFeature(double[] position, VectorEntity ve)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d7c8347fa9db194e99888f8337ed5e2
|
||||
timeCreated: 1516628160
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f15de9092560a644913bb912f5642ec
|
||||
folderAsset: yes
|
||||
timeCreated: 1519738933
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace KDTree
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface which enables flexible distance functions.
|
||||
/// </summary>
|
||||
public interface DistanceFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute a distance between two n-dimensional points.
|
||||
/// </summary>
|
||||
/// <param name="p1">The first point.</param>
|
||||
/// <param name="p2">The second point.</param>
|
||||
/// <returns>The n-dimensional distance.</returns>
|
||||
double Distance(double[] p1, double[] p2);
|
||||
|
||||
/// <summary>
|
||||
/// Find the shortest distance from a point to an axis aligned rectangle in n-dimensional space.
|
||||
/// </summary>
|
||||
/// <param name="point">The point of interest.</param>
|
||||
/// <param name="min">The minimum coordinate of the rectangle.</param>
|
||||
/// <param name="max">The maximum coorindate of the rectangle.</param>
|
||||
/// <returns>The shortest n-dimensional distance between the point and rectangle.</returns>
|
||||
double DistanceToRectangle(double[] point, double[] min, double[] max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A distance function for our KD-Tree which returns squared euclidean distances.
|
||||
/// </summary>
|
||||
public class SquareEuclideanDistanceFunction : DistanceFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Find the squared distance between two n-dimensional points.
|
||||
/// </summary>
|
||||
/// <param name="p1">The first point.</param>
|
||||
/// <param name="p2">The second point.</param>
|
||||
/// <returns>The n-dimensional squared distance.</returns>
|
||||
public double Distance(double[] p1, double[] p2)
|
||||
{
|
||||
double fSum = 0;
|
||||
for (int i = 0; i < p1.Length; i++)
|
||||
{
|
||||
double fDifference = (p1[i] - p2[i]);
|
||||
fSum += fDifference * fDifference;
|
||||
}
|
||||
return fSum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the shortest distance from a point to an axis aligned rectangle in n-dimensional space.
|
||||
/// </summary>
|
||||
/// <param name="point">The point of interest.</param>
|
||||
/// <param name="min">The minimum coordinate of the rectangle.</param>
|
||||
/// <param name="max">The maximum coorindate of the rectangle.</param>
|
||||
/// <returns>The shortest squared n-dimensional squared distance between the point and rectangle.</returns>
|
||||
public double DistanceToRectangle(double[] point, double[] min, double[] max)
|
||||
{
|
||||
double fSum = 0;
|
||||
double fDifference = 0;
|
||||
for (int i = 0; i < point.Length; ++i)
|
||||
{
|
||||
fDifference = 0;
|
||||
if (point[i] > max[i])
|
||||
fDifference = (point[i] - max[i]);
|
||||
else if (point[i] < min[i])
|
||||
fDifference = (point[i] - min[i]);
|
||||
fSum += fDifference * fDifference;
|
||||
}
|
||||
return fSum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a40f078f2196e048b6b95fcc9738e90
|
||||
timeCreated: 1516636092
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,474 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace KDTree
|
||||
{
|
||||
/// <summary>
|
||||
/// A binary interval heap is double-ended priority queue is a priority queue that it allows
|
||||
/// for efficient removal of both the maximum and minimum element.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data type contained at each key.</typeparam>
|
||||
/// <remarks>This is based on this: https://bitbucket.org/rednaxela/knn-benchmark/src/tip/ags/utils/dataStructures/trees/thirdGenKD/ </remarks>
|
||||
public class IntervalHeap<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default size for a new interval heap.
|
||||
/// </summary>
|
||||
private const int DEFAULT_SIZE = 64;
|
||||
|
||||
/// <summary>
|
||||
/// The internal data array which contains the stored objects.
|
||||
/// </summary>
|
||||
private T[] tData;
|
||||
|
||||
/// <summary>
|
||||
/// The array of keys which
|
||||
/// </summary>
|
||||
private double[] tKeys;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new interval heap with the default capacity.
|
||||
/// </summary>
|
||||
public IntervalHeap() : this(DEFAULT_SIZE)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new interval heap with a custom capacity.
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public IntervalHeap(int capacity)
|
||||
{
|
||||
this.tData = new T[capacity];
|
||||
this.tKeys = new double[capacity];
|
||||
this.Capacity = capacity;
|
||||
this.Size = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of items in this interval heap.
|
||||
/// </summary>
|
||||
public int Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current capacity of this interval heap.
|
||||
/// </summary>
|
||||
public int Capacity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the data with the smallest key.
|
||||
/// </summary>
|
||||
public T Min
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
return tData[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data with the largest key.
|
||||
/// </summary>
|
||||
public T Max
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size == 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
else if (Size == 1)
|
||||
{
|
||||
return tData[0];
|
||||
}
|
||||
|
||||
return tData[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the smallest key.
|
||||
/// </summary>
|
||||
public double MinKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
return tKeys[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the largest key.
|
||||
/// </summary>
|
||||
public double MaxKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size == 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
else if (Size == 1)
|
||||
{
|
||||
return tKeys[0];
|
||||
}
|
||||
|
||||
return tKeys[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new data item at a given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The value which represents our data (i.e. a distance).</param>
|
||||
/// <param name="value">The data we want to store.</param>
|
||||
public void Insert(double key, T value)
|
||||
{
|
||||
// If more room is needed, double the array size.
|
||||
if (Size >= Capacity)
|
||||
{
|
||||
// Double the capacity.
|
||||
Capacity *= 2;
|
||||
|
||||
// Expand the data array.
|
||||
var newData = new T[Capacity];
|
||||
Array.Copy(tData, newData, tData.Length);
|
||||
tData = newData;
|
||||
|
||||
// Expand the key array.
|
||||
var newKeys = new double[Capacity];
|
||||
Array.Copy(tKeys, newKeys, tKeys.Length);
|
||||
tKeys = newKeys;
|
||||
}
|
||||
|
||||
// Insert the new value at the end.
|
||||
Size++;
|
||||
tData[Size-1] = value;
|
||||
tKeys[Size-1] = key;
|
||||
|
||||
// Ensure it is in the right place.
|
||||
SiftInsertedValueUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the item with the smallest key from the queue.
|
||||
/// </summary>
|
||||
public void RemoveMin()
|
||||
{
|
||||
// Check for errors.
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
|
||||
// Remove the item by
|
||||
Size--;
|
||||
tData[0] = tData[Size];
|
||||
tKeys[0] = tKeys[Size];
|
||||
tData[Size] = default(T);
|
||||
SiftDownMin(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the item with the smallest key in the queue.
|
||||
/// </summary>
|
||||
/// <param name="key">The new minimum key.</param>
|
||||
/// <param name="value">The new minumum data value.</param>
|
||||
public void ReplaceMin(double key, T value)
|
||||
{
|
||||
// Check for errors.
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
|
||||
// Add the data.
|
||||
tData[0] = value;
|
||||
tKeys[0] = key;
|
||||
|
||||
// If we have more than one item.
|
||||
if (Size > 1)
|
||||
{
|
||||
// Swap with pair if necessary.
|
||||
if (tKeys[1] < key)
|
||||
Swap(0, 1);
|
||||
SiftDownMin(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the item with the largest key in the queue.
|
||||
/// </summary>
|
||||
public void RemoveMax()
|
||||
{
|
||||
// If we have no items in the queue.
|
||||
if (Size == 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
// If we have one item, remove the min.
|
||||
else if (Size == 1)
|
||||
{
|
||||
RemoveMin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the max.
|
||||
Size--;
|
||||
tData[1] = tData[Size];
|
||||
tKeys[1] = tKeys[Size];
|
||||
tData[Size] = default(T);
|
||||
SiftDownMax(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swap out the item with the largest key in the queue.
|
||||
/// </summary>
|
||||
/// <param name="key">The new key for the largest item.</param>
|
||||
/// <param name="value">The new data for the largest item.</param>
|
||||
public void ReplaceMax(double key, T value)
|
||||
{
|
||||
if (Size == 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
else if (Size == 1)
|
||||
{
|
||||
ReplaceMin(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
tData[1] = value;
|
||||
tKeys[1] = key;
|
||||
// Swap with pair if necessary
|
||||
if (key < tKeys[0]) {
|
||||
Swap(0, 1);
|
||||
}
|
||||
SiftDownMax(1);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internal helper method which swaps two values in the arrays.
|
||||
/// This swaps both data and key entries.
|
||||
/// </summary>
|
||||
/// <param name="x">The first index.</param>
|
||||
/// <param name="y">The second index.</param>
|
||||
/// <returns>The second index.</returns>
|
||||
private int Swap(int x, int y)
|
||||
{
|
||||
// Store temp.
|
||||
T yData = tData[y];
|
||||
double yDist = tKeys[y];
|
||||
|
||||
// Swap
|
||||
tData[y] = tData[x];
|
||||
tKeys[y] = tKeys[x];
|
||||
tData[x] = yData;
|
||||
tKeys[x] = yDist;
|
||||
|
||||
// Return.
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Min-side (u % 2 == 0):
|
||||
* - leftchild: 2u + 2
|
||||
* - rightchild: 2u + 4
|
||||
* - parent: (x/2-1)&~1
|
||||
*
|
||||
* Max-side (u % 2 == 1):
|
||||
* - leftchild: 2u + 1
|
||||
* - rightchild: 2u + 3
|
||||
* - parent: (x/2-1)|1
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Place a newly inserted element a into the correct tree position.
|
||||
/// </summary>
|
||||
private void SiftInsertedValueUp()
|
||||
{
|
||||
// Work out where the element was inserted.
|
||||
int u = Size-1;
|
||||
|
||||
// If it is the only element, nothing to do.
|
||||
if (u == 0)
|
||||
{
|
||||
}
|
||||
|
||||
// If it is the second element, sort with it's pair.
|
||||
else if (u == 1)
|
||||
{
|
||||
// Swap if less than paired item.
|
||||
if (tKeys[u] < tKeys[u-1])
|
||||
Swap(u, u-1);
|
||||
}
|
||||
|
||||
// If it is on the max side,
|
||||
else if (u % 2 == 1)
|
||||
{
|
||||
// Already paired. Ensure pair is ordered right
|
||||
int p = (u/2-1)|1; // The larger value of the parent pair
|
||||
if (tKeys[u] < tKeys[u-1])
|
||||
{ // If less than it's pair
|
||||
u = Swap(u, u-1); // Swap with it's pair
|
||||
if (tKeys[u] < tKeys[p-1])
|
||||
{ // If smaller than smaller parent pair
|
||||
// Swap into min-heap side
|
||||
u = Swap(u, p-1);
|
||||
SiftUpMin(u);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tKeys[u] > tKeys[p])
|
||||
{ // If larger that larger parent pair
|
||||
// Swap into max-heap side
|
||||
u = Swap(u, p);
|
||||
SiftUpMax(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inserted in the lower-value slot without a partner
|
||||
int p = (u/2-1)|1; // The larger value of the parent pair
|
||||
if (tKeys[u] > tKeys[p])
|
||||
{ // If larger that larger parent pair
|
||||
// Swap into max-heap side
|
||||
u = Swap(u, p);
|
||||
SiftUpMax(u);
|
||||
}
|
||||
else if (tKeys[u] < tKeys[p-1])
|
||||
{ // If smaller than smaller parent pair
|
||||
// Swap into min-heap side
|
||||
u = Swap(u, p-1);
|
||||
SiftUpMin(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bubble elements up the min side of the tree.
|
||||
/// </summary>
|
||||
/// <param name="iChild">The child index.</param>
|
||||
private void SiftUpMin(int iChild)
|
||||
{
|
||||
// Min-side parent: (x/2-1)&~1
|
||||
for (int iParent = (iChild/2-1)&~1;
|
||||
iParent >= 0 && tKeys[iChild] < tKeys[iParent];
|
||||
iChild = iParent, iParent = (iChild/2-1)&~1)
|
||||
{
|
||||
Swap(iChild, iParent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bubble elements up the max side of the tree.
|
||||
/// </summary>
|
||||
/// <param name="iChild">The child index.</param>
|
||||
private void SiftUpMax(int iChild)
|
||||
{
|
||||
// Max-side parent: (x/2-1)|1
|
||||
for (int iParent = (iChild/2-1)|1;
|
||||
iParent >= 0 && tKeys[iChild] > tKeys[iParent];
|
||||
iChild = iParent, iParent = (iChild/2-1)|1)
|
||||
{
|
||||
Swap(iChild, iParent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bubble elements down the min side of the tree.
|
||||
/// </summary>
|
||||
/// <param name="iParent">The parent index.</param>
|
||||
private void SiftDownMin(int iParent)
|
||||
{
|
||||
// For each child of the parent.
|
||||
for (int iChild = iParent * 2 + 2; iChild < Size; iParent = iChild, iChild = iParent * 2 + 2)
|
||||
{
|
||||
// If the next child is less than the current child, select the next one.
|
||||
if (iChild + 2 < Size && tKeys[iChild + 2] < tKeys[iChild])
|
||||
{
|
||||
iChild += 2;
|
||||
}
|
||||
|
||||
// If it is less than our parent swap.
|
||||
if (tKeys[iChild] < tKeys[iParent])
|
||||
{
|
||||
Swap(iParent, iChild);
|
||||
|
||||
// Swap the pair if necessary.
|
||||
if (iChild+1 < Size && tKeys[iChild+1] < tKeys[iChild])
|
||||
{
|
||||
Swap(iChild, iChild+1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bubble elements down the max side of the tree.
|
||||
/// </summary>
|
||||
/// <param name="iParent"></param>
|
||||
private void SiftDownMax(int iParent)
|
||||
{
|
||||
// For each child on the max side of the tree.
|
||||
for (int iChild = iParent * 2 + 1; iChild <= Size; iParent = iChild, iChild = iParent * 2 + 1)
|
||||
{
|
||||
// If the child is the last one (and only has half a pair).
|
||||
if (iChild == Size)
|
||||
{
|
||||
// CHeck if we need to swap with th parent.
|
||||
if (tKeys[iChild - 1] > tKeys[iParent])
|
||||
Swap(iParent, iChild - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// If there is only room for a right child lower pair.
|
||||
else if (iChild + 2 == Size)
|
||||
{
|
||||
// Swap the children.
|
||||
if (tKeys[iChild + 1] > tKeys[iChild])
|
||||
{
|
||||
// Swap with the parent.
|
||||
if (tKeys[iChild + 1] > tKeys[iParent])
|
||||
Swap(iParent, iChild + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
else if (iChild + 2 < Size)
|
||||
{
|
||||
// If there is room for a right child upper pair
|
||||
if (tKeys[iChild + 2] > tKeys[iChild])
|
||||
{
|
||||
iChild += 2;
|
||||
}
|
||||
}
|
||||
if (tKeys[iChild] > tKeys[iParent])
|
||||
{
|
||||
Swap(iParent, iChild);
|
||||
// Swap with pair if necessary
|
||||
if (tKeys[iChild-1] > tKeys[iChild])
|
||||
{
|
||||
Swap(iChild, iChild-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e090ab286001c3488cf724a6a3325cf
|
||||
timeCreated: 1519738987
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace KDTree
|
||||
{
|
||||
/// <summary>
|
||||
/// A KD-Tree node which supports a generic number of dimensions. All data items
|
||||
/// need the same number of dimensions.
|
||||
/// This node splits based on the largest range of any dimension.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The generic data type this structure contains.</typeparam>
|
||||
/// <remarks>This is based on this: https://bitbucket.org/rednaxela/knn-benchmark/src/tip/ags/utils/dataStructures/trees/thirdGenKD/ </remarks>
|
||||
public class KDNode<T>
|
||||
{
|
||||
#region Internal properties and constructor
|
||||
// All types
|
||||
/// <summary>
|
||||
/// The number of dimensions for this node.
|
||||
/// </summary>
|
||||
protected internal int iDimensions;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum capacity of this node.
|
||||
/// </summary>
|
||||
protected internal int iBucketCapacity;
|
||||
|
||||
// Leaf only
|
||||
/// <summary>
|
||||
/// The array of locations. [index][dimension]
|
||||
/// </summary>
|
||||
protected internal double[][] tPoints;
|
||||
|
||||
/// <summary>
|
||||
/// The array of data values. [index]
|
||||
/// </summary>
|
||||
protected internal T[] tData;
|
||||
|
||||
// Stem only
|
||||
/// <summary>
|
||||
/// The left and right children.
|
||||
/// </summary>
|
||||
protected internal KDNode<T> pLeft, pRight;
|
||||
/// <summary>
|
||||
/// The split dimension.
|
||||
/// </summary>
|
||||
protected internal int iSplitDimension;
|
||||
/// <summary>
|
||||
/// The split value (larger go into the right, smaller go into left)
|
||||
/// </summary>
|
||||
protected internal double fSplitValue;
|
||||
|
||||
// Bounds
|
||||
/// <summary>
|
||||
/// The min and max bound for this node. All dimensions.
|
||||
/// </summary>
|
||||
protected internal double[] tMinBound, tMaxBound;
|
||||
|
||||
/// <summary>
|
||||
/// Does this node represent only one point.
|
||||
/// </summary>
|
||||
protected internal bool bSinglePoint;
|
||||
|
||||
/// <summary>
|
||||
/// Protected method which constructs a new KDNode.
|
||||
/// </summary>
|
||||
/// <param name="iDimensions">The number of dimensions for this node (all the same in the tree).</param>
|
||||
/// <param name="iBucketCapacity">The initial capacity of the bucket.</param>
|
||||
protected KDNode(int iDimensions, int iBucketCapacity)
|
||||
{
|
||||
// Variables.
|
||||
this.iDimensions = iDimensions;
|
||||
this.iBucketCapacity = iBucketCapacity;
|
||||
this.Size = 0;
|
||||
this.bSinglePoint = true;
|
||||
|
||||
// Setup leaf elements.
|
||||
this.tPoints = new double[iBucketCapacity+1][];
|
||||
this.tData = new T[iBucketCapacity+1];
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region External Operations
|
||||
/// <summary>
|
||||
/// The number of items in this leaf node and all children.
|
||||
/// </summary>
|
||||
public int Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this KDNode a leaf or not?
|
||||
/// </summary>
|
||||
public bool IsLeaf { get { return tPoints != null; } }
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new point into this leaf node.
|
||||
/// </summary>
|
||||
/// <param name="tPoint">The position which represents the data.</param>
|
||||
/// <param name="kValue">The value of the data.</param>
|
||||
public void AddPoint(double[] tPoint, T kValue)
|
||||
{
|
||||
// Find the correct leaf node.
|
||||
KDNode<T> pCursor = this;
|
||||
while (!pCursor.IsLeaf)
|
||||
{
|
||||
// Extend the size of the leaf.
|
||||
pCursor.ExtendBounds(tPoint);
|
||||
pCursor.Size++;
|
||||
|
||||
// If it is larger select the right, or lower, select the left.
|
||||
if (tPoint[pCursor.iSplitDimension] > pCursor.fSplitValue)
|
||||
{
|
||||
pCursor = pCursor.pRight;
|
||||
}
|
||||
else
|
||||
{
|
||||
pCursor = pCursor.pLeft;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert it into the leaf.
|
||||
pCursor.AddLeafPoint(tPoint, kValue);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal Operations
|
||||
/// <summary>
|
||||
/// Insert the point into the leaf.
|
||||
/// </summary>
|
||||
/// <param name="tPoint">The point to insert the data at.</param>
|
||||
/// <param name="kValue">The value at the point.</param>
|
||||
private void AddLeafPoint(double[] tPoint, T kValue)
|
||||
{
|
||||
// Add the data point to this node.
|
||||
tPoints[Size] = tPoint;
|
||||
tData[Size] = kValue;
|
||||
ExtendBounds(tPoint);
|
||||
Size++;
|
||||
|
||||
// Split if the node is getting too large in terms of data.
|
||||
if (Size == tPoints.Length - 1)
|
||||
{
|
||||
// If the node is getting too physically large.
|
||||
if (CalculateSplit())
|
||||
{
|
||||
// If the node successfully had it's split value calculated, split node.
|
||||
SplitLeafNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the node could not be split, enlarge node data capacity.
|
||||
IncreaseLeafCapacity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the point lies outside the boundaries, return false else true.
|
||||
/// </summary>
|
||||
/// <param name="tPoint">The point.</param>
|
||||
/// <returns>True if the point is inside the boundaries, false outside.</returns>
|
||||
private bool CheckBounds(double[] tPoint)
|
||||
{
|
||||
for (int i = 0; i < iDimensions; ++i)
|
||||
{
|
||||
if (tPoint[i] > tMaxBound[i]) return false;
|
||||
if (tPoint[i] < tMinBound[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extend this node to contain a new point.
|
||||
/// </summary>
|
||||
/// <param name="tPoint">The point to contain.</param>
|
||||
private void ExtendBounds(double[] tPoint)
|
||||
{
|
||||
// If we don't have bounds, create them using the new point then bail.
|
||||
if (tMinBound == null)
|
||||
{
|
||||
tMinBound = new double[iDimensions];
|
||||
tMaxBound = new double[iDimensions];
|
||||
Array.Copy(tPoint, tMinBound, iDimensions);
|
||||
Array.Copy(tPoint, tMaxBound, iDimensions);
|
||||
return;
|
||||
}
|
||||
|
||||
// For each dimension.
|
||||
for (int i = 0; i < iDimensions; ++i)
|
||||
{
|
||||
if (Double.IsNaN(tPoint[i]))
|
||||
{
|
||||
if (!Double.IsNaN(tMinBound[i]) || !Double.IsNaN(tMaxBound[i]))
|
||||
bSinglePoint = false;
|
||||
|
||||
tMinBound[i] = Double.NaN;
|
||||
tMaxBound[i] = Double.NaN;
|
||||
}
|
||||
else if (tMinBound[i] > tPoint[i])
|
||||
{
|
||||
tMinBound[i] = tPoint[i];
|
||||
bSinglePoint = false;
|
||||
}
|
||||
else if (tMaxBound[i] < tPoint[i])
|
||||
{
|
||||
tMaxBound[i] = tPoint[i];
|
||||
bSinglePoint = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Double the capacity of this leaf.
|
||||
/// </summary>
|
||||
private void IncreaseLeafCapacity()
|
||||
{
|
||||
Array.Resize<double[]>(ref tPoints, tPoints.Length * 2);
|
||||
Array.Resize<T>(ref tData, tData.Length * 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Work out if this leaf node should split. If it should, a new split value and dimension is calculated
|
||||
/// based on the dimension with the largest range.
|
||||
/// </summary>
|
||||
/// <returns>True if the node split, false if not.</returns>
|
||||
private bool CalculateSplit()
|
||||
{
|
||||
// Don't split if we are just one point.
|
||||
if (bSinglePoint)
|
||||
return false;
|
||||
|
||||
// Find the dimension with the largest range. This will be our split dimension.
|
||||
double fWidth = 0;
|
||||
for (int i = 0; i < iDimensions; i++)
|
||||
{
|
||||
double fDelta = (tMaxBound[i] - tMinBound[i]);
|
||||
if (Double.IsNaN(fDelta))
|
||||
fDelta = 0;
|
||||
|
||||
if (fDelta > fWidth)
|
||||
{
|
||||
iSplitDimension = i;
|
||||
fWidth = fDelta;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are not wide (i.e. all the points are in one place), don't split.
|
||||
if (fWidth == 0)
|
||||
return false;
|
||||
|
||||
// Split in the middle of the node along the widest dimension.
|
||||
fSplitValue = (tMinBound[iSplitDimension] + tMaxBound[iSplitDimension]) * 0.5;
|
||||
|
||||
// Never split on infinity or NaN.
|
||||
if (fSplitValue == Double.PositiveInfinity)
|
||||
fSplitValue = Double.MaxValue;
|
||||
else if (fSplitValue == Double.NegativeInfinity)
|
||||
fSplitValue = Double.MinValue;
|
||||
|
||||
// Don't let the split value be the same as the upper value as
|
||||
// can happen due to rounding errors!
|
||||
if (fSplitValue == tMaxBound[iSplitDimension])
|
||||
fSplitValue = tMinBound[iSplitDimension];
|
||||
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split this leaf node by creating left and right children, then moving all the children of
|
||||
/// this node into the respective buckets.
|
||||
/// </summary>
|
||||
private void SplitLeafNode()
|
||||
{
|
||||
// Create the new children.
|
||||
pRight = new KDNode<T>(iDimensions, iBucketCapacity);
|
||||
pLeft = new KDNode<T>(iDimensions, iBucketCapacity);
|
||||
|
||||
// Move each item in this leaf into the children.
|
||||
for (int i = 0; i < Size; ++i)
|
||||
{
|
||||
// Store.
|
||||
double[] tOldPoint = tPoints[i];
|
||||
T kOldData = tData[i];
|
||||
|
||||
// If larger, put it in the right.
|
||||
if (tOldPoint[iSplitDimension] > fSplitValue)
|
||||
pRight.AddLeafPoint(tOldPoint, kOldData);
|
||||
|
||||
// If smaller, put it in the left.
|
||||
else
|
||||
pLeft.AddLeafPoint(tOldPoint, kOldData);
|
||||
}
|
||||
|
||||
// Wipe the data from this KDNode.
|
||||
tPoints = null;
|
||||
tData = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec5d72f02adf6254aad038350ce771a3
|
||||
timeCreated: 1516636093
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace KDTree
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A KDTree class represents the root of a variable-dimension KD-Tree.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The generic data type we want this tree to contain.</typeparam>
|
||||
/// <remarks>This is based on this: https://bitbucket.org/rednaxela/knn-benchmark/src/tip/ags/utils/dataStructures/trees/thirdGenKD/ </remarks>
|
||||
public class KDTree<T> : KDNode<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new KD-Tree given a number of dimensions.
|
||||
/// </summary>
|
||||
/// <param name="iDimensions">The number of data sorting dimensions. i.e. 3 for a 3D point.</param>
|
||||
public KDTree(int iDimensions)
|
||||
: base(iDimensions, 24)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new KD-Tree given a number of dimensions and initial bucket capacity.
|
||||
/// </summary>
|
||||
/// <param name="iDimensions">The number of data sorting dimensions. i.e. 3 for a 3D point.</param>
|
||||
/// <param name="iBucketCapacity">The default number of items that can be stored in each node.</param>
|
||||
public KDTree(int iDimensions, int iBucketCapacity)
|
||||
: base(iDimensions, iBucketCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the nearest neighbours to a point in the kd tree using a square euclidean distance function.
|
||||
/// </summary>
|
||||
/// <param name="tSearchPoint">The point of interest.</param>
|
||||
/// <param name="iMaxReturned">The maximum number of points which can be returned by the iterator.</param>
|
||||
/// <param name="fDistance">A threshold distance to apply. Optional. Negative values mean that it is not applied.</param>
|
||||
/// <returns>A new nearest neighbour iterator with the given parameters.</returns>
|
||||
public NearestNeighbour<T> NearestNeighbors(double[] tSearchPoint, int iMaxReturned, double fDistance = -1)
|
||||
{
|
||||
DistanceFunctions distanceFunction = new SquareEuclideanDistanceFunction();
|
||||
return NearestNeighbors(tSearchPoint, distanceFunction, iMaxReturned, fDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the nearest neighbours to a point in the kd tree using a user defined distance function.
|
||||
/// </summary>
|
||||
/// <param name="tSearchPoint">The point of interest.</param>
|
||||
/// <param name="iMaxReturned">The maximum number of points which can be returned by the iterator.</param>
|
||||
/// <param name="kDistanceFunction">The distance function to use.</param>
|
||||
/// <param name="fDistance">A threshold distance to apply. Optional. Negative values mean that it is not applied.</param>
|
||||
/// <returns>A new nearest neighbour iterator with the given parameters.</returns>
|
||||
public NearestNeighbour<T> NearestNeighbors(double[] tSearchPoint, DistanceFunctions kDistanceFunction, int iMaxReturned, double fDistance)
|
||||
{
|
||||
return new NearestNeighbour<T>(this, tSearchPoint, kDistanceFunction, iMaxReturned, fDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c297650f28b0efc4d97523269824786d
|
||||
timeCreated: 1516636093
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace KDTree
|
||||
{
|
||||
/// <summary>
|
||||
/// A MinHeap is a smallest-first queue based around a binary heap so it is quick to insert / remove items.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data this MinHeap stores.</typeparam>
|
||||
/// <remarks>This is based on this: https://bitbucket.org/rednaxela/knn-benchmark/src/tip/ags/utils/dataStructures/trees/thirdGenKD/ </remarks>
|
||||
public class MinHeap<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default size for a min heap.
|
||||
/// </summary>
|
||||
private static int DEFAULT_SIZE = 64;
|
||||
|
||||
/// <summary>
|
||||
/// The data array. This stores the data items in the heap.
|
||||
/// </summary>
|
||||
private T[] tData;
|
||||
|
||||
/// <summary>
|
||||
/// The key array. This determines how items are ordered. Smallest first.
|
||||
/// </summary>
|
||||
private double[] tKeys;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new min heap with the default capacity.
|
||||
/// </summary>
|
||||
public MinHeap() : this(DEFAULT_SIZE)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new min heap with a given capacity.
|
||||
/// </summary>
|
||||
/// <param name="iCapacity"></param>
|
||||
public MinHeap(int iCapacity)
|
||||
{
|
||||
this.tData = new T[iCapacity];
|
||||
this.tKeys = new double[iCapacity];
|
||||
this.Capacity = iCapacity;
|
||||
this.Size = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of items in this queue.
|
||||
/// </summary>
|
||||
public int Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of space in this queue.
|
||||
/// </summary>
|
||||
public int Capacity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new element.
|
||||
/// </summary>
|
||||
/// <param name="key">The key which represents its position in the priority queue (ie. distance).</param>
|
||||
/// <param name="value">The value to be stored at the key.</param>
|
||||
public void Insert(double key, T value)
|
||||
{
|
||||
// If we need more room, double the space.
|
||||
if (Size >= Capacity)
|
||||
{
|
||||
// Calcualte the new capacity.
|
||||
Capacity *= 2;
|
||||
|
||||
// Copy the data array.
|
||||
var newData = new T[Capacity];
|
||||
Array.Copy(tData, newData, tData.Length);
|
||||
tData = newData;
|
||||
|
||||
// Copy the key array.
|
||||
var newKeys = new double[Capacity];
|
||||
Array.Copy(tKeys, newKeys, tKeys.Length);
|
||||
tKeys = newKeys;
|
||||
}
|
||||
|
||||
// Insert new value at the end
|
||||
tData[Size] = value;
|
||||
tKeys[Size] = key;
|
||||
SiftUp(Size);
|
||||
Size++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the smallest element.
|
||||
/// </summary>
|
||||
public void RemoveMin()
|
||||
{
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
|
||||
Size--;
|
||||
tData[0] = tData[Size];
|
||||
tKeys[0] = tKeys[Size];
|
||||
tData[Size] = default(T);
|
||||
SiftDown(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data stored at the minimum element.
|
||||
/// </summary>
|
||||
public T Min
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
|
||||
return tData[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the key which represents the minimum element.
|
||||
/// </summary>
|
||||
public double MinKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size == 0)
|
||||
throw new Exception();
|
||||
|
||||
return tKeys[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bubble a child item up the tree.
|
||||
/// </summary>
|
||||
/// <param name="iChild"></param>
|
||||
private void SiftUp(int iChild)
|
||||
{
|
||||
// For each parent above the child, if the parent is smaller then bubble it up.
|
||||
for (int iParent = (iChild - 1) / 2;
|
||||
iChild != 0 && tKeys[iChild] < tKeys[iParent];
|
||||
iChild = iParent, iParent = (iChild - 1) / 2)
|
||||
{
|
||||
T kData = tData[iParent];
|
||||
double dDist = tKeys[iParent];
|
||||
|
||||
tData[iParent] = tData[iChild];
|
||||
tKeys[iParent] = tKeys[iChild];
|
||||
|
||||
tData[iChild] = kData;
|
||||
tKeys[iChild] = dDist;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bubble a parent down through the children so it goes in the right place.
|
||||
/// </summary>
|
||||
/// <param name="iParent">The index of the parent.</param>
|
||||
private void SiftDown(int iParent)
|
||||
{
|
||||
// For each child.
|
||||
for (int iChild = iParent * 2 + 1; iChild < Size; iParent = iChild, iChild = iParent * 2 + 1)
|
||||
{
|
||||
// If the child is larger, select the next child.
|
||||
if (iChild + 1 < Size && tKeys[iChild] > tKeys[iChild + 1])
|
||||
iChild++;
|
||||
|
||||
// If the parent is larger than the largest child, swap.
|
||||
if (tKeys[iParent] > tKeys[iChild])
|
||||
{
|
||||
// Swap the points
|
||||
T pData = tData[iParent];
|
||||
double pDist = tKeys[iParent];
|
||||
|
||||
tData[iParent] = tData[iChild];
|
||||
tKeys[iParent] = tKeys[iChild];
|
||||
|
||||
tData[iChild] = pData;
|
||||
tKeys[iChild] = pDist;
|
||||
}
|
||||
|
||||
// TODO: REMOVE THE BREAK
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3dd3caca8a74224cab6c8dc36751097
|
||||
timeCreated: 1516636092
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace KDTree
|
||||
{
|
||||
/// <summary>
|
||||
/// A NearestNeighbour iterator for the KD-tree which intelligently iterates and captures relevant data in the search space.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data the iterator should handle.</typeparam>
|
||||
public class NearestNeighbour<T> : IEnumerator<T>, IEnumerable<T>
|
||||
{
|
||||
/// <summary>The point from which are searching in n-dimensional space.</summary>
|
||||
private double[] tSearchPoint;
|
||||
/// <summary>A distance function which is used to compare nodes and value positions.</summary>
|
||||
private DistanceFunctions kDistanceFunction;
|
||||
/// <summary>The tree nodes which have yet to be evaluated.</summary>
|
||||
private MinHeap<KDNode<T>> pPending;
|
||||
/// <summary>The values which have been evaluated and selected.</summary>
|
||||
private IntervalHeap<T> pEvaluated;
|
||||
/// <summary>The root of the kd tree to begin searching from.</summary>
|
||||
private KDNode<T> pRoot = null;
|
||||
|
||||
/// <summary>The max number of points we can return through this iterator.</summary>
|
||||
private int iMaxPointsReturned = 0;
|
||||
/// <summary>The number of points we can still test before conclusion.</summary>
|
||||
private int iPointsRemaining;
|
||||
/// <summary>Threshold to apply to tree iteration. Negative numbers mean no threshold applied.</summary>
|
||||
private double fThreshold;
|
||||
|
||||
/// <summary>Current value distance.</summary>
|
||||
private double _CurrentDistance = -1;
|
||||
/// <summary>Current value reference.</summary>
|
||||
private T _Current = default(T);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new nearest neighbour iterator.
|
||||
/// </summary>
|
||||
/// <param name="pRoot">The root of the tree to begin searching from.</param>
|
||||
/// <param name="tSearchPoint">The point in n-dimensional space to search.</param>
|
||||
/// <param name="kDistance">The distance function used to evaluate the points.</param>
|
||||
/// <param name="iMaxPoints">The max number of points which can be returned by this iterator. Capped to max in tree.</param>
|
||||
/// <param name="fThreshold">Threshold to apply to the search space. Negative numbers indicate that no threshold is applied.</param>
|
||||
public NearestNeighbour(KDNode<T> pRoot, double[] tSearchPoint, DistanceFunctions kDistance, int iMaxPoints, double fThreshold)
|
||||
{
|
||||
// Check the dimensionality of the search point.
|
||||
if (tSearchPoint.Length != pRoot.iDimensions)
|
||||
throw new Exception("Dimensionality of search point and kd-tree are not the same.");
|
||||
|
||||
// Store the search point.
|
||||
this.tSearchPoint = new double[tSearchPoint.Length];
|
||||
Array.Copy(tSearchPoint, this.tSearchPoint, tSearchPoint.Length);
|
||||
|
||||
// Store the point count, distance function and tree root.
|
||||
this.iPointsRemaining = Math.Min(iMaxPoints, pRoot.Size);
|
||||
this.fThreshold = fThreshold;
|
||||
this.kDistanceFunction = kDistance;
|
||||
this.pRoot = pRoot;
|
||||
this.iMaxPointsReturned = iMaxPoints;
|
||||
_CurrentDistance = -1;
|
||||
|
||||
// Create an interval heap for the points we check.
|
||||
this.pEvaluated = new IntervalHeap<T>();
|
||||
|
||||
// Create a min heap for the things we need to check.
|
||||
this.pPending = new MinHeap<KDNode<T>>();
|
||||
this.pPending.Insert(0, pRoot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for the next iterator item.
|
||||
/// </summary>
|
||||
/// <returns>True if we have one, false if not.</returns>
|
||||
public bool MoveNext()
|
||||
{
|
||||
// Bail if we are finished.
|
||||
if (iPointsRemaining == 0)
|
||||
{
|
||||
_Current = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
// While we still have paths to evaluate.
|
||||
while (pPending.Size > 0 && (pEvaluated.Size == 0 || (pPending.MinKey < pEvaluated.MinKey)))
|
||||
{
|
||||
// If there are pending paths possibly closer than the nearest evaluated point, check it out
|
||||
KDNode<T> pCursor = pPending.Min;
|
||||
pPending.RemoveMin();
|
||||
|
||||
// Descend the tree, recording paths not taken
|
||||
while (!pCursor.IsLeaf)
|
||||
{
|
||||
KDNode<T> pNotTaken;
|
||||
|
||||
// If the seach point is larger, select the right path.
|
||||
if (tSearchPoint[pCursor.iSplitDimension] > pCursor.fSplitValue)
|
||||
{
|
||||
pNotTaken = pCursor.pLeft;
|
||||
pCursor = pCursor.pRight;
|
||||
}
|
||||
else
|
||||
{
|
||||
pNotTaken = pCursor.pRight;
|
||||
pCursor = pCursor.pLeft;
|
||||
}
|
||||
|
||||
// Calculate the shortest distance between the search point and the min and max bounds of the kd-node.
|
||||
double fDistance = kDistanceFunction.DistanceToRectangle(tSearchPoint, pNotTaken.tMinBound, pNotTaken.tMaxBound);
|
||||
|
||||
// If it is greater than the threshold, skip.
|
||||
if (fThreshold >= 0 && fDistance > fThreshold)
|
||||
{
|
||||
//pPending.Insert(fDistance, pNotTaken);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only add the path we need more points or the node is closer than furthest point on list so far.
|
||||
if (pEvaluated.Size < iPointsRemaining || fDistance <= pEvaluated.MaxKey)
|
||||
{
|
||||
pPending.Insert(fDistance, pNotTaken);
|
||||
}
|
||||
}
|
||||
|
||||
// If all the points in this KD node are in one place.
|
||||
if (pCursor.bSinglePoint)
|
||||
{
|
||||
// Work out the distance between this point and the search point.
|
||||
double fDistance = kDistanceFunction.Distance(pCursor.tPoints[0], tSearchPoint);
|
||||
|
||||
// Skip if the point exceeds the threshold.
|
||||
// Technically this should never happen, but be prescise.
|
||||
if (fThreshold >= 0 && fDistance >= fThreshold)
|
||||
continue;
|
||||
|
||||
// Add the point if either need more points or it's closer than furthest on list so far.
|
||||
if (pEvaluated.Size < iPointsRemaining || fDistance <= pEvaluated.MaxKey)
|
||||
{
|
||||
for (int i = 0; i < pCursor.Size; ++i)
|
||||
{
|
||||
// If we don't need any more, replace max
|
||||
if (pEvaluated.Size == iPointsRemaining)
|
||||
pEvaluated.ReplaceMax(fDistance, pCursor.tData[i]);
|
||||
|
||||
// Otherwise insert.
|
||||
else
|
||||
pEvaluated.Insert(fDistance, pCursor.tData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the points in the KD node are spread out.
|
||||
else
|
||||
{
|
||||
// Treat the distance of each point seperately.
|
||||
for (int i = 0; i < pCursor.Size; ++i)
|
||||
{
|
||||
// Compute the distance between the points.
|
||||
double fDistance = kDistanceFunction.Distance(pCursor.tPoints[i], tSearchPoint);
|
||||
|
||||
// Skip if it exceeds the threshold.
|
||||
if (fThreshold >= 0 && fDistance >= fThreshold)
|
||||
continue;
|
||||
|
||||
// Insert the point if we have more to take.
|
||||
if (pEvaluated.Size < iPointsRemaining)
|
||||
pEvaluated.Insert(fDistance, pCursor.tData[i]);
|
||||
|
||||
// Otherwise replace the max.
|
||||
else if (fDistance < pEvaluated.MaxKey)
|
||||
pEvaluated.ReplaceMax(fDistance, pCursor.tData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select the point with the smallest distance.
|
||||
if (pEvaluated.Size == 0)
|
||||
return false;
|
||||
|
||||
iPointsRemaining--;
|
||||
_CurrentDistance = pEvaluated.MinKey;
|
||||
_Current = pEvaluated.Min;
|
||||
pEvaluated.RemoveMin();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the iterator.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
// Store the point count and the distance function.
|
||||
this.iPointsRemaining = Math.Min(iMaxPointsReturned, pRoot.Size);
|
||||
_CurrentDistance = -1;
|
||||
|
||||
// Create an interval heap for the points we check.
|
||||
this.pEvaluated = new IntervalHeap<T>();
|
||||
|
||||
// Create a min heap for the things we need to check.
|
||||
this.pPending = new MinHeap<KDNode<T>>();
|
||||
this.pPending.Insert(0, pRoot);
|
||||
}
|
||||
|
||||
public T Current
|
||||
{
|
||||
get { return _Current; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the distance of the current value to the search point.
|
||||
/// </summary>
|
||||
public double CurrentDistance
|
||||
{
|
||||
get { return _CurrentDistance; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the current value referenced by the iterator as an object.
|
||||
/// </summary>
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get { return _Current; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the current value referenced by the iterator.
|
||||
/// </summary>
|
||||
T IEnumerator<T>.Current
|
||||
{
|
||||
get { return _Current; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 838799301f7b90c4c9ddad55847c8093
|
||||
timeCreated: 1516636092
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Mapbox.Unity.MeshGeneration
|
||||
{
|
||||
using UnityEngine;
|
||||
using KDTree;
|
||||
using Mapbox.Unity.MeshGeneration.Data;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// KdTree Collection is a feature collection using KdTree mainly for distance based searchs like "find all buildings 100m around
|
||||
/// player" or "find 10 closest buildings to this point".
|
||||
/// KdTree structures focus on search performance so querying for features will be very fast using this collection. On the other
|
||||
/// hand it's not good for dynamic/moving entities but we don't have such features on map so it's one of the best options for maps.
|
||||
/// </summary>
|
||||
|
||||
[CreateAssetMenu(menuName = "Mapbox/Feature Collections/Kd Tree Collection")]
|
||||
public class KdTreeCollection : FeatureCollectionBase
|
||||
{
|
||||
private KDTree<VectorEntity> _entities;
|
||||
public int Count;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_entities = new KDTree.KDTree<VectorEntity>(2);
|
||||
}
|
||||
|
||||
public override void AddFeature(double[] position, VectorEntity ve)
|
||||
{
|
||||
_entities.AddPoint(position, ve);
|
||||
Count = _entities.Size;
|
||||
}
|
||||
|
||||
public NearestNeighbour<VectorEntity> NearestNeighbors(double[] v, int maxCount, float range)
|
||||
{
|
||||
return _entities.NearestNeighbors(v, maxCount, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82ed8ed837e25084bbe8a37d53c5b77b
|
||||
timeCreated: 1519741039
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace Mapbox.Unity.MeshGeneration.Data
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Utils;
|
||||
|
||||
// TODO: Do we need this class? Why not just use `Mesh`?
|
||||
public class MeshData
|
||||
{
|
||||
public Vector3 PositionInTile;
|
||||
public List<int> Edges;
|
||||
public Vector2 MercatorCenter;
|
||||
public RectD TileRect;
|
||||
public List<Vector3> Vertices;
|
||||
public List<Vector3> Normals;
|
||||
public List<Vector4> Tangents;
|
||||
public List<List<int>> Triangles;
|
||||
public List<List<Vector2>> UV;
|
||||
|
||||
public MeshData()
|
||||
{
|
||||
Edges = new List<int>();
|
||||
Vertices = new List<Vector3>();
|
||||
Normals = new List<Vector3>();
|
||||
Tangents = new List<Vector4>();
|
||||
Triangles = new List<List<int>>();
|
||||
UV = new List<List<Vector2>>();
|
||||
UV.Add(new List<Vector2>());
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
Edges.Clear();
|
||||
Vertices.Clear();
|
||||
Normals.Clear();
|
||||
Tangents.Clear();
|
||||
|
||||
foreach (var item in Triangles)
|
||||
{
|
||||
item.Clear();
|
||||
}
|
||||
foreach (var item in UV)
|
||||
{
|
||||
item.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2dc52b40d4a914f4d88ef8d5547272b5
|
||||
timeCreated: 1478552106
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
356
Assets/Mapbox SDK/Mapbox/Unity/MeshGeneration/Data/UnityTile.cs
Normal file
356
Assets/Mapbox SDK/Mapbox/Unity/MeshGeneration/Data/UnityTile.cs
Normal file
@@ -0,0 +1,356 @@
|
||||
using Mapbox.Unity.Map.Interfaces;
|
||||
|
||||
namespace Mapbox.Unity.MeshGeneration.Data
|
||||
{
|
||||
using UnityEngine;
|
||||
using Mapbox.Unity.MeshGeneration.Enums;
|
||||
using Mapbox.Unity.Utilities;
|
||||
using Utils;
|
||||
using Mapbox.Map;
|
||||
using System;
|
||||
using Mapbox.Unity.Map;
|
||||
using System.Collections.Generic;
|
||||
using Mapbox.Unity.MeshGeneration.Factories;
|
||||
|
||||
public class UnityTile : MonoBehaviour
|
||||
{
|
||||
public TileTerrainType ElevationType;
|
||||
|
||||
[SerializeField]
|
||||
private Texture2D _rasterData;
|
||||
public VectorTile VectorData { get; private set; }
|
||||
private Texture2D _heightTexture;
|
||||
private float[] _heightData;
|
||||
|
||||
private Texture2D _loadingTexture;
|
||||
//keeping track of tile objects to be able to cancel them safely if tile is destroyed before data fetching finishes
|
||||
private List<Tile> _tiles = new List<Tile>();
|
||||
|
||||
public bool IsRecycled = false;
|
||||
|
||||
#region CachedUnityComponents
|
||||
MeshRenderer _meshRenderer;
|
||||
public MeshRenderer MeshRenderer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_meshRenderer == null)
|
||||
{
|
||||
_meshRenderer = GetComponent<MeshRenderer>();
|
||||
if (_meshRenderer == null)
|
||||
{
|
||||
_meshRenderer = gameObject.AddComponent<MeshRenderer>();
|
||||
}
|
||||
}
|
||||
return _meshRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
private MeshFilter _meshFilter;
|
||||
public MeshFilter MeshFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_meshFilter == null)
|
||||
{
|
||||
_meshFilter = GetComponent<MeshFilter>();
|
||||
if (_meshFilter == null)
|
||||
{
|
||||
_meshFilter = gameObject.AddComponent<MeshFilter>();
|
||||
ElevationType = TileTerrainType.None;
|
||||
}
|
||||
}
|
||||
return _meshFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private Collider _collider;
|
||||
public Collider Collider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_collider == null)
|
||||
{
|
||||
_collider = GetComponent<Collider>();
|
||||
}
|
||||
return _collider;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Tile Positon/Scale Properties
|
||||
public RectD Rect { get; private set; }
|
||||
public int InitialZoom { get; private set; }
|
||||
public int CurrentZoom { get; private set; }
|
||||
public float TileScale { get; private set; }
|
||||
public UnwrappedTileId UnwrappedTileId { get; private set; }
|
||||
public CanonicalTileId CanonicalTileId { get; private set; }
|
||||
|
||||
private float _relativeScale;
|
||||
#endregion
|
||||
|
||||
[SerializeField]
|
||||
private TilePropertyState _rasterDataState;
|
||||
public TilePropertyState RasterDataState
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rasterDataState;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (_rasterDataState != value)
|
||||
{
|
||||
_rasterDataState = value;
|
||||
OnRasterDataChanged(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
[SerializeField]
|
||||
private TilePropertyState _heightDataState;
|
||||
public TilePropertyState HeightDataState
|
||||
{
|
||||
get
|
||||
{
|
||||
return _heightDataState;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (_heightDataState != value)
|
||||
{
|
||||
_heightDataState = value;
|
||||
OnHeightDataChanged(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
[SerializeField]
|
||||
private TilePropertyState _vectorDataState;
|
||||
public TilePropertyState VectorDataState
|
||||
{
|
||||
get
|
||||
{
|
||||
return _vectorDataState;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (_vectorDataState != value)
|
||||
{
|
||||
_vectorDataState = value;
|
||||
OnVectorDataChanged(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
private TilePropertyState _tileState = TilePropertyState.None;
|
||||
public TilePropertyState TileState
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tileState;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_tileState != value)
|
||||
{
|
||||
_tileState = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<UnityTile> OnHeightDataChanged = delegate { };
|
||||
public event Action<UnityTile> OnRasterDataChanged = delegate { };
|
||||
public event Action<UnityTile> OnVectorDataChanged = delegate { };
|
||||
|
||||
private bool _isInitialized = false;
|
||||
|
||||
internal void Initialize(IMapReadable map, UnwrappedTileId tileId, float scale, int zoom, Texture2D loadingTexture = null)
|
||||
{
|
||||
ElevationType = TileTerrainType.None;
|
||||
TileScale = scale;
|
||||
_relativeScale = 1 / Mathf.Cos(Mathf.Deg2Rad * (float)map.CenterLatitudeLongitude.x);
|
||||
Rect = Conversions.TileBounds(tileId);
|
||||
UnwrappedTileId = tileId;
|
||||
CanonicalTileId = tileId.Canonical;
|
||||
_loadingTexture = loadingTexture;
|
||||
|
||||
float scaleFactor = 1.0f;
|
||||
if (_isInitialized == false)
|
||||
{
|
||||
_isInitialized = true;
|
||||
InitialZoom = zoom;
|
||||
}
|
||||
CurrentZoom = zoom;
|
||||
scaleFactor = Mathf.Pow(2, (map.InitialZoom - zoom));
|
||||
gameObject.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor);
|
||||
gameObject.SetActive(true);
|
||||
|
||||
IsRecycled = false;
|
||||
//MeshRenderer.enabled = true;
|
||||
|
||||
|
||||
// Setup Loading as initial state - Unregistered
|
||||
// When tile registers with factories, it will set the appropriate state.
|
||||
// None, if Factory source is None, Loading otherwise.
|
||||
}
|
||||
|
||||
internal void Recycle()
|
||||
{
|
||||
if (_loadingTexture && MeshRenderer != null)
|
||||
{
|
||||
MeshRenderer.material.mainTexture = _loadingTexture;
|
||||
//MeshRenderer.enabled = false;
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
IsRecycled = true;
|
||||
|
||||
// Reset internal state.
|
||||
RasterDataState = TilePropertyState.Unregistered;
|
||||
HeightDataState = TilePropertyState.Unregistered;
|
||||
VectorDataState = TilePropertyState.Unregistered;
|
||||
TileState = TilePropertyState.Unregistered;
|
||||
|
||||
OnHeightDataChanged = delegate { };
|
||||
OnRasterDataChanged = delegate { };
|
||||
OnVectorDataChanged = delegate { };
|
||||
|
||||
Cancel();
|
||||
_tiles.Clear();
|
||||
}
|
||||
|
||||
public void SetHeightData(byte[] data, float heightMultiplier = 1f, bool useRelative = false, bool addCollider = false)
|
||||
{
|
||||
if (HeightDataState != TilePropertyState.Unregistered)
|
||||
{
|
||||
//reset height data
|
||||
if(data == null)
|
||||
{
|
||||
_heightData = new float[256 * 256];
|
||||
HeightDataState = TilePropertyState.None;
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: compute height values for terrain. We could probably do this without a texture2d.
|
||||
if (_heightTexture == null)
|
||||
{
|
||||
_heightTexture = new Texture2D(0, 0);
|
||||
}
|
||||
|
||||
_heightTexture.LoadImage(data);
|
||||
byte[] rgbData = _heightTexture.GetRawTextureData();
|
||||
|
||||
// Get rid of this temporary texture. We don't need to bloat memory.
|
||||
_heightTexture.LoadImage(null);
|
||||
|
||||
if (_heightData == null)
|
||||
{
|
||||
_heightData = new float[256 * 256];
|
||||
}
|
||||
|
||||
var relativeScale = useRelative ? _relativeScale : 1f;
|
||||
for (int xx = 0; xx < 256; ++xx)
|
||||
{
|
||||
for (int yy = 0; yy < 256; ++yy)
|
||||
{
|
||||
float r = rgbData[(xx * 256 + yy) * 4 + 1];
|
||||
float g = rgbData[(xx * 256 + yy) * 4 + 2];
|
||||
float b = rgbData[(xx * 256 + yy) * 4 + 3];
|
||||
_heightData[xx * 256 + yy] = relativeScale * heightMultiplier * Conversions.GetAbsoluteHeightFromColor(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
if (addCollider && gameObject.GetComponent<MeshCollider>() == null)
|
||||
{
|
||||
gameObject.AddComponent<MeshCollider>();
|
||||
}
|
||||
|
||||
HeightDataState = TilePropertyState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRasterData(byte[] data, bool useMipMap = true, bool useCompression = false)
|
||||
{
|
||||
// Don't leak the texture, just reuse it.
|
||||
if (RasterDataState != TilePropertyState.Unregistered)
|
||||
{
|
||||
//reset image on null data
|
||||
if (data == null)
|
||||
{
|
||||
MeshRenderer.material.mainTexture = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_rasterData == null)
|
||||
{
|
||||
_rasterData = new Texture2D(0, 0, TextureFormat.RGB24, useMipMap);
|
||||
_rasterData.wrapMode = TextureWrapMode.Clamp;
|
||||
}
|
||||
|
||||
_rasterData.LoadImage(data);
|
||||
if (useCompression)
|
||||
{
|
||||
// High quality = true seems to decrease image quality?
|
||||
_rasterData.Compress(false);
|
||||
}
|
||||
|
||||
MeshRenderer.material.mainTexture = _rasterData;
|
||||
RasterDataState = TilePropertyState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVectorData(VectorTile vectorTile)
|
||||
{
|
||||
if (VectorDataState != TilePropertyState.Unregistered)
|
||||
{
|
||||
VectorData = vectorTile;
|
||||
}
|
||||
}
|
||||
|
||||
public float QueryHeightData(float x, float y)
|
||||
{
|
||||
if (_heightData != null)
|
||||
{
|
||||
var intX = (int)Mathf.Clamp(x * 256, 0, 255);
|
||||
var intY = (int)Mathf.Clamp(y * 256, 0, 255);
|
||||
return _heightData[intY * 256 + intX] * TileScale;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void SetLoadingTexture(Texture2D texture)
|
||||
{
|
||||
MeshRenderer.material.mainTexture = texture;
|
||||
}
|
||||
|
||||
public Texture2D GetRasterData()
|
||||
{
|
||||
return _rasterData;
|
||||
}
|
||||
|
||||
internal void AddTile(Tile tile)
|
||||
{
|
||||
_tiles.Add(tile);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
for (int i = 0, _tilesCount = _tiles.Count; i < _tilesCount; i++)
|
||||
{
|
||||
_tiles[i].Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Cancel();
|
||||
if (_heightTexture != null)
|
||||
{
|
||||
Destroy(_heightTexture);
|
||||
}
|
||||
if (_rasterData != null)
|
||||
{
|
||||
Destroy(_rasterData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: def0795d2b7c06547bcfdb57ec6f201b
|
||||
timeCreated: 1478541275
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,124 @@
|
||||
namespace Mapbox.Unity.MeshGeneration.Data
|
||||
{
|
||||
using Mapbox.VectorTile;
|
||||
using System.Collections.Generic;
|
||||
using Mapbox.VectorTile.Geometry;
|
||||
using UnityEngine;
|
||||
using Mapbox.Utils;
|
||||
using Mapbox.Unity.Utilities;
|
||||
|
||||
public class VectorFeatureUnity
|
||||
{
|
||||
public VectorTileFeature Data;
|
||||
public Dictionary<string, object> Properties;
|
||||
public List<List<Vector3>> Points = new List<List<Vector3>>();
|
||||
public UnityTile Tile;
|
||||
|
||||
private double _rectSizex;
|
||||
private double _rectSizey;
|
||||
private int _geomCount;
|
||||
private int _pointCount;
|
||||
private List<Vector3> _newPoints = new List<Vector3>();
|
||||
private List<List<Point2d<float>>> _geom;
|
||||
|
||||
public VectorFeatureUnity()
|
||||
{
|
||||
Points = new List<List<Vector3>>();
|
||||
}
|
||||
|
||||
public VectorFeatureUnity(VectorTileFeature feature, UnityTile tile, float layerExtent, bool buildingsWithUniqueIds = false)
|
||||
{
|
||||
Data = feature;
|
||||
Properties = Data.GetProperties();
|
||||
Points.Clear();
|
||||
Tile = tile;
|
||||
|
||||
//this is a temp hack until we figure out how streets ids works
|
||||
if (buildingsWithUniqueIds == true) //ids from building dataset is big ulongs
|
||||
{
|
||||
_geom = feature.Geometry<float>(); //and we're not clipping by passing no parameters
|
||||
}
|
||||
else //streets ids, will require clipping
|
||||
{
|
||||
_geom = feature.Geometry<float>(0); //passing zero means clip at tile edge
|
||||
}
|
||||
|
||||
_rectSizex = tile.Rect.Size.x;
|
||||
_rectSizey = tile.Rect.Size.y;
|
||||
|
||||
_geomCount = _geom.Count;
|
||||
for (int i = 0; i < _geomCount; i++)
|
||||
{
|
||||
_pointCount = _geom[i].Count;
|
||||
_newPoints = new List<Vector3>(_pointCount);
|
||||
for (int j = 0; j < _pointCount; j++)
|
||||
{
|
||||
var point = _geom[i][j];
|
||||
_newPoints.Add(new Vector3((float)(point.X / layerExtent * _rectSizex - (_rectSizex / 2)) * tile.TileScale, 0, (float)((layerExtent - point.Y) / layerExtent * _rectSizey - (_rectSizey / 2)) * tile.TileScale));
|
||||
}
|
||||
Points.Add(_newPoints);
|
||||
}
|
||||
}
|
||||
|
||||
public VectorFeatureUnity(VectorTileFeature feature, List<List<Point2d<float>>> geom, UnityTile tile, float layerExtent, bool buildingsWithUniqueIds = false)
|
||||
{
|
||||
Data = feature;
|
||||
Properties = Data.GetProperties();
|
||||
Points.Clear();
|
||||
Tile = tile;
|
||||
_geom = geom;
|
||||
|
||||
_rectSizex = tile.Rect.Size.x;
|
||||
_rectSizey = tile.Rect.Size.y;
|
||||
|
||||
_geomCount = _geom.Count;
|
||||
for (int i = 0; i < _geomCount; i++)
|
||||
{
|
||||
_pointCount = _geom[i].Count;
|
||||
_newPoints = new List<Vector3>(_pointCount);
|
||||
for (int j = 0; j < _pointCount; j++)
|
||||
{
|
||||
var point = _geom[i][j];
|
||||
_newPoints.Add(new Vector3((float)(point.X / layerExtent * _rectSizex - (_rectSizex / 2)) * tile.TileScale, 0, (float)((layerExtent - point.Y) / layerExtent * _rectSizey - (_rectSizey / 2)) * tile.TileScale));
|
||||
}
|
||||
Points.Add(_newPoints);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsLatLon(Vector2d coord)
|
||||
{
|
||||
////first check tile
|
||||
var coordinateTileId = Conversions.LatitudeLongitudeToTileId(
|
||||
coord.x, coord.y, Tile.CurrentZoom);
|
||||
|
||||
if (Points.Count > 0)
|
||||
{
|
||||
var from = Conversions.LatLonToMeters(coord.x, coord.y);
|
||||
|
||||
var to = new Vector2d((Points[0][0].x / Tile.TileScale) + Tile.Rect.Center.x, (Points[0][0].z / Tile.TileScale) + Tile.Rect.Center.y);
|
||||
var dist = Vector2d.Distance(from, to);
|
||||
if (Mathd.Abs(dist) < 50)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Debug.Log("Distance -> " + dist);
|
||||
{
|
||||
if ((!coordinateTileId.Canonical.Equals(Tile.CanonicalTileId)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//then check polygon
|
||||
var point = Conversions.LatitudeLongitudeToVectorTilePosition(coord, Tile.CurrentZoom);
|
||||
var output = PolygonUtils.PointInPolygon(new Point2d<float>(point.x, point.y), _geom);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 161842d5b19bcf24a842752aadb1da26
|
||||
timeCreated: 1485209878
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user