diff --git a/Editor/PSXObjectExporterEditor.cs b/Editor/PSXObjectExporterEditor.cs index ccacea9..3508677 100644 --- a/Editor/PSXObjectExporterEditor.cs +++ b/Editor/PSXObjectExporterEditor.cs @@ -47,16 +47,7 @@ namespace PSXSplash.EditorCode writer.Write(value); } } - } - GUIUtility.ExitGUI(); - } - - if (comp.Texture.TextureType != PSXTextureType.TEX16_BPP) - { - if (GUILayout.Button("Export clut")) - { - ushort[] clutData = comp.Texture.ExportClut(comp.gameObject); - string path = EditorUtility.SaveFilePanel( + Hodně string path = EditorUtility.SaveFilePanel( "Save clut data", "", "clut_data", @@ -71,15 +62,7 @@ namespace PSXSplash.EditorCode foreach (ushort value in clutData) { writer.Write(value); - } - } - } - GUIUtility.ExitGUI(); - } - } - EditorGUILayout.EndVertical(); - - serializedObject.ApplyModifiedProperties(); + }Hodně plyModifiedProperties(); */ } diff --git a/Runtime/Heap.meta b/Runtime/Heap.meta new file mode 100644 index 0000000..2abd863 --- /dev/null +++ b/Runtime/Heap.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5d9bff9b871f6052f9cb0cea865b5608 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Heap/BaseHeap.cs b/Runtime/Heap/BaseHeap.cs new file mode 100644 index 0000000..3707cf7 --- /dev/null +++ b/Runtime/Heap/BaseHeap.cs @@ -0,0 +1,211 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; + +namespace DataStructures.ViliWonka.Heap { + + // array start at index 1, optimisation reason + public abstract class BaseHeap { + + protected int nodesCount; + protected int maxSize; + + protected float[] heap; + + protected BaseHeap(int initialSize) { + + maxSize = initialSize; + heap = new float[initialSize + 1]; + } + + public int Count { get { return nodesCount; } } + + public float HeadValue { get { return heap[1]; } } + + public void Clear() { + nodesCount = 0; + } + + protected int Parent(int index) { return (index >> 1); } + protected int Left (int index) { return (index << 1); } + protected int Right (int index) { return (index << 1) | 1; } + + // bubble down, MaxHeap version + protected void BubbleDownMax(int index) { + + int L = Left(index); + int R = Right(index); + + // bubbling down, 2 kids + while (R <= nodesCount) { + + // if heap property is violated between index and Left child + if(heap[index] < heap[L]) { + + if (heap[L] < heap[R]) { + + Swap(index, R); // left has bigger priority + index = R; + } + else { + + Swap(index, L); // right has bigger priority + index = L; + } + } + else { + // if heap property is violated between index and R + if (heap[index] < heap[R]) { + + Swap(index, R); + index = R; + } + else { + + index = L; + L = Left(index); + break; + } + + } + + L = Left(index); + R = Right(index); + } + + // only left & last children available to test and swap + if (L <= nodesCount && heap[index] < heap[L]) { + Swap(index, L); + } + } + + // bubble up, MaxHeap version + protected void BubbleUpMax(int index) { + + int P = Parent(index); + + //swap, until Heap property isn't violated anymore + while (P > 0 && heap[P] < heap[index]) { + + Swap(P, index); + + index = P; + P = Parent(index); + } + } + + // bubble down, MinHeap version + protected void BubbleDownMin(int index) { + + int L = Left(index); + int R = Right(index); + + // bubbling down, 2 kids + while(R <= nodesCount) { + + // if heap property is violated between index and Left child + if(heap[index] > heap[L]) { + + if(heap[L] > heap[R]) { + + Swap(index, R); // right has smaller priority + index = R; + } + else { + + Swap(index, L); // left has smaller priority + index = L; + } + } + else { + // if heap property is violated between index and R + if(heap[index] > heap[R]) { + + Swap(index, R); + index = R; + } + else { + + index = L; + L = Left(index); + break; + } + + } + + L = Left(index); + R = Right(index); + } + + // only left & last children available to test and swap + if(L <= nodesCount && heap[index] > heap[L]) { + Swap(index, L); + } + } + + // bubble up, MinHeap version + protected void BubbleUpMin(int index) { + + int P = Parent(index); + + //swap, until Heap property isn't violated anymore + while(P > 0 && heap[P] > heap[index]) { + + Swap(P, index); + + index = P; + P = Parent(index); + } + } + + protected float tempHeap; + protected virtual void Swap(int A, int B) { + + tempHeap = heap[A]; + heap[A] = heap[B]; + heap[B] = tempHeap; + } + + protected virtual void UpsizeHeap() { + + maxSize *= 2; + System.Array.Resize(ref heap, maxSize + 1); + } + + public virtual void PushValue(float h) { + throw new System.NotImplementedException(); + } + + public virtual float PopValue() { + throw new System.NotImplementedException(); + } + + public void FlushHeapResult(List heapList) { + + for(int i = 1; i < Count; i++) { + heapList.Add(heap[i]); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Heap/BaseHeap.cs.meta b/Runtime/Heap/BaseHeap.cs.meta new file mode 100644 index 0000000..9366d6a --- /dev/null +++ b/Runtime/Heap/BaseHeap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0a58014cb612f86d1ab500d7c5f6360d \ No newline at end of file diff --git a/Runtime/Heap/KSmallest.cs b/Runtime/Heap/KSmallest.cs new file mode 100644 index 0000000..e943045 --- /dev/null +++ b/Runtime/Heap/KSmallest.cs @@ -0,0 +1,229 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; + +namespace DataStructures.ViliWonka.Heap { + + public class KSmallestHeap : BaseHeap { + + public KSmallestHeap(int maxEntries) : base(maxEntries) { + + } + + public bool Full { + get { + return maxSize == nodesCount; + } + } + + // in lots of cases, max head gets removed + public override void PushValue(float h) { + + // if heap full + if(nodesCount == maxSize) { + + // if Heads priority is smaller than input priority, then ignore that item + if(HeadValue < h) { + + return; + } + else { + + heap[1] = h; // remove top element + BubbleDownMax(1); // bubble it down + } + } + else { + + nodesCount++; + heap[nodesCount] = h; + BubbleUpMax(nodesCount); + } + } + + public override float PopValue() { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + float result = heap[1]; + + heap[1] = heap[nodesCount]; + nodesCount--; + BubbleDownMax(1); + + return result; + } + + public void Print() { + + UnityEngine.Debug.Log("HeapPropertyHolds? " + HeapPropertyHolds(1)); + } + + //should remove + public bool HeapPropertyHolds(int index, int depth = 0) { + + if (index > nodesCount) + return true; + + UnityEngine.Debug.Log(heap[index]); + + int L = Left(index); + int R = Right(index); + + bool bothHold = true; + + if(L <= nodesCount) { + + UnityEngine.Debug.Log(heap[index] + " => " + heap[L]); + + if (heap[index] < heap[L]) + bothHold = false; + } + + // if L <= nodesCount, then R <= nodesCount can also happen + if (R <= nodesCount) { + + UnityEngine.Debug.Log(heap[index] + " => " + heap[R]); + + if (bothHold && heap[index] < heap[R]) + bothHold = false; + + } + + return bothHold & HeapPropertyHolds(L, depth + 1) & HeapPropertyHolds(R, depth + 1); + } + + } + + // array start at index 1 + // generic version + public class KSmallestHeap : KSmallestHeap { + + T[] objs; //objects + + public KSmallestHeap(int maxEntries) : base(maxEntries) { + objs = new T[maxEntries + 1]; + } + + public T HeadHeapObject { get { return objs[1]; } } + + T tempObjs; + protected override void Swap(int A, int B) { + + tempHeap = heap[A]; + tempObjs = objs[A]; + + heap[A] = heap[B]; + objs[A] = objs[B]; + + heap[B] = tempHeap; + objs[B] = tempObjs; + } + + public override void PushValue(float h) { + throw new System.ArgumentException("Use Push(T, float)!"); + } + + public void PushObj(T obj, float h) { + + // if heap full + if(nodesCount == maxSize) { + + // if Heads priority is smaller than input priority, then ignore that item + if(HeadValue < h) { + + return; + } + else { + + heap[1] = h; // remove top element + objs[1] = obj; + BubbleDownMax(1); // bubble it down + } + } + else { + + nodesCount++; + heap[nodesCount] = h; + objs[nodesCount] = obj; + BubbleUpMax(nodesCount); + } + } + + public override float PopValue() { + throw new System.ArgumentException("Use PopObj()!"); + } + + public T PopObj() { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + T result = objs[1]; + + heap[1] = heap[nodesCount]; + objs[1] = objs[nodesCount]; + + nodesCount--; + BubbleDownMax(1); + + return result; + } + + public T PopObj(ref float heapValue) { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + heapValue = heap[1]; + T result = PopObj(); + + return result; + } + + //flush internal results, returns ordered data + public void FlushResult(List resultList, List heapList = null) { + + int count = nodesCount + 1; + + + if(heapList == null) { + + for(int i = 1; i < count; i++) { + resultList.Add(PopObj()); + } + } + else { + + float h = 0f; + + for(int i = 1; i < count; i++) { + resultList.Add(PopObj(ref h)); + heapList.Add(h); + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/Heap/KSmallest.cs.meta b/Runtime/Heap/KSmallest.cs.meta new file mode 100644 index 0000000..dc3a3eb --- /dev/null +++ b/Runtime/Heap/KSmallest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 902305de5cb00eb74a5d1d6b430ab942 \ No newline at end of file diff --git a/Runtime/Heap/MaxHeap.cs b/Runtime/Heap/MaxHeap.cs new file mode 100644 index 0000000..63c3c76 --- /dev/null +++ b/Runtime/Heap/MaxHeap.cs @@ -0,0 +1,168 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; + +namespace DataStructures.ViliWonka.Heap { + + public class MaxHeap : BaseHeap { + + public MaxHeap(int initialSize = 2048) : base(initialSize) { + + + } + + public override void PushValue(float h) { + + // if heap array is full + if(nodesCount == maxSize) { + + UpsizeHeap(); + } + + nodesCount++; + heap[nodesCount] = h; + BubbleUpMax(nodesCount); + } + + public override float PopValue() { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + float result = heap[1]; + + heap[1] = heap[nodesCount]; + nodesCount--; + BubbleDownMax(1); + + return result; + } + } + + // generic version + public class MaxHeap : MaxHeap { + + T[] objs; // objects + + public MaxHeap(int maxNodes) : base(maxNodes) { + objs = new T[maxNodes + 1]; + } + + public T HeadHeapObject { get { return objs[1]; } } + + T tempObjs; + protected override void Swap(int A, int B) { + + tempHeap = heap[A]; + tempObjs = objs[A]; + + heap[A] = heap[B]; + objs[A] = objs[B]; + + heap[B] = tempHeap; + objs[B] = tempObjs; + } + + public override void PushValue(float h) { + throw new System.ArgumentException("Use PushObj(T, float)!"); + } + + public override float PopValue() { + throw new System.ArgumentException("Use Push(T, float)!"); + } + + public void PushObj(T obj, float h) { + + // if heap array is full + if(nodesCount == maxSize) { + UpsizeHeap(); + } + + nodesCount++; + heap[nodesCount] = h; + objs[nodesCount] = obj; + + BubbleUpMin(nodesCount); + } + + public T PopObj() { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + T result = objs[1]; + + heap[1] = heap[nodesCount]; + objs[1] = objs[nodesCount]; + + objs[nodesCount] = default(T); + + nodesCount--; + BubbleDownMin(1); + + return result; + } + + + public T PopObj(ref float heapValue) { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + heapValue = heap[1]; + T result = PopObj(); + + return result; + } + + protected override void UpsizeHeap() { + + maxSize *= 2; + System.Array.Resize(ref heap, maxSize + 1); + System.Array.Resize(ref objs, maxSize + 1); + } + + //flush internal results, returns ordered data + public void FlushResult(List resultList, List heapList = null) { + + int count = nodesCount + 1; + + if(heapList == null) { + + for(int i = 1; i < count; i++) { + resultList.Add(PopObj()); + } + } + else { + + float h = 0f; + + for(int i = 1; i < count; i++) { + resultList.Add(PopObj(ref h)); + heapList.Add(h); + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/Heap/MaxHeap.cs.meta b/Runtime/Heap/MaxHeap.cs.meta new file mode 100644 index 0000000..5fe48f2 --- /dev/null +++ b/Runtime/Heap/MaxHeap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4de1738443a82f33b9102bb09da91d2f \ No newline at end of file diff --git a/Runtime/Heap/MinHeap.cs b/Runtime/Heap/MinHeap.cs new file mode 100644 index 0000000..61d2657 --- /dev/null +++ b/Runtime/Heap/MinHeap.cs @@ -0,0 +1,171 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; + +namespace DataStructures.ViliWonka.Heap { + + public class MinHeap : BaseHeap { + + public MinHeap(int initialSize = 2048) : base(initialSize) { + + } + + public override void PushValue(float h) { + + // if heap array is full + if(nodesCount == maxSize) { + + UpsizeHeap(); + } + + nodesCount++; + heap[nodesCount] = h; + BubbleUpMin(nodesCount); + } + + public override float PopValue() { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + float result = heap[1]; + + heap[1] = heap[nodesCount]; + + nodesCount--; + + if(nodesCount != 0) + BubbleDownMin(1); + + return result; + } + } + + // generic version + public class MinHeap : MinHeap { + + T[] objs; // objects + + public MinHeap(int maxNodes = 2048) : base(maxNodes) { + objs = new T[maxNodes + 1]; + } + + public T HeadHeapObject { get { return objs[1]; } } + + T tempObjs; + protected override void Swap(int A, int B) { + + tempHeap = heap[A]; + tempObjs = objs[A]; + + heap[A] = heap[B]; + objs[A] = objs[B]; + + heap[B] = tempHeap; + objs[B] = tempObjs; + } + + public override void PushValue(float h) { + throw new System.ArgumentException("Use Push(T, float)!"); + } + + public override float PopValue() { + throw new System.ArgumentException("Use Push(T, float)!"); + } + + public void PushObj(T obj, float h) { + + // if heap array is full + if(nodesCount == maxSize) { + UpsizeHeap(); + } + + nodesCount++; + heap[nodesCount] = h; + objs[nodesCount] = obj; + + BubbleUpMin(nodesCount); + } + + public T PopObj() { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + T result = objs[1]; + + heap[1] = heap[nodesCount]; + objs[1] = objs[nodesCount]; + + objs[nodesCount] = default(T); + + nodesCount--; + + if(nodesCount != 0) + BubbleDownMin(1); + + return result; + } + + public T PopObj(ref float heapValue) { + + if(nodesCount == 0) + throw new System.ArgumentException("Heap is empty!"); + + heapValue = heap[1]; + T result = PopObj(); + + return result; + } + + protected override void UpsizeHeap() { + + maxSize *= 2; + System.Array.Resize(ref heap, maxSize + 1); + System.Array.Resize(ref objs, maxSize + 1); + } + + //flush internal array, returns ordered data + public void FlushResult(List resultList, List heapList = null) { + + int count = nodesCount + 1; + + if(heapList == null) { + + for(int i = 1; i < count; i++) { + resultList.Add(PopObj()); + } + } + else { + + float h = 0f; + + for(int i = 1; i < count; i++) { + resultList.Add(PopObj(ref h)); + heapList.Add(h); + } + } + } + } +} \ No newline at end of file diff --git a/Runtime/Heap/MinHeap.cs.meta b/Runtime/Heap/MinHeap.cs.meta new file mode 100644 index 0000000..499dce6 --- /dev/null +++ b/Runtime/Heap/MinHeap.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ebf0e20ba274e5a59101ce360358b04 \ No newline at end of file diff --git a/Runtime/Heap/heapTestScene.unity b/Runtime/Heap/heapTestScene.unity new file mode 100644 index 0000000..3138904 --- /dev/null +++ b/Runtime/Heap/heapTestScene.unity @@ -0,0 +1,298 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_TemporalCoherenceThreshold: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 1 + m_LightmapEditorSettings: + serializedVersion: 10 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringMode: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ShowResolutionOverlay: 1 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &653832527 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 653832530} + - component: {fileID: 653832529} + - component: {fileID: 653832528} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &653832528 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 653832527} + m_Enabled: 1 +--- !u!20 &653832529 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 653832527} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &653832530 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 653832527} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1730864583 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1730864585} + - component: {fileID: 1730864584} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1730864584 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1730864583} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &1730864585 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1730864583} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &1883466098 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1883466100} + - component: {fileID: 1883466099} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1883466099 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1883466098} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e3f22e25c6d555d45b86d62ecaaf8895, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &1883466100 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1883466098} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.6106011, y: 0.41596547, z: 1.608385} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Runtime/Heap/heapTestScene.unity.meta b/Runtime/Heap/heapTestScene.unity.meta new file mode 100644 index 0000000..dc5fc4b --- /dev/null +++ b/Runtime/Heap/heapTestScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 57dce109462253f30bda1904173334b8 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ImageProcessing.cs b/Runtime/ImageProcessing.cs index 3880c20..5598799 100644 --- a/Runtime/ImageProcessing.cs +++ b/Runtime/ImageProcessing.cs @@ -1,28 +1,36 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Codice.CM.Common; +using DataStructures.ViliWonka.KDTree; using UnityEngine; namespace PSXSplash.RuntimeCode { + public class ImageQuantizer { private int _maxColors; - private Color[] _pixels; - private Color[] _centroids; - private int[] _assignments; - private List _uniqueColors; + private Vector3[,] _pixels; + private Vector3[] _centroids; + private KDTree kdTree; + private int[,] _assignments; + private List _uniqueColors; - public Color[] Palette + public int Width { get; private set; } + public int Height { get; private set; } + + public Vector3[] Palette { get => _centroids; } - public int[] Pixels + public int[,] Pixels { get => _assignments; } @@ -32,38 +40,50 @@ namespace PSXSplash.RuntimeCode { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - _pixels = texture2D.GetPixels(); + Color[] pixels = texture2D.GetPixels(); + + Width = texture2D.width; + Height = texture2D.height; + + _pixels = new Vector3[Width, Height]; + + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + Color pixel = pixels[x + y * Width]; + Vector3 pixelAsVector = new Vector3(pixel.r, pixel.g, pixel.b); + _pixels[x, y] = pixelAsVector; + } + } + _maxColors = maxColors; - _centroids = new Color[_maxColors]; - _uniqueColors = new List(); + _centroids = new Vector3[_maxColors]; + _uniqueColors = new List(); FillRandomCentroids(); bool hasChanged; - _assignments = new int[_pixels.Count()]; + _assignments = new int[Width, Height]; do { hasChanged = false; - Parallel.For(0, _pixels.Count(), i => + for (int x = 0; x < Width; x++) { - int newAssignment = GetNearestCentroid(_pixels[i]); - - if (_assignments[i] != newAssignment) + for (int y = 0; y < Height; y++) { - lock (_assignments) - { - _assignments[i] = newAssignment; - } - - lock (this) + Vector3 color = _pixels[x, y]; + int newAssignment = GetNearestCentroid(color); + + if (_assignments[x, y] != newAssignment) { + _assignments[x, y] = newAssignment; hasChanged = true; } } - }); - + } RecalculateCentroids(); } while (hasChanged); @@ -75,9 +95,11 @@ namespace PSXSplash.RuntimeCode private void FillRandomCentroids() { - foreach (Color pixel in _pixels) + + List uniqueColors = new List(); + foreach (Vector3 pixel in _pixels) { - if (!_uniqueColors.Contains(pixel)) + if (!uniqueColors.Contains(pixel)) { _uniqueColors.Add(pixel); } @@ -85,55 +107,44 @@ namespace PSXSplash.RuntimeCode for (int i = 0; i < _maxColors; i++) { - _centroids[i] = _uniqueColors[UnityEngine.Random.Range(0, _uniqueColors.Count - 1)]; + Vector3 color = _uniqueColors[UnityEngine.Random.Range(0, _uniqueColors.Count - 1)]; + _centroids[i] = color; } + kdTree = new KDTree(_centroids); + } - private double CalculateColorDistance(Color color1, Color color2) + private int GetNearestCentroid(Vector3 color) { - float rDiff = color1.r - color2.r; - float gDiff = color1.g - color2.g; - float bDiff = color1.b - color2.b; - - return Math.Sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff); - } - - private int GetNearestCentroid(Color color) - { - double minDistance = double.MaxValue; - int closestCentroidIndex = 0; - - for (int i = 0; i < _maxColors; i++) - { - double distance = CalculateColorDistance(_centroids[i], color); - if (distance < minDistance) - { - minDistance = distance; - closestCentroidIndex = i; - } - } - - return closestCentroidIndex; + KDQuery query = new KDQuery(); + List resultIndices = new List(); + query.ClosestPoint(kdTree, color, resultIndices); + return resultIndices[0]; } private void RecalculateCentroids() { - Color[] newCentroids = new Color[_maxColors]; + Vector3[] newCentroids = new Vector3[_maxColors]; - Parallel.For(0, _maxColors, i => + for(int i = 0; i < _maxColors; i++) { - List clusterColors = new List(); - for (int j = 0; j < _pixels.Length; j++) + List clusterColors = new List(); + for (int x = 0; x < Width; x++) { - if (_assignments[j] == i) + for (int y = 0; y < Height; y++) { - clusterColors.Add(_pixels[j]); + { + if (_assignments[x, y] == i) + { + clusterColors.Add(_pixels[x, y]); + } + } } } - Color newCentroid; + Vector3 newCentroid; try { @@ -146,18 +157,20 @@ namespace PSXSplash.RuntimeCode } newCentroids[i] = newCentroid; - }); + } _centroids = newCentroids; + + kdTree = new KDTree(_centroids); + } - private Color AverageColor(List colors) + private Vector3 AverageColor(List colors) { - float r = colors.Average(c => c.r); - float g = colors.Average(c => c.g); - float b = colors.Average(c => c.b); - float a = colors.Average(c => c.a); - return new Color(r, g, b, a); + float r = colors.Average(c => c.x); + float g = colors.Average(c => c.y); + float b = colors.Average(c => c.z); + return new Vector3(r, g, b); } } } diff --git a/Runtime/KDTree.meta b/Runtime/KDTree.meta new file mode 100644 index 0000000..50984df --- /dev/null +++ b/Runtime/KDTree.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ec47b0ae95f0bc9ac90d793b3b8192e9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/KDTree/KDBounds.cs b/Runtime/KDTree/KDBounds.cs new file mode 100644 index 0000000..27f3f81 --- /dev/null +++ b/Runtime/KDTree/KDBounds.cs @@ -0,0 +1,73 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using UnityEngine; +using UnityEditor; + +namespace DataStructures.ViliWonka.KDTree { + + public struct KDBounds { + + public Vector3 min; + public Vector3 max; + + public Vector3 size { + + get { + return max - min; + } + } + + // returns unity bounds + public Bounds Bounds { + + get { + return new Bounds( + (min + max) / 2, + (max - min) + ); + } + } + + + public Vector3 ClosestPoint(Vector3 point) { + + // X axis + if(point.x < min.x) point.x = min.x; + else + if(point.x > max.x) point.x = max.x; + + // Y axis + if(point.y < min.y) point.y = min.y; + else + if(point.y > max.y) point.y = max.y; + + // Z axis + if(point.z < min.z) point.z = min.z; + else + if(point.z > max.z) point.z = max.z; + + return point; + } + } +} \ No newline at end of file diff --git a/Runtime/KDTree/KDBounds.cs.meta b/Runtime/KDTree/KDBounds.cs.meta new file mode 100644 index 0000000..e961db9 --- /dev/null +++ b/Runtime/KDTree/KDBounds.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8d2bf691fd08219d89c9c4d729a8d6d7 \ No newline at end of file diff --git a/Runtime/KDTree/KDNode.cs b/Runtime/KDTree/KDNode.cs new file mode 100644 index 0000000..7d80a1a --- /dev/null +++ b/Runtime/KDTree/KDNode.cs @@ -0,0 +1,49 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace DataStructures.ViliWonka.KDTree { + + public class KDNode { + + public float partitionCoordinate; + public int partitionAxis = -1; + + public KDNode negativeChild; + public KDNode positiveChild; + + public int start; + public int end; + + public int Count { get { return end - start; } } + + public bool Leaf { get { return partitionAxis == -1; } } + + public KDBounds bounds; + + }; + +} diff --git a/Runtime/KDTree/KDNode.cs.meta b/Runtime/KDTree/KDNode.cs.meta new file mode 100644 index 0000000..befd6f6 --- /dev/null +++ b/Runtime/KDTree/KDNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f0f1ff21a62e87ca0a4a847161f67fbd \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery.cs b/Runtime/KDTree/KDQuery.cs new file mode 100644 index 0000000..e69de29 diff --git a/Runtime/KDTree/KDQuery.cs.meta b/Runtime/KDTree/KDQuery.cs.meta new file mode 100644 index 0000000..d805b4e --- /dev/null +++ b/Runtime/KDTree/KDQuery.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9065ed313aead20618eb497fa6c25bc9 \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery.meta b/Runtime/KDTree/KDQuery.meta new file mode 100644 index 0000000..756b513 --- /dev/null +++ b/Runtime/KDTree/KDQuery.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 59c04f18f4c9203109233947fa24b400 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/KDTree/KDQuery/Base.cs b/Runtime/KDTree/KDQuery/Base.cs new file mode 100644 index 0000000..1467ac0 --- /dev/null +++ b/Runtime/KDTree/KDQuery/Base.cs @@ -0,0 +1,136 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* +The object used for querying. This object should be persistent - re-used for querying. +Contains internal array for pooling, so that it doesn't generate (too much) garbage. +The array never down-sizes, only up-sizes, so the more you use this object, less garbage will it make over time. + +Should be used only by 1 thread, +which means each thread should have it's own KDQuery object in order for querying to be thread safe. + +KDQuery can query different KDTrees. +*/ + + +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DataStructures.ViliWonka.KDTree { + + public partial class KDQuery { + + protected KDQueryNode[] queueArray; // queue array + protected Heap.MinHeap minHeap; //heap for k-nearest + protected int count = 0; // size of queue + protected int queryIndex = 0; // current index at stack + + /// + /// Returns initialized node from stack that also acts as a pool + /// The returned reference to node stays in stack + /// + /// Reference to pooled node + private KDQueryNode PushGetQueue() { + + KDQueryNode node = null; + + if (count < queueArray.Length) { + + if (queueArray[count] == null) + queueArray[count] = node = new KDQueryNode(); + else + node = queueArray[count]; + } + else { + + // automatic resize of pool + Array.Resize(ref queueArray, queueArray.Length * 2); + node = queueArray[count] = new KDQueryNode(); + } + + count++; + + return node; + } + + protected void PushToQueue(KDNode node, Vector3 tempClosestPoint) { + + var queryNode = PushGetQueue(); + queryNode.node = node; + queryNode.tempClosestPoint = tempClosestPoint; + } + + protected void PushToHeap(KDNode node, Vector3 tempClosestPoint, Vector3 queryPosition) { + + var queryNode = PushGetQueue(); + queryNode.node = node; + queryNode.tempClosestPoint = tempClosestPoint; + + float sqrDist = Vector3.SqrMagnitude(tempClosestPoint - queryPosition); + queryNode.distance = sqrDist; + minHeap.PushObj(queryNode, sqrDist); + } + + protected int LeftToProcess { + + get { + return count - queryIndex; + } + } + + // just gets unprocessed node from stack + // increases queryIndex + protected KDQueryNode PopFromQueue() { + + var node = queueArray[queryIndex]; + queryIndex++; + + return node; + } + + protected KDQueryNode PopFromHeap() { + + KDQueryNode heapNode = minHeap.PopObj(); + + queueArray[queryIndex]= heapNode; + queryIndex++; + + return heapNode; + } + + protected void Reset() { + + count = 0; + queryIndex = 0; + minHeap.Clear(); + } + + public KDQuery(int queryNodesContainersInitialSize = 2048) { + queueArray = new KDQueryNode[queryNodesContainersInitialSize]; + minHeap = new Heap.MinHeap(queryNodesContainersInitialSize); + } + + } + +} \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/Base.cs.meta b/Runtime/KDTree/KDQuery/Base.cs.meta new file mode 100644 index 0000000..c253741 --- /dev/null +++ b/Runtime/KDTree/KDQuery/Base.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7967af123067d1622a5c5fbc948b1f19 \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/Debug.cs b/Runtime/KDTree/KDQuery/Debug.cs new file mode 100644 index 0000000..3664f1f --- /dev/null +++ b/Runtime/KDTree/KDQuery/Debug.cs @@ -0,0 +1,56 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#define KDTREE_VISUAL_DEBUG + +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DataStructures.ViliWonka.KDTree { + + public partial class KDQuery { + + // uses gizmos + public void DrawLastQuery() { + + Color start = Color.red; + Color end = Color.green; + + start.a = 0.25f; + end.a = 0.25f; + + for(int i = 0; i < queryIndex; i++) { + + float val = i / (float)queryIndex; + + Gizmos.color = Color.Lerp(end, start, val); + + Bounds b = queueArray[i].node.bounds.Bounds; + + Gizmos.DrawWireCube(b.center, b.size); + } + } + } + +} \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/Debug.cs.meta b/Runtime/KDTree/KDQuery/Debug.cs.meta new file mode 100644 index 0000000..cfe0a7e --- /dev/null +++ b/Runtime/KDTree/KDQuery/Debug.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e2a2d436a25ed2ea89df09bc3572fb7d \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/KDQueryNode.cs b/Runtime/KDTree/KDQuery/KDQueryNode.cs new file mode 100644 index 0000000..c0a5bc2 --- /dev/null +++ b/Runtime/KDTree/KDQuery/KDQueryNode.cs @@ -0,0 +1,45 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using UnityEngine; +using UnityEditor; + +namespace DataStructures.ViliWonka.KDTree { + + public class KDQueryNode { + + public KDNode node; + public Vector3 tempClosestPoint; + public float distance; + + public KDQueryNode() { + + } + + public KDQueryNode(KDNode node, Vector3 tempClosestPoint) { + this.node = node; + this.tempClosestPoint = tempClosestPoint; + } + + } +} \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/KDQueryNode.cs.meta b/Runtime/KDTree/KDQuery/KDQueryNode.cs.meta new file mode 100644 index 0000000..b139347 --- /dev/null +++ b/Runtime/KDTree/KDQuery/KDQueryNode.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0db79addaf1dc6c12939c505533a7fe7 \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryClosest.cs b/Runtime/KDTree/KDQuery/QueryClosest.cs new file mode 100644 index 0000000..20586ae --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryClosest.cs @@ -0,0 +1,144 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DataStructures.ViliWonka.KDTree { + + using Heap; + + public partial class KDQuery { + + public void ClosestPoint(KDTree tree, Vector3 queryPosition, List resultIndices, List resultDistances = null) { + + Reset(); + + Vector3[] points = tree.Points; + int[] permutation = tree.Permutation; + + if (points.Length == 0) { + return; + } + + int smallestIndex = 0; + /// Smallest Squared Radius + float SSR = Single.PositiveInfinity; + + + var rootNode = tree.RootNode; + + Vector3 rootClosestPoint = rootNode.bounds.ClosestPoint(queryPosition); + + PushToHeap(rootNode, rootClosestPoint, queryPosition); + + KDQueryNode queryNode = null; + KDNode node = null; + + int partitionAxis; + float partitionCoord; + + Vector3 tempClosestPoint; + + // searching + while(minHeap.Count > 0) { + + queryNode = PopFromHeap(); + + if(queryNode.distance > SSR) + continue; + + node = queryNode.node; + + if(!node.Leaf) { + + partitionAxis = node.partitionAxis; + partitionCoord = node.partitionCoordinate; + + tempClosestPoint = queryNode.tempClosestPoint; + + if((tempClosestPoint[partitionAxis] - partitionCoord) < 0) { + + // we already know we are on the side of negative bound/node, + // so we don't need to test for distance + // push to stack for later querying + + PushToHeap(node.negativeChild, tempClosestPoint, queryPosition); + // project the tempClosestPoint to other bound + tempClosestPoint[partitionAxis] = partitionCoord; + + if(node.positiveChild.Count != 0) { + + PushToHeap(node.positiveChild, tempClosestPoint, queryPosition); + } + + } + else { + + // we already know we are on the side of positive bound/node, + // so we don't need to test for distance + // push to stack for later querying + + PushToHeap(node.positiveChild, tempClosestPoint, queryPosition); + // project the tempClosestPoint to other bound + tempClosestPoint[partitionAxis] = partitionCoord; + + if(node.positiveChild.Count != 0) { + + PushToHeap(node.negativeChild, tempClosestPoint, queryPosition); + } + + } + } + else { + + float sqrDist; + // LEAF + for(int i = node.start; i < node.end; i++) { + + int index = permutation[i]; + + sqrDist = Vector3.SqrMagnitude(points[index] - queryPosition); + + if(sqrDist <= SSR) { + + SSR = sqrDist; + smallestIndex = index; + } + } + + } + } + + resultIndices.Add(smallestIndex); + + if(resultDistances != null) { + resultDistances.Add(SSR); + } + + } + + } + +} diff --git a/Runtime/KDTree/KDQuery/QueryClosest.cs.meta b/Runtime/KDTree/KDQuery/QueryClosest.cs.meta new file mode 100644 index 0000000..0f0395c --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryClosest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0c02618e93fdf8299bc09f6285de4d5e \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryInterval.cs b/Runtime/KDTree/KDQuery/QueryInterval.cs new file mode 100644 index 0000000..88d1f1d --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryInterval.cs @@ -0,0 +1,151 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DataStructures.ViliWonka.KDTree { + + public partial class KDQuery { + + public void Interval(KDTree tree, Vector3 min, Vector3 max, List resultIndices) { + + Reset(); + + Vector3[] points = tree.Points; + int[] permutation = tree.Permutation; + + var rootNode = tree.RootNode; + + PushToQueue( + + rootNode, + rootNode.bounds.ClosestPoint((min + max) / 2) + ); + + KDQueryNode queryNode = null; + KDNode node = null; + + + // KD search with pruning (don't visit areas which distance is more away than range) + // Recursion done on Stack + while(LeftToProcess > 0) { + + queryNode = PopFromQueue(); + node = queryNode.node; + + if(!node.Leaf) { + + int partitionAxis = node.partitionAxis; + float partitionCoord = node.partitionCoordinate; + + Vector3 tempClosestPoint = queryNode.tempClosestPoint; + + if((tempClosestPoint[partitionAxis] - partitionCoord) < 0) { + + // we already know we are inside negative bound/node, + // so we don't need to test for distance + // push to stack for later querying + + // tempClosestPoint is inside negative side + // assign it to negativeChild + PushToQueue(node.negativeChild, tempClosestPoint); + + tempClosestPoint[partitionAxis] = partitionCoord; + + // testing other side + if(node.positiveChild.Count != 0 + && tempClosestPoint[partitionAxis] <= max[partitionAxis]) { + + PushToQueue(node.positiveChild, tempClosestPoint); + } + } + else { + + // we already know we are inside positive bound/node, + // so we don't need to test for distance + // push to stack for later querying + + // tempClosestPoint is inside positive side + // assign it to positiveChild + PushToQueue(node.positiveChild, tempClosestPoint); + + // project the tempClosestPoint to other bound + tempClosestPoint[partitionAxis] = partitionCoord; + + // testing other side + if(node.negativeChild.Count != 0 + && tempClosestPoint[partitionAxis] >= min[partitionAxis]) { + + PushToQueue(node.negativeChild, tempClosestPoint); + } + } + } + else { + + // LEAF + + // testing if node bounds are inside the query interval + if(node.bounds.min[0] >= min[0] + && node.bounds.min[1] >= min[1] + && node.bounds.min[2] >= min[2] + + && node.bounds.max[0] <= max[0] + && node.bounds.max[1] <= max[1] + && node.bounds.max[2] <= max[2]) { + + for(int i = node.start; i < node.end; i++) { + + resultIndices.Add(permutation[i]); + } + + } + // node is not inside query interval, need to do test on each point separately + else { + + for(int i = node.start; i < node.end; i++) { + + int index = permutation[i]; + + Vector3 v = points[index]; + + if(v[0] >= min[0] + && v[1] >= min[1] + && v[2] >= min[2] + + && v[0] <= max[0] + && v[1] <= max[1] + && v[2] <= max[2]) { + + resultIndices.Add(index); + } + } + } + + } + } + } + } + +} \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryInterval.cs.meta b/Runtime/KDTree/KDQuery/QueryInterval.cs.meta new file mode 100644 index 0000000..befc2f9 --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryInterval.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 201cd9cbfb7b7005285e57eb971698cf \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryKNearest.cs b/Runtime/KDTree/KDQuery/QueryKNearest.cs new file mode 100644 index 0000000..d1b5044 --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryKNearest.cs @@ -0,0 +1,160 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#define KDTREE_VISUAL_DEBUG + +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DataStructures.ViliWonka.KDTree { + + using Heap; + + public partial class KDQuery { + + SortedList> _heaps = new SortedList>(); + /// + /// Returns indices to k closest points, and optionaly can return distances + /// + /// Tree to do search on + /// Position + /// Max number of points + /// List where resulting indices will be stored + /// Optional list where resulting distances will be stored + public void KNearest(KDTree tree, Vector3 queryPosition, int k, List resultIndices, List resultDistances = null) { + + // pooled heap arrays + KSmallestHeap kHeap; + + _heaps.TryGetValue(k, out kHeap); + + if(kHeap == null) { + + kHeap = new KSmallestHeap(k); + _heaps.Add(k, kHeap); + } + + kHeap.Clear(); + Reset(); + + Vector3[] points = tree.Points; + int[] permutation = tree.Permutation; + + ///Biggest Smallest Squared Radius + float BSSR = Single.PositiveInfinity; + + var rootNode = tree.RootNode; + + Vector3 rootClosestPoint = rootNode.bounds.ClosestPoint(queryPosition); + + PushToHeap(rootNode, rootClosestPoint, queryPosition); + + KDQueryNode queryNode = null; + KDNode node = null; + + int partitionAxis; + float partitionCoord; + + Vector3 tempClosestPoint; + + // searching + while(minHeap.Count > 0) { + + queryNode = PopFromHeap(); + + if(queryNode.distance > BSSR) + continue; + + node = queryNode.node; + + if(!node.Leaf) { + + partitionAxis = node.partitionAxis; + partitionCoord = node.partitionCoordinate; + + tempClosestPoint = queryNode.tempClosestPoint; + + if((tempClosestPoint[partitionAxis] - partitionCoord) < 0) { + + // we already know we are on the side of negative bound/node, + // so we don't need to test for distance + // push to stack for later querying + + PushToHeap(node.negativeChild, tempClosestPoint, queryPosition); + // project the tempClosestPoint to other bound + tempClosestPoint[partitionAxis] = partitionCoord; + + if(node.positiveChild.Count != 0) { + + PushToHeap(node.positiveChild, tempClosestPoint, queryPosition); + } + + } + else { + + // we already know we are on the side of positive bound/node, + // so we don't need to test for distance + // push to stack for later querying + + PushToHeap(node.positiveChild, tempClosestPoint, queryPosition); + // project the tempClosestPoint to other bound + tempClosestPoint[partitionAxis] = partitionCoord; + + if(node.positiveChild.Count != 0) { + + PushToHeap(node.negativeChild, tempClosestPoint, queryPosition); + } + + } + } + else { + + float sqrDist; + // LEAF + for(int i = node.start; i < node.end; i++) { + + int index = permutation[i]; + + sqrDist = Vector3.SqrMagnitude(points[index] - queryPosition); + + if(sqrDist <= BSSR) { + + kHeap.PushObj(index, sqrDist); + + if(kHeap.Full) { + BSSR = kHeap.HeadValue; + } + } + } + + } + } + + kHeap.FlushResult(resultIndices, resultDistances); + + } + + } + +} \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryKNearest.cs.meta b/Runtime/KDTree/KDQuery/QueryKNearest.cs.meta new file mode 100644 index 0000000..ed57065 --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryKNearest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b3a3e4ea20ad993c68c63d2dccd56e56 \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryRadius.cs b/Runtime/KDTree/KDQuery/QueryRadius.cs new file mode 100644 index 0000000..41c6720 --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryRadius.cs @@ -0,0 +1,131 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DataStructures.ViliWonka.KDTree { + + public partial class KDQuery { + + /// + /// Search by radius method. + /// + /// Tree to do search on + /// Position + /// Radius + /// Initialized list, cleared. + public void Radius(KDTree tree, Vector3 queryPosition, float queryRadius, List resultIndices) { + + Reset(); + + Vector3[] points = tree.Points; + int[] permutation = tree.Permutation; + + float squaredRadius = queryRadius * queryRadius; + + var rootNode = tree.RootNode; + + PushToQueue(rootNode, rootNode.bounds.ClosestPoint(queryPosition)); + + KDQueryNode queryNode = null; + KDNode node = null; + + // KD search with pruning (don't visit areas which distance is more away than range) + // Recursion done on Stack + while(LeftToProcess > 0) { + + queryNode = PopFromQueue(); + node = queryNode.node; + + if(!node.Leaf) { + + int partitionAxis = node.partitionAxis; + float partitionCoord = node.partitionCoordinate; + + Vector3 tempClosestPoint = queryNode.tempClosestPoint; + + if((tempClosestPoint[partitionAxis] - partitionCoord) < 0) { + + // we already know we are inside negative bound/node, + // so we don't need to test for distance + // push to stack for later querying + + // tempClosestPoint is inside negative side + // assign it to negativeChild + PushToQueue(node.negativeChild, tempClosestPoint); + + tempClosestPoint[partitionAxis] = partitionCoord; + + float sqrDist = Vector3.SqrMagnitude(tempClosestPoint - queryPosition); + + // testing other side + if(node.positiveChild.Count != 0 + && sqrDist <= squaredRadius) { + + PushToQueue(node.positiveChild, tempClosestPoint); + } + } + else { + + // we already know we are inside positive bound/node, + // so we don't need to test for distance + // push to stack for later querying + + // tempClosestPoint is inside positive side + // assign it to positiveChild + PushToQueue(node.positiveChild, tempClosestPoint); + + // project the tempClosestPoint to other bound + tempClosestPoint[partitionAxis] = partitionCoord; + + float sqrDist = Vector3.SqrMagnitude(tempClosestPoint - queryPosition); + + // testing other side + if(node.negativeChild.Count != 0 + && sqrDist <= squaredRadius) { + + PushToQueue(node.negativeChild, tempClosestPoint); + } + } + } + else { + + // LEAF + for(int i = node.start; i < node.end; i++) { + + int index = permutation[i]; + + if(Vector3.SqrMagnitude(points[index] - queryPosition) <= squaredRadius) { + + resultIndices.Add(index); + } + } + + } + } + } + + } +} \ No newline at end of file diff --git a/Runtime/KDTree/KDQuery/QueryRadius.cs.meta b/Runtime/KDTree/KDQuery/QueryRadius.cs.meta new file mode 100644 index 0000000..212cee3 --- /dev/null +++ b/Runtime/KDTree/KDQuery/QueryRadius.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ac26aab538b2e7b3934a1ef1e96549a \ No newline at end of file diff --git a/Runtime/KDTree/KDTree.cs b/Runtime/KDTree/KDTree.cs new file mode 100644 index 0000000..60c193f --- /dev/null +++ b/Runtime/KDTree/KDTree.cs @@ -0,0 +1,459 @@ +/*MIT License + +Copyright(c) 2018 Vili Volčini / viliwonka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +// change to !KDTREE_DUPLICATES +// if you know for sure you will not use duplicate coordinates (all unique) +#define KDTREE_DUPLICATES + +using System.Collections; +using System.Collections.Generic; +using System; +using UnityEngine; + +namespace DataStructures.ViliWonka.KDTree { + + public class KDTree { + + public KDNode RootNode { get; private set; } + + public Vector3[] Points { get { return points; } } // points on which kd-tree will build on. This array will stay unchanged when re/building kdtree! + private Vector3[] points; + + public int[] Permutation { get { return permutation; } } // index aray, that will be permuted + private int[] permutation; + + public int Count { get; private set; } + + private int maxPointsPerLeafNode = 32; + + private KDNode[] kdNodesStack; + private int kdNodesCount = 0; + + public KDTree(int maxPointsPerLeafNode = 32) { + + Count = 0; + points = new Vector3[0]; + permutation = new int[0]; + + kdNodesStack = new KDNode[64]; + + this.maxPointsPerLeafNode = maxPointsPerLeafNode; + } + + public KDTree(Vector3[] points, int maxPointsPerLeafNode = 32) { + + this.points = points; + this.permutation = new int[points.Length]; + + Count = points.Length; + kdNodesStack = new KDNode[64]; + + this.maxPointsPerLeafNode = maxPointsPerLeafNode; + + Rebuild(); + } + + public void Build(Vector3[] newPoints, int maxPointsPerLeafNode = -1) { + + SetCount(newPoints.Length); + + for(int i = 0; i < Count; i++) { + points[i] = newPoints[i]; + } + + Rebuild(maxPointsPerLeafNode); + } + + public void Build(List newPoints, int maxPointsPerLeafNode = -1) { + + SetCount(newPoints.Count); + + for(int i = 0; i < Count; i++) { + points[i] = newPoints[i]; + } + + Rebuild(maxPointsPerLeafNode); + } + + public void Rebuild(int maxPointsPerLeafNode = -1) { + + for(int i = 0; i < Count; i++) { + permutation[i] = i; + } + + if(maxPointsPerLeafNode > 0) { + this.maxPointsPerLeafNode = maxPointsPerLeafNode; + } + + BuildTree(); + } + + public void SetCount(int newSize) { + + Count = newSize; + // upsize internal arrays + if(Count > points.Length) { + + Array.Resize(ref points, Count); + Array.Resize(ref permutation, Count); + } + } + + void BuildTree() { + + ResetKDNodeStack(); + + RootNode = GetKDNode(); + RootNode.bounds = MakeBounds(); + RootNode.start = 0; + RootNode.end = Count; + + SplitNode(RootNode); + } + + KDNode GetKDNode() { + + KDNode node = null; + + if(kdNodesCount < kdNodesStack.Length) { + + if(kdNodesStack[kdNodesCount] == null) { + kdNodesStack[kdNodesCount] = node = new KDNode(); + } + else { + node = kdNodesStack[kdNodesCount]; + node.partitionAxis = -1; + } + } + else { + + // automatic resize of KDNode pool array + Array.Resize(ref kdNodesStack, kdNodesStack.Length * 2); + node = kdNodesStack[kdNodesCount] = new KDNode(); + } + + kdNodesCount++; + + return node; + } + + void ResetKDNodeStack() { + kdNodesCount = 0; + } + + /// + /// For calculating root node bounds + /// + /// Boundary of all Vector3 points + KDBounds MakeBounds() { + + Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue); + Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + + int even = Count & ~1; // calculate even Length + + // min, max calculations + // 3n/2 calculations instead of 2n + for (int i0 = 0; i0 < even; i0 += 2) { + + int i1 = i0 + 1; + + // X Coords + if (points[i0].x > points[i1].x) { + // i0 is bigger, i1 is smaller + if (points[i1].x < min.x) + min.x = points[i1].x; + + if (points[i0].x > max.x) + max.x = points[i0].x; + } + else { + // i1 is smaller, i0 is bigger + if (points[i0].x < min.x) + min.x = points[i0].x; + + if (points[i1].x > max.x) + max.x = points[i1].x; + } + + // Y Coords + if (points[i0].y > points[i1].y) { + // i0 is bigger, i1 is smaller + if (points[i1].y < min.y) + min.y = points[i1].y; + + if (points[i0].y > max.y) + max.y = points[i0].y; + } + else { + // i1 is smaller, i0 is bigger + if (points[i0].y < min.y) + min.y = points[i0].y; + + if (points[i1].y > max.y) + max.y = points[i1].y; + } + + // Z Coords + if (points[i0].z > points[i1].z) { + // i0 is bigger, i1 is smaller + if (points[i1].z < min.z) + min.z = points[i1].z; + + if (points[i0].z > max.z) + max.z = points[i0].z; + } + else { + // i1 is smaller, i0 is bigger + if (points[i0].z < min.z) + min.z = points[i0].z; + + if (points[i1].z > max.z) + max.z = points[i1].z; + } + } + + // if array was odd, calculate also min/max for the last element + if(even != Count) { + // X + if (min.x > points[even].x) + min.x = points[even].x; + + if (max.x < points[even].x) + max.x = points[even].x; + // Y + if (min.y > points[even].y) + min.y = points[even].y; + + if (max.y < points[even].y) + max.y = points[even].y; + // Z + if (min.z > points[even].z) + min.z = points[even].z; + + if (max.z < points[even].z) + max.z = points[even].z; + } + + KDBounds b = new KDBounds(); + b.min = min; + b.max = max; + + return b; + } + + /// + /// Recursive splitting procedure + /// + /// This is where root node goes + /// + /// + void SplitNode(KDNode parent) { + + // center of bounding box + KDBounds parentBounds = parent.bounds; + Vector3 parentBoundsSize = parentBounds.size; + + // Find axis where bounds are largest + int splitAxis = 0; + float axisSize = parentBoundsSize.x; + + if (axisSize < parentBoundsSize.y) { + splitAxis = 1; + axisSize = parentBoundsSize.y; + } + + if (axisSize < parentBoundsSize.z) { + splitAxis = 2; + } + + // Our axis min-max bounds + float boundsStart = parentBounds.min[splitAxis]; + float boundsEnd = parentBounds.max[splitAxis]; + + // Calculate the spliting coords + float splitPivot = CalculatePivot(parent.start, parent.end, boundsStart, boundsEnd, splitAxis); + + parent.partitionAxis = splitAxis; + parent.partitionCoordinate = splitPivot; + + // 'Spliting' array to two subarrays + int splittingIndex = Partition(parent.start, parent.end, splitPivot, splitAxis); + + // Negative / Left node + Vector3 negMax = parentBounds.max; + negMax[splitAxis] = splitPivot; + + KDNode negNode = GetKDNode(); + negNode.bounds = parentBounds; + negNode.bounds.max = negMax; + negNode.start = parent.start; + negNode.end = splittingIndex; + parent.negativeChild = negNode; + + // Positive / Right node + Vector3 posMin = parentBounds.min; + posMin[splitAxis] = splitPivot; + + KDNode posNode = GetKDNode(); + posNode.bounds = parentBounds; + posNode.bounds.min = posMin; + posNode.start = splittingIndex; + posNode.end = parent.end; + parent.positiveChild = posNode; + + // check if we are actually splitting it anything + // this if check enables duplicate coordinates, but makes construction a bit slower +#if KDTREE_DUPLICATES + if(negNode.Count != 0 && posNode.Count != 0) { + #endif + // Constraint function deciding if split should be continued + if(ContinueSplit(negNode)) + SplitNode(negNode); + + + if(ContinueSplit(posNode)) + SplitNode(posNode); + +#if KDTREE_DUPLICATES + } +#endif + } + + /// + /// Sliding midpoint splitting pivot calculation + /// 1. First splits node to two equal parts (midPoint) + /// 2. Checks if elements are in both sides of splitted bounds + /// 3a. If they are, just return midPoint + /// 3b. If they are not, then points are only on left or right bound. + /// 4. Move the splitting pivot so that it shrinks part with points completely (calculate min or max dependent) and return. + /// + /// + /// + /// + /// + /// + /// + float CalculatePivot(int start, int end, float boundsStart, float boundsEnd, int axis) { + + //! sliding midpoint rule + float midPoint = (boundsStart + boundsEnd) / 2f; + + bool negative = false; + bool positive = false; + + float negMax = Single.MinValue; + float posMin = Single.MaxValue; + + // this for loop section is used both for sorted and unsorted data + for (int i = start; i < end; i++) { + + if (points[permutation[i]][axis] < midPoint) + negative = true; + else + positive = true; + + if (negative == true && positive == true) + return midPoint; + } + + if (negative) { + + for (int i = start; i < end; i++) + if (negMax < points[permutation[i]][axis]) + negMax = points[permutation[i]][axis]; + + return negMax; + } + else { + + for (int i = start; i < end; i++) + if (posMin > points[permutation[i]][axis]) + posMin = points[permutation[i]][axis]; + + return posMin; + } + } + + /// + /// Similar to Hoare partitioning algorithm (used in Quick Sort) + /// Modification: pivot is not left-most element but is instead argument of function + /// Calculates splitting index and partially sorts elements (swaps them until they are on correct side - depending on pivot) + /// Complexity: O(n) + /// + /// Start index + /// End index + /// Pivot that decides boundary between left and right + /// Axis of this pivoting + /// + /// Returns splitting index that subdivides array into 2 smaller arrays + /// left = [start, pivot), + /// right = [pivot, end) + /// + int Partition(int start, int end, float partitionPivot, int axis) { + + // note: increasing right pointer is actually decreasing! + int LP = start - 1; // left pointer (negative side) + int RP = end; // right pointer (positive side) + + int temp; // temporary var for swapping permutation indexes + + while (true) { + + do { + // move from left to the right until "out of bounds" value is found + LP++; + } + while (LP < RP && points[permutation[LP]][axis] < partitionPivot); + + do { + // move from right to the left until "out of bounds" value found + RP--; + } + while (LP < RP && points[permutation[RP]][axis] >= partitionPivot); + + if (LP < RP) { + // swap + temp = permutation[LP]; + permutation[LP] = permutation[RP]; + permutation[RP] = temp; + } + else { + + return LP; + } + } + } + + /// + /// Constraint function. You can add custom constraints here - if you have some other data/classes binded to Vector3 points + /// Can hardcode it into + /// + /// + /// + bool ContinueSplit(KDNode node) { + + return (node.Count > maxPointsPerLeafNode); + } + } +} \ No newline at end of file diff --git a/Runtime/KDTree/KDTree.cs.meta b/Runtime/KDTree/KDTree.cs.meta new file mode 100644 index 0000000..e17f376 --- /dev/null +++ b/Runtime/KDTree/KDTree.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cb2a53f24c1d57ed6adef63d87b95550 \ No newline at end of file diff --git a/Runtime/PSXTexture2D.cs b/Runtime/PSXTexture2D.cs index dec79e6..e795d3b 100644 --- a/Runtime/PSXTexture2D.cs +++ b/Runtime/PSXTexture2D.cs @@ -93,12 +93,23 @@ namespace PSXSplash.RuntimeCode ImageQuantizer quantizer = new ImageQuantizer(); quantizer.Quantize(inputTexture, psxTex._maxColors); - foreach (Color pixel in quantizer.Palette) - { + foreach (Vector3 color in quantizer.Palette) + { + Color pixel = new Color(color.x, color.y, color.z); VRAMPixel vramPixel = new VRAMPixel { R = (ushort)(pixel.r * 31), G = (ushort)(pixel.g * 31), B = (ushort)(pixel.b * 31) }; psxTex.ColorPalette.Add(vramPixel); } - psxTex.Pixels = quantizer.Pixels; + + + psxTex.Pixels = new int[quantizer.Width * quantizer.Height]; + for (int x = 0; x < quantizer.Width; x++) + { + for (int y = 0; y < quantizer.Height; y++) + { + psxTex.Pixels[x+y*quantizer.Width] = quantizer.Pixels[x,y]; + } + } + return psxTex; } @@ -119,8 +130,8 @@ namespace PSXSplash.RuntimeCode float r = pixel.R / 31f; float g = pixel.G / 31f; float b = pixel.B / 31f; - - colors16[i] = new Color(r,g,b); + + colors16[i] = new Color(r, g, b); } tex.SetPixels(colors16); tex.Apply(); @@ -150,8 +161,9 @@ namespace PSXSplash.RuntimeCode public Texture2D GenerateVramPreview() { - - if(BitDepth == PSXBPP.TEX_16BIT) { + + if (BitDepth == PSXBPP.TEX_16BIT) + { return GeneratePreview(); }