├── Runtime ├── Bounds.cs.meta ├── SpatialHashingSystem.Debug.cs.meta ├── Utils │ ├── NativeArrayExtension.cs.meta │ ├── HMH.SpatialHash.Utils.asmdef.meta │ ├── MathHelpExtension.cs.meta │ ├── HMH.SpatialHash.Utils.asmdef │ ├── NativeArrayExtension.cs │ └── MathHelpExtension.cs ├── Utils.meta ├── HMH.SpatialHash.asmdef.meta ├── VoxelRay.cs.meta ├── SpatialHash.cs.meta ├── SpatialHashingSystem.cs.meta ├── HMH.SpatialHash.asmdef ├── SpatialHashingSystem.Debug.cs ├── VoxelRay.cs ├── Bounds.cs ├── SpatialHashingSystem.cs └── SpatialHash.cs ├── Editor ├── OldDebug │ ├── HashTeleport.cs.meta │ ├── AABB Debug.unity.meta │ ├── OBB Debug.unity.meta │ ├── HashMapVisualDebug.cs.meta │ ├── HashMapVisualOBBDebug.cs.meta │ ├── HashTeleport.cs │ ├── HashMapVisualOBBDebug.cs │ ├── OBB Debug.unity │ ├── HashMapVisualDebug.cs │ └── AABB Debug.unity ├── OldDebug.meta ├── HMH.SpatialHash.Debug.asmdef.meta └── HMH.SpatialHash.Debug.asmdef ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── Runtime.meta ├── Tests.meta ├── Tests ├── HMH.SpatialHash.Tests.asmdef.meta ├── ECSTestsFixture.cs.meta ├── SpatialHashTest.cs.meta ├── SpatialHashingSystemTest.cs.meta ├── HMH.SpatialHash.Tests.asmdef ├── ECSTestsFixture.cs ├── SpatialHashTest.cs └── SpatialHashingSystemTest.cs ├── package.json ├── LICENSE.md └── README.md /Runtime/Bounds.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b131934f6e64e688dc70c3cca580c3c 3 | timeCreated: 1553202613 -------------------------------------------------------------------------------- /Editor/OldDebug/HashTeleport.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a07c3dddced34a4bad145507420f738c 3 | timeCreated: 1553374954 -------------------------------------------------------------------------------- /Runtime/SpatialHashingSystem.Debug.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 45bbeb5ba48c410ba497761a892c175d 3 | timeCreated: 1632149390 -------------------------------------------------------------------------------- /Runtime/Utils/NativeArrayExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7618ee5d03fe46a6a88cb6d7edcf9a57 3 | timeCreated: 1684588204 -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 827be23d8b4d0ef44ba15a59bf0d1347 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3e993e631ddefc74591904f9ae4ad214 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3e0f7c15d62ea0646924f7e7d02d9754 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3cabefc7e403c794db157f72938b10e1 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 93a7dbbe3beba1d438235d4242697af2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8b6cba517cd34a2479f995ad7d9bf020 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/OldDebug/AABB Debug.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79d2706ce3bd8014ca414495b46d3223 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/OldDebug/OBB Debug.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37a544b50ba51f74aa92f65958c83b12 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Utils.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cae6e0d019db46d4e84cc67d35d6d445 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/OldDebug.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 556fcc96b2c9bdf478bf7c1446211e45 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/HMH.SpatialHash.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 94c278f022aeb5b45a7a14aea5ecb854 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests/HMH.SpatialHash.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e5a8f17ccfbf69f48aa5de4ade17d338 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/HMH.SpatialHash.Debug.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b3db58eaa09224e4189f725510bb9e9d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Utils/HMH.SpatialHash.Utils.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f827ff043d64b3468cce8395ffae24c 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/VoxelRay.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fc891708518e2d74bac9ececec92b382 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/SpatialHash.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 365cba59ca952be4aa081dda00ae455f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/ECSTestsFixture.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8b467d4849ecf74986aeeb25008119c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/SpatialHashTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 789ee1de9f837774c86dc8659f520f93 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/SpatialHashingSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d8b8b61977701094682a524fd075f744 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Utils/MathHelpExtension.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d4ece163e31f2c2498ad668a16d8e5d9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/SpatialHashingSystemTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66dba13515622e044a4f187c2bd86407 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/OldDebug/HashMapVisualDebug.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b9dfff59ca57b84fa7d77c91278898f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/OldDebug/HashMapVisualOBBDebug.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53c458d06068e1f418aacf004d55db61 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/OldDebug/HashTeleport.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace HMH.ECS.SpatialHashing.Debug 4 | { 5 | public class HashTeleport : MonoBehaviour 6 | { 7 | private void Update() 8 | { 9 | if (Random.Range(0, 3) == 2) 10 | transform.position += Random.insideUnitSphere * 5F; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.lennethproduction.spatialhashing", 3 | "version": "2.0.2", 4 | "displayName": "Spatial Hashing", 5 | "description": "Spatial hashing for Unity using ECS", 6 | "unity": "2022.2", 7 | "dependencies": { 8 | "com.unity.entities": "1.0.8" 9 | }, 10 | "keywords": [ 11 | "ECS", 12 | "Spatial hashing" 13 | ] 14 | } -------------------------------------------------------------------------------- /Runtime/Utils/HMH.SpatialHash.Utils.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HMH.SpatialHash.Utils", 3 | "rootNamespace": "HMH.SpatialHash.Utils", 4 | "references": [ 5 | "GUID:d8b63aba1907145bea998dd612889d6b" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": true, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Runtime/HMH.SpatialHash.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HMH.SpatialHash", 3 | "rootNamespace": "HMH.SpatialHash", 4 | "references": [ 5 | "GUID:734d92eba21c94caba915361bd5ac177", 6 | "GUID:d8b63aba1907145bea998dd612889d6b", 7 | "GUID:e0cd26848372d4e5c891c569017e11f1", 8 | "GUID:8f827ff043d64b3468cce8395ffae24c", 9 | "GUID:2665a8d13d1b3f18800f46e256720795" 10 | ], 11 | "includePlatforms": [], 12 | "excludePlatforms": [], 13 | "allowUnsafeCode": true, 14 | "overrideReferences": false, 15 | "precompiledReferences": [], 16 | "autoReferenced": true, 17 | "defineConstraints": [], 18 | "versionDefines": [], 19 | "noEngineReferences": false 20 | } -------------------------------------------------------------------------------- /Editor/HMH.SpatialHash.Debug.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HMH.SpatialHash.Debug", 3 | "rootNamespace": "HMH.SpatialHash.Debug", 4 | "references": [ 5 | "GUID:734d92eba21c94caba915361bd5ac177", 6 | "GUID:d8b63aba1907145bea998dd612889d6b", 7 | "GUID:e0cd26848372d4e5c891c569017e11f1", 8 | "GUID:2665a8d13d1b3f18800f46e256720795", 9 | "GUID:8a2eafa29b15f444eb6d74f94a930e1d", 10 | "GUID:94c278f022aeb5b45a7a14aea5ecb854", 11 | "GUID:8f827ff043d64b3468cce8395ffae24c" 12 | ], 13 | "includePlatforms": [ 14 | "Editor" 15 | ], 16 | "excludePlatforms": [], 17 | "allowUnsafeCode": true, 18 | "overrideReferences": false, 19 | "precompiledReferences": [], 20 | "autoReferenced": false, 21 | "defineConstraints": [], 22 | "versionDefines": [], 23 | "noEngineReferences": false 24 | } -------------------------------------------------------------------------------- /Runtime/Utils/NativeArrayExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Unity.Collections; 3 | using Unity.Collections.LowLevel.Unsafe; 4 | 5 | namespace HMH.ECS 6 | { 7 | public static class NativeArrayExtension 8 | { 9 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 10 | public static unsafe ref T GetElementAsRef(this ref NativeArray array, int index) where T : struct 11 | { 12 | return ref UnsafeUtility.ArrayElementAsRef(array.GetUnsafePtr(), index); 13 | } 14 | 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static unsafe ref T GetElementAsRefReadOnly(this ref NativeArray array, int index) where T : struct 17 | { 18 | return ref UnsafeUtility.ArrayElementAsRef(array.GetUnsafeReadOnlyPtr(), index); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Tests/HMH.SpatialHash.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HMH.SpatialHash.Tests", 3 | "rootNamespace": "HMH.SpatialHash.Tests", 4 | "references": [ 5 | "GUID:27619889b8ba8c24980f49ee34dbb44a", 6 | "GUID:0acc523941302664db1f4e527237feb3", 7 | "GUID:8a2eafa29b15f444eb6d74f94a930e1d", 8 | "GUID:d8b63aba1907145bea998dd612889d6b", 9 | "GUID:734d92eba21c94caba915361bd5ac177", 10 | "GUID:2665a8d13d1b3f18800f46e256720795", 11 | "GUID:e0cd26848372d4e5c891c569017e11f1", 12 | "GUID:94c278f022aeb5b45a7a14aea5ecb854" 13 | ], 14 | "includePlatforms": [ 15 | "Editor" 16 | ], 17 | "excludePlatforms": [], 18 | "allowUnsafeCode": true, 19 | "overrideReferences": true, 20 | "precompiledReferences": [ 21 | "nunit.framework.dll" 22 | ], 23 | "autoReferenced": false, 24 | "defineConstraints": [ 25 | "UNITY_INCLUDE_TESTS" 26 | ], 27 | "versionDefines": [], 28 | "noEngineReferences": false 29 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sylmerria 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spatial hashing for Unity using ECS 2 | A spatial hash is one way of indexing objects in space. 3 | This package allows you to indexing struct manually or automatically (via IComponentData) with high speed 4 | 5 | ![Spatial hashing image](https://zupimages.net/up/22/21/uw0s.png) 6 | 7 | Source : https://www.researchgate.net/figure/Spatial-hashing-where-objects-are-mapped-into-uniformly-sized-cells_fig1_326306568 8 | 9 | # Requirement 10 | - Unity 2022.2.20 11 | - Package Entities 1.0.8 12 | 13 | # How to add to your project 14 | - Open manifest.json at %RepertoryPath%/Packages 15 | - Add ```"com.lennethproduction.spatialhashing": "https://github.com/Sylmerria/Spatial-Hashing.git"``` in dependencies section 16 | 17 | # How to start 18 | 19 | ##Manually 20 | 1. Implement ISpatialHashingItem on a struct 21 | 2. Allocate a SpatialHash of this struct 22 | 3. Use it 23 | 24 | ##Automatically 25 | 1. Implement ISpatialHashingItem on a IComponentData struct 26 | 2. Implement a IComponentData struct to warn the system when an entity is dirty, typically a flag or an enablable component 27 | 3. Implement a child class inheritant SpatialHashingSystem (T is 1. component, TY will be HMH.ECS.SpatialHashing.SpatialHashingMirror (or similar), TZ will be 2. component 28 | 4. Add that before the child class (See unit test to concrete example) 29 | > [assembly: RegisterGenericJobType(typeof({CHILDCLASSTYPE}.IncreaseSpatialHashSizeJob))] 30 | [assembly: RegisterGenericJobType(typeof({CHILDCLASSTYPE}.AddSpatialHashingJob))] 31 | [assembly: RegisterGenericJobType(typeof({CHILDCLASSTYPE}.AddSpatialHashingEndJob))] 32 | [assembly: RegisterGenericJobType(typeof({CHILDCLASSTYPE}.UpdateSpatialHashingRemoveFastJob))] 33 | [assembly: RegisterGenericJobType(typeof({CHILDCLASSTYPE}.UpdateSpatialHashingAddFastJob))] 34 | [assembly: RegisterGenericJobType(typeof({CHILDCLASSTYPE}.RemoveSpatialHashingJob))] 35 | 5. good to go 36 | -------------------------------------------------------------------------------- /Runtime/Utils/MathHelpExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Unity.Mathematics; 3 | 4 | namespace HMH.ECS 5 | { 6 | public static class MathHelpExtension 7 | { 8 | /// Returns the result of rounding each component of a float2 vector value up to the nearest value greater or equal to the original value. 9 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 10 | public static int2 CeilToInt(this float2 x) 11 | { 12 | return new int2(math.ceil(x)); 13 | } 14 | 15 | /// Returns the result of rounding each component of a float3 vector value up to the nearest value greater or equal to the original value. 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | public static int3 CeilToInt(this float3 x) 18 | { 19 | return new int3(math.ceil(x)); 20 | } 21 | 22 | /// Returns the result of rounding each component of a float2 vector value up to the nearest value lower or equal to the original value. 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | public static int2 FloorToInt(this float2 x) 25 | { 26 | return new int2(math.floor(x)); 27 | } 28 | 29 | /// Returns the result of rounding each component of a float3 vector value up to the nearest value lower or equal to the original value. 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static int3 FloorToInt(this float3 x) 32 | { 33 | return new int3(math.floor(x)); 34 | } 35 | 36 | /// Returns the result of adding each component of a int2 vector value 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public static int Sum(this int2 x) 39 | { 40 | return math.csum(x); 41 | } 42 | 43 | /// Returns the result of adding each component of a int3 vector value 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static int Sum(this int3 x) 46 | { 47 | return math.csum(x); 48 | } 49 | 50 | /// Returns the result of multiplying each component with others 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static float Mul(this float2 x) 53 | { 54 | return x.x * x.y; 55 | } 56 | 57 | /// Returns the result of multiplying each component with others 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | public static float Mul(this float3 x) 60 | { 61 | return x.x * x.y * x.z; 62 | } 63 | 64 | /// Returns the result of multiplying each component with others 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public static int Mul(this int2 x) 67 | { 68 | return x.x * x.y; 69 | } 70 | 71 | /// Returns the result of multiplying each component with others 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | public static int Mul(this int3 x) 74 | { 75 | return x.x * x.y * x.z; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Tests/ECSTestsFixture.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Unity.Entities; 3 | using Unity.Jobs.LowLevel.Unsafe; 4 | using UnityEngine.LowLevel; 5 | 6 | namespace HMH.ECS.SpatialHashing.Test 7 | { 8 | public class ECSTestsCommonBase 9 | { 10 | [SetUp] 11 | public virtual void Setup() 12 | { 13 | #if UNITY_DOTSRUNTIME 14 | Unity.Runtime.TempMemoryScope.EnterScope(); 15 | #endif 16 | } 17 | 18 | [TearDown] 19 | public virtual void TearDown() 20 | { 21 | #if UNITY_DOTSRUNTIME 22 | Unity.Runtime.TempMemoryScope.ExitScope(); 23 | #endif 24 | } 25 | } 26 | 27 | public class ECSTestsFixture : ECSTestsCommonBase 28 | { 29 | #if !UNITY_DOTSRUNTIME 30 | protected PlayerLoopSystem m_PreviousPlayerLoop; 31 | #endif 32 | 33 | private bool JobsDebuggerWasEnabled; 34 | 35 | protected World PreviousWorld { get; private set; } 36 | protected World World { get; private set; } 37 | protected EntityManager EntityManager { get; private set; } 38 | protected EntityManager.EntityManagerDebug EntityManagerDebug { get; private set; } 39 | 40 | [SetUp] 41 | public override void Setup() 42 | { 43 | base.Setup(); 44 | 45 | #if !UNITY_DOTSRUNTIME 46 | 47 | // unit tests preserve the current player loop to restore later, and start from a blank slate. 48 | m_PreviousPlayerLoop = PlayerLoop.GetCurrentPlayerLoop(); 49 | PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop()); 50 | #endif 51 | 52 | PreviousWorld = World.DefaultGameObjectInjectionWorld; 53 | World = World.DefaultGameObjectInjectionWorld = new World("Test World"); 54 | 55 | EntityManager = World.EntityManager; 56 | EntityManagerDebug = new EntityManager.EntityManagerDebug(EntityManager); 57 | 58 | // Many ECS tests will only pass if the Jobs Debugger enabled; 59 | // force it enabled for all tests, and restore the original value at teardown. 60 | JobsDebuggerWasEnabled = JobsUtility.JobDebuggerEnabled; 61 | JobsUtility.JobDebuggerEnabled = true; 62 | #if !UNITY_DOTSRUNTIME 63 | //JobsUtility.ClearSystemIds(); 64 | #endif 65 | 66 | World.GetOrCreateSystem(); 67 | World.GetOrCreateSystem(); 68 | World.GetOrCreateSystem(); 69 | World.GetOrCreateSystem(); 70 | World.GetOrCreateSystem(); 71 | } 72 | 73 | [TearDown] 74 | public override void TearDown() 75 | { 76 | if (World != null && World.IsCreated) 77 | { 78 | // Clean up systems before calling CheckInternalConsistency because we might have filters etc 79 | // holding on SharedComponentData making checks fail 80 | while (World.Systems.Count > 0) 81 | { 82 | World.DestroySystemManaged(World.Systems[0]); 83 | } 84 | 85 | EntityManagerDebug.CheckInternalConsistency(); 86 | 87 | World.Dispose(); 88 | World = null; 89 | 90 | World.DefaultGameObjectInjectionWorld = PreviousWorld; 91 | PreviousWorld = null; 92 | EntityManager = default; 93 | } 94 | 95 | JobsUtility.JobDebuggerEnabled = JobsDebuggerWasEnabled; 96 | #if !UNITY_DOTSRUNTIME 97 | //JobsUtility.ClearSystemIds(); 98 | #endif 99 | 100 | #if !UNITY_DOTSRUNTIME 101 | PlayerLoop.SetPlayerLoop(m_PreviousPlayerLoop); 102 | #endif 103 | base.TearDown(); 104 | } 105 | 106 | public virtual Entity CreateEntity(int index, int version) 107 | { 108 | return new Entity { Index = index, Version = version }; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /Runtime/SpatialHashingSystem.Debug.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Unity.Collections; 3 | using Unity.Mathematics; 4 | using UnityEngine; 5 | 6 | namespace HMH.ECS.SpatialHashing 7 | { 8 | public abstract partial class SpatialHashingSystem 9 | { 10 | #if UNITY_EDITOR 11 | 12 | #region Variables 13 | 14 | public float RefreshTime = 0.2F; 15 | public bool ShowRay; 16 | public Ray DebugRay; 17 | public bool ShowLink; 18 | 19 | private float _timeLastRefresh = -99F; 20 | private Dictionary> _links = new Dictionary>(); 21 | private List _voxelTraversed = new List(); 22 | private VoxelRay _voxelRay; 23 | private RayDebug _rayDebug; 24 | 25 | #endregion 26 | 27 | public void EnableDebug() 28 | { 29 | _rayDebug.SpatialHash = _spatialHash; 30 | _rayDebug.VoxelTraversed = _voxelTraversed; 31 | } 32 | 33 | public void OnDrawGizmos() 34 | { 35 | if (Application.isPlaying == false || _spatialHash.IsCreated == false) 36 | return; 37 | 38 | if (UnityEngine.Time.realtimeSinceStartup - _timeLastRefresh > RefreshTime) 39 | RefreshLinks(); 40 | 41 | foreach (var l in _links) 42 | { 43 | DrawCell(l.Key); 44 | 45 | if (ShowLink) 46 | foreach (var item in l.Value) 47 | DrawLink(l.Key, item); 48 | } 49 | 50 | if (ShowRay) 51 | { 52 | var startRayPosition = DebugRay.origin; 53 | var startCellBound = new Bounds(_spatialHash.GetPositionVoxel(_spatialHash.GetIndexVoxel(startRayPosition), true), _spatialHash.CellSize); 54 | 55 | var raySize = math.max(_spatialHash.WorldBounds.Size.x, math.max(_spatialHash.WorldBounds.Size.y, _spatialHash.WorldBounds.Size.z)); 56 | 57 | Gizmos.color = Color.yellow; 58 | Gizmos.DrawLine(startRayPosition, DebugRay.GetPoint(raySize)); 59 | 60 | T hit = default; 61 | 62 | if (_spatialHash.RayCast(DebugRay, ref hit)) 63 | { 64 | Gizmos.color = Color.blue; 65 | Gizmos.DrawCube(hit.GetCenter(), hit.GetSize()); 66 | } 67 | 68 | _voxelTraversed.Clear(); 69 | 70 | _voxelRay.RayCast(ref _rayDebug, DebugRay.origin, DebugRay.direction, raySize); 71 | Gizmos.color = new Color(0.88F, 0.6F, 0.1F, 0.4F); 72 | 73 | foreach (var voxel in _voxelTraversed) 74 | { 75 | var position = _spatialHash.GetPositionVoxel(voxel, true); 76 | Gizmos.DrawCube(position, _spatialHash.CellSize); 77 | } 78 | 79 | var rayOffsetted = new Ray(DebugRay.origin - (Vector3)(DebugRay.direction * _spatialHash.CellSize), DebugRay.direction); 80 | startCellBound.GetEnterPositionAABB(rayOffsetted, 1 << 25, out var enterPoint); 81 | Gizmos.color = Color.white; 82 | Gizmos.DrawCube(enterPoint, Vector3.one * 0.3F); 83 | 84 | startCellBound.GetExitPosition(rayOffsetted, 1 << 25, out var exitPoint); 85 | Gizmos.color = Color.white; 86 | Gizmos.DrawCube(exitPoint, Vector3.one * 0.3F); 87 | } 88 | } 89 | 90 | private void RefreshLinks() 91 | { 92 | _timeLastRefresh = UnityEngine.Time.realtimeSinceStartup; 93 | _links.Clear(); 94 | 95 | var unic = new HashSet(); 96 | var values = _spatialHash.DebugBuckets.GetValueArray(Allocator.TempJob); 97 | 98 | foreach (var itemTest in values) 99 | unic.Add(_spatialHash.DebugIDToItem[itemTest]); 100 | values.Dispose(); 101 | 102 | var list = new NativeList(Allocator.Temp); 103 | 104 | foreach (var item in unic) 105 | { 106 | list.Clear(); 107 | _spatialHash.GetIndexiesVoxel(item, list); 108 | 109 | foreach (var index in list) 110 | { 111 | if (_links.TryGetValue(index, out var l) == false) 112 | { 113 | l = new List(); 114 | _links.Add(index, l); 115 | } 116 | 117 | l.Add(item); 118 | } 119 | } 120 | 121 | list.Dispose(); 122 | } 123 | 124 | private void DrawCell(int3 index) 125 | { 126 | var position = _spatialHash.GetPositionVoxel(index, true); 127 | Gizmos.color = new Color(0F, 1F, 0F, 0.3F); 128 | Gizmos.DrawCube(position, _spatialHash.CellSize); 129 | Gizmos.color = Color.black; 130 | Gizmos.DrawWireCube(position, _spatialHash.CellSize); 131 | } 132 | 133 | private void DrawLink(int3 cellIndex, T target) 134 | { 135 | var position = _spatialHash.GetPositionVoxel(cellIndex, true); 136 | 137 | Gizmos.color = Color.red; 138 | Gizmos.DrawLine(position, target.GetCenter()); 139 | } 140 | 141 | #endif 142 | 143 | private struct RayDebug : IRay 144 | { 145 | public SpatialHash SpatialHash; 146 | public List VoxelTraversed; 147 | 148 | #region Implementation of IRay 149 | 150 | /// 151 | public bool OnTraversingVoxel(int3 voxelIndex) 152 | { 153 | VoxelTraversed.Add(voxelIndex); 154 | 155 | return false; 156 | } 157 | 158 | /// 159 | public int3 GetIndexVoxel(float3 position) 160 | { 161 | return SpatialHash.GetIndexVoxel(position); 162 | } 163 | 164 | /// 165 | public float3 GetPositionVoxel(int3 index, bool center) 166 | { 167 | return SpatialHash.GetPositionVoxel(index, center); 168 | } 169 | 170 | /// 171 | public float3 CellSize => SpatialHash.CellSize; 172 | 173 | #endregion 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /Editor/OldDebug/HashMapVisualOBBDebug.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using Unity.Mathematics; 3 | using UnityEngine; 4 | using Random = UnityEngine.Random; 5 | 6 | namespace HMH.ECS.SpatialHashing.Debug 7 | { 8 | public class HashMapVisualOBBDebug : MonoBehaviour 9 | { 10 | private void Start() 11 | { 12 | Random.InitState(123456789); 13 | 14 | var copy = new Bounds(_worldBounds.Center, _worldBounds.Size); 15 | _spatialHashing = new SpatialHash(copy, new float3(10F), Allocator.Persistent); 16 | 17 | } 18 | 19 | private void OnDrawGizmos() 20 | { 21 | if (Application.isPlaying == false) 22 | return; 23 | 24 | var currentPosition = transform.position; 25 | var targetBounds = new Bounds(currentPosition, _boundSize); 26 | 27 | var transformRotation = transform.rotation; 28 | var bounds2 = SpatialHash.TransformBounds(in targetBounds, transformRotation); 29 | bounds2.Clamp(_spatialHashing.WorldBounds); 30 | 31 | 32 | //********************** ligne raycast 33 | var startPosition = start.transform.position; 34 | Ray r = new Ray(startPosition, end.transform.position - startPosition); 35 | 36 | var rr = new Ray(math.mul(math.inverse(transformRotation), (startPosition - currentPosition)) + (float3)currentPosition, 37 | math.mul(math.inverse(transformRotation), r.direction)); 38 | 39 | if (targetBounds.RayCastOBB(r.origin, r.direction, transformRotation, out var pp, math.length(end.transform.position - start.transform.position))) 40 | Gizmos.color = Color.yellow; 41 | else 42 | Gizmos.color = Color.red; 43 | 44 | Gizmos.DrawLine(start.transform.position, end.transform.position); 45 | 46 | //local recast 47 | Gizmos.color = Color.black; 48 | 49 | var localRotation = math.inverse(transformRotation); 50 | var origin = math.mul(localRotation, ((float3)r.origin - targetBounds.Center)) + targetBounds.Center; 51 | var directionNormalized = math.mul(localRotation, r.direction); 52 | Gizmos.DrawLine(origin, origin + directionNormalized * (math.length(start.transform.position - end.transform.position))); 53 | 54 | //*********************** interception point 55 | 56 | Gizmos.color = Color.cyan; 57 | Gizmos.DrawCube(pp, new Vector3(1F, 1F, 1F)); 58 | 59 | 60 | 61 | //************ Debug 62 | var list = new NativeList(20, Allocator.Temp); 63 | _spatialHashing.Query(targetBounds, transformRotation, list); 64 | 65 | 66 | 67 | 68 | int3 cell = list[indexToDraw]; //new int3(9, 14, 16); 69 | 70 | var bounds = SpatialHash.TransformBounds(in targetBounds, transformRotation); 71 | bounds.Clamp(_spatialHashing.WorldBounds); 72 | 73 | targetBounds.Size += _spatialHashing.CellSize; 74 | var pos = _spatialHashing.GetPositionVoxel(cell, true); 75 | 76 | var r0 = new Ray(pos-new float3(_spatialHashing.CellSize.x*0.5F,0F,0F), Vector3.right); 77 | 78 | var r0Y = new Ray( pos-new float3(0F,_spatialHashing.CellSize.y*0.5F,0F), Vector3.up); 79 | 80 | var r0Z = new Ray(pos-new float3(0F,0F,_spatialHashing.CellSize.z*0.5F), Vector3.forward); 81 | 82 | UnityEngine.Debug.Log("index " + list[indexToDraw]); 83 | Color c; 84 | 85 | if (targetBounds.RayCastOBB(r0.origin, r0.direction, transformRotation, out var px,_spatialHashing.CellSize.x)) 86 | { 87 | Gizmos.color = Color.cyan; 88 | Gizmos.DrawCube(px, new Vector3(1F, 1F, 1F)); 89 | } 90 | 91 | if (targetBounds.RayCastOBB(r0Y.origin, r0Y.direction, transformRotation, out var py,_spatialHashing.CellSize.y)) 92 | { 93 | Gizmos.color = Color.yellow; 94 | Gizmos.DrawCube(py, new Vector3(1F, 1F, 1F)); 95 | } 96 | 97 | if (targetBounds.RayCastOBB(r0Z.origin, r0Z.direction, transformRotation, out var pz,_spatialHashing.CellSize.z)) 98 | { 99 | Gizmos.color = Color.black; 100 | Gizmos.DrawCube(pz, new Vector3(1F, 1F, 1F)); 101 | } 102 | 103 | if (targetBounds.RayCastOBB(r0.origin, r0.direction, transformRotation,_spatialHashing.CellSize.x) && targetBounds.RayCastOBB(r0Y.origin, r0Y.direction, transformRotation,_spatialHashing.CellSize.y) && targetBounds.RayCastOBB(r0Z.origin, r0Z.direction, transformRotation,_spatialHashing.CellSize.z)) 104 | c = new Color(0F, 1F, 0F, 0.3F); 105 | else 106 | c = Gizmos.color = Color.red; 107 | 108 | DrawCell(cell, c); 109 | 110 | Gizmos.color = Color.yellow; 111 | Gizmos.DrawLine(r0.origin, r0.GetPoint(_spatialHashing.CellSize.x)); 112 | Gizmos.DrawLine(r0Y.origin, r0Y.GetPoint(_spatialHashing.CellSize.y)); 113 | Gizmos.DrawLine(r0Z.origin, r0Z.GetPoint(_spatialHashing.CellSize.z)); 114 | 115 | for (int i = 0; i < list.Length; i++) 116 | DrawCell(list[i], new Color(0F, 1F, 0F, 0.3F)); 117 | 118 | list.Dispose(); 119 | 120 | Gizmos.matrix = transform.localToWorldMatrix; 121 | Gizmos.color = new Color(0F, 0.5F, 0.5F, 0.3F); 122 | Gizmos.DrawCube(Vector3.zero, _boundSize); 123 | 124 | Gizmos.color = new Color(0.2F, 0.5F, 0.95F, 0.3F); 125 | Gizmos.DrawCube(Vector3.zero, _boundSize+_spatialHashing.CellSize); 126 | } 127 | 128 | public int indexToDraw; 129 | 130 | private void DrawCell(int3 index, Color c) 131 | { 132 | var position = _spatialHashing.GetPositionVoxel(index, true); 133 | Gizmos.color = c; 134 | Gizmos.DrawCube(position, _spatialHashing.CellSize); 135 | Gizmos.color = Color.black; 136 | Gizmos.DrawWireCube(position, _spatialHashing.CellSize); 137 | } 138 | 139 | #region Variables 140 | 141 | [SerializeField] 142 | private Bounds _worldBounds; 143 | [SerializeField] 144 | private float3 _boundSize = new float3(5F); 145 | public GameObject start; 146 | public GameObject end; 147 | private SpatialHash _spatialHashing; 148 | 149 | #endregion 150 | } 151 | } -------------------------------------------------------------------------------- /Runtime/VoxelRay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Mathematics; 3 | using UnityEngine.Assertions; 4 | 5 | namespace HMH.ECS.SpatialHashing 6 | { 7 | /// 8 | /// Ray for ray casting inside a voxel world. Each voxel is considered as a cube within this ray. A ray consists of a starting position, a direction and a length. 9 | /// Adaptation from https://www.gamedev.net/blogs/entry/2265248-voxel-traversal-algorithm-ray-casting/ 10 | /// 11 | public struct VoxelRay where T : IRay 12 | { 13 | /** 14 | * Casts the ray from its starting position towards its direction whilst keeping in mind its length. A lambda parameter is supplied and called each time a voxel is traversed. 15 | * This allows the lambda to stop anytime the algorithm to continue its loop. 16 | * 17 | * This method is local because the parameter voxelIndex is locally changed to avoid creating a new instance of {@link Vector3i}. 18 | * 19 | * @param voxelHalfExtent The half extent (radius) of a voxel. 20 | * @param onTraversingVoxel The operation to execute when traversing a voxel. This method called the same number of times as the value of {@link #getVoxelDistance()}. The 21 | * supplied {@link Vector3i} parameter is not a new instance but a local instance, so it is a reference. The return value {@link Boolean} defines if 22 | * the algorithm should stop. 23 | * @param voxelIndex The voxel index to locally modify in order to traverse voxels. This parameter exists simply to avoid creating a new {@link Vector3i} instance. 24 | * 25 | * @see Axel Traversal Algorithm 26 | */ 27 | public bool RayCast(ref T asker, float3 start, float3 direction, float length) 28 | { 29 | if (math.any(math.isnan(direction))) 30 | return true; 31 | 32 | Assert.IsTrue(Math.Abs(math.length(direction) - 1F) < 0.00001F); 33 | 34 | var currentVoxel = asker.GetIndexVoxel(start); 35 | 36 | var voxelDistance = ComputeVoxelDistance(ref asker, start + direction * length, currentVoxel); 37 | 38 | // In which direction the voxel ids are incremented. 39 | var directionStep = GetSignZeroPositive(direction); 40 | 41 | // Distance along the ray to the next voxel border from the current position (max.x, max.y, max.z). 42 | var nextVoxelBoundary = asker.GetPositionVoxel(currentVoxel + GetNegativeSign(directionStep) + 1, false); 43 | 44 | // distance until next intersection with voxel-border 45 | // the value of t at which the ray crosses the first vertical voxel boundary 46 | var max = new float3 47 | { 48 | x = math.abs(direction.x) > 0.00001F ? (nextVoxelBoundary.x - start.x) / direction.x : float.MaxValue, 49 | y = math.abs(direction.y) > 0.00001F ? (nextVoxelBoundary.y - start.y) / direction.y : float.MaxValue, 50 | z = math.abs(direction.z) > 0.00001F ? (nextVoxelBoundary.z - start.z) / direction.z : float.MaxValue 51 | }; 52 | 53 | // how far along the ray we must move for the horizontal component to equal the width of a voxel 54 | // the direction in which we traverse the grid 55 | // can only be FLT_MAX if we never go in that direction 56 | var delta = new float3 57 | { 58 | x = math.abs(direction.x) > 0.00001F ? directionStep.x * asker.CellSize.x / direction.x : float.MaxValue, 59 | y = math.abs(direction.y) > 0.00001F ? directionStep.y * asker.CellSize.y / direction.y : float.MaxValue, 60 | z = math.abs(direction.z) > 0.00001F ? directionStep.z * asker.CellSize.z / direction.z : float.MaxValue 61 | }; 62 | 63 | if (asker.OnTraversingVoxel(currentVoxel)) 64 | return true; 65 | 66 | int traversedVoxelCount = 0; 67 | 68 | while (++traversedVoxelCount < voxelDistance) 69 | { 70 | if (max.x < max.y && max.x < max.z) 71 | { 72 | currentVoxel.x += directionStep.x; 73 | max.x += delta.x; 74 | } 75 | else if (max.y < max.z) 76 | { 77 | currentVoxel.y += directionStep.y; 78 | max.y += delta.y; 79 | } 80 | else 81 | { 82 | currentVoxel.z += directionStep.z; 83 | max.z += delta.z; 84 | } 85 | 86 | if (asker.OnTraversingVoxel(currentVoxel)) 87 | return true; 88 | } 89 | 90 | return false; 91 | } 92 | 93 | /** 94 | * Computes the voxel distance, a.k.a. the number of voxel to traverse, for the ray cast. 95 | * 96 | * @param voxelExtent The extent of a voxel, which is the equivalent for a cube of a sphere's radius. 97 | * @param startIndex The starting position's index. 98 | */ 99 | private int ComputeVoxelDistance(ref T asker, float3 end, int3 startIndex) 100 | { 101 | return 1 + math.abs(asker.GetIndexVoxel(end) - startIndex).Sum(); 102 | } 103 | 104 | /// 105 | /// Gets the sign of the supplied number. The method being "zero position" means that the sign of zero is 1. 106 | /// 107 | public static int3 GetSignZeroPositive(float3 number) 108 | { 109 | return GetNegativeSign(number) | 1; 110 | } 111 | 112 | /// 113 | /// Gets the negative sign of the supplied number. So, in other words, if the number is negative, -1 is returned but if the number is positive or zero, then zero is returned. 114 | /// 115 | /// 116 | /// 117 | public static int3 GetNegativeSign(float3 number) 118 | { 119 | return new int3(math.asint(number.x) >> (32 - 1), 120 | math.asint(number.y) >> (32 - 1), 121 | math.asint(number.z) >> (32 - 1)); //float are always 32bit in c# and -1 for sign bit which is at position 31 122 | } 123 | 124 | /// 125 | /// Gets the negative sign of the supplied number. So, in other words, if the number is negative, -1 is returned but if the number is positive or zero, then zero is returned. 126 | /// 127 | /// 128 | /// 129 | public static int3 GetNegativeSign(int3 number) 130 | { 131 | return new int3(math.asint(number.x) >> (32 - 1), 132 | math.asint(number.y) >> (32 - 1), 133 | math.asint(number.z) >> (32 - 1)); //int are always 32bit in c# and -1 for sign bit which is at position 31 134 | } 135 | 136 | } 137 | 138 | public interface IRay 139 | { 140 | /// 141 | /// The operation to execute when traversing a voxel.The return value defines if the algorithm should stop. 142 | /// 143 | /// 144 | /// 145 | bool OnTraversingVoxel(int3 voxelIndex); 146 | 147 | int3 GetIndexVoxel(float3 position); 148 | float3 GetPositionVoxel(int3 index, bool center); 149 | float3 CellSize { get; } 150 | } 151 | } -------------------------------------------------------------------------------- /Tests/SpatialHashTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Unity.Collections; 3 | using Unity.Entities; 4 | using Unity.Mathematics; 5 | 6 | namespace HMH.ECS.SpatialHashing.Test 7 | { 8 | public class SpatialHashTest 9 | { 10 | [Test] 11 | public void SpatialHashSimpleAdd() 12 | { 13 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), new float3(1F), 15, Allocator.Temp); 14 | 15 | var item = new Item { Center = new float3(5.5F), Size = new float3(1F) }; 16 | sh.Add(ref item); 17 | 18 | Assert.AreEqual(1, sh.ItemCount); 19 | Assert.AreEqual(1, sh.BucketItemCount); 20 | sh.Dispose(); 21 | } 22 | 23 | [Test] 24 | public void SpatialHashAdd() 25 | { 26 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), new float3(1F), 15, Allocator.Temp); 27 | 28 | var item = new Item { Center = new float3(5.5F), Size = new float3(1.1F) }; 29 | sh.Add(ref item); 30 | 31 | Assert.AreEqual(1, sh.ItemCount); 32 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 33 | sh.Dispose(); 34 | } 35 | 36 | [Test] 37 | public void SpatialHashAddOverWorld() 38 | { 39 | var worldSize = new float3(30); 40 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), worldSize), new float3(1F), 1, Allocator.Temp); 41 | 42 | var item = new Item { Center = new float3(15F), Size = worldSize + new float3(10F) }; 43 | sh.Add(ref item); 44 | 45 | Assert.AreEqual(1, sh.ItemCount); 46 | Assert.AreEqual(worldSize.x * worldSize.y * worldSize.z, sh.BucketItemCount); 47 | sh.Dispose(); 48 | } 49 | 50 | [Test] 51 | public void SpatialHashSimpleRemove() 52 | { 53 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), new float3(1F), 15, Allocator.Temp); 54 | 55 | var item = new Item { Center = new float3(5.5F), Size = new float3(1F) }; 56 | sh.Add(ref item); 57 | 58 | Assert.AreEqual(1, sh.ItemCount); 59 | Assert.AreEqual(1, sh.BucketItemCount); 60 | 61 | sh.Remove(item.SpatialHashingIndex); 62 | 63 | Assert.AreEqual(0, sh.ItemCount); 64 | Assert.AreEqual(0, sh.BucketItemCount); 65 | 66 | sh.Dispose(); 67 | } 68 | 69 | [Test] 70 | public void SpatialHashRemove() 71 | { 72 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), new float3(1F), 15, Allocator.Temp); 73 | 74 | var item = new Item { Center = new float3(5.5F), Size = new float3(1.1F) }; 75 | sh.Add(ref item); 76 | 77 | Assert.AreEqual(1, sh.ItemCount); 78 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 79 | 80 | sh.Remove(item.SpatialHashingIndex); 81 | 82 | Assert.AreEqual(0, sh.ItemCount); 83 | Assert.AreEqual(0, sh.BucketItemCount); 84 | 85 | sh.Dispose(); 86 | } 87 | 88 | [Test] 89 | public void SpatialHashSimpleQuerry() 90 | { 91 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), new float3(1F), 15, Allocator.Temp); 92 | 93 | var item = new Item { Center = new float3(5.5F), Size = new float3(1.1F) }; 94 | sh.Add(ref item); 95 | 96 | Assert.AreEqual(1, sh.ItemCount); 97 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 98 | 99 | var querryBound = new Bounds(5.5F, 0.1F); 100 | var results = new NativeList(5, Allocator.TempJob); 101 | sh.Query(querryBound, results); 102 | 103 | Assert.AreEqual(1, results.Length); 104 | Assert.AreEqual(item, results[0]); 105 | 106 | //check clear result 107 | results.Dispose(); 108 | sh.Dispose(); 109 | } 110 | 111 | [Test] 112 | public void SpatialHashOverWorldQuerry() 113 | { 114 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), new float3(1F), 15, Allocator.Temp); 115 | 116 | var item = new Item { Center = new float3(5.5F), Size = new float3(1.1F) }; 117 | sh.Add(ref item); 118 | 119 | Assert.AreEqual(1, sh.ItemCount); 120 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 121 | 122 | var querryBound = new Bounds(15F, 50F); 123 | var results = new NativeList(5, Allocator.TempJob); 124 | sh.Query(querryBound, results); 125 | 126 | Assert.AreEqual(1, results.Length); 127 | Assert.AreEqual(item, results[0]); 128 | 129 | //check clear result 130 | results.Dispose(); 131 | sh.Dispose(); 132 | } 133 | 134 | [Test] 135 | public void SpatialHashQuerry() 136 | { 137 | var cellSize = new float3(1F); 138 | SpatialHash sh = new SpatialHash(new Bounds(new float3(15F), new float3(30F)), cellSize, 15, Allocator.Temp); 139 | 140 | var item = new Item { Center = new float3(5.5F), Size = new float3(1.1F) }; 141 | sh.Add(ref item); 142 | 143 | Assert.AreEqual(1, sh.ItemCount); 144 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 145 | 146 | var results = new NativeList(5, Allocator.TempJob); 147 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 148 | 149 | sh.CalculStartEndIteration(bounds, out var start, out var end); 150 | 151 | var hashPosition = new int3(0F); 152 | 153 | for (int x = start.x; x < end.x; ++x) 154 | { 155 | hashPosition.x = x; 156 | 157 | for (int y = start.y; y < end.y; ++y) 158 | { 159 | hashPosition.y = y; 160 | 161 | for (int z = start.z; z < end.z; ++z) 162 | { 163 | hashPosition.z = z; 164 | 165 | var querryBound = new Bounds(sh.GetPositionVoxel(hashPosition, true), cellSize * 0.95F); 166 | 167 | results.Clear(); 168 | sh.Query(querryBound, results); 169 | 170 | Assert.AreEqual(1, results.Length); 171 | Assert.AreEqual(item, results[0]); 172 | } 173 | } 174 | } 175 | 176 | //check clear result 177 | results.Dispose(); 178 | sh.Dispose(); 179 | } 180 | } 181 | 182 | public struct Item : ISpatialHashingItem, IComponentData 183 | { 184 | public float3 Center; 185 | public float3 Size; 186 | 187 | #region Implementation of IEquatable 188 | 189 | /// 190 | public bool Equals(Item other) 191 | { 192 | return math.all(Center == other.Center) && math.all(Size == other.Size); 193 | } 194 | 195 | #region Overrides of ValueType 196 | 197 | /// 198 | public override int GetHashCode() 199 | { 200 | return Center.GetHashCode(); 201 | } 202 | 203 | #endregion 204 | 205 | #endregion 206 | 207 | #region Implementation of ISpacialHasingItem 208 | 209 | public float3 GetCenter() 210 | { 211 | return Center; 212 | } 213 | 214 | public float3 GetSize() 215 | { 216 | return Size; 217 | } 218 | 219 | /// 220 | public int SpatialHashingIndex { get; set; } 221 | 222 | #endregion 223 | } 224 | } -------------------------------------------------------------------------------- /Editor/OldDebug/OBB Debug.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 3 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 0} 41 | m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 11 47 | m_GIWorkflowMode: 1 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 0 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 12 58 | m_Resolution: 2 59 | m_BakeResolution: 40 60 | m_AtlasSize: 1024 61 | m_AO: 0 62 | m_AOMaxDistance: 1 63 | m_CompAOExponent: 1 64 | m_CompAOExponentDirect: 0 65 | m_ExtractAmbientOcclusion: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 512 79 | m_PVRBounces: 2 80 | m_PVREnvironmentSampleCount: 256 81 | m_PVREnvironmentReferencePointCount: 2048 82 | m_PVRFilteringMode: 1 83 | m_PVRDenoiserTypeDirect: 1 84 | m_PVRDenoiserTypeIndirect: 1 85 | m_PVRDenoiserTypeAO: 1 86 | m_PVRFilterTypeDirect: 0 87 | m_PVRFilterTypeIndirect: 0 88 | m_PVRFilterTypeAO: 0 89 | m_PVREnvironmentMIS: 1 90 | m_PVRCulling: 1 91 | m_PVRFilteringGaussRadiusDirect: 1 92 | m_PVRFilteringGaussRadiusIndirect: 5 93 | m_PVRFilteringGaussRadiusAO: 2 94 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 95 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 96 | m_PVRFilteringAtrousPositionSigmaAO: 1 97 | m_ShowResolutionOverlay: 1 98 | m_ExportTrainingData: 0 99 | m_LightingDataAsset: {fileID: 0} 100 | m_UseShadowmask: 1 101 | --- !u!196 &4 102 | NavMeshSettings: 103 | serializedVersion: 2 104 | m_ObjectHideFlags: 0 105 | m_BuildSettings: 106 | serializedVersion: 2 107 | agentTypeID: 0 108 | agentRadius: 0.5 109 | agentHeight: 2 110 | agentSlope: 45 111 | agentClimb: 0.4 112 | ledgeDropHeight: 0 113 | maxJumpAcrossDistance: 0 114 | minRegionArea: 2 115 | manualCellSize: 0 116 | cellSize: 0.16666667 117 | manualTileSize: 0 118 | tileSize: 256 119 | accuratePlacement: 0 120 | debug: 121 | m_Flags: 0 122 | m_NavMeshData: {fileID: 0} 123 | --- !u!1 &318993937 124 | GameObject: 125 | m_ObjectHideFlags: 0 126 | m_CorrespondingSourceObject: {fileID: 0} 127 | m_PrefabInstance: {fileID: 0} 128 | m_PrefabAsset: {fileID: 0} 129 | serializedVersion: 6 130 | m_Component: 131 | - component: {fileID: 318993938} 132 | m_Layer: 0 133 | m_Name: End 134 | m_TagString: Untagged 135 | m_Icon: {fileID: 0} 136 | m_NavMeshLayer: 0 137 | m_StaticEditorFlags: 0 138 | m_IsActive: 1 139 | --- !u!4 &318993938 140 | Transform: 141 | m_ObjectHideFlags: 0 142 | m_CorrespondingSourceObject: {fileID: 0} 143 | m_PrefabInstance: {fileID: 0} 144 | m_PrefabAsset: {fileID: 0} 145 | m_GameObject: {fileID: 318993937} 146 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 147 | m_LocalPosition: {x: 69.5, y: 0, z: 0} 148 | m_LocalScale: {x: 1, y: 1, z: 1} 149 | m_Children: [] 150 | m_Father: {fileID: 952617784} 151 | m_RootOrder: 0 152 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 153 | --- !u!1 &952617783 154 | GameObject: 155 | m_ObjectHideFlags: 0 156 | m_CorrespondingSourceObject: {fileID: 0} 157 | m_PrefabInstance: {fileID: 0} 158 | m_PrefabAsset: {fileID: 0} 159 | serializedVersion: 6 160 | m_Component: 161 | - component: {fileID: 952617784} 162 | m_Layer: 0 163 | m_Name: Start 164 | m_TagString: Untagged 165 | m_Icon: {fileID: 0} 166 | m_NavMeshLayer: 0 167 | m_StaticEditorFlags: 0 168 | m_IsActive: 1 169 | --- !u!4 &952617784 170 | Transform: 171 | m_ObjectHideFlags: 0 172 | m_CorrespondingSourceObject: {fileID: 0} 173 | m_PrefabInstance: {fileID: 0} 174 | m_PrefabAsset: {fileID: 0} 175 | m_GameObject: {fileID: 952617783} 176 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 177 | m_LocalPosition: {x: -55, y: -5, z: 15} 178 | m_LocalScale: {x: 1, y: 1, z: 1} 179 | m_Children: 180 | - {fileID: 318993938} 181 | m_Father: {fileID: 0} 182 | m_RootOrder: 2 183 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 184 | --- !u!1 &1631769917 185 | GameObject: 186 | m_ObjectHideFlags: 0 187 | m_CorrespondingSourceObject: {fileID: 0} 188 | m_PrefabInstance: {fileID: 0} 189 | m_PrefabAsset: {fileID: 0} 190 | serializedVersion: 6 191 | m_Component: 192 | - component: {fileID: 1631769919} 193 | - component: {fileID: 1631769918} 194 | m_Layer: 0 195 | m_Name: OBB Debug 196 | m_TagString: Untagged 197 | m_Icon: {fileID: 0} 198 | m_NavMeshLayer: 0 199 | m_StaticEditorFlags: 0 200 | m_IsActive: 1 201 | --- !u!114 &1631769918 202 | MonoBehaviour: 203 | m_ObjectHideFlags: 0 204 | m_CorrespondingSourceObject: {fileID: 0} 205 | m_PrefabInstance: {fileID: 0} 206 | m_PrefabAsset: {fileID: 0} 207 | m_GameObject: {fileID: 1631769917} 208 | m_Enabled: 1 209 | m_EditorHideFlags: 0 210 | m_Script: {fileID: 11500000, guid: 53c458d06068e1f418aacf004d55db61, type: 3} 211 | m_Name: 212 | m_EditorClassIdentifier: 213 | indexToDraw: 0 214 | _worldBounds: 215 | _center: 216 | x: 0 217 | y: 0 218 | z: 0 219 | _extents: 220 | x: 500 221 | y: 500 222 | z: 500 223 | _boundSize: 224 | x: 103 225 | y: 103 226 | z: 20 227 | start: {fileID: 952617783} 228 | end: {fileID: 318993937} 229 | --- !u!4 &1631769919 230 | Transform: 231 | m_ObjectHideFlags: 0 232 | m_CorrespondingSourceObject: {fileID: 0} 233 | m_PrefabInstance: {fileID: 0} 234 | m_PrefabAsset: {fileID: 0} 235 | m_GameObject: {fileID: 1631769917} 236 | m_LocalRotation: {x: 0, y: 0, z: 0.38268343, w: 0.92387956} 237 | m_LocalPosition: {x: 0, y: 0, z: 0} 238 | m_LocalScale: {x: 1, y: 1, z: 1} 239 | m_Children: [] 240 | m_Father: {fileID: 0} 241 | m_RootOrder: 1 242 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 45} 243 | --- !u!1 &1825070958 244 | GameObject: 245 | m_ObjectHideFlags: 0 246 | m_CorrespondingSourceObject: {fileID: 0} 247 | m_PrefabInstance: {fileID: 0} 248 | m_PrefabAsset: {fileID: 0} 249 | serializedVersion: 6 250 | m_Component: 251 | - component: {fileID: 1825070961} 252 | - component: {fileID: 1825070960} 253 | - component: {fileID: 1825070959} 254 | m_Layer: 0 255 | m_Name: Main Camera 256 | m_TagString: MainCamera 257 | m_Icon: {fileID: 0} 258 | m_NavMeshLayer: 0 259 | m_StaticEditorFlags: 0 260 | m_IsActive: 1 261 | --- !u!81 &1825070959 262 | AudioListener: 263 | m_ObjectHideFlags: 0 264 | m_CorrespondingSourceObject: {fileID: 0} 265 | m_PrefabInstance: {fileID: 0} 266 | m_PrefabAsset: {fileID: 0} 267 | m_GameObject: {fileID: 1825070958} 268 | m_Enabled: 1 269 | --- !u!20 &1825070960 270 | Camera: 271 | m_ObjectHideFlags: 0 272 | m_CorrespondingSourceObject: {fileID: 0} 273 | m_PrefabInstance: {fileID: 0} 274 | m_PrefabAsset: {fileID: 0} 275 | m_GameObject: {fileID: 1825070958} 276 | m_Enabled: 1 277 | serializedVersion: 2 278 | m_ClearFlags: 1 279 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 280 | m_projectionMatrixMode: 1 281 | m_GateFitMode: 2 282 | m_FOVAxisMode: 0 283 | m_SensorSize: {x: 36, y: 24} 284 | m_LensShift: {x: 0, y: 0} 285 | m_FocalLength: 50 286 | m_NormalizedViewPortRect: 287 | serializedVersion: 2 288 | x: 0 289 | y: 0 290 | width: 1 291 | height: 1 292 | near clip plane: 0.3 293 | far clip plane: 1000 294 | field of view: 60 295 | orthographic: 1 296 | orthographic size: 5 297 | m_Depth: -1 298 | m_CullingMask: 299 | serializedVersion: 2 300 | m_Bits: 4294967295 301 | m_RenderingPath: -1 302 | m_TargetTexture: {fileID: 0} 303 | m_TargetDisplay: 0 304 | m_TargetEye: 3 305 | m_HDR: 1 306 | m_AllowMSAA: 1 307 | m_AllowDynamicResolution: 0 308 | m_ForceIntoRT: 0 309 | m_OcclusionCulling: 1 310 | m_StereoConvergence: 10 311 | m_StereoSeparation: 0.022 312 | --- !u!4 &1825070961 313 | Transform: 314 | m_ObjectHideFlags: 0 315 | m_CorrespondingSourceObject: {fileID: 0} 316 | m_PrefabInstance: {fileID: 0} 317 | m_PrefabAsset: {fileID: 0} 318 | m_GameObject: {fileID: 1825070958} 319 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 320 | m_LocalPosition: {x: 0, y: 0, z: -10} 321 | m_LocalScale: {x: 1, y: 1, z: 1} 322 | m_Children: [] 323 | m_Father: {fileID: 0} 324 | m_RootOrder: 0 325 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 326 | -------------------------------------------------------------------------------- /Runtime/Bounds.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using Unity.Mathematics; 4 | using UnityEngine; 5 | 6 | namespace HMH.ECS.SpatialHashing 7 | { 8 | [Serializable] 9 | public struct Bounds : IEquatable 10 | { 11 | public Bounds(float3 center, float3 size) 12 | { 13 | _center = center; 14 | _extents = size * 0.5f; 15 | } 16 | 17 | public void SetMinMax(float3 min, float3 max) 18 | { 19 | Extents = (max - min) * 0.5f; 20 | Center = min + Extents; 21 | } 22 | 23 | public void Encapsulate(float3 point) 24 | { 25 | SetMinMax(math.min(Min, point), math.max(Max, point)); 26 | } 27 | 28 | public void Encapsulate(Bounds bounds) 29 | { 30 | Encapsulate(bounds.Center - bounds.Extents); 31 | Encapsulate(bounds.Center + bounds.Extents); 32 | } 33 | 34 | public void Clamp(Bounds bounds) 35 | { 36 | var bMin = bounds.Min; 37 | var bMax = bounds.Max; 38 | SetMinMax(math.clamp(Min, bMin, bMax), math.clamp(Max, bMin, bMax)); 39 | } 40 | 41 | public void Expand(float amount) 42 | { 43 | amount *= 0.5f; 44 | Extents += new float3(amount, amount, amount); 45 | } 46 | 47 | public void Expand(float3 amount) 48 | { 49 | Extents += amount * 0.5f; 50 | } 51 | 52 | public bool Intersects(Bounds bounds) 53 | { 54 | return math.all(Min <= bounds.Max) && math.all(Max >= bounds.Min); 55 | } 56 | 57 | public int3 GetCellCount(float3 cellSize) 58 | { 59 | var min = Min; 60 | var max = Max; 61 | 62 | var diff = max - min; 63 | diff /= cellSize; 64 | 65 | return diff.CeilToInt(); 66 | } 67 | 68 | public bool RayCastOBB(Ray ray, quaternion worldRotation) 69 | { 70 | return RayCastOBB(ray.origin, ray.direction, worldRotation); 71 | } 72 | 73 | public bool RayCastOBB(float3 origin, float3 directionNormalized, quaternion worldRotation, float length =1<<25) 74 | { 75 | var localRoation = math.inverse(worldRotation); 76 | return RayCastOBBFast(origin, directionNormalized, localRoation,length); 77 | } 78 | 79 | public bool RayCastOBBFast(float3 origin, float3 directionNormalized, quaternion localRotation,float length=1<<25) 80 | { 81 | origin = math.mul(localRotation, origin-_center)+_center; 82 | directionNormalized = math.mul(localRotation, directionNormalized); 83 | 84 | return GetEnterPositionAABB(origin, directionNormalized, length); 85 | } 86 | 87 | public bool RayCastOBB(float3 origin, float3 directionNormalized, quaternion worldRotation, out float3 enterPoint, float length =1<<25) 88 | { 89 | var localRotation = math.inverse(worldRotation); 90 | origin = math.mul(localRotation, origin-_center)+_center; 91 | directionNormalized = math.mul(localRotation, directionNormalized); 92 | 93 | bool res = GetEnterPositionAABB(origin, directionNormalized, length, out enterPoint); 94 | enterPoint = math.mul(worldRotation, enterPoint); 95 | 96 | return res; 97 | } 98 | 99 | public bool GetEnterPositionAABB(Ray ray, float length, out float3 enterPoint) 100 | { 101 | return GetEnterPositionAABB(ray.origin, ray.direction, length, out enterPoint); 102 | } 103 | 104 | /// 105 | /// Find the intersection of a line from v0 to v1 and an axis-aligned bounding box http://www.youtube.com/watch?v=USjbg5QXk3g 106 | /// 107 | /// 108 | /// 109 | /// 110 | /// 111 | /// 112 | /// 113 | // 114 | public bool GetEnterPositionAABB(float3 origin, float3 direction, float length) 115 | { 116 | var start = origin + direction * length; 117 | 118 | float low = 0F; 119 | float high = 1F; 120 | 121 | return ClipLine(0, origin, start, ref low, ref high) && ClipLine(1, origin, start, ref low, ref high) && ClipLine(2, origin, start, ref low, ref high); 122 | } 123 | 124 | /// 125 | /// Find the intersection of a line from v0 to v1 and an axis-aligned bounding box http://www.youtube.com/watch?v=USjbg5QXk3g 126 | /// 127 | /// 128 | /// 129 | /// 130 | /// 131 | /// 132 | /// 133 | 134 | // 135 | public bool GetEnterPositionAABB(float3 origin, float3 direction, float length, out float3 enterPoint) 136 | { 137 | enterPoint = new float3(); 138 | var start = origin + direction * length; 139 | 140 | float low = 0F; 141 | float high = 1F; 142 | 143 | if (ClipLine(0, origin, start, ref low, ref high) == false || ClipLine(1, origin, start, ref low, ref high) == false || ClipLine(2, origin, start, ref low, ref high) == false) 144 | return false; 145 | 146 | // The formula for I: http://youtu.be/USjbg5QXk3g?t=6m24s 147 | var b = start - origin; 148 | enterPoint = origin + b * low; 149 | 150 | return true; 151 | } 152 | 153 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 154 | private bool ClipLine(int d, float3 v0, float3 v1,ref float low,ref float high) 155 | { 156 | // f_low and f_high are the results from all clipping so far. We'll write our results back out to those parameters. 157 | 158 | // f_dim_low and f_dim_high are the results we're calculating for this current dimension. 159 | // Find the point of intersection in this dimension only as a fraction of the total vector http://youtu.be/USjbg5QXk3g?t=3m12s 160 | var dimensionLow = (Min[d] - v0[d])/(v1[d] - v0[d]); 161 | var dimensionHigh = (Max[d] - v0[d])/(v1[d] - v0[d]); 162 | 163 | // Make sure low is less than high 164 | if (dimensionHigh < dimensionLow) 165 | { 166 | var tmp = dimensionHigh; 167 | dimensionHigh = dimensionLow; 168 | dimensionLow = tmp; 169 | } 170 | 171 | // If this dimension's high is less than the low we got then we definitely missed. http://youtu.be/USjbg5QXk3g?t=7m16s 172 | if (dimensionHigh < low) 173 | return false; 174 | 175 | // Likewise if the low is less than the high. 176 | if (dimensionLow > high) 177 | return false; 178 | 179 | // Add the clip from this dimension to the previous results http://youtu.be/USjbg5QXk3g?t=5m32s 180 | low = math.max(dimensionLow, low); 181 | high = math.min(dimensionHigh, high); 182 | 183 | if (low > high) 184 | return false; 185 | 186 | return true; 187 | } 188 | 189 | /// 190 | /// Return enter position of ray in this bound.Ray need to start outside of bound! 191 | /// 192 | /// 193 | /// 194 | /// 195 | public bool GetExitPosition(Ray ray, float length, out float3 exitPoint) 196 | { 197 | exitPoint = new float3(); 198 | 199 | var minBounds = Min; 200 | var maxBounds = Max; 201 | 202 | var rayProjectionLength = ray.direction * length; 203 | 204 | var minProjection = (minBounds - (float3)ray.origin) / rayProjectionLength; 205 | var maxProjection = (maxBounds - (float3)ray.origin) / rayProjectionLength; 206 | var temp = math.min(minProjection, maxProjection); 207 | maxProjection = math.max(minProjection, maxProjection); 208 | minProjection = temp; 209 | 210 | 211 | if (minProjection.x > maxProjection.y || minProjection.y > maxProjection.x) 212 | return false; 213 | 214 | var tMin = math.max(minProjection.x, minProjection.y); //Get Greatest Min 215 | var tMax = math.min(maxProjection.x, maxProjection.y); //Get Smallest Max 216 | 217 | if (tMin > maxProjection.z || minProjection.z > tMax) 218 | return false; 219 | 220 | tMax = math.min(maxProjection.z, tMax); 221 | 222 | exitPoint = ray.origin + ray.direction * length * tMax; 223 | return true; 224 | } 225 | 226 | public override string ToString() 227 | { 228 | return $"Center: {_center}, Extents: {_extents}"; 229 | } 230 | 231 | public override int GetHashCode() 232 | { 233 | return Center.GetHashCode() ^ (Extents.GetHashCode() << 2); 234 | } 235 | 236 | public override bool Equals(object other) 237 | { 238 | if (!(other is Bounds)) 239 | return false; 240 | return Equals((Bounds)other); 241 | } 242 | 243 | public bool Equals(Bounds other) 244 | { 245 | return Center.Equals(other.Center) && Extents.Equals(other.Extents); 246 | } 247 | 248 | public static bool operator ==(Bounds lhs, Bounds rhs) 249 | { 250 | return math.all(lhs.Center == rhs.Center) & math.all(lhs.Extents == rhs.Extents); 251 | } 252 | 253 | public static bool operator !=(Bounds lhs, Bounds rhs) 254 | { 255 | return !(lhs == rhs); 256 | } 257 | 258 | #region Variables 259 | 260 | [SerializeField] 261 | private float3 _center; 262 | [SerializeField] 263 | private float3 _extents; 264 | 265 | #endregion 266 | 267 | #region Properties 268 | 269 | public float3 Center { get { return _center; } set { _center = value; } } 270 | 271 | public float3 Size { get { return _extents * 2f; } set { _extents = value * 0.5f; } } 272 | 273 | public float3 Extents { get { return _extents; } set { _extents = value; } } 274 | 275 | public float3 Min { get { return Center - Extents; } set { SetMinMax(value, Max); } } 276 | 277 | public float3 Max { get { return Center + Extents; } set { SetMinMax(Min, value); } } 278 | 279 | #endregion 280 | } 281 | 282 | 283 | } -------------------------------------------------------------------------------- /Tests/SpatialHashingSystemTest.cs: -------------------------------------------------------------------------------- 1 | using HMH.ECS.SpatialHashing.Test; 2 | using NUnit.Framework; 3 | using Unity.Collections; 4 | using Unity.Entities; 5 | using Unity.Jobs; 6 | using Unity.Mathematics; 7 | 8 | [assembly: RegisterGenericJobType(typeof(SpatialHashingSystemTest.SystemTest.IncreaseSpatialHashSizeJob))] 9 | [assembly: RegisterGenericJobType(typeof(SpatialHashingSystemTest.SystemTest.AddSpatialHashingJob))] 10 | [assembly: RegisterGenericJobType(typeof(SpatialHashingSystemTest.SystemTest.AddSpatialHashingEndJob))] 11 | [assembly: RegisterGenericJobType(typeof(SpatialHashingSystemTest.SystemTest.UpdateSpatialHashingRemoveFastJob))] 12 | [assembly: RegisterGenericJobType(typeof(SpatialHashingSystemTest.SystemTest.UpdateSpatialHashingAddFastJob))] 13 | [assembly: RegisterGenericJobType(typeof(SpatialHashingSystemTest.SystemTest.RemoveSpatialHashingJob))] 14 | 15 | namespace HMH.ECS.SpatialHashing.Test 16 | { 17 | public class SpatialHashingSystemTest : ECSTestsFixture 18 | { 19 | [Test] 20 | public void TestAdd() 21 | { 22 | var e = EntityManager.CreateEntity(); 23 | var item = new Item { Center = new float3(50.5F), Size = new float3(1.1F) }; 24 | EntityManager.AddComponentData(e, item); 25 | 26 | var system = World.CreateSystemManaged(); 27 | 28 | system.Update(); 29 | EntityManager.CompleteAllTrackedJobs(); 30 | system.Barrier.Update(); 31 | EntityManager.CompleteAllTrackedJobs(); 32 | 33 | Assert.IsTrue(EntityManager.HasComponent(e, ComponentType.ReadOnly(typeof(SpatialHashingMirror)))); 34 | var getItemID = EntityManager.GetComponentData(e).GetItemID; 35 | Assert.AreEqual(1, getItemID); 36 | 37 | var sh = system.SpatialHash; 38 | Assert.AreEqual(1, sh.ItemCount); 39 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 40 | 41 | var results = new NativeList(5, Allocator.TempJob); 42 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 43 | 44 | sh.CalculStartEndIteration(bounds, out var start, out var end); 45 | 46 | var hashPosition = new int3(0F); 47 | 48 | for (int x = start.x; x < end.x; ++x) 49 | { 50 | hashPosition.x = x; 51 | 52 | for (int y = start.y; y < end.y; ++y) 53 | { 54 | hashPosition.y = y; 55 | 56 | for (int z = start.z; z < end.z; ++z) 57 | { 58 | hashPosition.z = z; 59 | 60 | var querryBound = new Bounds(sh.GetPositionVoxel(hashPosition, true), sh.CellSize * 0.99F); 61 | 62 | results.Clear(); 63 | sh.Query(querryBound, results); 64 | 65 | Assert.AreEqual(1, results.Length); 66 | Assert.AreEqual(item, results[0]); 67 | } 68 | } 69 | } 70 | 71 | results.Dispose(); 72 | } 73 | 74 | [Test] 75 | public void TestRemove() 76 | { 77 | #region Add 78 | 79 | var e = EntityManager.CreateEntity(); 80 | var item = new Item { Center = new float3(50.5F), Size = new float3(1.1F) }; 81 | EntityManager.AddComponentData(e, item); 82 | 83 | var system = World.CreateSystemManaged(); 84 | 85 | system.Update(); 86 | EntityManager.CompleteAllTrackedJobs(); 87 | system.Barrier.Update(); 88 | EntityManager.CompleteAllTrackedJobs(); 89 | 90 | Assert.IsTrue(EntityManager.HasComponent(e, ComponentType.ReadOnly(typeof(SpatialHashingMirror)))); 91 | Assert.AreEqual(1, EntityManager.GetComponentData(e).GetItemID); 92 | 93 | var sh = system.SpatialHash; 94 | Assert.AreEqual(1, sh.ItemCount); 95 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 96 | 97 | var results = new NativeList(5, Allocator.TempJob); 98 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 99 | 100 | sh.CalculStartEndIteration(bounds, out var start, out var end); 101 | 102 | var hashPosition = new int3(0F); 103 | 104 | for (int x = start.x; x < end.x; ++x) 105 | { 106 | hashPosition.x = x; 107 | 108 | for (int y = start.y; y < end.y; ++y) 109 | { 110 | hashPosition.y = y; 111 | 112 | for (int z = start.z; z < end.z; ++z) 113 | { 114 | hashPosition.z = z; 115 | 116 | var querryBound = new Bounds(sh.GetPositionVoxel(hashPosition, true), sh.CellSize * 0.99F); 117 | 118 | results.Clear(); 119 | sh.Query(querryBound, results); 120 | 121 | Assert.AreEqual(1, results.Length); 122 | Assert.AreEqual(item, results[0]); 123 | } 124 | } 125 | } 126 | 127 | results.Dispose(); 128 | 129 | #endregion 130 | 131 | EntityManager.RemoveComponent(e); 132 | 133 | system.Update(); 134 | EntityManager.CompleteAllTrackedJobs(); 135 | system.Barrier.Update(); 136 | EntityManager.CompleteAllTrackedJobs(); 137 | 138 | Assert.IsFalse(EntityManager.HasComponent(e)); 139 | 140 | sh = system.SpatialHash; 141 | Assert.AreEqual(0, sh.ItemCount); 142 | Assert.AreEqual(0, sh.BucketItemCount); 143 | } 144 | 145 | [Test] 146 | public void TestMove() 147 | { 148 | #region Add 149 | 150 | var e = EntityManager.CreateEntity(); 151 | var item = new Item { Center = new float3(50.5F), Size = new float3(1.1F) }; 152 | EntityManager.AddComponentData(e, item); 153 | 154 | var system = World.CreateSystemManaged(); 155 | 156 | system.Update(); 157 | EntityManager.CompleteAllTrackedJobs(); 158 | system.Barrier.Update(); 159 | EntityManager.CompleteAllTrackedJobs(); 160 | 161 | Assert.IsTrue(EntityManager.HasComponent(e, ComponentType.ReadOnly(typeof(SpatialHashingMirror)))); 162 | Assert.AreEqual(1, EntityManager.GetComponentData(e).GetItemID); 163 | 164 | var sh = system.SpatialHash; 165 | Assert.AreEqual(1, sh.ItemCount); 166 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 167 | 168 | var results = new NativeList(5, Allocator.TempJob); 169 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 170 | 171 | sh.CalculStartEndIteration(bounds, out var start, out var end); 172 | 173 | var hashPosition = new int3(0F); 174 | 175 | for (int x = start.x; x < end.x; ++x) 176 | { 177 | hashPosition.x = x; 178 | 179 | for (int y = start.y; y < end.y; ++y) 180 | { 181 | hashPosition.y = y; 182 | 183 | for (int z = start.z; z < end.z; ++z) 184 | { 185 | hashPosition.z = z; 186 | 187 | var querryBound = new Bounds(sh.GetPositionVoxel(hashPosition, true), sh.CellSize * 0.99F); 188 | 189 | results.Clear(); 190 | sh.Query(querryBound, results); 191 | 192 | Assert.AreEqual(1, results.Length); 193 | Assert.AreEqual(item, results[0]); 194 | } 195 | } 196 | } 197 | 198 | results.Dispose(); 199 | 200 | #endregion 201 | 202 | EntityManager.AddComponentData(e, new EmptyData()); 203 | item = EntityManager.GetComponentData(e); 204 | item.Center = new float3(51.5F); 205 | EntityManager.SetComponentData(e, item); 206 | 207 | system.Update(); 208 | EntityManager.CompleteAllTrackedJobs(); 209 | system.Barrier.Update(); 210 | EntityManager.CompleteAllTrackedJobs(); 211 | system.Barrier.Update(); 212 | 213 | Assert.IsTrue(EntityManager.HasComponent(e, typeof(SpatialHashingMirror))); 214 | Assert.IsFalse(EntityManager.HasComponent(e, typeof(EmptyData))); 215 | Assert.AreEqual(1, EntityManager.GetComponentData(e).GetItemID); 216 | 217 | sh = system.SpatialHash; 218 | Assert.AreEqual(1, sh.ItemCount); 219 | Assert.AreEqual(3 * 3 * 3, sh.BucketItemCount); 220 | 221 | results = new NativeList(5, Allocator.TempJob); 222 | bounds = new Bounds(item.GetCenter(), item.GetSize()); 223 | 224 | sh.CalculStartEndIteration(bounds, out start, out end); 225 | 226 | hashPosition = new int3(0F); 227 | 228 | for (int x = start.x; x < end.x; ++x) 229 | { 230 | hashPosition.x = x; 231 | 232 | for (int y = start.y; y < end.y; ++y) 233 | { 234 | hashPosition.y = y; 235 | 236 | for (int z = start.z; z < end.z; ++z) 237 | { 238 | hashPosition.z = z; 239 | 240 | var querryBound = new Bounds(sh.GetPositionVoxel(hashPosition, true), sh.CellSize * 0.99F); 241 | 242 | results.Clear(); 243 | sh.Query(querryBound, results); 244 | 245 | Assert.AreEqual(1, results.Length); 246 | Assert.AreEqual(item, results[0]); 247 | } 248 | } 249 | } 250 | 251 | results.Dispose(); 252 | } 253 | 254 | [DisableAutoCreation] 255 | public partial class SystemTest : SpatialHashingSystem 256 | { 257 | #region Overrides of SpatialHashingSystem 258 | 259 | /// 260 | protected override void InitSpatialHashing() 261 | { 262 | _spatialHash = new SpatialHash(new Bounds(new float3(50), new float3(20)), new float3(1), Allocator.Persistent); 263 | } 264 | 265 | #region Overrides of SpatialHashingSystem 266 | 267 | /// 268 | protected override void OnCreate() 269 | { 270 | Barrier = World.GetOrCreateSystemManaged(); 271 | base.OnCreate(); 272 | } 273 | 274 | #endregion 275 | 276 | /// 277 | protected override void AddJobHandleForProducer(JobHandle inputDeps) 278 | { 279 | Barrier.AddJobHandleForProducer(inputDeps); 280 | } 281 | 282 | /// 283 | protected override EntityCommandBuffer CommandBuffer => Barrier.CreateCommandBuffer(); 284 | 285 | #endregion 286 | 287 | #region Variables 288 | 289 | public EntityCommandBufferSystem Barrier; 290 | 291 | #endregion 292 | 293 | #region Properties 294 | 295 | public SpatialHash SpatialHash => _spatialHash; 296 | 297 | #endregion 298 | } 299 | 300 | public struct EmptyData : IComponentData 301 | { } 302 | } 303 | } -------------------------------------------------------------------------------- /Runtime/SpatialHashingSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Burst; 3 | using Unity.Burst.Intrinsics; 4 | using Unity.Collections; 5 | using Unity.Entities; 6 | using Unity.Jobs; 7 | 8 | namespace HMH.ECS.SpatialHashing 9 | { 10 | [BurstCompile] 11 | public abstract partial class SpatialHashingSystem : SystemBase where T : unmanaged, ISpatialHashingItem, IComponentData 12 | where TY : unmanaged, ISpatialHashingItemMiror 13 | where TZ : unmanaged, IComponentData 14 | { 15 | #region Variables 16 | 17 | protected EntityQuery _addGroup; 18 | protected EntityQuery _removeGroup; 19 | protected EntityQuery _updateGroup; 20 | 21 | protected SpatialHash _spatialHash; 22 | 23 | protected bool _cleanSpatialHashmapAtStopRunning = true; 24 | protected JobHandle _lastDependency; 25 | 26 | private CountNewAdditionJob _countNewAdditionJob; 27 | #endregion 28 | 29 | #region Properties 30 | 31 | protected abstract EntityCommandBuffer CommandBuffer { get; } 32 | public bool RemoveUpdateComponent { get; set; } = true; 33 | 34 | #endregion 35 | 36 | /// 37 | protected override void OnCreate() 38 | { 39 | InitSpatialHashing(); 40 | 41 | var entityQueryBuilder = new EntityQueryBuilder(Allocator.Temp); 42 | _addGroup = entityQueryBuilder.WithAllRW().WithNone().Build(this); 43 | 44 | entityQueryBuilder.Reset(); 45 | _updateGroup = entityQueryBuilder.WithAllRW().WithAll().Build(this); 46 | 47 | entityQueryBuilder.Reset(); 48 | _removeGroup = entityQueryBuilder.WithNone().WithAll().Build(this); 49 | 50 | _countNewAdditionJob = new CountNewAdditionJob { Counter = new NativeReference(0, Allocator.Persistent) }; 51 | } 52 | 53 | /// 54 | protected override void OnStartRunning() 55 | { 56 | base.OnStartRunning(); 57 | 58 | var entityQueryBuilder = new EntityQueryBuilder(Allocator.Temp); 59 | var alreadyExistingGroup = entityQueryBuilder.WithAllRW().Build(this); 60 | 61 | if (alreadyExistingGroup.IsEmptyIgnoreFilter == false) 62 | { 63 | var items = alreadyExistingGroup.ToComponentDataArray(World.UpdateAllocator.ToAllocator); 64 | 65 | for (var i = 0; i < items.Length; i++) 66 | { 67 | ref var item = ref items.GetElementAsRef(i); 68 | _spatialHash.Add(ref item); 69 | } 70 | } 71 | } 72 | 73 | /// 74 | protected override void OnStopRunning() 75 | { 76 | base.OnStopRunning(); 77 | 78 | if (_spatialHash.IsCreated && _cleanSpatialHashmapAtStopRunning) 79 | { 80 | _lastDependency.Complete(); 81 | _spatialHash.Clear(); 82 | } 83 | } 84 | 85 | /// 86 | protected override void OnDestroy() 87 | { 88 | base.OnDestroy(); 89 | 90 | _countNewAdditionJob.Dispose(); 91 | 92 | if (_spatialHash.IsCreated) 93 | { 94 | _lastDependency.Complete(); 95 | _spatialHash.Dispose(); 96 | } 97 | } 98 | 99 | protected abstract void InitSpatialHashing(); 100 | 101 | /// 102 | protected sealed override void OnUpdate() 103 | { 104 | OnPreUpdate(); 105 | 106 | _countNewAdditionJob.Counter.Value = 0; 107 | 108 | Dependency = _countNewAdditionJob.Schedule(_addGroup, Dependency); 109 | 110 | var increaseSpatialHashSizeJob = new IncreaseSpatialHashSizeJob { Counter = _countNewAdditionJob.Counter, SpatialHash = _spatialHash }; 111 | 112 | Dependency = increaseSpatialHashSizeJob.Schedule(Dependency); 113 | 114 | var addSpatialHashingJob = new AddSpatialHashingJob { ComponentTTypeHandle = GetComponentTypeHandle(), SpatialHash = _spatialHash.ToConcurrent() }; 115 | 116 | Dependency = addSpatialHashingJob.Schedule(_addGroup, Dependency); 117 | 118 | var addSpatialHashingEndJob = new AddSpatialHashingEndJob 119 | { 120 | ComponentTTypeHandle = GetComponentTypeHandle(true), EntityTypeHandle = GetEntityTypeHandle(), CommandBuffer = CommandBuffer.AsParallelWriter() 121 | }; 122 | 123 | Dependency = addSpatialHashingEndJob.ScheduleParallel(_addGroup, Dependency); 124 | 125 | var updateRemoveJob = new UpdateSpatialHashingRemoveFastJob { ComponentTTypeHandle = GetComponentTypeHandle(true), SpatialHash = _spatialHash }; 126 | Dependency = updateRemoveJob.Schedule(_updateGroup, Dependency); 127 | 128 | var updateSpatialHashingAddFastJob = new UpdateSpatialHashingAddFastJob { ComponentTTypeHandle = GetComponentTypeHandle(true), SpatialHash = _spatialHash.ToConcurrent() }; 129 | Dependency = updateSpatialHashingAddFastJob.Schedule(_updateGroup, Dependency); 130 | 131 | if (RemoveUpdateComponent) 132 | CommandBuffer.RemoveComponent(_updateGroup, typeof(TZ)); 133 | 134 | var removeSpatialHashingJob = new RemoveSpatialHashingJob { ComponentMirrorTypeHandle = GetComponentTypeHandle(), SpatialHash = _spatialHash }; 135 | Dependency = removeSpatialHashingJob.Schedule(_removeGroup, Dependency); 136 | 137 | CommandBuffer.RemoveComponent(_removeGroup, typeof(TY)); 138 | 139 | OnPostUpdate(); 140 | 141 | AddJobHandleForProducer(Dependency); 142 | _lastDependency = Dependency; 143 | } 144 | 145 | protected virtual void OnPreUpdate() 146 | { } 147 | 148 | protected virtual void OnPostUpdate() 149 | { } 150 | 151 | protected abstract void AddJobHandleForProducer(JobHandle inputDeps); 152 | 153 | #region Job 154 | 155 | [BurstCompile] 156 | public struct CountNewAdditionJob : IJobChunk, IDisposable 157 | { 158 | public NativeReference Counter; 159 | 160 | [BurstCompile] 161 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 162 | { 163 | Counter.Value += chunk.Count; 164 | } 165 | 166 | /// 167 | public void Dispose() 168 | { 169 | Counter.Dispose(); 170 | } 171 | } 172 | 173 | [BurstCompile] 174 | public struct IncreaseSpatialHashSizeJob : IJob 175 | { 176 | public NativeReference Counter; 177 | public SpatialHash SpatialHash; 178 | 179 | [BurstCompile] 180 | public void Execute() 181 | { 182 | //strangely resizing just for the truth length doesn't give enough space 183 | SpatialHash.PrepareFreePlace((int)(Counter.Value * 1.5F)); 184 | } 185 | } 186 | 187 | [BurstCompile] 188 | public struct AddSpatialHashingJob : IJobChunk 189 | { 190 | public ComponentTypeHandle ComponentTTypeHandle; 191 | public SpatialHash.Concurrent SpatialHash; 192 | 193 | [BurstCompile] 194 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 195 | { 196 | NativeArray itemArray = chunk.GetNativeArray(ref ComponentTTypeHandle); 197 | 198 | var enumerator = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); 199 | 200 | while (enumerator.NextEntityIndex(out var i)) 201 | { 202 | ref var item = ref itemArray.GetElementAsRef(i); 203 | SpatialHash.TryAdd(ref item); 204 | } 205 | } 206 | } 207 | 208 | [BurstCompile] 209 | public struct AddSpatialHashingEndJob : IJobChunk 210 | { 211 | [ReadOnly] 212 | public ComponentTypeHandle ComponentTTypeHandle; 213 | [ReadOnly] 214 | public EntityTypeHandle EntityTypeHandle; 215 | public EntityCommandBuffer.ParallelWriter CommandBuffer; 216 | 217 | [BurstCompile] 218 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 219 | { 220 | NativeArray itemArray = chunk.GetNativeArray(ref ComponentTTypeHandle); 221 | NativeArray entityArray = chunk.GetNativeArray(EntityTypeHandle); 222 | 223 | var enumerator = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); 224 | 225 | while (enumerator.NextEntityIndex(out var i)) 226 | { 227 | ref T item = ref itemArray.GetElementAsRefReadOnly(i); 228 | var mirror = new TY { GetItemID = item.SpatialHashingIndex }; 229 | CommandBuffer.AddComponent(unfilteredChunkIndex, entityArray[i], mirror); 230 | } 231 | } 232 | } 233 | 234 | [BurstCompile] 235 | public struct RemoveSpatialHashingJob : IJobChunk 236 | { 237 | [ReadOnly] 238 | public ComponentTypeHandle ComponentMirrorTypeHandle; 239 | public SpatialHash SpatialHash; 240 | 241 | [BurstCompile] 242 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 243 | { 244 | NativeArray mirrorArray = chunk.GetNativeArray(ref ComponentMirrorTypeHandle); 245 | 246 | var enumerator = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); 247 | 248 | while (enumerator.NextEntityIndex(out var i)) 249 | { 250 | ref var item = ref mirrorArray.GetElementAsRefReadOnly(i); 251 | SpatialHash.Remove(item.GetItemID); 252 | } 253 | } 254 | } 255 | 256 | [BurstCompile] 257 | public struct UpdateSpatialHashingRemoveFastJob : IJobChunk 258 | { 259 | [ReadOnly] 260 | public ComponentTypeHandle ComponentTTypeHandle; 261 | public SpatialHash SpatialHash; 262 | 263 | [BurstCompile] 264 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 265 | { 266 | NativeArray mirrorArray = chunk.GetNativeArray(ref ComponentTTypeHandle); 267 | 268 | var enumerator = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); 269 | 270 | while (enumerator.NextEntityIndex(out var i)) 271 | { 272 | ref var item = ref mirrorArray.GetElementAsRefReadOnly(i); 273 | SpatialHash.Remove(item.SpatialHashingIndex); 274 | } 275 | } 276 | } 277 | 278 | [BurstCompile] 279 | public struct UpdateSpatialHashingAddFastJob : IJobChunk 280 | { 281 | [ReadOnly] 282 | public ComponentTypeHandle ComponentTTypeHandle; 283 | public SpatialHash.Concurrent SpatialHash; 284 | 285 | [BurstCompile] 286 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 287 | { 288 | NativeArray mirrorArray = chunk.GetNativeArray(ref ComponentTTypeHandle); 289 | 290 | var enumerator = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count); 291 | 292 | while (enumerator.NextEntityIndex(out var i)) 293 | { 294 | ref var item = ref mirrorArray.GetElementAsRefReadOnly(i); 295 | SpatialHash.AddFast(in item); 296 | } 297 | } 298 | } 299 | 300 | #endregion 301 | } 302 | 303 | public interface ISpatialHashingItemMiror : ICleanupComponentData 304 | { 305 | int GetItemID { get; set; } 306 | } 307 | 308 | public struct SpatialHashingMirror : ISpatialHashingItemMiror 309 | { 310 | public int GetItemID { get; set; } 311 | } 312 | } -------------------------------------------------------------------------------- /Editor/OldDebug/HashMapVisualDebug.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Unity.Burst; 3 | using Unity.Collections; 4 | using Unity.Entities; 5 | using Unity.Jobs; 6 | using Unity.Mathematics; 7 | using UnityEngine; 8 | using UnityEngine.Profiling; 9 | using Random = UnityEngine.Random; 10 | 11 | namespace HMH.ECS.SpatialHashing.Debug 12 | { 13 | public class HashMapVisualDebug : MonoBehaviour, IRay 14 | { 15 | private void Start() 16 | { 17 | Random.InitState(123456789); 18 | 19 | var copy = new Bounds(_worldBounds.Center, _worldBounds.Size * 4F); 20 | _spatialHashing = new SpatialHash(copy, new float3(10F), Allocator.Persistent); 21 | 22 | for (int i = 0; i < _spawnCount; i++) 23 | { 24 | var g = GameObject.CreatePrimitive(PrimitiveType.Cube); 25 | 26 | if (_useRaycast == false) 27 | g.AddComponent(); 28 | Destroy(g.GetComponent()); 29 | 30 | g.transform.position = new Vector3(Random.Range(_worldBounds.Min.x + 1, _worldBounds.Max.x), 31 | Random.Range(_worldBounds.Min.y + 1, _worldBounds.Max.y), 32 | Random.Range(_worldBounds.Min.z + 1, _worldBounds.Max.z)); 33 | 34 | var item = new ItemTest() { ID = i, Position = g.transform.position }; 35 | 36 | Profiler.BeginSample("SpatialHasing Add"); 37 | _spatialHashing.Add(ref item); 38 | Profiler.EndSample(); 39 | 40 | _listItemGameobject.Add(g); 41 | _listItem.Add(item); 42 | } 43 | } 44 | 45 | [BurstCompile] 46 | public struct MoveItemTestJob : IJob 47 | { 48 | #region Implementation of IJob 49 | 50 | /// 51 | public void Execute() 52 | { 53 | for (int i = 0; i < ItemList.Length; i++) 54 | SpatialHash.Move(ItemList[i]); 55 | } 56 | 57 | public NativeList ItemList; 58 | public SpatialHash SpatialHash; 59 | 60 | #endregion 61 | } 62 | 63 | [BurstCompile] 64 | public struct AddItemTestJob : IJobParallelFor 65 | { 66 | public void Execute(int index) 67 | { 68 | var item = ItemList[index]; 69 | SpatialHash.TryAdd(ref item); 70 | ItemList[index] = item; 71 | } 72 | 73 | [NativeDisableParallelForRestriction] 74 | public NativeList ItemList; 75 | public SpatialHash.Concurrent SpatialHash; 76 | } 77 | [BurstCompile] 78 | public struct RemoveItemTestJob : IJob 79 | { 80 | public void Execute() 81 | { 82 | for (int i = 0; i < ItemList.Length; i++) 83 | SpatialHash.Remove(ItemList[i].SpatialHashingIndex); 84 | } 85 | 86 | public NativeList ItemList; 87 | public SpatialHash SpatialHash; 88 | } 89 | 90 | private void FixedUpdate() 91 | { 92 | World.DefaultGameObjectInjectionWorld.EntityManager.CompleteAllTrackedJobs(); 93 | 94 | var itemList = new NativeList(_spawnCount, Allocator.TempJob); 95 | 96 | for (var i = 0; i < _listItem.Count; i++) 97 | { 98 | if (math.any(_listItem[i].Position != (float3)_listItemGameobject[i].transform.position)) 99 | { 100 | var item = _listItem[i]; 101 | Profiler.BeginSample("SpatialHasing Move"); 102 | itemList.Add(item); 103 | Profiler.EndSample(); 104 | } 105 | } 106 | // new MoveItemTestJob() { SpatialHash = _spatialHashing, ItemList = itemList }.Schedule().Complete(); 107 | int length = itemList.Length; 108 | var inputDep = new JobHandle(); 109 | inputDep= new RemoveItemTestJob() { SpatialHash = _spatialHashing, ItemList = itemList }.Schedule(inputDep); 110 | inputDep = new AddItemTestJob() { SpatialHash = _spatialHashing.ToConcurrent(), ItemList = itemList }.Schedule(length, 32, inputDep); 111 | inputDep.Complete(); 112 | 113 | int delta = 0; 114 | for (var i = 0; i < _listItem.Count; i++) 115 | { 116 | if (math.any(_listItem[i].Position != (float3)_listItemGameobject[i].transform.position)) 117 | { 118 | var item =itemList[delta++]; 119 | item.Position = _listItemGameobject[i].transform.position; 120 | _listItem[i] = item; 121 | } 122 | } 123 | itemList.Dispose(); 124 | } 125 | 126 | private void OnDrawGizmos() 127 | { 128 | if (Application.isPlaying == false) 129 | return; 130 | 131 | if (Time.realtimeSinceStartup - _timeLastRefresh > _refreshTime) 132 | RefreshLinks(); 133 | 134 | foreach (var l in _links) 135 | { 136 | DrawCell(l.Key); 137 | 138 | foreach (var item in l.Value) 139 | DrawLink(l.Key, item); 140 | } 141 | 142 | if (_useRaycast) 143 | { 144 | var startRayPosition = _startRay.position; 145 | var startCellBound = new Bounds(_spatialHashing.GetPositionVoxel(_spatialHashing.GetIndexVoxel(startRayPosition), true), _spatialHashing.CellSize); 146 | 147 | Gizmos.color = Color.yellow; 148 | Gizmos.DrawLine(startRayPosition, _endRay.position); 149 | 150 | ItemTest hit = new ItemTest(); 151 | var ray = new Ray(startRayPosition, _endRay.position - startRayPosition); 152 | 153 | if (_spatialHashing.RayCast(ray, ref hit, (_endRay.position - _startRay.position).magnitude)) 154 | { 155 | Gizmos.color = Color.blue; 156 | Gizmos.DrawCube(hit.GetCenter(), hit.GetSize() * 3F); 157 | } 158 | 159 | _voxelTraversed.Clear(); 160 | 161 | var me = this; 162 | _voxelRay.RayCast(ref me, ray.origin, ray.direction, (_endRay.position - _startRay.position).magnitude); 163 | Gizmos.color = new Color(0.88F, 0.6F, 0.1F, 0.4F); 164 | 165 | foreach (var voxel in _voxelTraversed) 166 | { 167 | var position = _spatialHashing.GetPositionVoxel(voxel, true); 168 | Gizmos.DrawCube(position, _spatialHashing.CellSize); 169 | } 170 | 171 | var rayOffsetted = new Ray(ray.origin - (Vector3)(ray.direction * _spatialHashing.CellSize), ray.direction); 172 | startCellBound.GetEnterPositionAABB(rayOffsetted, 1 << 25, out var enterPoint); 173 | Gizmos.color = Color.white; 174 | Gizmos.DrawCube(enterPoint, Vector3.one * 0.3F); 175 | 176 | startCellBound.GetExitPosition(rayOffsetted, 1 << 25, out var exitPoint); 177 | Gizmos.color = Color.white; 178 | Gizmos.DrawCube(exitPoint, Vector3.one * 0.3F); 179 | 180 | } 181 | } 182 | 183 | private void RefreshLinks() 184 | { 185 | _timeLastRefresh = Time.realtimeSinceStartup; 186 | _links.Clear(); 187 | 188 | var unic = new HashSet(); 189 | var values = _spatialHashing.DebugBuckets.GetValueArray(Allocator.TempJob); 190 | 191 | foreach (var itemTest in values) 192 | unic.Add(_spatialHashing.DebugIDToItem[itemTest]); 193 | values.Dispose(); 194 | 195 | var list = new NativeList(Allocator.Temp); 196 | 197 | foreach (var item in unic) 198 | { 199 | list.Clear(); 200 | _spatialHashing.GetIndexiesVoxel(item, list); 201 | 202 | for (int i = 0; i < list.Length; i++) 203 | { 204 | var index = list[i]; 205 | 206 | if (_links.TryGetValue(index, out var l) == false) 207 | { 208 | l = new List(); 209 | _links.Add(index, l); 210 | } 211 | 212 | l.Add(item); 213 | } 214 | } 215 | 216 | list.Dispose(); 217 | } 218 | 219 | private void DrawCell(int3 index) 220 | { 221 | var position = _spatialHashing.GetPositionVoxel(index, true); 222 | Gizmos.color = new Color(0F, 1F, 0F, 0.3F); 223 | Gizmos.DrawCube(position, _spatialHashing.CellSize); 224 | Gizmos.color = Color.black; 225 | Gizmos.DrawWireCube(position, _spatialHashing.CellSize); 226 | } 227 | 228 | private void DrawLink(int3 cellIndex, ItemTest target) 229 | { 230 | var position = _spatialHashing.GetPositionVoxel(cellIndex, true); 231 | 232 | Gizmos.color = Color.red; 233 | Gizmos.DrawLine(position, target.Position); 234 | } 235 | 236 | #region Implementation of IRay 237 | 238 | /// 239 | public bool OnTraversingVoxel(int3 voxelIndex) 240 | { 241 | _voxelTraversed.Add(voxelIndex); 242 | 243 | return false; 244 | } 245 | 246 | /// 247 | public int3 GetIndexVoxel(float3 position) 248 | { 249 | return _spatialHashing.GetIndexVoxel(position); 250 | } 251 | 252 | /// 253 | public float3 GetPositionVoxel(int3 index, bool center) 254 | { 255 | return _spatialHashing.GetPositionVoxel(index, center); 256 | } 257 | 258 | /// 259 | public float3 CellSize { get { return _spatialHashing.CellSize; } } 260 | 261 | #endregion 262 | 263 | #region Variables 264 | 265 | [SerializeField, Range(0F, 1F)] 266 | private float _refreshTime = 0.2F; 267 | [SerializeField] 268 | private Bounds _worldBounds; 269 | [SerializeField] 270 | private int _spawnCount; 271 | [SerializeField] 272 | private bool _useRaycast; 273 | [SerializeField] 274 | private Transform _startRay; 275 | [SerializeField] 276 | private Transform _endRay; 277 | 278 | private List _listItem = new List(); 279 | private List _listItemGameobject = new List(); 280 | private SpatialHash _spatialHashing; 281 | private float _timeLastRefresh = -99F; 282 | private Dictionary> _links = new Dictionary>(); 283 | private List _voxelTraversed = new List(); 284 | private VoxelRay _voxelRay = new VoxelRay(); 285 | 286 | #endregion 287 | 288 | public struct ItemTest : ISpatialHashingItem, IComponentData 289 | { 290 | public int ID; 291 | public float3 Position; 292 | 293 | #region Implementation of IEquatable 294 | 295 | /// 296 | public bool Equals(ItemTest other) 297 | { 298 | return ID == other.ID; 299 | } 300 | 301 | /// 302 | public override int GetHashCode() 303 | { 304 | return ID; 305 | } 306 | 307 | #region Overrides of ValueType 308 | 309 | /// 310 | public override string ToString() 311 | { 312 | return "Item " + ID; 313 | } 314 | 315 | #endregion 316 | 317 | #endregion 318 | 319 | #region Implementation of ISpatialHashingItem 320 | 321 | /// 322 | public float3 GetCenter() 323 | { 324 | return Position; 325 | } 326 | 327 | /// 328 | public float3 GetSize() 329 | { 330 | return new float3(1F); 331 | } 332 | 333 | /// 334 | public int SpatialHashingIndex { get; set; } 335 | 336 | #endregion 337 | } 338 | } 339 | } -------------------------------------------------------------------------------- /Editor/OldDebug/AABB Debug.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 3 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 0} 41 | m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 11 47 | m_GIWorkflowMode: 1 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 0 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 12 58 | m_Resolution: 2 59 | m_BakeResolution: 40 60 | m_AtlasSize: 1024 61 | m_AO: 0 62 | m_AOMaxDistance: 1 63 | m_CompAOExponent: 1 64 | m_CompAOExponentDirect: 0 65 | m_ExtractAmbientOcclusion: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 512 79 | m_PVRBounces: 2 80 | m_PVREnvironmentSampleCount: 256 81 | m_PVREnvironmentReferencePointCount: 2048 82 | m_PVRFilteringMode: 1 83 | m_PVRDenoiserTypeDirect: 1 84 | m_PVRDenoiserTypeIndirect: 1 85 | m_PVRDenoiserTypeAO: 1 86 | m_PVRFilterTypeDirect: 0 87 | m_PVRFilterTypeIndirect: 0 88 | m_PVRFilterTypeAO: 0 89 | m_PVREnvironmentMIS: 1 90 | m_PVRCulling: 1 91 | m_PVRFilteringGaussRadiusDirect: 1 92 | m_PVRFilteringGaussRadiusIndirect: 5 93 | m_PVRFilteringGaussRadiusAO: 2 94 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 95 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 96 | m_PVRFilteringAtrousPositionSigmaAO: 1 97 | m_ShowResolutionOverlay: 1 98 | m_ExportTrainingData: 0 99 | m_LightingDataAsset: {fileID: 0} 100 | m_UseShadowmask: 1 101 | --- !u!196 &4 102 | NavMeshSettings: 103 | serializedVersion: 2 104 | m_ObjectHideFlags: 0 105 | m_BuildSettings: 106 | serializedVersion: 2 107 | agentTypeID: 0 108 | agentRadius: 0.5 109 | agentHeight: 2 110 | agentSlope: 45 111 | agentClimb: 0.4 112 | ledgeDropHeight: 0 113 | maxJumpAcrossDistance: 0 114 | minRegionArea: 2 115 | manualCellSize: 0 116 | cellSize: 0.16666667 117 | manualTileSize: 0 118 | tileSize: 256 119 | accuratePlacement: 0 120 | debug: 121 | m_Flags: 0 122 | m_NavMeshData: {fileID: 0} 123 | --- !u!1 &215603133 124 | GameObject: 125 | m_ObjectHideFlags: 0 126 | m_CorrespondingSourceObject: {fileID: 0} 127 | m_PrefabInstance: {fileID: 0} 128 | m_PrefabAsset: {fileID: 0} 129 | serializedVersion: 6 130 | m_Component: 131 | - component: {fileID: 215603137} 132 | - component: {fileID: 215603136} 133 | - component: {fileID: 215603135} 134 | - component: {fileID: 215603134} 135 | m_Layer: 0 136 | m_Name: Main Camera 137 | m_TagString: MainCamera 138 | m_Icon: {fileID: 0} 139 | m_NavMeshLayer: 0 140 | m_StaticEditorFlags: 0 141 | m_IsActive: 1 142 | --- !u!114 &215603134 143 | MonoBehaviour: 144 | m_ObjectHideFlags: 0 145 | m_CorrespondingSourceObject: {fileID: 0} 146 | m_PrefabInstance: {fileID: 0} 147 | m_PrefabAsset: {fileID: 0} 148 | m_GameObject: {fileID: 215603133} 149 | m_Enabled: 1 150 | m_EditorHideFlags: 0 151 | m_Script: {fileID: 11500000, guid: 9b9dfff59ca57b84fa7d77c91278898f, type: 3} 152 | m_Name: 153 | m_EditorClassIdentifier: 154 | _refreshTime: 0.2 155 | _worldBounds: 156 | _center: 157 | x: 100 158 | y: 100 159 | z: 100 160 | _extents: 161 | x: 200 162 | y: 200 163 | z: 200 164 | _spawnCount: 12000 165 | _useRaycast: 0 166 | _startRay: {fileID: 1722627197} 167 | _endRay: {fileID: 1019243448} 168 | --- !u!81 &215603135 169 | AudioListener: 170 | m_ObjectHideFlags: 0 171 | m_CorrespondingSourceObject: {fileID: 0} 172 | m_PrefabInstance: {fileID: 0} 173 | m_PrefabAsset: {fileID: 0} 174 | m_GameObject: {fileID: 215603133} 175 | m_Enabled: 1 176 | --- !u!20 &215603136 177 | Camera: 178 | m_ObjectHideFlags: 0 179 | m_CorrespondingSourceObject: {fileID: 0} 180 | m_PrefabInstance: {fileID: 0} 181 | m_PrefabAsset: {fileID: 0} 182 | m_GameObject: {fileID: 215603133} 183 | m_Enabled: 1 184 | serializedVersion: 2 185 | m_ClearFlags: 1 186 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 187 | m_projectionMatrixMode: 1 188 | m_GateFitMode: 2 189 | m_FOVAxisMode: 0 190 | m_SensorSize: {x: 36, y: 24} 191 | m_LensShift: {x: 0, y: 0} 192 | m_FocalLength: 50 193 | m_NormalizedViewPortRect: 194 | serializedVersion: 2 195 | x: 0 196 | y: 0 197 | width: 1 198 | height: 1 199 | near clip plane: 0.3 200 | far clip plane: 1000 201 | field of view: 60 202 | orthographic: 1 203 | orthographic size: 5 204 | m_Depth: -1 205 | m_CullingMask: 206 | serializedVersion: 2 207 | m_Bits: 4294967295 208 | m_RenderingPath: -1 209 | m_TargetTexture: {fileID: 0} 210 | m_TargetDisplay: 0 211 | m_TargetEye: 3 212 | m_HDR: 1 213 | m_AllowMSAA: 1 214 | m_AllowDynamicResolution: 0 215 | m_ForceIntoRT: 0 216 | m_OcclusionCulling: 1 217 | m_StereoConvergence: 10 218 | m_StereoSeparation: 0.022 219 | --- !u!4 &215603137 220 | Transform: 221 | m_ObjectHideFlags: 0 222 | m_CorrespondingSourceObject: {fileID: 0} 223 | m_PrefabInstance: {fileID: 0} 224 | m_PrefabAsset: {fileID: 0} 225 | m_GameObject: {fileID: 215603133} 226 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 227 | m_LocalPosition: {x: 0, y: 0, z: -10} 228 | m_LocalScale: {x: 1, y: 1, z: 1} 229 | m_Children: [] 230 | m_Father: {fileID: 0} 231 | m_RootOrder: 0 232 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 233 | --- !u!1 &1019243444 234 | GameObject: 235 | m_ObjectHideFlags: 0 236 | m_CorrespondingSourceObject: {fileID: 0} 237 | m_PrefabInstance: {fileID: 0} 238 | m_PrefabAsset: {fileID: 0} 239 | serializedVersion: 6 240 | m_Component: 241 | - component: {fileID: 1019243448} 242 | - component: {fileID: 1019243447} 243 | - component: {fileID: 1019243446} 244 | - component: {fileID: 1019243445} 245 | m_Layer: 0 246 | m_Name: End 247 | m_TagString: Untagged 248 | m_Icon: {fileID: 0} 249 | m_NavMeshLayer: 0 250 | m_StaticEditorFlags: 0 251 | m_IsActive: 1 252 | --- !u!135 &1019243445 253 | SphereCollider: 254 | m_ObjectHideFlags: 0 255 | m_CorrespondingSourceObject: {fileID: 0} 256 | m_PrefabInstance: {fileID: 0} 257 | m_PrefabAsset: {fileID: 0} 258 | m_GameObject: {fileID: 1019243444} 259 | m_Material: {fileID: 0} 260 | m_IsTrigger: 0 261 | m_Enabled: 1 262 | serializedVersion: 2 263 | m_Radius: 0.5 264 | m_Center: {x: 0, y: 0, z: 0} 265 | --- !u!23 &1019243446 266 | MeshRenderer: 267 | m_ObjectHideFlags: 0 268 | m_CorrespondingSourceObject: {fileID: 0} 269 | m_PrefabInstance: {fileID: 0} 270 | m_PrefabAsset: {fileID: 0} 271 | m_GameObject: {fileID: 1019243444} 272 | m_Enabled: 1 273 | m_CastShadows: 1 274 | m_ReceiveShadows: 1 275 | m_DynamicOccludee: 1 276 | m_MotionVectors: 1 277 | m_LightProbeUsage: 1 278 | m_ReflectionProbeUsage: 1 279 | m_RenderingLayerMask: 1 280 | m_RendererPriority: 0 281 | m_Materials: 282 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 283 | m_StaticBatchInfo: 284 | firstSubMesh: 0 285 | subMeshCount: 0 286 | m_StaticBatchRoot: {fileID: 0} 287 | m_ProbeAnchor: {fileID: 0} 288 | m_LightProbeVolumeOverride: {fileID: 0} 289 | m_ScaleInLightmap: 1 290 | m_PreserveUVs: 0 291 | m_IgnoreNormalsForChartDetection: 0 292 | m_ImportantGI: 0 293 | m_StitchLightmapSeams: 1 294 | m_SelectedEditorRenderState: 3 295 | m_MinimumChartSize: 4 296 | m_AutoUVMaxDistance: 0.5 297 | m_AutoUVMaxAngle: 89 298 | m_LightmapParameters: {fileID: 0} 299 | m_SortingLayerID: 0 300 | m_SortingLayer: 0 301 | m_SortingOrder: 0 302 | --- !u!33 &1019243447 303 | MeshFilter: 304 | m_ObjectHideFlags: 0 305 | m_CorrespondingSourceObject: {fileID: 0} 306 | m_PrefabInstance: {fileID: 0} 307 | m_PrefabAsset: {fileID: 0} 308 | m_GameObject: {fileID: 1019243444} 309 | m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} 310 | --- !u!4 &1019243448 311 | Transform: 312 | m_ObjectHideFlags: 0 313 | m_CorrespondingSourceObject: {fileID: 0} 314 | m_PrefabInstance: {fileID: 0} 315 | m_PrefabAsset: {fileID: 0} 316 | m_GameObject: {fileID: 1019243444} 317 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 318 | m_LocalPosition: {x: 0, y: -9.558962, z: -6.7081604} 319 | m_LocalScale: {x: 1, y: 1, z: 1} 320 | m_Children: [] 321 | m_Father: {fileID: 1722627197} 322 | m_RootOrder: 0 323 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 324 | --- !u!1 &1722627193 325 | GameObject: 326 | m_ObjectHideFlags: 0 327 | m_CorrespondingSourceObject: {fileID: 0} 328 | m_PrefabInstance: {fileID: 0} 329 | m_PrefabAsset: {fileID: 0} 330 | serializedVersion: 6 331 | m_Component: 332 | - component: {fileID: 1722627197} 333 | - component: {fileID: 1722627196} 334 | - component: {fileID: 1722627195} 335 | - component: {fileID: 1722627194} 336 | m_Layer: 0 337 | m_Name: Start 338 | m_TagString: Untagged 339 | m_Icon: {fileID: 0} 340 | m_NavMeshLayer: 0 341 | m_StaticEditorFlags: 0 342 | m_IsActive: 1 343 | --- !u!135 &1722627194 344 | SphereCollider: 345 | m_ObjectHideFlags: 0 346 | m_CorrespondingSourceObject: {fileID: 0} 347 | m_PrefabInstance: {fileID: 0} 348 | m_PrefabAsset: {fileID: 0} 349 | m_GameObject: {fileID: 1722627193} 350 | m_Material: {fileID: 0} 351 | m_IsTrigger: 0 352 | m_Enabled: 1 353 | serializedVersion: 2 354 | m_Radius: 0.5 355 | m_Center: {x: 0, y: 0, z: 0} 356 | --- !u!23 &1722627195 357 | MeshRenderer: 358 | m_ObjectHideFlags: 0 359 | m_CorrespondingSourceObject: {fileID: 0} 360 | m_PrefabInstance: {fileID: 0} 361 | m_PrefabAsset: {fileID: 0} 362 | m_GameObject: {fileID: 1722627193} 363 | m_Enabled: 1 364 | m_CastShadows: 1 365 | m_ReceiveShadows: 1 366 | m_DynamicOccludee: 1 367 | m_MotionVectors: 1 368 | m_LightProbeUsage: 1 369 | m_ReflectionProbeUsage: 1 370 | m_RenderingLayerMask: 1 371 | m_RendererPriority: 0 372 | m_Materials: 373 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 374 | m_StaticBatchInfo: 375 | firstSubMesh: 0 376 | subMeshCount: 0 377 | m_StaticBatchRoot: {fileID: 0} 378 | m_ProbeAnchor: {fileID: 0} 379 | m_LightProbeVolumeOverride: {fileID: 0} 380 | m_ScaleInLightmap: 1 381 | m_PreserveUVs: 0 382 | m_IgnoreNormalsForChartDetection: 0 383 | m_ImportantGI: 0 384 | m_StitchLightmapSeams: 1 385 | m_SelectedEditorRenderState: 3 386 | m_MinimumChartSize: 4 387 | m_AutoUVMaxDistance: 0.5 388 | m_AutoUVMaxAngle: 89 389 | m_LightmapParameters: {fileID: 0} 390 | m_SortingLayerID: 0 391 | m_SortingLayer: 0 392 | m_SortingOrder: 0 393 | --- !u!33 &1722627196 394 | MeshFilter: 395 | m_ObjectHideFlags: 0 396 | m_CorrespondingSourceObject: {fileID: 0} 397 | m_PrefabInstance: {fileID: 0} 398 | m_PrefabAsset: {fileID: 0} 399 | m_GameObject: {fileID: 1722627193} 400 | m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} 401 | --- !u!4 &1722627197 402 | Transform: 403 | m_ObjectHideFlags: 0 404 | m_CorrespondingSourceObject: {fileID: 0} 405 | m_PrefabInstance: {fileID: 0} 406 | m_PrefabAsset: {fileID: 0} 407 | m_GameObject: {fileID: 1722627193} 408 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 409 | m_LocalPosition: {x: -177.62859, y: -9.181038, z: 206.52817} 410 | m_LocalScale: {x: 1, y: 1, z: 1} 411 | m_Children: 412 | - {fileID: 1019243448} 413 | m_Father: {fileID: 0} 414 | m_RootOrder: 1 415 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 416 | -------------------------------------------------------------------------------- /Runtime/SpatialHash.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Threading; 6 | using Unity.Collections; 7 | using Unity.Collections.LowLevel.Unsafe; 8 | using Unity.Mathematics; 9 | using UnityEngine; 10 | using UnityEngine.Assertions; 11 | 12 | namespace HMH.ECS.SpatialHashing 13 | { 14 | /// 15 | /// Spatial hashing logic. Have to be assign by ref ! 16 | /// 17 | /// 18 | public unsafe struct SpatialHash : IDisposable, IRay where T : unmanaged, ISpatialHashingItem 19 | { 20 | public SpatialHash(Bounds worldBounds, float3 cellSize, Allocator label) 21 | : this(worldBounds, cellSize, worldBounds.GetCellCount(cellSize).Mul() * 3, label) 22 | { } 23 | 24 | public SpatialHash(Bounds worldBounds, float3 cellSize, int initialSize, Allocator allocator) 25 | { 26 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 27 | switch (allocator) 28 | { 29 | case <= Allocator.None: 30 | throw new ArgumentException("Allocator must be Temp, TempJob or Persistent", nameof (allocator)); 31 | case >= Allocator.FirstUserIndex: 32 | throw new ArgumentException("Allocator must be Temp, TempJob or Persistent", nameof (allocator)); 33 | } 34 | 35 | if (initialSize < 1) 36 | throw new ArgumentOutOfRangeException(nameof(initialSize), "InitialSize must be > 0"); 37 | 38 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator); 39 | #endif 40 | 41 | _allocatorLabel = allocator; 42 | _data = (SpatialHashData*)UnsafeUtility.MallocTracked(sizeof(SpatialHashData), UnsafeUtility.AlignOf(), allocator, 0); 43 | _data -> WorldBounds = worldBounds; 44 | _data -> WorldBoundsMin = worldBounds.Min; 45 | _data -> CellSize = cellSize; 46 | _data -> CellCount = worldBounds.GetCellCount(cellSize); 47 | _data -> RayCastBound = new Bounds(); 48 | _data -> HasHit = false; 49 | _data -> Counter = 0; 50 | _data -> RayOrigin = float3.zero; 51 | _data -> RayDirection = float3.zero; 52 | 53 | _buckets = new NativeParallelMultiHashMap(initialSize, allocator); 54 | _itemIDToBounds = new NativeParallelHashMap(initialSize >> 1, allocator); 55 | _itemIDToItem = new NativeParallelHashMap(initialSize >> 1, allocator); 56 | _helpMoveHashMapOld = new NativeParallelHashSet(128, allocator); 57 | _helpMoveHashMapNew = new NativeParallelHashSet(128, allocator); 58 | 59 | _voxelRay = new VoxelRay>(); 60 | _rayHitValue = 0; 61 | } 62 | 63 | /// 64 | public void Dispose() 65 | { 66 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 67 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 68 | #endif 69 | UnsafeUtility.FreeTracked(_data, _allocatorLabel); 70 | 71 | _buckets.Dispose(); 72 | _itemIDToBounds.Dispose(); 73 | _itemIDToItem.Dispose(); 74 | _helpMoveHashMapOld.Dispose(); 75 | _helpMoveHashMapNew.Dispose(); 76 | } 77 | 78 | #region I/O 79 | 80 | public void Add(ref T item) 81 | { 82 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 83 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 84 | #endif 85 | 86 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 87 | 88 | bounds.Clamp(_data -> WorldBounds); 89 | 90 | // TODO Maintien free id to replace hashmap by array 91 | var itemID = ++_data -> Counter; 92 | 93 | item.SpatialHashingIndex = itemID; 94 | _itemIDToBounds.TryAdd(itemID, bounds); 95 | _itemIDToItem.TryAdd(itemID, item); 96 | 97 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 98 | 99 | var hashPosition = new int3(0F); 100 | 101 | for (int x = start.x; x < end.x; ++x) 102 | { 103 | hashPosition.x = x; 104 | 105 | for (int y = start.y; y < end.y; ++y) 106 | { 107 | hashPosition.y = y; 108 | 109 | for (int z = start.z; z < end.z; ++z) 110 | { 111 | hashPosition.z = z; 112 | 113 | AddInternal(hashPosition, itemID); 114 | } 115 | } 116 | } 117 | } 118 | 119 | public void AddFast(ref T item) 120 | { 121 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 122 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 123 | #endif 124 | 125 | var itemID = item.SpatialHashingIndex; 126 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 127 | 128 | bounds.Clamp(_data -> WorldBounds); 129 | 130 | _itemIDToBounds[itemID] = bounds; 131 | _itemIDToItem[itemID] = item; 132 | 133 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 134 | 135 | var hashPosition = new int3(0F); 136 | 137 | for (int x = start.x; x < end.x; ++x) 138 | { 139 | hashPosition.x = x; 140 | 141 | for (int y = start.y; y < end.y; ++y) 142 | { 143 | hashPosition.y = y; 144 | 145 | for (int z = start.z; z < end.z; ++z) 146 | { 147 | hashPosition.z = z; 148 | 149 | AddInternal(hashPosition, itemID); 150 | } 151 | } 152 | } 153 | } 154 | 155 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 156 | private void AddInternal(int3 position, int itemID) 157 | { 158 | _buckets.Add(Hash(position), itemID); 159 | } 160 | 161 | public void Remove(int itemID) 162 | { 163 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 164 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 165 | #endif 166 | var success = _itemIDToBounds.TryGetValue(itemID, out var bounds); 167 | 168 | Assert.IsTrue(success); 169 | 170 | _itemIDToBounds.Remove(itemID); 171 | _itemIDToItem.Remove(itemID); 172 | 173 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 174 | 175 | var hashPosition = new int3(0F); 176 | 177 | for (int x = start.x; x < end.x; ++x) 178 | { 179 | hashPosition.x = x; 180 | 181 | for (int y = start.y; y < end.y; ++y) 182 | { 183 | hashPosition.y = y; 184 | 185 | for (int z = start.z; z < end.z; ++z) 186 | { 187 | hashPosition.z = z; 188 | 189 | RemoveInternal(hashPosition, itemID); 190 | } 191 | } 192 | } 193 | } 194 | 195 | /// 196 | /// Remove method used for move or scale an item; 197 | /// 198 | /// 199 | public void RemoveFast(int itemID) 200 | { 201 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 202 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 203 | #endif 204 | var success = _itemIDToBounds.TryGetValue(itemID, out var bounds); 205 | 206 | Assert.IsTrue(success); 207 | 208 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 209 | 210 | var hashPosition = new int3(0F); 211 | 212 | for (int x = start.x; x < end.x; ++x) 213 | { 214 | hashPosition.x = x; 215 | 216 | for (int y = start.y; y < end.y; ++y) 217 | { 218 | hashPosition.y = y; 219 | 220 | for (int z = start.z; z < end.z; ++z) 221 | { 222 | hashPosition.z = z; 223 | 224 | RemoveInternal(hashPosition, itemID); 225 | } 226 | } 227 | } 228 | } 229 | 230 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 231 | private void RemoveInternal(int3 voxelPosition, int itemID) 232 | { 233 | _buckets.Remove(Hash(voxelPosition), itemID); 234 | } 235 | 236 | public void Move(T item) 237 | { 238 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 239 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 240 | #endif 241 | var itemID = item.SpatialHashingIndex; 242 | var success = _itemIDToBounds.TryGetValue(itemID, out var oldBounds); 243 | Assert.IsTrue(success); 244 | 245 | var newBounds = new Bounds(item.GetCenter(), item.GetSize()); 246 | newBounds.Clamp(_data -> WorldBounds); 247 | _itemIDToBounds.Remove(itemID); 248 | _itemIDToBounds.TryAdd(itemID, newBounds); 249 | _itemIDToItem.Remove(itemID); 250 | _itemIDToItem.TryAdd(itemID, item); 251 | 252 | _helpMoveHashMapOld.Clear(); 253 | SetVoxelIndexForBounds(_data, oldBounds, _helpMoveHashMapOld); 254 | _helpMoveHashMapNew.Clear(); 255 | SetVoxelIndexForBounds(_data, newBounds, _helpMoveHashMapNew); 256 | 257 | foreach (var oldVoxelPosition in _helpMoveHashMapOld) 258 | { 259 | if (_helpMoveHashMapNew.Contains(oldVoxelPosition) == false) 260 | RemoveInternal(oldVoxelPosition, itemID); 261 | } 262 | 263 | foreach (var newVoxelPosition in _helpMoveHashMapOld) 264 | { 265 | if (_helpMoveHashMapOld.Contains(newVoxelPosition) == false) 266 | AddInternal(newVoxelPosition, itemID); 267 | } 268 | } 269 | 270 | public void Resize(T item) 271 | { 272 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 273 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 274 | #endif 275 | 276 | Move(item); 277 | } 278 | 279 | public void Clear() 280 | { 281 | _itemIDToBounds.Clear(); 282 | _buckets.Clear(); 283 | _itemIDToBounds.Clear(); 284 | } 285 | 286 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 287 | private static void SetVoxelIndexForBounds(SpatialHashData* data, Bounds bounds, NativeParallelHashSet collection) 288 | { 289 | CalculStartEndIterationInternal(data, bounds, out var start, out var end); 290 | 291 | var position = new int3(0F); 292 | 293 | for (int x = start.x; x < end.x; ++x) 294 | { 295 | position.x = x; 296 | 297 | for (int y = start.y; y < end.y; ++y) 298 | { 299 | position.y = y; 300 | 301 | for (int z = start.z; z < end.z; ++z) 302 | { 303 | position.z = z; 304 | 305 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 306 | bool success = collection.Add(position); 307 | 308 | if (success == false) 309 | { 310 | throw new Exception("Try to add a position already in bound collection"); 311 | } 312 | #else 313 | collection.Add(position); 314 | #endif 315 | } 316 | } 317 | } 318 | } 319 | 320 | public void CalculStartEndIteration(Bounds bounds, out int3 start, out int3 end) 321 | { 322 | CalculStartEndIterationInternal(_data, bounds, out start, out end); 323 | } 324 | 325 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 326 | private static void CalculStartEndIterationInternal(SpatialHashData* data, Bounds bounds, out int3 start, out int3 end) 327 | { 328 | start = ((bounds.Min - data -> WorldBoundsMin) / data -> CellSize).FloorToInt(); 329 | end = ((bounds.Max - data -> WorldBoundsMin) / data -> CellSize).CeilToInt(); 330 | } 331 | 332 | public T GetObject(int index) 333 | { 334 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 335 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 336 | #endif 337 | 338 | if (_itemIDToItem.TryGetValue(index, out var value)) 339 | return value; 340 | 341 | Assert.IsTrue(false); 342 | 343 | return default; 344 | } 345 | 346 | public void PrepareFreePlace(int count) 347 | { 348 | if (_buckets.Capacity - _buckets.Count() < count) 349 | _buckets.Capacity = math.ceilpow2(_buckets.Count() + count); 350 | 351 | if (_itemIDToBounds.Capacity - _itemIDToBounds.Count() < count) 352 | _itemIDToBounds.Capacity = math.ceilpow2(_itemIDToBounds.Count() + count); 353 | 354 | if (_itemIDToItem.Capacity - _itemIDToItem.Count() < count) 355 | _itemIDToItem.Capacity = math.ceilpow2(_itemIDToItem.Count() + count); 356 | } 357 | 358 | #endregion 359 | 360 | #region Query 361 | 362 | public void Query(int3 chunkIndex, NativeList resultList) 363 | { 364 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 365 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 366 | #endif 367 | 368 | var hashMapUnic = new NativeParallelHashMap(64, Allocator.Temp); 369 | var hash = Hash(chunkIndex); 370 | 371 | if (_buckets.TryGetFirstValue(hash, out var item, out var it) == false) 372 | return; 373 | 374 | do 375 | { 376 | if (hashMapUnic.TryAdd(item, 0)) 377 | resultList.Add(_itemIDToItem[item]); 378 | } while (_buckets.TryGetNextValue(out item, ref it)); 379 | } 380 | 381 | /// 382 | /// Query system to find object in . 383 | /// 384 | public void Query(Bounds bounds, NativeList resultList) 385 | { 386 | Assert.IsTrue(resultList.IsCreated); 387 | 388 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 389 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 390 | #endif 391 | bounds.Clamp(_data -> WorldBounds); 392 | 393 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 394 | 395 | var hashMapUnic = new NativeHashSet(64, Allocator.Temp); 396 | var hashPosition = new int3(0F); 397 | 398 | for (int x = start.x; x < end.x; ++x) 399 | { 400 | hashPosition.x = x; 401 | 402 | for (int y = start.y; y < end.y; ++y) 403 | { 404 | hashPosition.y = y; 405 | 406 | for (int z = start.z; z < end.z; ++z) 407 | { 408 | hashPosition.z = z; 409 | 410 | var hash = Hash(hashPosition); 411 | 412 | if (_buckets.TryGetFirstValue(hash, out var item, out var it)) 413 | { 414 | do 415 | hashMapUnic.Add(item); 416 | while (_buckets.TryGetNextValue(out item, ref it)); 417 | } 418 | } 419 | } 420 | } 421 | 422 | ExtractValueFromHashMap(hashMapUnic, bounds, resultList); 423 | } 424 | 425 | public void Query(Bounds bounds, NativeList voxelIndexes) 426 | { 427 | Assert.IsTrue(voxelIndexes.IsCreated); 428 | 429 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 430 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 431 | #endif 432 | bounds.Clamp(_data -> WorldBounds); 433 | 434 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 435 | 436 | var hashPosition = new int3(0F); 437 | 438 | for (int x = start.x; x < end.x; ++x) 439 | { 440 | hashPosition.x = x; 441 | 442 | for (int y = start.y; y < end.y; ++y) 443 | { 444 | hashPosition.y = y; 445 | 446 | for (int z = start.z; z < end.z; ++z) 447 | { 448 | hashPosition.z = z; 449 | 450 | voxelIndexes.Add(hashPosition); 451 | } 452 | } 453 | } 454 | } 455 | 456 | public void Query(Bounds obbBounds, quaternion rotation, NativeList resultList) 457 | { 458 | Assert.IsTrue(resultList.IsCreated); 459 | 460 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 461 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 462 | #endif 463 | 464 | var bounds = TransformBounds(in obbBounds, in rotation); 465 | bounds.Clamp(_data -> WorldBounds); 466 | 467 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 468 | 469 | var hashMapUnic = new NativeHashSet(64, Allocator.Temp); 470 | 471 | var hashPosition = new int3(0F); 472 | 473 | //add offset for simplify logic and allowed pruning 474 | obbBounds.Size += _data -> CellSize * 1F; 475 | 476 | var inverseRotation = math.inverse(rotation); 477 | 478 | for (int x = start.x; x < end.x; ++x) 479 | { 480 | hashPosition.x = x; 481 | 482 | for (int y = start.y; y < end.y; ++y) 483 | { 484 | hashPosition.y = y; 485 | 486 | for (int z = start.z; z < end.z; ++z) 487 | { 488 | hashPosition.z = z; 489 | 490 | var pos = GetPositionVoxel(hashPosition, true); 491 | 492 | if (obbBounds.RayCastOBBFast(pos - new float3(_data -> CellSize.x * 0.5F, 0F, 0F), Right, inverseRotation, _data -> CellSize.x) || 493 | obbBounds.RayCastOBBFast(pos - new float3(0F, _data -> CellSize.y * 0.5F, 0F), Up, inverseRotation, _data -> CellSize.y) || 494 | obbBounds.RayCastOBBFast(pos - new float3(0F, 0F, _data -> CellSize.z * 0.5F), Forward, inverseRotation, _data -> CellSize.z)) 495 | { 496 | var hash = Hash(hashPosition); 497 | 498 | if (_buckets.TryGetFirstValue(hash, out var item, out var it)) 499 | { 500 | do 501 | hashMapUnic.Add(item); 502 | while (_buckets.TryGetNextValue(out item, ref it)); 503 | } 504 | } 505 | } 506 | } 507 | } 508 | 509 | ExtractValueFromHashMap(hashMapUnic, bounds, resultList); 510 | } 511 | 512 | public void Query(Bounds obbBounds, quaternion rotation, NativeList voxelIndexes) 513 | { 514 | Assert.IsTrue(voxelIndexes.IsCreated); 515 | 516 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 517 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 518 | #endif 519 | 520 | var bounds = TransformBounds(in obbBounds, in rotation); 521 | bounds.Clamp(_data -> WorldBounds); 522 | 523 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 524 | 525 | var hashPosition = new int3(0F); 526 | 527 | //add offset for simplify logic and allowed pruning 528 | obbBounds.Size += _data -> CellSize * 1F; 529 | 530 | var inverseRotation = math.inverse(rotation); 531 | 532 | for (int x = start.x; x < end.x; ++x) 533 | { 534 | hashPosition.x = x; 535 | 536 | for (int y = start.y; y < end.y; ++y) 537 | { 538 | hashPosition.y = y; 539 | 540 | for (int z = start.z; z < end.z; ++z) 541 | { 542 | hashPosition.z = z; 543 | 544 | var pos = GetPositionVoxel(hashPosition, true); 545 | 546 | if (obbBounds.RayCastOBBFast(pos - new float3(_data -> CellSize.x * 0.5F, 0F, 0F), Right, inverseRotation, _data -> CellSize.x) || 547 | obbBounds.RayCastOBBFast(pos - new float3(0F, _data -> CellSize.y * 0.5F, 0F), Up, inverseRotation, _data -> CellSize.y) || 548 | obbBounds.RayCastOBBFast(pos - new float3(0F, 0F, _data -> CellSize.z * 0.5F), Forward, inverseRotation, _data -> CellSize.z)) 549 | voxelIndexes.Add(hashPosition); 550 | } 551 | } 552 | } 553 | } 554 | 555 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 556 | public static Bounds TransformBounds(in Bounds boundTarget, in quaternion rotation) 557 | { 558 | var b = new Bounds(boundTarget.Center, math.abs(math.mul(rotation, boundTarget.Size))); 559 | return b; 560 | } 561 | 562 | private void ExtractValueFromHashMap(NativeHashSet hashMapUnic, Bounds bounds, NativeList resultList) 563 | { 564 | using var iterator = hashMapUnic.GetEnumerator(); 565 | while (iterator.MoveNext()) 566 | { 567 | var itemID = iterator.Current; 568 | 569 | _itemIDToBounds.TryGetValue(itemID, out var b); 570 | 571 | if (bounds.Intersects(b) == false) 572 | continue; 573 | 574 | resultList.Add(_itemIDToItem[itemID]); 575 | } 576 | } 577 | 578 | public bool RayCast(Ray ray, ref T item, float length = 99999F) 579 | { 580 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 581 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 582 | #endif 583 | 584 | _data -> HasHit = false; 585 | 586 | _data -> RayOrigin = ray.origin; 587 | _data -> RayDirection = ray.direction; 588 | 589 | _voxelRay.RayCast(ref this, _data -> RayOrigin, _data -> RayDirection, length); 590 | 591 | if (_data -> HasHit == false) 592 | return false; 593 | 594 | item = _itemIDToItem[_rayHitValue]; 595 | return true; 596 | } 597 | 598 | public int QueryCount(int3 chunkIndex) 599 | { 600 | var hash = Hash(chunkIndex); 601 | 602 | int counter = 0; 603 | 604 | if (_buckets.TryGetFirstValue(hash, out _, out var it)) 605 | { 606 | ++counter; 607 | 608 | while (_buckets.TryGetNextValue(out _, ref it)) 609 | ++counter; 610 | } 611 | 612 | return counter; 613 | } 614 | 615 | #endregion 616 | 617 | #region Hashing 618 | 619 | public static uint Hash(int3 cellIndex) 620 | { 621 | return math.hash(cellIndex); 622 | } 623 | 624 | private int GetCellCountFromSize(float3 size) 625 | { 626 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 627 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 628 | #endif 629 | 630 | var deltaSize = size / _data -> CellSize; 631 | 632 | return (int)math.ceil(deltaSize.x) * (int)math.ceil(deltaSize.y) * (int)math.ceil(deltaSize.z); 633 | } 634 | 635 | public float3 GetPositionVoxel(int3 index, bool center) 636 | { 637 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 638 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 639 | #endif 640 | var pos = index * _data -> CellSize + _data -> WorldBoundsMin; 641 | 642 | if (center) 643 | pos += _data -> CellSize * 0.5F; 644 | 645 | return pos; 646 | } 647 | 648 | public int3 GetIndexVoxel(float3 position) 649 | { 650 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 651 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 652 | #endif 653 | position -= _data -> WorldBoundsMin; 654 | 655 | position /= _data -> CellSize; 656 | 657 | return position.FloorToInt(); 658 | } 659 | 660 | #endregion 661 | 662 | #region Implementation of IRay 663 | 664 | /// 665 | public bool OnTraversingVoxel(int3 voxelIndex) 666 | { 667 | if (math.any(voxelIndex > _data -> CellCount)) //if voxel still in world 668 | return true; 669 | 670 | var hash = Hash(voxelIndex); 671 | 672 | if (_buckets.TryGetFirstValue(hash, out var itemID, out var it)) 673 | do 674 | { 675 | _itemIDToBounds.TryGetValue(itemID, out var b); 676 | 677 | if (b.GetEnterPositionAABB(_data -> RayOrigin, _data -> RayDirection, 1 << 25, out _) == false) 678 | continue; 679 | 680 | _data -> HasHit = true; 681 | _rayHitValue = itemID; 682 | 683 | return true; 684 | } while (_buckets.TryGetNextValue(out itemID, ref it)); 685 | 686 | return false; 687 | } 688 | 689 | /// 690 | public void GetIndexiesVoxel(T item, NativeList results) 691 | { 692 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 693 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 694 | #endif 695 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 696 | 697 | bounds.Clamp(_data -> WorldBounds); 698 | 699 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 700 | 701 | var hashPosition = new int3(0F); 702 | 703 | for (int x = start.x; x < end.x; ++x) 704 | { 705 | hashPosition.x = x; 706 | 707 | for (int y = start.y; y < end.y; ++y) 708 | { 709 | hashPosition.y = y; 710 | 711 | for (int z = start.z; z < end.z; ++z) 712 | { 713 | hashPosition.z = z; 714 | 715 | results.Add(hashPosition); 716 | } 717 | } 718 | } 719 | } 720 | 721 | #endregion 722 | 723 | public Concurrent ToConcurrent() 724 | { 725 | return new Concurrent 726 | { 727 | _data = _data, 728 | _itemIDToBounds = _itemIDToBounds.AsParallelWriter(), 729 | _itemIDToItem = _itemIDToItem.AsParallelWriter(), 730 | _buckets = _buckets.AsParallelWriter(), 731 | 732 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 733 | m_Safety = m_Safety 734 | #endif 735 | }; 736 | } 737 | 738 | #region Variables 739 | 740 | private static readonly float3 Forward = new float3(0F, 0F, 1F); 741 | private static readonly float3 Up = new float3(0F, 1F, 0F); 742 | private static readonly float3 Right = new float3(1F, 0F, 0F); 743 | 744 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 745 | // ReSharper disable InconsistentNaming 746 | private AtomicSafetyHandle m_Safety; 747 | [NativeSetClassTypeToNullOnSchedule] 748 | private DisposeSentinel m_DisposeSentinel; 749 | // ReSharper restore InconsistentNaming 750 | #endif 751 | 752 | private Allocator _allocatorLabel; 753 | 754 | [NativeDisableUnsafePtrRestriction] 755 | private SpatialHashData* _data; 756 | private NativeParallelMultiHashMap _buckets; //4 757 | private NativeParallelHashMap _itemIDToBounds; //4 758 | private NativeParallelHashMap _itemIDToItem; //4 759 | 760 | private NativeParallelHashSet _helpMoveHashMapOld; 761 | private NativeParallelHashSet _helpMoveHashMapNew; 762 | private VoxelRay> _voxelRay; 763 | private int _rayHitValue; 764 | 765 | #endregion 766 | 767 | #region Proprieties 768 | 769 | public bool IsCreated => _data != null; 770 | 771 | public int ItemCount 772 | { 773 | get 774 | { 775 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 776 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 777 | #endif 778 | return _itemIDToBounds.Count(); 779 | } 780 | } 781 | 782 | public int BucketItemCount 783 | { 784 | get 785 | { 786 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 787 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 788 | #endif 789 | return _buckets.Count(); 790 | } 791 | } 792 | 793 | public float3 CellSize 794 | { 795 | get 796 | { 797 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 798 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 799 | #endif 800 | return _data -> CellSize; 801 | } 802 | } 803 | 804 | public Bounds WorldBounds 805 | { 806 | get 807 | { 808 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 809 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 810 | #endif 811 | return _data -> WorldBounds; 812 | } 813 | } 814 | 815 | public int3 CellCount 816 | { 817 | get 818 | { 819 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 820 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 821 | #endif 822 | return _data -> CellCount; 823 | } 824 | } 825 | 826 | #if UNITY_EDITOR 827 | public NativeParallelMultiHashMap DebugBuckets => _buckets; 828 | public NativeParallelHashMap DebugIDToItem => _itemIDToItem; 829 | public Bounds DebugRayCastBounds => _data -> RayCastBound; 830 | public VoxelRay> DebugVoxelRay => _voxelRay; 831 | #endif 832 | 833 | #endregion 834 | 835 | public struct Concurrent 836 | { 837 | public bool TryAdd(ref T item) 838 | { 839 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 840 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 841 | #endif 842 | 843 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 844 | 845 | bounds.Clamp(_data -> WorldBounds); 846 | 847 | var itemID = Interlocked.Increment(ref _data -> Counter); 848 | item.SpatialHashingIndex = itemID; 849 | 850 | if (_itemIDToBounds.TryAdd(itemID, bounds) == false || _itemIDToItem.TryAdd(itemID, item) == false) 851 | return false; 852 | 853 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 854 | 855 | var hashPosition = new int3(0F); 856 | 857 | for (int x = start.x; x < end.x; ++x) 858 | { 859 | hashPosition.x = x; 860 | 861 | for (int y = start.y; y < end.y; ++y) 862 | { 863 | hashPosition.y = y; 864 | 865 | for (int z = start.z; z < end.z; ++z) 866 | { 867 | hashPosition.z = z; 868 | 869 | var hash = Hash(hashPosition); 870 | _buckets.Add(hash, itemID); 871 | } 872 | } 873 | } 874 | 875 | return true; 876 | } 877 | 878 | /// 879 | /// Add fast after a remove for moving or scaling item 880 | /// DOESN'T WORK YET WAIT UNITY OVERRIDE VALUE IN HASHMAP 881 | /// 882 | /// 883 | /// 884 | public void AddFast(in T item) 885 | { 886 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 887 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 888 | #endif 889 | 890 | var itemID = item.SpatialHashingIndex; 891 | var bounds = new Bounds(item.GetCenter(), item.GetSize()); 892 | 893 | bounds.Clamp(_data -> WorldBounds); 894 | 895 | //TODO Replace with Override 896 | if (_itemIDToBounds.TryAdd(itemID, bounds) == false || _itemIDToItem.TryAdd(itemID, item) == false) 897 | return; 898 | 899 | CalculStartEndIterationInternal(_data, bounds, out var start, out var end); 900 | 901 | var hashPosition = new int3(0F); 902 | 903 | for (int x = start.x; x < end.x; ++x) 904 | { 905 | hashPosition.x = x; 906 | 907 | for (int y = start.y; y < end.y; ++y) 908 | { 909 | hashPosition.y = y; 910 | 911 | for (int z = start.z; z < end.z; ++z) 912 | { 913 | hashPosition.z = z; 914 | 915 | var hash = Hash(hashPosition); 916 | _buckets.Add(hash, itemID); 917 | } 918 | } 919 | } 920 | } 921 | 922 | #region Variables 923 | 924 | [NativeDisableUnsafePtrRestriction] 925 | internal SpatialHashData* _data; 926 | 927 | internal NativeParallelMultiHashMap.ParallelWriter _buckets; //4 928 | internal NativeParallelHashMap.ParallelWriter _itemIDToBounds; //4 929 | internal NativeParallelHashMap.ParallelWriter _itemIDToItem; //4 930 | 931 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 932 | internal AtomicSafetyHandle m_Safety; 933 | #endif 934 | 935 | [NativeSetThreadIndex] 936 | #pragma warning disable 649 937 | internal int _threadIndex; 938 | #pragma warning restore 649 939 | 940 | #endregion 941 | } 942 | } 943 | 944 | [StructLayout(LayoutKind.Sequential)] 945 | public struct SpatialHashData 946 | { 947 | public Bounds WorldBounds; //24 948 | public float3 WorldBoundsMin; //12 949 | 950 | public Bounds RayCastBound; //24 951 | public float3 CellSize; //12 952 | 953 | public float3 RayOrigin; //12 954 | public float3 RayDirection; //12 955 | public int3 CellCount; //12 956 | 957 | public int Counter; //4 958 | public bool HasHit; //1 959 | } 960 | 961 | public interface ISpatialHashingItem : IEquatable 962 | { 963 | [Pure] 964 | float3 GetCenter(); 965 | 966 | [Pure] 967 | float3 GetSize(); 968 | 969 | int SpatialHashingIndex { get; set; } 970 | } 971 | } --------------------------------------------------------------------------------