diff --git a/SpaceScrapper/Assets/Debug.meta b/SpaceScrapper/Assets/Debug.meta new file mode 100644 index 0000000..9b5bc0f --- /dev/null +++ b/SpaceScrapper/Assets/Debug.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 80c01fc74d6d14a6e896c17b9c1785db +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/DebugAssembly.asmdef b/SpaceScrapper/Assets/Debug/DebugAssembly.asmdef new file mode 100644 index 0000000..976caf2 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/DebugAssembly.asmdef @@ -0,0 +1,18 @@ +{ + "name": "DebugAssembly", + "rootNamespace": "", + "references": [ + "GUID:a1d73af5f72ca4e929569ac515d86db8" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/SpaceScrapper/Assets/Debug/DebugAssembly.asmdef.meta b/SpaceScrapper/Assets/Debug/DebugAssembly.asmdef.meta new file mode 100644 index 0000000..79c4b56 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/DebugAssembly.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7e8fe2f19b9c44435b92ca65d71ee22a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest.meta b/SpaceScrapper/Assets/Debug/PoissonSamplingTest.meta new file mode 100644 index 0000000..a16bd75 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb817273efde44856b65e9acb427324d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManager.cs b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManager.cs new file mode 100644 index 0000000..dc7b7fd --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManager.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using SpaceScrapper.MathLib; +using UnityEditor; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace SpaceScrapper.Debug.PoissonSamplingTest +{ + [ExecuteInEditMode] + public class PointsManager : MonoBehaviour + { + [SerializeField] + private int seed; + + [Tooltip("Region within which points will be distributed.")] + [SerializeField] + private Bounds region; + + [Tooltip("Maximum iteration before stopping sampling with current point")] + [Range(10, 500)] + [SerializeField] + private int maxSamplingTries = 30; + + [Range(0.1f, 100f)] + [SerializeField] + private float samplingRadius = 1; + + private List points; + + public void GeneratePoints() + { + Random.InitState(seed); + points = PoissonDiscSampling.GeneratePoints(samplingRadius, region.size, maxSamplingTries); + UnityEngine.Debug.Log($"Generated {points.Count} points"); + SceneView.RepaintAll(); + } + + private void OnDrawGizmos() + { + // Draw Bounding box + Gizmos.color = Color.yellow; + Gizmos.DrawWireCube(region.center, region.size); + + if (points == null) return; + + Gizmos.color = Color.red; + foreach (var v in points) + { + Gizmos.DrawSphere((Vector3)v - region.extents, samplingRadius/10f); + } + } + } +} \ No newline at end of file diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManager.cs.meta b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManager.cs.meta new file mode 100644 index 0000000..3cdc554 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 035d5b4013f64468ab6834503176d698 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManagerEditor.cs b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManagerEditor.cs new file mode 100644 index 0000000..79b5c06 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManagerEditor.cs @@ -0,0 +1,22 @@ +using UnityEditor; +using UnityEngine; + +namespace SpaceScrapper.Debug.PoissonSamplingTest +{ + + [CustomEditor(typeof(PointsManager), false)] + public class PointsManagerEditor : Editor + { + public override void OnInspectorGUI() + { + DrawDefaultInspector(); + + EditorGUI.BeginDisabledGroup(serializedObject.isEditingMultipleObjects); + if (GUILayout.Button("Generate")) + { + ((PointsManager)target).GeneratePoints(); + } + EditorGUI.EndDisabledGroup(); + } + } +} diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManagerEditor.cs.meta b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManagerEditor.cs.meta new file mode 100644 index 0000000..ee1392e --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PointsManagerEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7cc653bfc8c794330ae92444c2842d5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PoissonSamplingTest.unity b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PoissonSamplingTest.unity new file mode 100644 index 0000000..781fd07 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PoissonSamplingTest.unity @@ -0,0 +1,408 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.18028355, g: 0.22571374, b: 0.30692255, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &183106022 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 183106024} + - component: {fileID: 183106023} + - component: {fileID: 183106025} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &183106023 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 183106022} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &183106024 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 183106022} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &183106025 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 183106022} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 1 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} +--- !u!1 &709152955 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 709152956} + - component: {fileID: 709152957} + m_Layer: 0 + m_Name: PointsManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &709152956 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 709152955} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &709152957 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 709152955} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 035d5b4013f64468ab6834503176d698, type: 3} + m_Name: + m_EditorClassIdentifier: + seed: 1000 + region: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 10, y: 10, z: 0} + maxSamplingTries: 30 + samplingRadius: 1 +--- !u!1 &1466664674 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1466664677} + - component: {fileID: 1466664676} + - component: {fileID: 1466664675} + - component: {fileID: 1466664678} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1466664675 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1466664674} + m_Enabled: 1 +--- !u!20 &1466664676 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1466664674} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1466664677 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1466664674} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1466664678 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1466664674} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 diff --git a/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PoissonSamplingTest.unity.meta b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PoissonSamplingTest.unity.meta new file mode 100644 index 0000000..94914b8 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/PoissonSamplingTest/PoissonSamplingTest.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 136867acf013941208cad8567399771c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest.meta b/SpaceScrapper/Assets/Debug/VoronoiTest.meta new file mode 100644 index 0000000..41f90b4 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f56a20f506a6941daa88c03c13fc26ad +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManager.cs b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManager.cs new file mode 100644 index 0000000..c712d4e --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManager.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Linq; +using SpaceScrapper.MathLib; +using SpaceScrapper.MathLib.VoronoiLib; +using SpaceScrapper.MathLib.VoronoiLib.Structures; +using UnityEditor; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace SpaceScrapper.Debug.VoronoiTest +{ + public class VoronoiManager : MonoBehaviour + { + [SerializeField] + private int seed; + + [Tooltip("Region within which points will be distributed.")] + [SerializeField] + private Bounds region; + + [Tooltip("Maximum iteration before stopping sampling with current point")] + [Range(10, 500)] + [SerializeField] + private int maxSamplingTries = 30; + + [Range(0.1f, 100f)] + [SerializeField] + private float samplingRadius = 1; + + [SerializeField] + private bool drawPointDistribution = false; + + [SerializeField] + private bool removeOpenTiles = false; + + [SerializeField] + [Tooltip("Radius for vertices when displaying Voronoi")] + private float vertexRadius = 0.1f; + + private List points; + private List sites; + + public void GenerateVoronoi() + { + UnityEngine.Debug.Log($"Generating Voronoi diagram ..."); + Random.InitState(seed); + + points = PoissonDiscSampling.GeneratePoints(samplingRadius, region.size, maxSamplingTries); + UnityEngine.Debug.Log($"Generated {points.Count} points"); + + sites = points.Select(p => new FortuneSite(p.x, p.y)).ToList(); + FortunesAlgorithm.Run(sites, 0, 0, region.size.x, region.size.y); + + UnityEngine.Debug.Log($"Generated {sites.Count} voronoi \"tiles\""); + + UnityEngine.Debug.Log($"... Voronoi generation complete."); + SceneView.RepaintAll(); + } + + private void OnDrawGizmos() + { + Gizmos.color = Color.yellow; + Gizmos.DrawWireCube(region.center, region.size); + + DrawPointDistribution(); + DrawVoronoi(); + } + + private void DrawPointDistribution() + { + if (!drawPointDistribution) return; + + if (points == null) return; + + Gizmos.color = Color.red; + foreach (var v in points) + { + Gizmos.DrawSphere((Vector3)v - region.extents, vertexRadius); + } + } + + private void DrawVoronoi() + { + if (sites == null) return; + var deltaBounds = region.center - region.extents; + + foreach (FortuneSite s in sites) + { + if (removeOpenTiles && s.ToClose) continue; + + Handles.color = Color.green; + Handles.DrawSolidDisc(new Vector3((float)s.X, (float)s.Y, 0f) + deltaBounds, Vector3.back, + vertexRadius); + + Handles.color = Color.red; + foreach (var c in s.Corners) + { + Handles.DrawSolidDisc(new Vector3((float)c.X, (float)c.Y, 0f) + deltaBounds, Vector3.back, + vertexRadius); + } + + //draw site edges + Handles.color = Color.white; + foreach (VEdge e in s.Cell) + { + if (e.Start != null && e.End != null) + { + Vector2 start, end; + start.x = (float)e.Start.X; + start.y = (float)e.Start.Y; + end.x = (float)e.End.X; + end.y = (float)e.End.Y; + Handles.DrawLine((Vector3)start + deltaBounds, (Vector3)end + deltaBounds, 3f); + } + else + { + UnityEngine.Debug.Log($"{e.ID} - Edge missing vertex"); + } + } + } + } + } +} \ No newline at end of file diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManager.cs.meta b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManager.cs.meta new file mode 100644 index 0000000..70b6ea7 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1480bfcf5185468590d4833a642a7422 +timeCreated: 1638914760 \ No newline at end of file diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManagerEditor.cs b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManagerEditor.cs new file mode 100644 index 0000000..26a2c33 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManagerEditor.cs @@ -0,0 +1,21 @@ +using UnityEditor; +using UnityEngine; + +namespace SpaceScrapper.Debug.VoronoiTest +{ + [CustomEditor(typeof(VoronoiManager), false)] + public class VoronoiManagerEditor : Editor + { + public override void OnInspectorGUI() + { + DrawDefaultInspector(); + + EditorGUI.BeginDisabledGroup(serializedObject.isEditingMultipleObjects); + if (GUILayout.Button("Generate")) + { + ((VoronoiManager)target).GenerateVoronoi(); + } + EditorGUI.EndDisabledGroup(); + } + } +} \ No newline at end of file diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManagerEditor.cs.meta b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManagerEditor.cs.meta new file mode 100644 index 0000000..d90e943 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiManagerEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 596eced433e64292a45cedd14546d404 +timeCreated: 1638914857 \ No newline at end of file diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiTest.unity b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiTest.unity new file mode 100644 index 0000000..ef2ce40 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiTest.unity @@ -0,0 +1,464 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.18028355, g: 0.22571374, b: 0.30692255, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &17075729 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 17075732} + - component: {fileID: 17075731} + - component: {fileID: 17075730} + - component: {fileID: 17075733} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &17075730 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 17075729} + m_Enabled: 1 +--- !u!20 &17075731 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 17075729} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0, g: 0, b: 0, a: 1} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5.79 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &17075732 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 17075729} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &17075733 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 17075729} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 +--- !u!1 &55362383 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 55362385} + - component: {fileID: 55362384} + m_Layer: 0 + m_Name: VoronoiManager_2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &55362384 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 55362383} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1480bfcf5185468590d4833a642a7422, type: 3} + m_Name: + m_EditorClassIdentifier: + seed: 2 + region: + m_Center: {x: 22, y: 0, z: 0} + m_Extent: {x: 10, y: 10, z: 0} + maxSamplingTries: 30 + samplingRadius: 2 + drawPointDistribution: 0 + removeOpenTiles: 1 + vertexRadius: 0.1 +--- !u!4 &55362385 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 55362383} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 22, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &368227167 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 368227169} + - component: {fileID: 368227168} + - component: {fileID: 368227170} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &368227168 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 368227167} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &368227169 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 368227167} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &368227170 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 368227167} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 1 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} +--- !u!1 &1754886272 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1754886273} + - component: {fileID: 1754886274} + m_Layer: 0 + m_Name: VoronoiManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1754886273 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1754886272} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1754886274 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1754886272} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1480bfcf5185468590d4833a642a7422, type: 3} + m_Name: + m_EditorClassIdentifier: + seed: 2 + region: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 10, y: 10, z: 0} + maxSamplingTries: 30 + samplingRadius: 2 + drawPointDistribution: 0 + removeOpenTiles: 0 + vertexRadius: 0.1 diff --git a/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiTest.unity.meta b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiTest.unity.meta new file mode 100644 index 0000000..e7099c6 --- /dev/null +++ b/SpaceScrapper/Assets/Debug/VoronoiTest/VoronoiTest.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7dc27bfc6100e4054beb69d66668dea6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib.meta new file mode 100644 index 0000000..42a8078 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4cb04cb64aa2413e96a1de45f76dabfa +timeCreated: 1638907589 \ No newline at end of file diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/MathLib.asmdef b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/MathLib.asmdef new file mode 100644 index 0000000..5869611 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/MathLib.asmdef @@ -0,0 +1,3 @@ +{ + "name": "MathLib" +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/MathLib.asmdef.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/MathLib.asmdef.meta new file mode 100644 index 0000000..f94c8f7 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/MathLib.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a1d73af5f72ca4e929569ac515d86db8 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/PoissonDiscSampling.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/PoissonDiscSampling.cs new file mode 100644 index 0000000..623bb53 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/PoissonDiscSampling.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace SpaceScrapper.MathLib +{ + public static class PoissonDiscSampling + { + public static List GeneratePoints(float radius, Vector2 sampleRegionSize, + int numSamplesBeforeRejection = 30) + { + float cellSize = radius / Mathf.Sqrt(2); + + int[,] grid = new int[Mathf.CeilToInt(sampleRegionSize.x / cellSize), + Mathf.CeilToInt(sampleRegionSize.y / cellSize)]; + List points = new List(); + List spawnPoints = new List(); + + spawnPoints.Add(sampleRegionSize / 2); + while (spawnPoints.Count > 0) + { + int spawnIndex = Random.Range(0, spawnPoints.Count); + Vector2 spawnCentre = spawnPoints[spawnIndex]; + bool candidateAccepted = false; + + for (int i = 0; i < numSamplesBeforeRejection; i++) + { + float angle = Random.value * Mathf.PI * 2; + Vector2 dir = new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)); + Vector2 candidate = spawnCentre + dir * Random.Range(radius, 2 * radius); + if (IsValid(candidate, sampleRegionSize, cellSize, radius, points, grid)) + { + points.Add(candidate); + spawnPoints.Add(candidate); + grid[(int)(candidate.x / cellSize), (int)(candidate.y / cellSize)] = points.Count; + candidateAccepted = true; + break; + } + } + + if (!candidateAccepted) + { + spawnPoints.RemoveAt(spawnIndex); + } + + } + + return points; + } + + private static bool IsValid(Vector2 candidate, Vector2 sampleRegionSize, float cellSize, float radius, + List points, int[,] grid) + { + if (candidate.x >= 0 && candidate.x < sampleRegionSize.x && candidate.y >= 0 && + candidate.y < sampleRegionSize.y) + { + int cellX = (int)(candidate.x / cellSize); + int cellY = (int)(candidate.y / cellSize); + int searchStartX = Mathf.Max(0, cellX - 2); + int searchEndX = Mathf.Min(cellX + 2, grid.GetLength(0) - 1); + int searchStartY = Mathf.Max(0, cellY - 2); + int searchEndY = Mathf.Min(cellY + 2, grid.GetLength(1) - 1); + + for (int x = searchStartX; x <= searchEndX; x++) + { + for (int y = searchStartY; y <= searchEndY; y++) + { + int pointIndex = grid[x, y] - 1; + if (pointIndex != -1) + { + float sqrDst = (candidate - points[pointIndex]).sqrMagnitude; + if (sqrDst < radius * radius) + { + return false; + } + } + } + } + + return true; + } + + return false; + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/PoissonDiscSampling.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/PoissonDiscSampling.cs.meta new file mode 100644 index 0000000..4571c7e --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/PoissonDiscSampling.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 842ef795684e94eef83a056097165c7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib.meta new file mode 100644 index 0000000..c6d34bc --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 98f87f025dd8429ea163e0ff18374c0a +timeCreated: 1638907605 \ No newline at end of file diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/FortunesAlgorithm.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/FortunesAlgorithm.cs new file mode 100644 index 0000000..4bc9fcf --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/FortunesAlgorithm.cs @@ -0,0 +1,403 @@ +using System.Collections.Generic; +using SpaceScrapper.MathLib.VoronoiLib.Structures; +using UnityEngine; + +namespace SpaceScrapper.MathLib.VoronoiLib +{ + public static class FortunesAlgorithm + { + public static LinkedList Run(List sites, double minX, double minY, double maxX, double maxY) + { + VEdge.IDCounter = 0; + + var eventQueue = new MinHeap(5 * sites.Count); + foreach (var s in sites) + { + eventQueue.Insert(new FortuneSiteEvent(s)); + } + //init tree + var beachLine = new BeachLine(); + var edges = new LinkedList(); + var deleted = new HashSet(); + + //init edge list + while (eventQueue.Count != 0) + { + var fEvent = eventQueue.Pop(); + if (fEvent is FortuneSiteEvent) + beachLine.AddBeachSection((FortuneSiteEvent)fEvent, eventQueue, deleted, edges); + else + { + if (deleted.Contains((FortuneCircleEvent)fEvent)) + { + deleted.Remove((FortuneCircleEvent)fEvent); + } + else + { + beachLine.RemoveBeachSection((FortuneCircleEvent)fEvent, eventQueue, deleted, edges); + } + } + } + + + //clip edges + var edgeNode = edges.First; + while (edgeNode != null) + { + var edge = edgeNode.Value; + var next = edgeNode.Next; + + var valid = ClipEdge(edge, minX, minY, maxX, maxY); + if (!valid) + { + if (edge.Left != null) + { + edge.Left.Cell.Remove(edge); + } + if (edge.Right != null) + { + edge.Right.Cell.Remove(edge); + } + + TagSitesForClosing(edge); + edges.Remove(edgeNode); + } + //advance + edgeNode = next; + } + + AssignCornersToSites(sites, minX, minY, maxX, maxY); + CleanNeighbors(sites); + + return edges; + } + + private static void CleanNeighbors(List sites) + { + foreach(var s in sites) + { + s.Neighbors.Clear(); + foreach(var e in s.Cell) + { + s.Neighbors.Add(e.Left); + s.Neighbors.Add(e.Right); + } + + s.Neighbors.Remove(s); // site not a neighbor to itself + } + } + + private static void AssignCornersToSites(List sites, double minX, double minY, double maxX, double maxY) + { + Vector2[] corners = { + new Vector2((float)minX, (float)minY), + new Vector2((float)minX, (float)maxY), + new Vector2((float)maxX, (float)minY), + new Vector2((float)maxX, (float)maxY) + }; + + FortuneSite[] cornerSites = new FortuneSite[4]; + float[] cornerDistances = { + float.MaxValue, + float.MaxValue, + float.MaxValue, + float.MaxValue + }; + + foreach (FortuneSite s in sites) + { + // If site needs closing, + if (s.ToClose) + { + Vector2 vs = new Vector2((float)s.X, (float)s.Y); + for(int c = 0; c < corners.Length; c++) + { + float dist = Vector2.Distance(vs, corners[c]); + + // if site is closer to corner, add site at corner position in array + if (dist < cornerDistances[c]) + { + cornerDistances[c] = dist; + cornerSites[c] = s; + } + } + } + } + + // go through array and assign corner to respective site + for (int c = 0; c < corners.Length; c++) + { + cornerSites[c].Corners.Add(new VPoint(corners[c].x, corners[c].y)); + } + } + + //combination of personal ray clipping alg and cohen sutherland + private static bool ClipEdge(VEdge edge, double minX, double minY, double maxX, double maxY) + { + var accept = false; + + //if its a ray + if (edge.End == null) + { + accept = ClipRay(edge, minX, minY, maxX, maxY); + } + else + { + //Cohen–Sutherland + var start = ComputeOutCode(edge.Start.X, edge.Start.Y, minX, minY, maxX, maxY); + var end = ComputeOutCode(edge.End.X, edge.End.Y, minX, minY, maxX, maxY); + + while (true) + { + if ((start | end) == 0) + { + accept = true; + break; + } + if ((start & end) != 0) + { + break; + } + + double x = -1, y = -1; + var outcode = start != 0 ? start : end; + + if ((outcode & 0x8) != 0) // top + { + x = edge.Start.X + (edge.End.X - edge.Start.X) * (maxY - edge.Start.Y) / (edge.End.Y - edge.Start.Y); + y = maxY; + } + else if ((outcode & 0x4) != 0) // bottom + { + x = edge.Start.X + (edge.End.X - edge.Start.X) * (minY - edge.Start.Y) / (edge.End.Y - edge.Start.Y); + y = minY; + } + else if ((outcode & 0x2) != 0) //right + { + y = edge.Start.Y + (edge.End.Y - edge.Start.Y) * (maxX - edge.Start.X) / (edge.End.X - edge.Start.X); + x = maxX; + } + else if ((outcode & 0x1) != 0) //left + { + y = edge.Start.Y + (edge.End.Y - edge.Start.Y) * (minX - edge.Start.X) / (edge.End.X - edge.Start.X); + x = minX; + } + + if (outcode == start) + { + edge.Start = new VPoint(x, y); + start = ComputeOutCode(x, y, minX, minY, maxX, maxY); + } + else + { + edge.End = new VPoint(x, y); + end = ComputeOutCode(x, y, minX, minY, maxX, maxY); + } + + TagSitesForClosing(edge); + } + } + //if we have a neighbor + if (edge.Neighbor != null) + { + //check it + var valid = ClipEdge(edge.Neighbor, minX, minY, maxX, maxY); + //both are valid + if (accept && valid) + { + edge.Start = edge.Neighbor.End; + } + //this edge isn't valid, but the neighbor is + //flip and set + if (!accept && valid) + { + edge.Start = edge.Neighbor.End; + edge.End = edge.Neighbor.Start; + accept = true; + } + } + return accept; + } + + private static int ComputeOutCode(double x, double y, double minX, double minY, double maxX, double maxY) + { + int code = 0; + if (x.ApproxEqual(minX) || x.ApproxEqual(maxX)) + { } + else if (x < minX) + code |= 0x1; + else if (x > maxX) + code |= 0x2; + + if (y.ApproxEqual(minY) || x.ApproxEqual(maxY)) + { } + else if (y < minY) + code |= 0x4; + else if (y > maxY) + code |= 0x8; + return code; + } + + private static bool ClipRay(VEdge edge, double minX, double minY, double maxX, double maxY) + { + var start = edge.Start; + //horizontal ray + if (edge.SlopeRise.ApproxEqual(0)) + { + if (!Within(start.Y, minY, maxY)) + return false; + if (edge.SlopeRun > 0 && start.X > maxX) + return false; + if (edge.SlopeRun < 0 && start.X < minX) + return false; + + if (Within(start.X, minX, maxX)) + { + if (edge.SlopeRun > 0) + edge.End = new VPoint(maxX, start.Y); + else + edge.End = new VPoint(minX, start.Y); + } + else + { + if (edge.SlopeRun > 0) + { + edge.Start = new VPoint(minX, start.Y); + edge.End = new VPoint(maxX, start.Y); + } + else + { + edge.Start = new VPoint(maxX, start.Y); + edge.End = new VPoint(minX, start.Y); + } + } + + TagSitesForClosing(edge); + + return true; + } + //vertical ray + if (edge.SlopeRun.ApproxEqual(0)) + { + if (start.X < minX || start.X > maxX) + return false; + if (edge.SlopeRise > 0 && start.Y > maxY) + return false; + if (edge.SlopeRise < 0 && start.Y < minY) + return false; + if (Within(start.Y, minY, maxY)) + { + if (edge.SlopeRise > 0) + edge.End = new VPoint(start.X, maxY); + else + edge.End = new VPoint(start.X, minY); + } + else + { + if (edge.SlopeRise > 0) + { + edge.Start = new VPoint(start.X, minY); + edge.End = new VPoint(start.X, maxY); + } + else + { + edge.Start = new VPoint(start.X, maxY); + edge.End = new VPoint(start.X, minY); + } + } + + TagSitesForClosing(edge); + return true; + } + + //works for outside + Debug.Assert(edge.Slope != null, "edge.Slope != null"); + Debug.Assert(edge.Intercept != null, "edge.Intercept != null"); + var topX = new VPoint(CalcX(edge.Slope.Value, maxY, edge.Intercept.Value), maxY); + var bottomX = new VPoint(CalcX(edge.Slope.Value, minY, edge.Intercept.Value), minY); + var leftY = new VPoint(minX, CalcY(edge.Slope.Value, minX, edge.Intercept.Value)); + var rightY = new VPoint(maxX, CalcY(edge.Slope.Value, maxX, edge.Intercept.Value)); + + //reject intersections not within bounds + var candidates = new List(); + if (Within(topX.X, minX, maxX)) + candidates.Add(topX); + if (Within(bottomX.X, minX, maxX)) + candidates.Add(bottomX); + if (Within(leftY.Y, minY, maxY)) + candidates.Add(leftY); + if (Within(rightY.Y, minY, maxY)) + candidates.Add(rightY); + + //reject candidates which don't align with the slope + for (var i = candidates.Count - 1; i > -1; i--) + { + var candidate = candidates[i]; + //grab vector representing the edge + var ax = candidate.X - start.X; + var ay = candidate.Y - start.Y; + if (edge.SlopeRun * ax + edge.SlopeRise * ay < 0) + candidates.RemoveAt(i); + } + + //if there are two candidates we are outside the closer one is start + //the further one is the end + if (candidates.Count == 2) + { + var ax = candidates[0].X - start.X; + var ay = candidates[0].Y - start.Y; + var bx = candidates[1].X - start.X; + var by = candidates[1].Y - start.Y; + if (ax * ax + ay * ay > bx * bx + by * by) + { + edge.Start = candidates[1]; + edge.End = candidates[0]; + } + else + { + edge.Start = candidates[0]; + edge.End = candidates[1]; + } + + TagSitesForClosing(edge); + } + + //if there is one candidate we are inside + if (candidates.Count == 1) + { + edge.End = candidates[0]; + TagSitesForClosing(edge); + } + + //there were no candidates + return edge.End != null; + } + + private static void TagSitesForClosing(VEdge edge) + { + if (edge.Left != null) + { + edge.Left.ToClose = true; + } + if (edge.Right != null) + { + edge.Right.ToClose = true; + } + } + + private static bool Within(double x, double a, double b) + { + return x.ApproxGreaterThanOrEqualTo(a) && x.ApproxLessThanOrEqualTo(b); + } + + private static double CalcY(double m, double x, double b) + { + return m * x + b; + } + + private static double CalcX(double m, double y, double b) + { + return (y - b) / m; + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/FortunesAlgorithm.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/FortunesAlgorithm.cs.meta new file mode 100644 index 0000000..965b3a5 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/FortunesAlgorithm.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eda1aad6d1f9c43fca0dc3ef24de87a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/ParabolaMath.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/ParabolaMath.cs new file mode 100644 index 0000000..9b5be99 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/ParabolaMath.cs @@ -0,0 +1,46 @@ +using System.Runtime.CompilerServices; + +namespace SpaceScrapper.MathLib.VoronoiLib +{ + public static class ParabolaMath + { + public const double EPSILON = double.Epsilon*1E100; + + public static double EvalParabola(double focusX, double focusY, double directrix, double x) + { + return .5*( (x - focusX) * (x - focusX) /(focusY - directrix) + focusY + directrix); + } + + //gives the intersect point such that parabola 1 will be on top of parabola 2 slightly before the intersect + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double IntersectParabolaX(double focus1X, double focus1Y, double focus2X, double focus2Y, + double directrix) + { + //admittedly this is pure voodoo. + //there is attached documentation for this function + return focus1Y.ApproxEqual(focus2Y) + ? (focus1X + focus2X)/2 + : (focus1X*(directrix - focus2Y) + focus2X*(focus1Y - directrix) + + System.Math.Sqrt((directrix - focus1Y)*(directrix - focus2Y)* + ((focus1X - focus2X)*(focus1X - focus2X) + + (focus1Y - focus2Y)*(focus1Y - focus2Y)) + ) + )/(focus1Y - focus2Y); + } + + public static bool ApproxEqual(this double value1, double value2) + { + return System.Math.Abs(value1 - value2) <= EPSILON; + } + + public static bool ApproxGreaterThanOrEqualTo(this double value1, double value2) + { + return value1 > value2 || value1.ApproxEqual(value2); + } + + public static bool ApproxLessThanOrEqualTo(this double value1, double value2) + { + return value1 < value2 || value1.ApproxEqual(value2); + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/ParabolaMath.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/ParabolaMath.cs.meta new file mode 100644 index 0000000..26275ba --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/ParabolaMath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1ce1cfd568c24cfaba63aea5db3d68f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures.meta new file mode 100644 index 0000000..34d821e --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a33125806c2b49028b93049e084981f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/BeachLine.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/BeachLine.cs new file mode 100644 index 0000000..af3e8a4 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/BeachLine.cs @@ -0,0 +1,413 @@ +using System.Collections.Generic; + +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + internal class BeachSection + { + internal FortuneSite Site { get;} + internal VEdge Edge { get; set; } + //NOTE: this will change + internal FortuneCircleEvent CircleEvent { get; set; } + + internal BeachSection(FortuneSite site) + { + Site = site; + CircleEvent = null; + } + } + + internal class BeachLine + { + private readonly RBTree beachLine; + + internal BeachLine() + { + beachLine = new RBTree(); + } + + internal void AddFirst(LinkedList edges, VEdge e) + { + if (e.Left != null) + { + e.Left.Cell.Add(e); + } + if (e.Right != null) + { + e.Right.Cell.Add(e); + } + + edges.AddFirst(e); + } + + internal void AddBeachSection(FortuneSiteEvent siteEvent, MinHeap eventQueue, HashSet deleted, LinkedList edges) + { + var site = siteEvent.Site; + var x = site.X; + var directrix = site.Y; + + RBTreeNode leftSection = null; + RBTreeNode rightSection = null; + var node = beachLine.Root; + + //find the parabola(s) above this site + while (node != null && leftSection == null && rightSection == null) + { + var distanceLeft = LeftBreakpoint(node, directrix) - x; + if (distanceLeft > 0) + { + //the new site is before the left breakpoint + if (node.Left == null) + { + rightSection = node; + } + else + { + node = node.Left; + } + continue; + } + + var distanceRight = x - RightBreakpoint(node, directrix); + if (distanceRight > 0) + { + //the new site is after the right breakpoint + if (node.Right == null) + { + leftSection = node; + } + else + { + node = node.Right; + } + continue; + } + + //the point lies below the left breakpoint + if (distanceLeft.ApproxEqual(0)) + { + leftSection = node.Previous; + rightSection = node; + continue; + } + + //the point lies below the right breakpoint + if (distanceRight.ApproxEqual(0)) + { + leftSection = node; + rightSection = node.Next; + continue; + } + + // distance Right < 0 and distance Left < 0 + // this section is above the new site + leftSection = rightSection = node; + } + + //our goal is to insert the new node between the + //left and right sections + var section = new BeachSection(site); + + //left section could be null, in which case this node is the first + //in the tree + var newSection = beachLine.InsertSuccessor(leftSection, section); + + //new beach section is the first beach section to be added + if (leftSection == null && rightSection == null) + { + return; + } + + //main case: + //if both left section and right section point to the same valid arc + //we need to split the arc into a left arc and a right arc with our + //new arc sitting in the middle + if (leftSection != null && leftSection == rightSection) + { + //if the arc has a circle event, it was a false alarm. + //remove it + if (leftSection.Data.CircleEvent != null) + { + deleted.Add(leftSection.Data.CircleEvent); + leftSection.Data.CircleEvent = null; + } + + //we leave the existing arc as the left section in the tree + //however we need to insert the right section defined by the arc + var copy = new BeachSection(leftSection.Data.Site); + rightSection = beachLine.InsertSuccessor(newSection, copy); + + //grab the projection of this site onto the parabola + var y = ParabolaMath.EvalParabola(leftSection.Data.Site.X, leftSection.Data.Site.Y, directrix, x); + var intersection = new VPoint(x, y); + + //create the two half edges corresponding to this intersection + var leftEdge = new VEdge(intersection, site, leftSection.Data.Site); + var rightEdge = new VEdge(intersection, leftSection.Data.Site, site); + leftEdge.Neighbor = rightEdge; + + //put the edge in the list + //edges.AddFirst(leftEdge); + AddFirst(edges, leftEdge); + + //store the left edge on each arc section + newSection.Data.Edge = leftEdge; + rightSection.Data.Edge = rightEdge; + + //store neighbors for delaunay + leftSection.Data.Site.Neighbors.Add(newSection.Data.Site); + newSection.Data.Site.Neighbors.Add(leftSection.Data.Site); + + //create circle events + CheckCircle(leftSection, eventQueue); + CheckCircle(rightSection, eventQueue); + } + + //site is the last beach section on the beach line + //this can only happen if all previous sites + //had the same y value + else if (leftSection != null && rightSection == null) + { + var start = new VPoint((leftSection.Data.Site.X + site.X)/ 2, double.MinValue); + var infEdge = new VEdge(start, leftSection.Data.Site, site); + var newEdge = new VEdge(start, site, leftSection.Data.Site); + + newEdge.Neighbor = infEdge; + //edges.AddFirst(newEdge); + AddFirst(edges, newEdge); + + leftSection.Data.Site.Neighbors.Add(newSection.Data.Site); + newSection.Data.Site.Neighbors.Add(leftSection.Data.Site); + + newSection.Data.Edge = newEdge; + + //cant check circles since they are colinear + } + + //site is directly above a break point + else if (leftSection != null && leftSection != rightSection) + { + //remove false alarms + if (leftSection.Data.CircleEvent != null) + { + deleted.Add(leftSection.Data.CircleEvent); + leftSection.Data.CircleEvent = null; + } + + if (rightSection.Data.CircleEvent != null) + { + deleted.Add(rightSection.Data.CircleEvent); + rightSection.Data.CircleEvent = null; + } + + //the breakpoint will dissapear if we add this site + //which means we will create an edge + //we treat this very similar to a circle event since + //an edge is finishing at the center of the circle + //created by circumscribing the left center and right + //sites + + //bring a to the origin + var leftSite = leftSection.Data.Site; + var ax = leftSite.X; + var ay = leftSite.Y; + var bx = site.X - ax; + var by = site.Y - ay; + + var rightSite = rightSection.Data.Site; + var cx = rightSite.X - ax; + var cy = rightSite.Y - ay; + var d = bx*cy - by*cx; + var magnitudeB = bx*bx + by*by; + var magnitudeC = cx*cx + cy*cy; + var vertex = new VPoint( + (cy*magnitudeB - by * magnitudeC)/(2*d) + ax, + (bx*magnitudeC - cx * magnitudeB)/(2*d) + ay); + + rightSection.Data.Edge.End = vertex; + + //next we create a two new edges + newSection.Data.Edge = new VEdge(vertex, site, leftSection.Data.Site); + rightSection.Data.Edge = new VEdge(vertex, rightSection.Data.Site, site); + + //edges.AddFirst(newSection.Data.Edge); + AddFirst(edges, newSection.Data.Edge); + //edges.AddFirst(rightSection.Data.Edge); + AddFirst(edges, rightSection.Data.Edge); + + //add neighbors for delaunay + newSection.Data.Site.Neighbors.Add(leftSection.Data.Site); + leftSection.Data.Site.Neighbors.Add(newSection.Data.Site); + + newSection.Data.Site.Neighbors.Add(rightSection.Data.Site); + rightSection.Data.Site.Neighbors.Add(newSection.Data.Site); + + CheckCircle(leftSection, eventQueue); + CheckCircle(rightSection, eventQueue); + } + } + + internal void RemoveBeachSection(FortuneCircleEvent circle, MinHeap eventQueue, HashSet deleted, LinkedList edges) + { + var section = circle.ToDelete; + var x = circle.X; + var y = circle.YCenter; + var vertex = new VPoint(x, y); + + //multiple edges could end here + var toBeRemoved = new List>(); + + //look left + var prev = section.Previous; + while (prev.Data.CircleEvent != null && + (x - prev.Data.CircleEvent.X).ApproxEqual(0) && + (y - prev.Data.CircleEvent.Y).ApproxEqual(0)) + { + toBeRemoved.Add(prev); + prev = prev.Previous; + } + + var next = section.Next; + while (next.Data.CircleEvent != null && + (x - next.Data.CircleEvent.X).ApproxEqual(0) && + (y - next.Data.CircleEvent.Y).ApproxEqual(0)) + { + toBeRemoved.Add(next); + next = next.Next; + } + + section.Data.Edge.End = vertex; + section.Next.Data.Edge.End = vertex; + section.Data.CircleEvent = null; + + //odds are this double writes a few edges but this is clean... + foreach (var remove in toBeRemoved) + { + remove.Data.Edge.End = vertex; + remove.Next.Data.Edge.End = vertex; + deleted.Add(remove.Data.CircleEvent); + remove.Data.CircleEvent = null; + } + + + //need to delete all upcoming circle events with this node + if (prev.Data.CircleEvent != null) + { + deleted.Add(prev.Data.CircleEvent); + prev.Data.CircleEvent = null; + } + if (next.Data.CircleEvent != null) + { + deleted.Add(next.Data.CircleEvent); + next.Data.CircleEvent = null; + } + + + //create a new edge with start point at the vertex and assign it to next + var newEdge = new VEdge(vertex, next.Data.Site, prev.Data.Site); + next.Data.Edge = newEdge; + //edges.AddFirst(newEdge); + AddFirst(edges, newEdge); + + //add neighbors for delaunay + prev.Data.Site.Neighbors.Add(next.Data.Site); + next.Data.Site.Neighbors.Add(prev.Data.Site); + + //remove the sectionfrom the tree + beachLine.RemoveNode(section); + foreach (var remove in toBeRemoved) + { + beachLine.RemoveNode(remove); + } + + CheckCircle(prev, eventQueue); + CheckCircle(next, eventQueue); + } + + private static double LeftBreakpoint(RBTreeNode node, double directrix) + { + var leftNode = node.Previous; + //degenerate parabola + if ((node.Data.Site.Y - directrix).ApproxEqual(0)) + return node.Data.Site.X; + //node is the first piece of the beach line + if (leftNode == null) + return double.NegativeInfinity; + //left node is degenerate + if ((leftNode.Data.Site.Y - directrix).ApproxEqual(0)) + return leftNode.Data.Site.X; + var site = node.Data.Site; + var leftSite = leftNode.Data.Site; + return ParabolaMath.IntersectParabolaX(leftSite.X, leftSite.Y, site.X, site.Y, directrix); + } + + private static double RightBreakpoint(RBTreeNode node, double directrix) + { + var rightNode = node.Next; + //degenerate parabola + if ((node.Data.Site.Y - directrix).ApproxEqual(0)) + return node.Data.Site.X; + //node is the last piece of the beach line + if (rightNode == null) + return double.PositiveInfinity; + //left node is degenerate + if ((rightNode.Data.Site.Y - directrix).ApproxEqual(0)) + return rightNode.Data.Site.X; + var site = node.Data.Site; + var rightSite = rightNode.Data.Site; + return ParabolaMath.IntersectParabolaX(site.X, site.Y, rightSite.X, rightSite.Y, directrix); + } + + private static void CheckCircle(RBTreeNode section, MinHeap eventQueue) + { + //if (section == null) + // return; + var left = section.Previous; + var right = section.Next; + if (left == null || right == null) + return; + + var leftSite = left.Data.Site; + var centerSite = section.Data.Site; + var rightSite = right.Data.Site; + + //if the left arc and right arc are defined by the same + //focus, the two arcs cannot converge + if (leftSite == rightSite) + return; + + // http://mathforum.org/library/drmath/view/55002.html + // because every piece of this program needs to be demoed in maple >.< + + //MATH HACKS: place center at origin and + //draw vectors a and c to + //left and right respectively + double bx = centerSite.X, + by = centerSite.Y, + ax = leftSite.X - bx, + ay = leftSite.Y - by, + cx = rightSite.X - bx, + cy = rightSite.Y - by; + + //The center beach section can only dissapear when + //the angle between a and c is negative + var d = ax*cy - ay*cx; + if (d.ApproxGreaterThanOrEqualTo(0)) + return; + + var magnitudeA = ax*ax + ay*ay; + var magnitudeC = cx*cx + cy*cy; + var x = (cy*magnitudeA - ay*magnitudeC)/(2*d); + var y = (ax*magnitudeC - cx*magnitudeA)/(2*d); + + //add back offset + var ycenter = y + by; + //y center is off + var circleEvent = new FortuneCircleEvent( + new VPoint(x + bx, ycenter + System.Math.Sqrt(x * x + y * y)), + ycenter, section + ); + section.Data.CircleEvent = circleEvent; + eventQueue.Insert(circleEvent); + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/BeachLine.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/BeachLine.cs.meta new file mode 100644 index 0000000..93f1bc1 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/BeachLine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a55a026f0aa4e407b905960fcf57e9d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneCircleEvent.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneCircleEvent.cs new file mode 100644 index 0000000..b7d551a --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneCircleEvent.cs @@ -0,0 +1,25 @@ +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + internal class FortuneCircleEvent : FortuneEvent + { + internal VPoint Lowest { get; } + internal double YCenter { get; } + internal RBTreeNode ToDelete { get; } + + internal FortuneCircleEvent(VPoint lowest, double yCenter, RBTreeNode toDelete) + { + Lowest = lowest; + YCenter = yCenter; + ToDelete = toDelete; + } + + public int CompareTo(FortuneEvent other) + { + var c = Y.CompareTo(other.Y); + return c == 0 ? X.CompareTo(other.X) : c; + } + + public double X => Lowest.X; + public double Y => Lowest.Y; + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneCircleEvent.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneCircleEvent.cs.meta new file mode 100644 index 0000000..7a07061 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneCircleEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ceb596df5e91f4ca489eba0028a8b949 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneEvent.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneEvent.cs new file mode 100644 index 0000000..4d430d4 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneEvent.cs @@ -0,0 +1,10 @@ +using System; + +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + interface FortuneEvent : IComparable + { + double X { get; } + double Y { get; } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneEvent.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneEvent.cs.meta new file mode 100644 index 0000000..b39c795 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04dc09597304a46de8375201a57dab06 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSite.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSite.cs new file mode 100644 index 0000000..c4e3a89 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSite.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + public class FortuneSite: IEquatable + { + public static int IDCounter = 0; + + public int ID { get; } + public double X { get; } + public double Y { get; } + public List Corners { get; } + + public bool ToClose { get; set; } = false; + + public HashSet Cell { get; private set; } + + public HashSet Neighbors { get; private set; } + + public FortuneSite(double x, double y) + { + ID = IDCounter++; + X = x; + Y = y; + Cell = new HashSet(); + Neighbors = new HashSet(); + Corners = new List(); + } + + public bool Equals(FortuneSite other) + { + return ID.Equals(other.ID); + } + + public HashSet GetUniquePoints() + { + HashSet uniquePoints = new HashSet(); + foreach (var v in Corners) + { + uniquePoints.Add(v); + } + foreach (var e in Cell) + { + uniquePoints.Add(e.Start); + uniquePoints.Add(e.End); + } + + return uniquePoints; + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSite.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSite.cs.meta new file mode 100644 index 0000000..50f9873 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSite.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c3a4f1bd8d8f488592683f446572e27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSiteEvent.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSiteEvent.cs new file mode 100644 index 0000000..20094dd --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSiteEvent.cs @@ -0,0 +1,21 @@ +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + internal class FortuneSiteEvent : FortuneEvent + { + public double X => Site.X; + public double Y => Site.Y; + internal FortuneSite Site { get; } + + internal FortuneSiteEvent(FortuneSite site) + { + Site = site; + } + + public int CompareTo(FortuneEvent other) + { + var c = Y.CompareTo(other.Y); + return c == 0 ? X.CompareTo(other.X) : c; + } + + } +} \ No newline at end of file diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSiteEvent.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSiteEvent.cs.meta new file mode 100644 index 0000000..1d6e757 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/FortuneSiteEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce3cde43dbd8143b6a43f80697d9327a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/MinHeap.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/MinHeap.cs new file mode 100644 index 0000000..f593e59 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/MinHeap.cs @@ -0,0 +1,129 @@ +using System; +using System.Runtime.CompilerServices; + +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + public class MinHeap where T : IComparable + { + private readonly T[] items; + public int Capacity { get; } + public int Count { get; private set; } + + public MinHeap(int capacity) + { + if (capacity < 2) + { + capacity = 2; + } + Capacity = capacity; + items = new T[Capacity]; + Count = 0; + } + + public bool Insert(T obj) + { + if (Count == Capacity) + return false; + items[Count] = obj; + Count++; + PercolateUp(Count - 1); + return true; + } + + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("Min heap is empty"); + if (Count == 1) + { + Count--; + return items[Count]; + } + + var min = items[0]; + items[0] = items[Count - 1]; + Count--; + PercolateDown(0); + return min; + } + + public T Peek() + { + if (Count == 0) + throw new InvalidOperationException("Min heap is empty"); + return items[0]; + } + + public bool Remove(T item) + { + int index = -1; + for (var i = 0; i < Count; i++) + { + if (items[i].Equals(item)) + { + index = i; + break; + } + } + + if (index == -1) + return false; + + Count--; + Swap(index, Count); + if (LeftLessThanRight(index, (index - 1)/2)) + PercolateUp(index); + else + PercolateDown(index); + return true; + } + + private void PercolateDown(int index) + { + while (true) + { + var left = 2*index + 1; + var right = 2*index + 2; + var largest = index; + + if (left < Count && LeftLessThanRight(left, largest)) + largest = left; + if (right < Count && LeftLessThanRight(right, largest)) + largest = right; + if (largest == index) + return; + Swap(index, largest); + index = largest; + } + } + + private void PercolateUp(int index) + { + while (true) + { + if (index >= Count || index <= 0) + return; + var parent = (index - 1)/2; + + if (LeftLessThanRight(parent, index)) + return; + + Swap(index, parent); + index = parent; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool LeftLessThanRight(int left, int right) + { + return items[left].CompareTo(items[right]) < 0; + } + + private void Swap(int left, int right) + { + var temp = items[left]; + items[left] = items[right]; + items[right] = temp; + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/MinHeap.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/MinHeap.cs.meta new file mode 100644 index 0000000..f073330 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/MinHeap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16431adeb93094a73abd7f5ccd6c654d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/RBTree.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/RBTree.cs new file mode 100644 index 0000000..dd30fc0 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/RBTree.cs @@ -0,0 +1,360 @@ +// credit https://github.com/mdally/Voronoi/blob/master/src/RBTree.h MIT LICENSE + +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + public class RBTreeNode + { + public T Data { get; internal set; } + public RBTreeNode Left { get; internal set; } + public RBTreeNode Right { get; internal set; } + public RBTreeNode Parent { get; internal set; } + + //cached ordered traversal + public RBTreeNode Previous { get; internal set; } + public RBTreeNode Next { get; internal set; } + + internal bool Red { get; set; } + + internal RBTreeNode () + { + + } + } + + public class RBTree + { + public RBTreeNode Root { get; private set; } + + public RBTreeNode InsertSuccessor(RBTreeNode node, T successorData) + { + var successor = new RBTreeNode {Data = successorData}; + + RBTreeNode parent; + + if (node != null) + { + //insert new node between node and its successor + successor.Previous = node; + successor.Next = node.Next; + if (node.Next != null) + node.Next.Previous = successor; + node.Next = successor; + + //insert successor into the tree + if (node.Right != null) + { + node = GetFirst(node.Right); + node.Left = successor; + } + else + { + node.Right = successor; + } + parent = node; + } + else if (Root != null) + { + //if the node is null, successor must be inserted + //into the left most part of the tree + node = GetFirst(Root); + //successor.Previous = null; + successor.Next = node; + node.Previous = successor; + node.Left = successor; + parent = node; + } + else + { + //first insert + //successor.Previous = successor.Next = null; + Root = successor; + parent = null; + } + + //successor.Left = successor.Right = null; + successor.Parent = parent; + successor.Red = true; + + //the magic of the red black tree + RBTreeNode grandma; + RBTreeNode aunt; + node = successor; + while (parent != null && parent.Red) + { + grandma = parent.Parent; + if (parent == grandma.Left) + { + aunt = grandma.Right; + if (aunt != null && aunt.Red) + { + parent.Red = false; + aunt.Red = false; + grandma.Red = true; + node = grandma; + } + else + { + if (node == parent.Right) + { + RotateLeft(parent); + node = parent; + parent = node.Parent; + } + parent.Red = false; + grandma.Red = true; + RotateRight(grandma); + } + } + else + { + aunt = grandma.Left; + if (aunt != null && aunt.Red) + { + parent.Red = false; + aunt.Red = false; + grandma.Red = true; + node = grandma; + } + else + { + if (node == parent.Left) + { + RotateRight(parent); + node = parent; + parent = node.Parent; + } + parent.Red = false; + grandma.Red = true; + RotateLeft(grandma); + } + } + parent = node.Parent; + } + Root.Red = false; + return successor; + } + + public void RemoveNode(RBTreeNode node) + { + //fix up linked list structure + if (node.Next != null) + node.Next.Previous = node.Previous; + if (node.Previous != null) + node.Previous.Next = node.Next; + + //replace the node + var original = node; + var parent = node.Parent; + var left = node.Left; + var right = node.Right; + + RBTreeNode next; + //figure out what to replace this node with + if (left == null) + next = right; + else if (right == null) + next = left; + else + next = GetFirst(right); + + //fix up the parent relation + if (parent != null) + { + if (parent.Left == node) + parent.Left = next; + else + parent.Right = next; + } + else + { + Root = next; + } + + bool red; + if (left != null && right != null) + { + red = next.Red; + next.Red = node.Red; + next.Left = left; + left.Parent = next; + + // if we reached down the tree + if (next != right) + { + parent = next.Parent; + next.Parent = node.Parent; + + node = next.Right; + parent.Left = node; + + next.Right = right; + right.Parent = next; + } + else + { + // the direct right will replace the node + next.Parent = parent; + parent = next; + node = next.Right; + } + } + else + { + red = node.Red; + node = next; + } + + if (node != null) + { + node.Parent = parent; + } + + if (red) + { + return; + } + + if (node != null && node.Red) + { + node.Red = false; + return; + } + + //node is null or black + + // fair warning this code gets nasty + + //how do we guarantee sibling is not null + RBTreeNode sibling = null; + do + { + if (node == Root) + break; + if (node == parent.Left) + { + sibling = parent.Right; + if (sibling.Red) + { + sibling.Red = false; + parent.Red = true; + RotateLeft(parent); + sibling = parent.Right; + } + if ((sibling.Left != null && sibling.Left.Red) || (sibling.Right != null && sibling.Right.Red)) + { + //pretty sure this can be sibling.Left!= null && sibling.Left.Red + if (sibling.Right == null || !sibling.Right.Red) + { + sibling.Left.Red = false; + sibling.Red = true; + RotateRight(sibling); + sibling = parent.Right; + } + sibling.Red = parent.Red; + parent.Red = sibling.Right.Red = false; + RotateLeft(parent); + node = Root; + break; + } + } + else + { + sibling = parent.Left; + if (sibling.Red) + { + sibling.Red = false; + parent.Red = true; + RotateRight(parent); + sibling = parent.Left; + } + if ((sibling.Left != null && sibling.Left.Red) || (sibling.Right != null && sibling.Right.Red)) + { + if (sibling.Left == null || !sibling.Left.Red) + { + sibling.Right.Red = false; + sibling.Red = true; + RotateLeft(sibling); + sibling = parent.Left; + } + sibling.Red = parent.Red; + parent.Red = sibling.Left.Red = false; + RotateRight(parent); + node = Root; + break; + } + } + sibling.Red = true; + node = parent; + parent = parent.Parent; + } while (!node.Red); + + if (node != null) + node.Red = false; + + } + + public static RBTreeNode GetFirst(RBTreeNode node) + { + if (node == null) + return null; + while (node.Left != null) + node = node.Left; + return node; + } + + public static RBTreeNode GetLast(RBTreeNode node) + { + if (node == null) + return null; + while (node.Right != null) + node = node.Right; + return node; + } + + private void RotateLeft(RBTreeNode node) + { + var p = node; + var q = node.Right; + var parent = p.Parent; + + if (parent != null) + { + if (parent.Left == p) + parent.Left = q; + else + parent.Right = q; + } + else + Root = q; + q.Parent = parent; + p.Parent = q; + p.Right = q.Left; + if (p.Right != null) + p.Right.Parent = p; + q.Left = p; + } + + private void RotateRight(RBTreeNode node) + { + var p = node; + var q = node.Left; + var parent = p.Parent; + if (parent != null) + { + if (parent.Left == p) + parent.Left = q; + else + parent.Right = q; + } + else + Root = q; + q.Parent = parent; + p.Parent = q; + p.Left = q.Right; + if (p.Left != null) + p.Left.Parent = p; + q.Right = p; + } + + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/RBTree.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/RBTree.cs.meta new file mode 100644 index 0000000..d772422 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/RBTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cee39df0c7a5b480e87ac30ffeebc1a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VEdge.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VEdge.cs new file mode 100644 index 0000000..c70e18d --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VEdge.cs @@ -0,0 +1,57 @@ +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + public class VEdge + { + public static int IDCounter; + + public readonly int ID; + public VPoint Start { get; internal set; } + public VPoint End { get; internal set; } + public FortuneSite Left { get; } + public FortuneSite Right { get; } + + internal double SlopeRise { get; } + internal double SlopeRun { get; } + internal double? Slope { get; } + internal double? Intercept { get; } + + public VEdge Neighbor { get; internal set; } + + internal VEdge(VPoint start, FortuneSite left, FortuneSite right) + { + ID = IDCounter++; + Start = start; + Left = left; + Right = right; + + //for bounding box edges + if (left == null || right == null) + return; + + //from negative reciprocal of slope of line from left to right + //ala m = (left.y -right.y / left.x - right.x) + SlopeRise = left.X - right.X; + SlopeRun = -(left.Y - right.Y); + Intercept = null; + + if (SlopeRise.ApproxEqual(0) || SlopeRun.ApproxEqual(0)) return; + Slope = SlopeRise/SlopeRun; + Intercept = start.Y - Slope*start.X; + } + + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + return ((VEdge)obj).ID.Equals(ID); + } + + public override int GetHashCode() + { + return 1213502048 + ID.GetHashCode(); + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VEdge.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VEdge.cs.meta new file mode 100644 index 0000000..a7f82d5 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VEdge.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58f0c76cdb760470aa5c91f5d08c9155 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VPoint.cs b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VPoint.cs new file mode 100644 index 0000000..7fa60ca --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VPoint.cs @@ -0,0 +1,52 @@ +using System; +using UnityEngine; + +namespace SpaceScrapper.MathLib.VoronoiLib.Structures +{ + public class VPoint: IEquatable + { + public double X { get; } + public double Y { get; } + + internal VPoint(double x, double y) + { + X = x; + Y = y; + } + + public Vector2 ToVector2() + { + return new Vector2((float)X, (float)Y); + } + + public Vector3 ToVector3() + { + return new Vector3((float)X, (float)Y, 0f); + } + + //public override bool Equals(object obj) + //{ + // if (obj != null) + // { + // return false; + // } + + // VPoint other = (VPoint)obj; + + // return other.X.ApproxEqual(X) && other.Y.ApproxEqual(Y); + //} + + public override int GetHashCode() + { + var hashCode = 1861411795; + hashCode = hashCode * -1521134295 + X.GetHashCode(); + hashCode = hashCode * -1521134295 + Y.GetHashCode(); + return hashCode; + } + + public bool Equals(VPoint other) + { + return other.X.ApproxEqual(X) && other.Y.ApproxEqual(Y); + } + } +} diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VPoint.cs.meta b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VPoint.cs.meta new file mode 100644 index 0000000..4c32927 --- /dev/null +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/MathLib/VoronoiLib/Structures/VPoint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e98665179d1b44ba1929a0a31c456b86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/SpaceScrapper.asmdef b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/SpaceScrapper.asmdef index 89a7152..9451a9d 100644 --- a/SpaceScrapper/Assets/_Game/_Scripts/Runtime/SpaceScrapper.asmdef +++ b/SpaceScrapper/Assets/_Game/_Scripts/Runtime/SpaceScrapper.asmdef @@ -5,7 +5,8 @@ "GUID:8c0f230a147e28649b1eee2aaf4d2547", "GUID:6055be8ebefd69e48b49212b09b47b2f", "GUID:776d03a35f1b52c4a9aed9f56d7b4229", - "GUID:4307f53044263cf4b835bd812fc161a4" + "GUID:4307f53044263cf4b835bd812fc161a4", + "GUID:a1d73af5f72ca4e929569ac515d86db8" ], "includePlatforms": [], "excludePlatforms": [],