diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd87bab --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ + +# Custom + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Recordings can get excessive in size +/[Rr]ecordings/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage +*.app + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* diff --git a/.vsconfig b/.vsconfig new file mode 100644 index 0000000..d70cd98 --- /dev/null +++ b/.vsconfig @@ -0,0 +1,6 @@ +{ + "version": "1.0", + "components": [ + "Microsoft.VisualStudio.Workload.ManagedGame" + ] +} diff --git a/Assets/Scenes.meta b/Assets/Scenes.meta new file mode 100644 index 0000000..fd0709f --- /dev/null +++ b/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 99f1f7026e45c1d46a679acd73c9ccbb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Fortification.unity b/Assets/Scenes/Fortification.unity new file mode 100644 index 0000000..1023795 --- /dev/null +++ b/Assets/Scenes/Fortification.unity @@ -0,0 +1,646 @@ +%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: 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: 1607.5831, g: 1674.3088, b: 2143.0486, 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 &157207112 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 157207114} + - component: {fileID: 157207113} + - component: {fileID: 157207115} + m_Layer: 0 + m_Name: Sun + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &157207113 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 130000 + 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: 2 + m_AreaSize: {x: 0.5, y: 0.5} + m_BounceIntensity: 1 + m_ColorTemperature: 6500 + m_UseColorTemperature: 1 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 2.5 +--- !u!4 &157207114 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_LocalRotation: {x: 0.38268343, y: 0, z: 0, w: 0.92387956} + 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: 45, y: 0, z: 0} +--- !u!114 &157207115 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7a68c43fe1f2a47cfa234b5eeaa98012, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Intensity: 130000 + m_EnableSpotReflector: 0 + m_LuxAtDistance: 1 + m_InnerSpotPercent: 0 + m_SpotIESCutoffPercent: 100 + m_LightDimmer: 1 + m_VolumetricDimmer: 1 + m_LightUnit: 2 + m_FadeDistance: 10000 + m_VolumetricFadeDistance: 10000 + m_AffectDiffuse: 1 + m_AffectSpecular: 1 + m_NonLightmappedOnly: 0 + m_ShapeWidth: 0.5 + m_ShapeHeight: 0.5 + m_AspectRatio: 1 + m_ShapeRadius: 0.025 + m_SoftnessScale: 1 + m_UseCustomSpotLightShadowCone: 0 + m_CustomSpotLightShadowCone: 30 + m_MaxSmoothness: 0.99 + m_ApplyRangeAttenuation: 1 + m_DisplayAreaLightEmissiveMesh: 0 + m_AreaLightCookie: {fileID: 0} + m_IESPoint: {fileID: 0} + m_IESSpot: {fileID: 0} + m_IncludeForRayTracing: 1 + m_AreaLightShadowCone: 120 + m_UseScreenSpaceShadows: 0 + m_InteractsWithSky: 1 + m_AngularDiameter: 2.5 + m_FlareSize: 2 + m_FlareTint: {r: 1, g: 1, b: 1, a: 1} + m_FlareFalloff: 4 + m_SurfaceTexture: {fileID: 0} + m_SurfaceTint: {r: 1, g: 1, b: 1, a: 1} + m_Distance: 1.5e+11 + m_UseRayTracedShadows: 0 + m_NumRayTracingSamples: 4 + m_FilterTracedShadow: 1 + m_FilterSizeTraced: 16 + m_SunLightConeAngle: 0.5 + m_LightShadowRadius: 0.5 + m_SemiTransparentShadow: 0 + m_ColorShadow: 1 + m_DistanceBasedFiltering: 0 + m_EvsmExponent: 15 + m_EvsmLightLeakBias: 0 + m_EvsmVarianceBias: 0.00001 + m_EvsmBlurPasses: 0 + m_LightlayersMask: 1 + m_LinkShadowLayers: 1 + m_ShadowNearPlane: 0.1 + m_BlockerSampleCount: 24 + m_FilterSampleCount: 16 + m_MinFilterSize: 0.1 + m_KernelSize: 5 + m_LightAngle: 1 + m_MaxDepthBias: 0.001 + m_ShadowResolution: + m_Override: 512 + m_UseOverride: 1 + m_Level: 0 + m_ShadowDimmer: 1 + m_VolumetricShadowDimmer: 1 + m_ShadowFadeDistance: 10000 + m_UseContactShadow: + m_Override: 0 + m_UseOverride: 1 + m_Level: 0 + m_RayTracedContactShadow: 0 + m_ShadowTint: {r: 0, g: 0, b: 0, a: 1} + m_PenumbraTint: 0 + m_NormalBias: 0.75 + m_SlopeBias: 0.5 + m_ShadowUpdateMode: 0 + m_AlwaysDrawDynamicShadows: 0 + m_UpdateShadowOnLightMovement: 0 + m_CachedShadowTranslationThreshold: 0.01 + m_CachedShadowAngularThreshold: 0.5 + m_BarnDoorAngle: 90 + m_BarnDoorLength: 0.05 + m_preserveCachedShadow: 0 + m_OnDemandShadowRenderOnPlacement: 1 + m_ShadowCascadeRatios: + - 0.05 + - 0.2 + - 0.3 + m_ShadowCascadeBorders: + - 0.2 + - 0.2 + - 0.2 + - 0.2 + m_ShadowAlgorithm: 0 + m_ShadowVariant: 0 + m_ShadowPrecision: 0 + useOldInspector: 0 + useVolumetric: 1 + featuresFoldout: 1 + m_AreaLightEmissiveMeshShadowCastingMode: 0 + m_AreaLightEmissiveMeshMotionVectorGenerationMode: 0 + m_AreaLightEmissiveMeshLayer: -1 + m_Version: 11 + m_ObsoleteShadowResolutionTier: 1 + m_ObsoleteUseShadowQualitySettings: 0 + m_ObsoleteCustomShadowResolution: 512 + m_ObsoleteContactShadows: 0 + m_PointlightHDType: 0 + m_SpotLightShape: 0 + m_AreaLightShape: 0 +--- !u!1 &432432459 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 432432461} + - component: {fileID: 432432460} + m_Layer: 0 + m_Name: Sky and Fog Volume + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &432432460 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 432432459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} + m_Name: + m_EditorClassIdentifier: + isGlobal: 1 + priority: 0 + blendDistance: 0 + weight: 1 + sharedProfile: {fileID: 11400000, guid: 8ba92e2dd7f884a0f88b98fa2d235fe7, type: 2} +--- !u!4 &432432461 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 432432459} + 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!1 &1823688464 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1823688467} + - component: {fileID: 1823688466} + - component: {fileID: 1823688465} + - component: {fileID: 1823688468} + 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 &1823688465 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 +--- !u!20 &1823688466 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_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: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 0 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1823688467 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + 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 &1823688468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 23c1ce4fb46143f46bc5cb5224c934f6, type: 3} + m_Name: + m_EditorClassIdentifier: + clearColorMode: 0 + backgroundColorHDR: {r: 0.025, g: 0.07, b: 0.19, a: 0} + clearDepth: 1 + volumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + volumeAnchorOverride: {fileID: 0} + antialiasing: 2 + SMAAQuality: 2 + dithering: 1 + stopNaNs: 0 + taaSharpenStrength: 0.5 + TAAQuality: 1 + taaHistorySharpening: 0.35 + taaAntiFlicker: 0.5 + taaMotionVectorRejection: 0 + taaAntiHistoryRinging: 0 + physicalParameters: + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + flipYMode: 0 + xrRendering: 1 + fullscreenPassthrough: 0 + allowDynamicResolution: 0 + customRenderingSettings: 0 + invertFaceCulling: 0 + probeLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + hasPersistentHistory: 0 + allowDeepLearningSuperSampling: 1 + deepLearningSuperSamplingUseCustomQualitySettings: 0 + deepLearningSuperSamplingQuality: 0 + deepLearningSuperSamplingUseCustomAttributes: 0 + deepLearningSuperSamplingUseOptimalSettings: 1 + deepLearningSuperSamplingSharpening: 0 + exposureTarget: {fileID: 0} + materialMipBias: 0 + m_RenderingPathCustomFrameSettings: + bitDatas: + data1: 72198262773251917 + data2: 13763000464465395712 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 9 + materialQuality: 0 + renderingPathCustomFrameSettingsOverrideMask: + mask: + data1: 0 + data2: 0 + defaultFrameSettings: 0 + m_Version: 8 + m_ObsoleteRenderingPath: 0 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 +--- !u!1 &2013410473 +GameObject: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2013410475} + - component: {fileID: 2013410474} + m_Layer: 0 + m_Name: StaticLightingSky + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2013410474 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2013410473} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 441482e8936e35048a1dffac814e3ef8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Profile: {fileID: 11400000, guid: 8ba92e2dd7f884a0f88b98fa2d235fe7, type: 2} + m_StaticLightingSkyUniqueID: 4 + m_StaticLightingCloudsUniqueID: 0 +--- !u!4 &2013410475 +Transform: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2013410473} + 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: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scenes/Fortification.unity.meta b/Assets/Scenes/Fortification.unity.meta new file mode 100644 index 0000000..12aee29 --- /dev/null +++ b/Assets/Scenes/Fortification.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3a1095ae137ab8e4b84acc41063a8e37 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/StandardWave.unity b/Assets/Scenes/StandardWave.unity new file mode 100644 index 0000000..1023795 --- /dev/null +++ b/Assets/Scenes/StandardWave.unity @@ -0,0 +1,646 @@ +%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: 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: 1607.5831, g: 1674.3088, b: 2143.0486, 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 &157207112 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 157207114} + - component: {fileID: 157207113} + - component: {fileID: 157207115} + m_Layer: 0 + m_Name: Sun + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &157207113 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 130000 + 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: 2 + m_AreaSize: {x: 0.5, y: 0.5} + m_BounceIntensity: 1 + m_ColorTemperature: 6500 + m_UseColorTemperature: 1 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 2.5 +--- !u!4 &157207114 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_LocalRotation: {x: 0.38268343, y: 0, z: 0, w: 0.92387956} + 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: 45, y: 0, z: 0} +--- !u!114 &157207115 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7a68c43fe1f2a47cfa234b5eeaa98012, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Intensity: 130000 + m_EnableSpotReflector: 0 + m_LuxAtDistance: 1 + m_InnerSpotPercent: 0 + m_SpotIESCutoffPercent: 100 + m_LightDimmer: 1 + m_VolumetricDimmer: 1 + m_LightUnit: 2 + m_FadeDistance: 10000 + m_VolumetricFadeDistance: 10000 + m_AffectDiffuse: 1 + m_AffectSpecular: 1 + m_NonLightmappedOnly: 0 + m_ShapeWidth: 0.5 + m_ShapeHeight: 0.5 + m_AspectRatio: 1 + m_ShapeRadius: 0.025 + m_SoftnessScale: 1 + m_UseCustomSpotLightShadowCone: 0 + m_CustomSpotLightShadowCone: 30 + m_MaxSmoothness: 0.99 + m_ApplyRangeAttenuation: 1 + m_DisplayAreaLightEmissiveMesh: 0 + m_AreaLightCookie: {fileID: 0} + m_IESPoint: {fileID: 0} + m_IESSpot: {fileID: 0} + m_IncludeForRayTracing: 1 + m_AreaLightShadowCone: 120 + m_UseScreenSpaceShadows: 0 + m_InteractsWithSky: 1 + m_AngularDiameter: 2.5 + m_FlareSize: 2 + m_FlareTint: {r: 1, g: 1, b: 1, a: 1} + m_FlareFalloff: 4 + m_SurfaceTexture: {fileID: 0} + m_SurfaceTint: {r: 1, g: 1, b: 1, a: 1} + m_Distance: 1.5e+11 + m_UseRayTracedShadows: 0 + m_NumRayTracingSamples: 4 + m_FilterTracedShadow: 1 + m_FilterSizeTraced: 16 + m_SunLightConeAngle: 0.5 + m_LightShadowRadius: 0.5 + m_SemiTransparentShadow: 0 + m_ColorShadow: 1 + m_DistanceBasedFiltering: 0 + m_EvsmExponent: 15 + m_EvsmLightLeakBias: 0 + m_EvsmVarianceBias: 0.00001 + m_EvsmBlurPasses: 0 + m_LightlayersMask: 1 + m_LinkShadowLayers: 1 + m_ShadowNearPlane: 0.1 + m_BlockerSampleCount: 24 + m_FilterSampleCount: 16 + m_MinFilterSize: 0.1 + m_KernelSize: 5 + m_LightAngle: 1 + m_MaxDepthBias: 0.001 + m_ShadowResolution: + m_Override: 512 + m_UseOverride: 1 + m_Level: 0 + m_ShadowDimmer: 1 + m_VolumetricShadowDimmer: 1 + m_ShadowFadeDistance: 10000 + m_UseContactShadow: + m_Override: 0 + m_UseOverride: 1 + m_Level: 0 + m_RayTracedContactShadow: 0 + m_ShadowTint: {r: 0, g: 0, b: 0, a: 1} + m_PenumbraTint: 0 + m_NormalBias: 0.75 + m_SlopeBias: 0.5 + m_ShadowUpdateMode: 0 + m_AlwaysDrawDynamicShadows: 0 + m_UpdateShadowOnLightMovement: 0 + m_CachedShadowTranslationThreshold: 0.01 + m_CachedShadowAngularThreshold: 0.5 + m_BarnDoorAngle: 90 + m_BarnDoorLength: 0.05 + m_preserveCachedShadow: 0 + m_OnDemandShadowRenderOnPlacement: 1 + m_ShadowCascadeRatios: + - 0.05 + - 0.2 + - 0.3 + m_ShadowCascadeBorders: + - 0.2 + - 0.2 + - 0.2 + - 0.2 + m_ShadowAlgorithm: 0 + m_ShadowVariant: 0 + m_ShadowPrecision: 0 + useOldInspector: 0 + useVolumetric: 1 + featuresFoldout: 1 + m_AreaLightEmissiveMeshShadowCastingMode: 0 + m_AreaLightEmissiveMeshMotionVectorGenerationMode: 0 + m_AreaLightEmissiveMeshLayer: -1 + m_Version: 11 + m_ObsoleteShadowResolutionTier: 1 + m_ObsoleteUseShadowQualitySettings: 0 + m_ObsoleteCustomShadowResolution: 512 + m_ObsoleteContactShadows: 0 + m_PointlightHDType: 0 + m_SpotLightShape: 0 + m_AreaLightShape: 0 +--- !u!1 &432432459 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 432432461} + - component: {fileID: 432432460} + m_Layer: 0 + m_Name: Sky and Fog Volume + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &432432460 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 432432459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} + m_Name: + m_EditorClassIdentifier: + isGlobal: 1 + priority: 0 + blendDistance: 0 + weight: 1 + sharedProfile: {fileID: 11400000, guid: 8ba92e2dd7f884a0f88b98fa2d235fe7, type: 2} +--- !u!4 &432432461 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 432432459} + 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!1 &1823688464 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1823688467} + - component: {fileID: 1823688466} + - component: {fileID: 1823688465} + - component: {fileID: 1823688468} + 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 &1823688465 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 +--- !u!20 &1823688466 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_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: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 0 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1823688467 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + 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 &1823688468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 23c1ce4fb46143f46bc5cb5224c934f6, type: 3} + m_Name: + m_EditorClassIdentifier: + clearColorMode: 0 + backgroundColorHDR: {r: 0.025, g: 0.07, b: 0.19, a: 0} + clearDepth: 1 + volumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + volumeAnchorOverride: {fileID: 0} + antialiasing: 2 + SMAAQuality: 2 + dithering: 1 + stopNaNs: 0 + taaSharpenStrength: 0.5 + TAAQuality: 1 + taaHistorySharpening: 0.35 + taaAntiFlicker: 0.5 + taaMotionVectorRejection: 0 + taaAntiHistoryRinging: 0 + physicalParameters: + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + flipYMode: 0 + xrRendering: 1 + fullscreenPassthrough: 0 + allowDynamicResolution: 0 + customRenderingSettings: 0 + invertFaceCulling: 0 + probeLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + hasPersistentHistory: 0 + allowDeepLearningSuperSampling: 1 + deepLearningSuperSamplingUseCustomQualitySettings: 0 + deepLearningSuperSamplingQuality: 0 + deepLearningSuperSamplingUseCustomAttributes: 0 + deepLearningSuperSamplingUseOptimalSettings: 1 + deepLearningSuperSamplingSharpening: 0 + exposureTarget: {fileID: 0} + materialMipBias: 0 + m_RenderingPathCustomFrameSettings: + bitDatas: + data1: 72198262773251917 + data2: 13763000464465395712 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 9 + materialQuality: 0 + renderingPathCustomFrameSettingsOverrideMask: + mask: + data1: 0 + data2: 0 + defaultFrameSettings: 0 + m_Version: 8 + m_ObsoleteRenderingPath: 0 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 +--- !u!1 &2013410473 +GameObject: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2013410475} + - component: {fileID: 2013410474} + m_Layer: 0 + m_Name: StaticLightingSky + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2013410474 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2013410473} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 441482e8936e35048a1dffac814e3ef8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Profile: {fileID: 11400000, guid: 8ba92e2dd7f884a0f88b98fa2d235fe7, type: 2} + m_StaticLightingSkyUniqueID: 4 + m_StaticLightingCloudsUniqueID: 0 +--- !u!4 &2013410475 +Transform: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2013410473} + 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: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scenes/StandardWave.unity.meta b/Assets/Scenes/StandardWave.unity.meta new file mode 100644 index 0000000..1344c1d --- /dev/null +++ b/Assets/Scenes/StandardWave.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0587602a8de3fe143bedca57099b416d +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/TitleAndMenu.unity b/Assets/Scenes/TitleAndMenu.unity new file mode 100644 index 0000000..1023795 --- /dev/null +++ b/Assets/Scenes/TitleAndMenu.unity @@ -0,0 +1,646 @@ +%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: 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: 1607.5831, g: 1674.3088, b: 2143.0486, 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 &157207112 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 157207114} + - component: {fileID: 157207113} + - component: {fileID: 157207115} + m_Layer: 0 + m_Name: Sun + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &157207113 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 130000 + 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: 2 + m_AreaSize: {x: 0.5, y: 0.5} + m_BounceIntensity: 1 + m_ColorTemperature: 6500 + m_UseColorTemperature: 1 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 2.5 +--- !u!4 &157207114 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_LocalRotation: {x: 0.38268343, y: 0, z: 0, w: 0.92387956} + 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: 45, y: 0, z: 0} +--- !u!114 &157207115 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 157207112} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7a68c43fe1f2a47cfa234b5eeaa98012, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Intensity: 130000 + m_EnableSpotReflector: 0 + m_LuxAtDistance: 1 + m_InnerSpotPercent: 0 + m_SpotIESCutoffPercent: 100 + m_LightDimmer: 1 + m_VolumetricDimmer: 1 + m_LightUnit: 2 + m_FadeDistance: 10000 + m_VolumetricFadeDistance: 10000 + m_AffectDiffuse: 1 + m_AffectSpecular: 1 + m_NonLightmappedOnly: 0 + m_ShapeWidth: 0.5 + m_ShapeHeight: 0.5 + m_AspectRatio: 1 + m_ShapeRadius: 0.025 + m_SoftnessScale: 1 + m_UseCustomSpotLightShadowCone: 0 + m_CustomSpotLightShadowCone: 30 + m_MaxSmoothness: 0.99 + m_ApplyRangeAttenuation: 1 + m_DisplayAreaLightEmissiveMesh: 0 + m_AreaLightCookie: {fileID: 0} + m_IESPoint: {fileID: 0} + m_IESSpot: {fileID: 0} + m_IncludeForRayTracing: 1 + m_AreaLightShadowCone: 120 + m_UseScreenSpaceShadows: 0 + m_InteractsWithSky: 1 + m_AngularDiameter: 2.5 + m_FlareSize: 2 + m_FlareTint: {r: 1, g: 1, b: 1, a: 1} + m_FlareFalloff: 4 + m_SurfaceTexture: {fileID: 0} + m_SurfaceTint: {r: 1, g: 1, b: 1, a: 1} + m_Distance: 1.5e+11 + m_UseRayTracedShadows: 0 + m_NumRayTracingSamples: 4 + m_FilterTracedShadow: 1 + m_FilterSizeTraced: 16 + m_SunLightConeAngle: 0.5 + m_LightShadowRadius: 0.5 + m_SemiTransparentShadow: 0 + m_ColorShadow: 1 + m_DistanceBasedFiltering: 0 + m_EvsmExponent: 15 + m_EvsmLightLeakBias: 0 + m_EvsmVarianceBias: 0.00001 + m_EvsmBlurPasses: 0 + m_LightlayersMask: 1 + m_LinkShadowLayers: 1 + m_ShadowNearPlane: 0.1 + m_BlockerSampleCount: 24 + m_FilterSampleCount: 16 + m_MinFilterSize: 0.1 + m_KernelSize: 5 + m_LightAngle: 1 + m_MaxDepthBias: 0.001 + m_ShadowResolution: + m_Override: 512 + m_UseOverride: 1 + m_Level: 0 + m_ShadowDimmer: 1 + m_VolumetricShadowDimmer: 1 + m_ShadowFadeDistance: 10000 + m_UseContactShadow: + m_Override: 0 + m_UseOverride: 1 + m_Level: 0 + m_RayTracedContactShadow: 0 + m_ShadowTint: {r: 0, g: 0, b: 0, a: 1} + m_PenumbraTint: 0 + m_NormalBias: 0.75 + m_SlopeBias: 0.5 + m_ShadowUpdateMode: 0 + m_AlwaysDrawDynamicShadows: 0 + m_UpdateShadowOnLightMovement: 0 + m_CachedShadowTranslationThreshold: 0.01 + m_CachedShadowAngularThreshold: 0.5 + m_BarnDoorAngle: 90 + m_BarnDoorLength: 0.05 + m_preserveCachedShadow: 0 + m_OnDemandShadowRenderOnPlacement: 1 + m_ShadowCascadeRatios: + - 0.05 + - 0.2 + - 0.3 + m_ShadowCascadeBorders: + - 0.2 + - 0.2 + - 0.2 + - 0.2 + m_ShadowAlgorithm: 0 + m_ShadowVariant: 0 + m_ShadowPrecision: 0 + useOldInspector: 0 + useVolumetric: 1 + featuresFoldout: 1 + m_AreaLightEmissiveMeshShadowCastingMode: 0 + m_AreaLightEmissiveMeshMotionVectorGenerationMode: 0 + m_AreaLightEmissiveMeshLayer: -1 + m_Version: 11 + m_ObsoleteShadowResolutionTier: 1 + m_ObsoleteUseShadowQualitySettings: 0 + m_ObsoleteCustomShadowResolution: 512 + m_ObsoleteContactShadows: 0 + m_PointlightHDType: 0 + m_SpotLightShape: 0 + m_AreaLightShape: 0 +--- !u!1 &432432459 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 432432461} + - component: {fileID: 432432460} + m_Layer: 0 + m_Name: Sky and Fog Volume + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &432432460 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 432432459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} + m_Name: + m_EditorClassIdentifier: + isGlobal: 1 + priority: 0 + blendDistance: 0 + weight: 1 + sharedProfile: {fileID: 11400000, guid: 8ba92e2dd7f884a0f88b98fa2d235fe7, type: 2} +--- !u!4 &432432461 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 432432459} + 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!1 &1823688464 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1823688467} + - component: {fileID: 1823688466} + - component: {fileID: 1823688465} + - component: {fileID: 1823688468} + 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 &1823688465 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 +--- !u!20 &1823688466 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_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: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 0 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1823688467 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + 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 &1823688468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1823688464} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 23c1ce4fb46143f46bc5cb5224c934f6, type: 3} + m_Name: + m_EditorClassIdentifier: + clearColorMode: 0 + backgroundColorHDR: {r: 0.025, g: 0.07, b: 0.19, a: 0} + clearDepth: 1 + volumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + volumeAnchorOverride: {fileID: 0} + antialiasing: 2 + SMAAQuality: 2 + dithering: 1 + stopNaNs: 0 + taaSharpenStrength: 0.5 + TAAQuality: 1 + taaHistorySharpening: 0.35 + taaAntiFlicker: 0.5 + taaMotionVectorRejection: 0 + taaAntiHistoryRinging: 0 + physicalParameters: + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + flipYMode: 0 + xrRendering: 1 + fullscreenPassthrough: 0 + allowDynamicResolution: 0 + customRenderingSettings: 0 + invertFaceCulling: 0 + probeLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + hasPersistentHistory: 0 + allowDeepLearningSuperSampling: 1 + deepLearningSuperSamplingUseCustomQualitySettings: 0 + deepLearningSuperSamplingQuality: 0 + deepLearningSuperSamplingUseCustomAttributes: 0 + deepLearningSuperSamplingUseOptimalSettings: 1 + deepLearningSuperSamplingSharpening: 0 + exposureTarget: {fileID: 0} + materialMipBias: 0 + m_RenderingPathCustomFrameSettings: + bitDatas: + data1: 72198262773251917 + data2: 13763000464465395712 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 9 + materialQuality: 0 + renderingPathCustomFrameSettingsOverrideMask: + mask: + data1: 0 + data2: 0 + defaultFrameSettings: 0 + m_Version: 8 + m_ObsoleteRenderingPath: 0 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 +--- !u!1 &2013410473 +GameObject: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2013410475} + - component: {fileID: 2013410474} + m_Layer: 0 + m_Name: StaticLightingSky + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2013410474 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2013410473} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 441482e8936e35048a1dffac814e3ef8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Profile: {fileID: 11400000, guid: 8ba92e2dd7f884a0f88b98fa2d235fe7, type: 2} + m_StaticLightingSkyUniqueID: 4 + m_StaticLightingCloudsUniqueID: 0 +--- !u!4 &2013410475 +Transform: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2013410473} + 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: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scenes/TitleAndMenu.unity.meta b/Assets/Scenes/TitleAndMenu.unity.meta new file mode 100644 index 0000000..e0af08b --- /dev/null +++ b/Assets/Scenes/TitleAndMenu.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8124e5870f4fd4c779e7a5f994e84ad1 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings.meta b/Assets/Settings.meta new file mode 100644 index 0000000..5704eb7 --- /dev/null +++ b/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b912c280ff3334ce98f15a14956a3e5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRP Balanced.asset b/Assets/Settings/HDRP Balanced.asset new file mode 100644 index 0000000..8c66504 --- /dev/null +++ b/Assets/Settings/HDRP Balanced.asset @@ -0,0 +1,476 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cf1dab834d4ec34195b920ea7bbf9ec, type: 3} + m_Name: HDRP Balanced + m_EditorClassIdentifier: + m_RenderPipelineSettings: + supportShadowMask: 0 + supportSSR: 0 + supportSSRTransparent: 0 + supportSSAO: 1 + supportSSGI: 1 + supportSubsurfaceScattering: 1 + sssSampleBudget: + m_Values: 140000002800000050000000 + m_SchemaId: + m_Id: With3Levels + supportVolumetrics: 1 + supportVolumetricClouds: 0 + supportLightLayers: 1 + supportDistortion: 1 + supportTransparentBackface: 1 + supportTransparentDepthPrepass: 1 + supportTransparentDepthPostpass: 1 + colorBufferFormat: 74 + supportCustomPass: 1 + customBufferFormat: 12 + supportedLitShaderMode: 2 + planarReflectionResolution: + m_Values: 000100000004000000040000 + m_SchemaId: + m_Id: With3Levels + supportDecals: 1 + supportDecalLayers: 0 + supportSurfaceGradient: 0 + decalNormalBufferHP: 0 + msaaSampleCount: 1 + supportMotionVectors: 1 + supportRuntimeAOVAPI: 0 + supportDitheringCrossFade: 1 + supportTerrainHole: 0 + supportProbeVolume: 0 + probeVolumeMemoryBudget: 1024 + probeVolumeSHBands: 1 + supportRayTracing: 0 + supportedRayTracingMode: 3 + lightLoopSettings: + cookieAtlasSize: 512 + cookieFormat: 74 + cookieAtlasLastValidMip: 0 + cookieTexArraySize: 16 + planarReflectionAtlasSize: 1024 + reflectionProbeCacheSize: 32 + reflectionCubemapSize: 256 + reflectionCacheCompressed: 1 + reflectionProbeFormat: 74 + skyReflectionSize: 512 + skyLightingOverrideLayerMask: + serializedVersion: 2 + m_Bits: 0 + supportFabricConvolution: 0 + maxDirectionalLightsOnScreen: 16 + maxPunctualLightsOnScreen: 512 + maxAreaLightsOnScreen: 64 + maxEnvLightsOnScreen: 32 + maxDecalsOnScreen: 512 + maxPlanarReflectionOnScreen: 16 + maxLightsPerClusterCell: 16 + maxLocalVolumetricFogSize: 32 + maxLocalVolumetricFogOnScreen: 64 + hdShadowInitParams: + maxShadowRequests: 128 + directionalShadowsDepthBits: 16 + shadowFilteringQuality: 1 + punctualLightShadowAtlas: + shadowAtlasResolution: 4096 + shadowAtlasDepthBits: 16 + useDynamicViewportRescale: 1 + areaLightShadowAtlas: + shadowAtlasResolution: 2048 + shadowAtlasDepthBits: 16 + useDynamicViewportRescale: 0 + cachedPunctualLightShadowAtlas: 2048 + cachedAreaLightShadowAtlas: 2048 + shadowResolutionDirectional: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionPunctual: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionArea: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + maxDirectionalShadowMapResolution: 2048 + maxPunctualShadowMapResolution: 2048 + maxAreaShadowMapResolution: 2048 + supportScreenSpaceShadows: 0 + maxScreenSpaceShadowSlots: 4 + screenSpaceShadowBufferFormat: 48 + decalSettings: + drawDistance: 1000 + atlasWidth: 2048 + atlasHeight: 2048 + perChannelMask: 1 + postProcessSettings: + m_LutSize: 32 + lutFormat: 48 + bufferFormat: 74 + dynamicResolutionSettings: + enabled: 0 + useMipBias: 0 + enableDLSS: 0 + DLSSPerfQualitySetting: 0 + DLSSUseOptimalSettings: 0 + DLSSSharpness: 0 + maxPercentage: 100 + minPercentage: 100 + dynResType: 1 + upsampleFilter: 1 + forceResolution: 0 + forcedPercentage: 100 + lowResTransparencyMinimumThreshold: 0 + rayTracingHalfResThreshold: 50 + lowresTransparentSettings: + enabled: 1 + checkerboardDepthBuffer: 1 + upsampleType: 1 + xrSettings: + singlePass: 1 + occlusionMesh: 1 + cameraJitter: 0 + postProcessQualitySettings: + NearBlurSampleCount: 030000000400000005000000 + NearBlurMaxRadius: + - 2 + - 3 + - 4 + FarBlurSampleCount: 040000000500000007000000 + FarBlurMaxRadius: + - 5 + - 6 + - 8 + DoFResolution: 040000000200000002000000 + DoFHighQualityFiltering: 000001 + DoFPhysicallyBased: 000000 + MotionBlurSampleCount: 04000000080000000c000000 + BloomRes: 040000000200000002000000 + BloomHighQualityFiltering: 000101 + BloomHighQualityPrefiltering: 000001 + ChromaticAberrationMaxSamples: 03000000060000000c000000 + lightSettings: + useContactShadow: + m_Values: 000101 + m_SchemaId: + m_Id: + maximumLODLevel: + m_Values: 000000000000000000000000 + m_SchemaId: + m_Id: With3Levels + lodBias: + m_Values: + - 1 + - 1 + - 1 + m_SchemaId: + m_Id: With3Levels + lightingQualitySettings: + AOStepCount: 040000000600000006000000 + AOFullRes: 000000 + AOMaximumRadiusPixels: 200000002000000028000000 + AOBilateralUpsample: 000001 + AODirectionCount: 010000000200000002000000 + ContactShadowSampleCount: 04000000080000000c000000 + SSRMaxRaySteps: 0c0000001800000030000000 + SSGIRaySteps: 200000004000000080000000 + SSGIDenoise: 010101 + SSGIHalfResDenoise: 010000 + SSGIDenoiserRadius: + - 0.75 + - 0.5 + - 0.5 + SSGISecondDenoise: 010101 + RTAORayLength: + - 0.5 + - 3 + - 20 + RTAOSampleCount: 010000000200000008000000 + RTAODenoise: 010101 + RTAODenoiserRadius: + - 0.25 + - 0.5 + - 0.65 + RTGIRayLength: + - 50 + - 50 + - 50 + RTGIFullResolution: 000001 + RTGIClampValue: + - 0.5 + - 0.8 + - 1.5 + RTGIRaySteps: 200000003000000040000000 + RTGIDenoise: 010101 + RTGIHalfResDenoise: 010000 + RTGIDenoiserRadius: + - 0.66 + - 0.66 + - 1 + RTGISecondDenoise: 010101 + RTRMinSmoothness: + - 0.6 + - 0.4 + - 0 + RTRSmoothnessFadeStart: + - 0.7 + - 0.5 + - 0 + RTRRayLength: + - 50 + - 50 + - 50 + RTRClampValue: + - 0.8 + - 1 + - 1.2 + RTRFullResolution: 000001 + RTRRayMaxIterations: 200000003000000040000000 + RTRDenoise: 010101 + RTRDenoiserRadius: 080000000c00000010000000 + RTRSmoothDenoising: 010000 + Fog_ControlMode: 000000000000000000000000 + Fog_Budget: + - 0.166 + - 0.333 + - 0.666 + Fog_DepthRatio: + - 0.5 + - 0.5 + - 0.5 + m_ObsoleteLightLayerName0: Light LayerDefault + m_ObsoleteLightLayerName1: InteriorOnly + m_ObsoleteLightLayerName2: ExteriorOnly + m_ObsoleteLightLayerName3: LampsOnly + m_ObsoleteLightLayerName4: ReflectionsOnly + m_ObsoleteLightLayerName5: Light Layer 5 + m_ObsoleteLightLayerName6: Light Layer 6 + m_ObsoleteLightLayerName7: Light Layer 7 + m_ObsoleteDecalLayerName0: Decal Layer default + m_ObsoleteDecalLayerName1: Decal Layer 1 + m_ObsoleteDecalLayerName2: Decal Layer 2 + m_ObsoleteDecalLayerName3: Decal Layer 3 + m_ObsoleteDecalLayerName4: Decal Layer 4 + m_ObsoleteDecalLayerName5: Decal Layer 5 + m_ObsoleteDecalLayerName6: Decal Layer 6 + m_ObsoleteDecalLayerName7: Decal Layer 7 + m_ObsoleteSupportRuntimeDebugDisplay: 0 + allowShaderVariantStripping: 1 + enableSRPBatcher: 1 + availableMaterialQualityLevels: -1 + m_DefaultMaterialQualityLevel: 4 + diffusionProfileSettings: {fileID: 0} + virtualTexturingSettings: + streamingCpuCacheSizeInMegaBytes: 256 + streamingGpuCacheSettings: + - format: 0 + sizeInMegaBytes: 128 + m_UseRenderGraph: 1 + m_Version: 21 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteRealtimeReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteDefaultVolumeProfile: {fileID: 0} + m_ObsoleteDefaultLookDevProfile: {fileID: 11400000, guid: 254c4fe87beb7be4fa72e1681edbed02, + type: 2} + m_ObsoleteFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 140666621263709 + data2: 4539628427610619928 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 139742655312669 + data2: 4539628424389459992 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRealtimeReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 139716617048837 + data2: 4539628424389459992 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRenderPipelineResources: {fileID: 11400000, guid: 3ce144cff5783da45aa5d4fdc2da14b7, + type: 2} + m_ObsoleteRenderPipelineRayTracingResources: {fileID: 0} + m_ObsoleteBeforeTransparentCustomPostProcesses: [] + m_ObsoleteBeforePostProcessCustomPostProcesses: [] + m_ObsoleteAfterPostProcessCustomPostProcesses: [] + m_ObsoleteBeforeTAACustomPostProcesses: [] + m_ObsoleteShaderVariantLogLevel: 0 + m_ObsoleteLensAttenuation: 0 + m_ObsoleteDiffusionProfileSettingsList: + - {fileID: 0} + - {fileID: 0} + - {fileID: 11400000, guid: 2b7005ba3a4d8474b8cdc34141ad766e, type: 2} + - {fileID: 0} diff --git a/Assets/Settings/HDRP Balanced.asset.meta b/Assets/Settings/HDRP Balanced.asset.meta new file mode 100644 index 0000000..0df13d1 --- /dev/null +++ b/Assets/Settings/HDRP Balanced.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3e2e6bfc59709614ab90c0cd7d755e48 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRP High Fidelity.asset b/Assets/Settings/HDRP High Fidelity.asset new file mode 100644 index 0000000..699f48b --- /dev/null +++ b/Assets/Settings/HDRP High Fidelity.asset @@ -0,0 +1,474 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cf1dab834d4ec34195b920ea7bbf9ec, type: 3} + m_Name: HDRP High Fidelity + m_EditorClassIdentifier: + m_RenderPipelineSettings: + supportShadowMask: 0 + supportSSR: 0 + supportSSRTransparent: 0 + supportSSAO: 1 + supportSSGI: 0 + supportSubsurfaceScattering: 1 + sssSampleBudget: + m_Values: 140000002800000050000000 + m_SchemaId: + m_Id: With3Levels + supportVolumetrics: 1 + supportVolumetricClouds: 0 + supportLightLayers: 1 + supportDistortion: 1 + supportTransparentBackface: 1 + supportTransparentDepthPrepass: 1 + supportTransparentDepthPostpass: 1 + colorBufferFormat: 48 + supportCustomPass: 1 + customBufferFormat: 12 + supportedLitShaderMode: 2 + planarReflectionResolution: + m_Values: 000100000004000000080000 + m_SchemaId: + m_Id: With3Levels + supportDecals: 1 + supportDecalLayers: 0 + supportSurfaceGradient: 0 + decalNormalBufferHP: 0 + msaaSampleCount: 1 + supportMotionVectors: 1 + supportRuntimeAOVAPI: 0 + supportDitheringCrossFade: 1 + supportTerrainHole: 0 + supportProbeVolume: 0 + probeVolumeMemoryBudget: 1024 + probeVolumeSHBands: 1 + supportRayTracing: 0 + supportedRayTracingMode: 3 + lightLoopSettings: + cookieAtlasSize: 512 + cookieFormat: 74 + cookieAtlasLastValidMip: 0 + cookieTexArraySize: 16 + planarReflectionAtlasSize: 2048 + reflectionProbeCacheSize: 32 + reflectionCubemapSize: 256 + reflectionCacheCompressed: 1 + reflectionProbeFormat: 74 + skyReflectionSize: 1024 + skyLightingOverrideLayerMask: + serializedVersion: 2 + m_Bits: 0 + supportFabricConvolution: 0 + maxDirectionalLightsOnScreen: 16 + maxPunctualLightsOnScreen: 512 + maxAreaLightsOnScreen: 64 + maxEnvLightsOnScreen: 32 + maxDecalsOnScreen: 512 + maxPlanarReflectionOnScreen: 16 + maxLightsPerClusterCell: 16 + maxLocalVolumetricFogSize: 32 + maxLocalVolumetricFogOnScreen: 64 + hdShadowInitParams: + maxShadowRequests: 128 + directionalShadowsDepthBits: 16 + shadowFilteringQuality: 2 + punctualLightShadowAtlas: + shadowAtlasResolution: 4096 + shadowAtlasDepthBits: 16 + useDynamicViewportRescale: 1 + areaLightShadowAtlas: + shadowAtlasResolution: 4096 + shadowAtlasDepthBits: 16 + useDynamicViewportRescale: 0 + cachedPunctualLightShadowAtlas: 4096 + cachedAreaLightShadowAtlas: 4096 + shadowResolutionDirectional: + m_Values: 00020000000400000008000000100000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionPunctual: + m_Values: 00020000000400000008000000100000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionArea: + m_Values: 00020000000400000008000000100000 + m_SchemaId: + m_Id: With4Levels + maxDirectionalShadowMapResolution: 4096 + maxPunctualShadowMapResolution: 4096 + maxAreaShadowMapResolution: 4096 + supportScreenSpaceShadows: 0 + maxScreenSpaceShadowSlots: 4 + screenSpaceShadowBufferFormat: 48 + decalSettings: + drawDistance: 1000 + atlasWidth: 2048 + atlasHeight: 2048 + perChannelMask: 1 + postProcessSettings: + m_LutSize: 32 + lutFormat: 48 + bufferFormat: 74 + dynamicResolutionSettings: + enabled: 0 + useMipBias: 0 + enableDLSS: 0 + DLSSPerfQualitySetting: 0 + DLSSUseOptimalSettings: 0 + DLSSSharpness: 0 + maxPercentage: 100 + minPercentage: 100 + dynResType: 1 + upsampleFilter: 1 + forceResolution: 0 + forcedPercentage: 100 + lowResTransparencyMinimumThreshold: 0 + rayTracingHalfResThreshold: 50 + lowresTransparentSettings: + enabled: 1 + checkerboardDepthBuffer: 1 + upsampleType: 1 + xrSettings: + singlePass: 1 + occlusionMesh: 1 + cameraJitter: 0 + postProcessQualitySettings: + NearBlurSampleCount: 030000000500000008000000 + NearBlurMaxRadius: + - 2 + - 4 + - 7 + FarBlurSampleCount: 04000000070000000e000000 + FarBlurMaxRadius: + - 5 + - 8 + - 13 + DoFResolution: 040000000200000001000000 + DoFHighQualityFiltering: 000101 + DoFPhysicallyBased: 000000 + MotionBlurSampleCount: 04000000080000000c000000 + BloomRes: 040000000200000002000000 + BloomHighQualityFiltering: 000101 + BloomHighQualityPrefiltering: 000001 + ChromaticAberrationMaxSamples: 03000000060000000c000000 + lightSettings: + useContactShadow: + m_Values: 000101 + m_SchemaId: + m_Id: + maximumLODLevel: + m_Values: 000000000000000000000000 + m_SchemaId: + m_Id: With3Levels + lodBias: + m_Values: + - 1 + - 1 + - 1 + m_SchemaId: + m_Id: With3Levels + lightingQualitySettings: + AOStepCount: 040000000600000010000000 + AOFullRes: 000001 + AOMaximumRadiusPixels: 200000002800000050000000 + AOBilateralUpsample: 000101 + AODirectionCount: 010000000200000004000000 + ContactShadowSampleCount: 060000000a00000010000000 + SSRMaxRaySteps: 100000002000000040000000 + SSGIRaySteps: 200000004000000080000000 + SSGIDenoise: 010101 + SSGIHalfResDenoise: 010000 + SSGIDenoiserRadius: + - 0.75 + - 0.5 + - 0.5 + SSGISecondDenoise: 010101 + RTAORayLength: + - 0.5 + - 3 + - 20 + RTAOSampleCount: 010000000200000008000000 + RTAODenoise: 010101 + RTAODenoiserRadius: + - 0.25 + - 0.5 + - 0.65 + RTGIRayLength: + - 50 + - 50 + - 50 + RTGIFullResolution: 000001 + RTGIClampValue: + - 0.5 + - 0.8 + - 1.5 + RTGIRaySteps: 200000003000000040000000 + RTGIDenoise: 010101 + RTGIHalfResDenoise: 010000 + RTGIDenoiserRadius: + - 0.66 + - 0.66 + - 1 + RTGISecondDenoise: 010101 + RTRMinSmoothness: + - 0.6 + - 0.4 + - 0 + RTRSmoothnessFadeStart: + - 0.7 + - 0.5 + - 0 + RTRRayLength: + - 50 + - 50 + - 50 + RTRClampValue: + - 0.8 + - 1 + - 1.2 + RTRFullResolution: 000001 + RTRRayMaxIterations: 200000003000000040000000 + RTRDenoise: 010101 + RTRDenoiserRadius: 080000000c00000010000000 + RTRSmoothDenoising: 010000 + Fog_ControlMode: 000000000000000000000000 + Fog_Budget: + - 0.25 + - 0.5 + - 0.75 + Fog_DepthRatio: + - 0.5 + - 0.5 + - 0.5 + m_ObsoleteLightLayerName0: Light LayerDefault + m_ObsoleteLightLayerName1: InteriorOnly + m_ObsoleteLightLayerName2: ExteriorOnly + m_ObsoleteLightLayerName3: LampsOnly + m_ObsoleteLightLayerName4: Light Layer 4 + m_ObsoleteLightLayerName5: Light Layer 5 + m_ObsoleteLightLayerName6: Light Layer 6 + m_ObsoleteLightLayerName7: Light Layer 7 + m_ObsoleteDecalLayerName0: Decal Layer default + m_ObsoleteDecalLayerName1: Decal Layer 1 + m_ObsoleteDecalLayerName2: Decal Layer 2 + m_ObsoleteDecalLayerName3: Decal Layer 3 + m_ObsoleteDecalLayerName4: Decal Layer 4 + m_ObsoleteDecalLayerName5: Decal Layer 5 + m_ObsoleteDecalLayerName6: Decal Layer 6 + m_ObsoleteDecalLayerName7: Decal Layer 7 + m_ObsoleteSupportRuntimeDebugDisplay: 0 + allowShaderVariantStripping: 1 + enableSRPBatcher: 1 + availableMaterialQualityLevels: -1 + m_DefaultMaterialQualityLevel: 4 + diffusionProfileSettings: {fileID: 0} + virtualTexturingSettings: + streamingCpuCacheSizeInMegaBytes: 256 + streamingGpuCacheSettings: + - format: 0 + sizeInMegaBytes: 128 + m_UseRenderGraph: 1 + m_Version: 21 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteRealtimeReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteDefaultVolumeProfile: {fileID: 0} + m_ObsoleteDefaultLookDevProfile: {fileID: 11400000, guid: 254c4fe87beb7be4fa72e1681edbed02, + type: 2} + m_ObsoleteFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 140666621394781 + data2: 4539628425463136280 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 139742655312669 + data2: 4539628424389459992 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRealtimeReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 139991494955789 + data2: 4539628424389459992 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRenderPipelineResources: {fileID: 11400000, guid: 3ce144cff5783da45aa5d4fdc2da14b7, + type: 2} + m_ObsoleteRenderPipelineRayTracingResources: {fileID: 0} + m_ObsoleteBeforeTransparentCustomPostProcesses: [] + m_ObsoleteBeforePostProcessCustomPostProcesses: [] + m_ObsoleteAfterPostProcessCustomPostProcesses: [] + m_ObsoleteBeforeTAACustomPostProcesses: [] + m_ObsoleteShaderVariantLogLevel: 0 + m_ObsoleteLensAttenuation: 0 + m_ObsoleteDiffusionProfileSettingsList: + - {fileID: 0} + - {fileID: 0} diff --git a/Assets/Settings/HDRP High Fidelity.asset.meta b/Assets/Settings/HDRP High Fidelity.asset.meta new file mode 100644 index 0000000..556a763 --- /dev/null +++ b/Assets/Settings/HDRP High Fidelity.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 36dd385e759c96147b6463dcd1149c11 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRP Performant.asset b/Assets/Settings/HDRP Performant.asset new file mode 100644 index 0000000..b705fd0 --- /dev/null +++ b/Assets/Settings/HDRP Performant.asset @@ -0,0 +1,475 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cf1dab834d4ec34195b920ea7bbf9ec, type: 3} + m_Name: HDRP Performant + m_EditorClassIdentifier: + m_RenderPipelineSettings: + supportShadowMask: 0 + supportSSR: 0 + supportSSRTransparent: 0 + supportSSAO: 1 + supportSSGI: 0 + supportSubsurfaceScattering: 1 + sssSampleBudget: + m_Values: 140000002800000050000000 + m_SchemaId: + m_Id: With3Levels + supportVolumetrics: 0 + supportVolumetricClouds: 0 + supportLightLayers: 1 + supportDistortion: 1 + supportTransparentBackface: 1 + supportTransparentDepthPrepass: 1 + supportTransparentDepthPostpass: 1 + colorBufferFormat: 74 + supportCustomPass: 1 + customBufferFormat: 12 + supportedLitShaderMode: 2 + planarReflectionResolution: + m_Values: 000100000002000000020000 + m_SchemaId: + m_Id: With3Levels + supportDecals: 1 + supportDecalLayers: 0 + supportSurfaceGradient: 0 + decalNormalBufferHP: 0 + msaaSampleCount: 1 + supportMotionVectors: 1 + supportRuntimeAOVAPI: 0 + supportDitheringCrossFade: 1 + supportTerrainHole: 0 + supportProbeVolume: 0 + probeVolumeMemoryBudget: 1024 + probeVolumeSHBands: 1 + supportRayTracing: 0 + supportedRayTracingMode: 3 + lightLoopSettings: + cookieAtlasSize: 512 + cookieFormat: 74 + cookieAtlasLastValidMip: 0 + cookieTexArraySize: 16 + planarReflectionAtlasSize: 512 + reflectionProbeCacheSize: 32 + reflectionCubemapSize: 256 + reflectionCacheCompressed: 1 + reflectionProbeFormat: 74 + skyReflectionSize: 256 + skyLightingOverrideLayerMask: + serializedVersion: 2 + m_Bits: 0 + supportFabricConvolution: 0 + maxDirectionalLightsOnScreen: 16 + maxPunctualLightsOnScreen: 512 + maxAreaLightsOnScreen: 64 + maxEnvLightsOnScreen: 32 + maxDecalsOnScreen: 512 + maxPlanarReflectionOnScreen: 16 + maxLightsPerClusterCell: 16 + maxLocalVolumetricFogSize: 32 + maxLocalVolumetricFogOnScreen: 64 + hdShadowInitParams: + maxShadowRequests: 128 + directionalShadowsDepthBits: 16 + shadowFilteringQuality: 1 + punctualLightShadowAtlas: + shadowAtlasResolution: 4096 + shadowAtlasDepthBits: 16 + useDynamicViewportRescale: 1 + areaLightShadowAtlas: + shadowAtlasResolution: 2048 + shadowAtlasDepthBits: 16 + useDynamicViewportRescale: 0 + cachedPunctualLightShadowAtlas: 2048 + cachedAreaLightShadowAtlas: 2048 + shadowResolutionDirectional: + m_Values: 80000000000100000002000000040000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionPunctual: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionArea: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + maxDirectionalShadowMapResolution: 1024 + maxPunctualShadowMapResolution: 2048 + maxAreaShadowMapResolution: 2048 + supportScreenSpaceShadows: 0 + maxScreenSpaceShadowSlots: 4 + screenSpaceShadowBufferFormat: 48 + decalSettings: + drawDistance: 1000 + atlasWidth: 2048 + atlasHeight: 2048 + perChannelMask: 1 + postProcessSettings: + m_LutSize: 32 + lutFormat: 48 + bufferFormat: 74 + dynamicResolutionSettings: + enabled: 0 + useMipBias: 0 + enableDLSS: 0 + DLSSPerfQualitySetting: 0 + DLSSUseOptimalSettings: 0 + DLSSSharpness: 0 + maxPercentage: 100 + minPercentage: 100 + dynResType: 1 + upsampleFilter: 1 + forceResolution: 0 + forcedPercentage: 100 + lowResTransparencyMinimumThreshold: 0 + rayTracingHalfResThreshold: 50 + lowresTransparentSettings: + enabled: 1 + checkerboardDepthBuffer: 1 + upsampleType: 1 + xrSettings: + singlePass: 1 + occlusionMesh: 1 + cameraJitter: 0 + postProcessQualitySettings: + NearBlurSampleCount: 030000000400000004000000 + NearBlurMaxRadius: + - 2 + - 3 + - 3 + FarBlurSampleCount: 040000000500000005000000 + FarBlurMaxRadius: + - 5 + - 6 + - 6 + DoFResolution: 040000000400000002000000 + DoFHighQualityFiltering: 000100 + DoFPhysicallyBased: 000000 + MotionBlurSampleCount: 04000000080000000c000000 + BloomRes: 040000000200000002000000 + BloomHighQualityFiltering: 000101 + BloomHighQualityPrefiltering: 000001 + ChromaticAberrationMaxSamples: 03000000060000000c000000 + lightSettings: + useContactShadow: + m_Values: 000101 + m_SchemaId: + m_Id: + maximumLODLevel: + m_Values: 000000000000000000000000 + m_SchemaId: + m_Id: With3Levels + lodBias: + m_Values: + - 1 + - 1 + - 1 + m_SchemaId: + m_Id: With3Levels + lightingQualitySettings: + AOStepCount: 030000000400000006000000 + AOFullRes: 000000 + AOMaximumRadiusPixels: 180000002000000020000000 + AOBilateralUpsample: 000000 + AODirectionCount: 010000000100000002000000 + ContactShadowSampleCount: 04000000060000000a000000 + SSRMaxRaySteps: 080000001000000020000000 + SSGIRaySteps: 200000004000000080000000 + SSGIDenoise: 010101 + SSGIHalfResDenoise: 010000 + SSGIDenoiserRadius: + - 0.75 + - 0.5 + - 0.5 + SSGISecondDenoise: 010101 + RTAORayLength: + - 0.5 + - 3 + - 20 + RTAOSampleCount: 010000000200000008000000 + RTAODenoise: 010101 + RTAODenoiserRadius: + - 0.25 + - 0.5 + - 0.65 + RTGIRayLength: + - 50 + - 50 + - 50 + RTGIFullResolution: 000001 + RTGIClampValue: + - 0.5 + - 0.8 + - 1.5 + RTGIRaySteps: 200000003000000040000000 + RTGIDenoise: 010101 + RTGIHalfResDenoise: 010000 + RTGIDenoiserRadius: + - 0.66 + - 0.66 + - 1 + RTGISecondDenoise: 010101 + RTRMinSmoothness: + - 0.6 + - 0.4 + - 0 + RTRSmoothnessFadeStart: + - 0.7 + - 0.5 + - 0 + RTRRayLength: + - 50 + - 50 + - 50 + RTRClampValue: + - 0.8 + - 1 + - 1.2 + RTRFullResolution: 000001 + RTRRayMaxIterations: 200000003000000040000000 + RTRDenoise: 010101 + RTRDenoiserRadius: 080000000c00000010000000 + RTRSmoothDenoising: 010000 + Fog_ControlMode: 000000000000000000000000 + Fog_Budget: + - 0.125 + - 0.25 + - 0.5 + Fog_DepthRatio: + - 0.5 + - 0.5 + - 0.5 + m_ObsoleteLightLayerName0: Light LayerDefault + m_ObsoleteLightLayerName1: InteriorOnly + m_ObsoleteLightLayerName2: ExteriorOnly + m_ObsoleteLightLayerName3: LampsOnly + m_ObsoleteLightLayerName4: ReflectionsOnly + m_ObsoleteLightLayerName5: Light Layer 5 + m_ObsoleteLightLayerName6: Light Layer 6 + m_ObsoleteLightLayerName7: Light Layer 7 + m_ObsoleteDecalLayerName0: Decal Layer default + m_ObsoleteDecalLayerName1: Decal Layer 1 + m_ObsoleteDecalLayerName2: Decal Layer 2 + m_ObsoleteDecalLayerName3: Decal Layer 3 + m_ObsoleteDecalLayerName4: Decal Layer 4 + m_ObsoleteDecalLayerName5: Decal Layer 5 + m_ObsoleteDecalLayerName6: Decal Layer 6 + m_ObsoleteDecalLayerName7: Decal Layer 7 + m_ObsoleteSupportRuntimeDebugDisplay: 0 + allowShaderVariantStripping: 1 + enableSRPBatcher: 1 + availableMaterialQualityLevels: -1 + m_DefaultMaterialQualityLevel: 4 + diffusionProfileSettings: {fileID: 0} + virtualTexturingSettings: + streamingCpuCacheSizeInMegaBytes: 256 + streamingGpuCacheSettings: + - format: 0 + sizeInMegaBytes: 128 + m_UseRenderGraph: 1 + m_Version: 21 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteRealtimeReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteDefaultVolumeProfile: {fileID: 0} + m_ObsoleteDefaultLookDevProfile: {fileID: 11400000, guid: 254c4fe87beb7be4fa72e1681edbed02, + type: 2} + m_ObsoleteFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 140666621394781 + data2: 4539628425463136280 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 139742655312669 + data2: 4539628424389459992 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRealtimeReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 139716617048837 + data2: 4539628424389459992 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRenderPipelineResources: {fileID: 11400000, guid: 3ce144cff5783da45aa5d4fdc2da14b7, + type: 2} + m_ObsoleteRenderPipelineRayTracingResources: {fileID: 0} + m_ObsoleteBeforeTransparentCustomPostProcesses: [] + m_ObsoleteBeforePostProcessCustomPostProcesses: [] + m_ObsoleteAfterPostProcessCustomPostProcesses: [] + m_ObsoleteBeforeTAACustomPostProcesses: [] + m_ObsoleteShaderVariantLogLevel: 0 + m_ObsoleteLensAttenuation: 0 + m_ObsoleteDiffusionProfileSettingsList: + - {fileID: 0} + - {fileID: 0} + - {fileID: 11400000, guid: 2b7005ba3a4d8474b8cdc34141ad766e, type: 2} diff --git a/Assets/Settings/HDRP Performant.asset.meta b/Assets/Settings/HDRP Performant.asset.meta new file mode 100644 index 0000000..e6f7b1c --- /dev/null +++ b/Assets/Settings/HDRP Performant.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 168a2336534e4e043b2a210b6f8d379a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources.meta b/Assets/Settings/HDRPDefaultResources.meta new file mode 100644 index 0000000..bc14cdc --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 181cd982040374fac84aed5329ef5583 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset b/Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset new file mode 100644 index 0000000..67114ab --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset @@ -0,0 +1,220 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: DefaultLookDevProfile + m_EditorClassIdentifier: + components: + - {fileID: 8761387877531654226} + - {fileID: 1902828633788537306} + - {fileID: 1880163708194025631} + - {fileID: 2340290907100754200} +--- !u!114 &1880163708194025631 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9008a067f4d626c4d8bc4bc48f04bb89, type: 3} + m_Name: AmbientOcclusion + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 0 + m_Value: 1 + rayTracing: + m_OverrideState: 0 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0.5 + directLightingStrength: + m_OverrideState: 0 + m_Value: 0 + radius: + m_OverrideState: 1 + m_Value: 1 + spatialBilateralAggressiveness: + m_OverrideState: 0 + m_Value: 0.15 + temporalAccumulation: + m_OverrideState: 0 + m_Value: 1 + ghostingReduction: + m_OverrideState: 0 + m_Value: 0.5 + blurSharpness: + m_OverrideState: 0 + m_Value: 0.1 + layerMask: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Bits: 4294967295 + m_StepCount: + m_OverrideState: 0 + m_Value: 6 + m_FullResolution: + m_OverrideState: 0 + m_Value: 0 + m_MaximumRadiusInPixels: + m_OverrideState: 0 + m_Value: 40 + m_BilateralUpsample: + m_OverrideState: 0 + m_Value: 1 + m_DirectionCount: + m_OverrideState: 0 + m_Value: 2 + m_RayLength: + m_OverrideState: 0 + m_Value: 3 + m_SampleCount: + m_OverrideState: 0 + m_Value: 2 + m_Denoise: + m_OverrideState: 0 + m_Value: 1 + m_DenoiserRadius: + m_OverrideState: 0 + m_Value: 0.5 +--- !u!114 &1902828633788537306 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7ddcec8a8eb2d684d833ac8f5d26aebd, type: 3} + m_Name: HDShadowSettings + m_EditorClassIdentifier: + active: 1 + maxShadowDistance: + m_OverrideState: 1 + m_Value: 25 + directionalTransmissionMultiplier: + m_OverrideState: 0 + m_Value: 1 + cascadeShadowSplitCount: + m_OverrideState: 1 + m_Value: 2 + cascadeShadowSplit0: + m_OverrideState: 0 + m_Value: 0.05 + cascadeShadowSplit1: + m_OverrideState: 0 + m_Value: 0.15 + cascadeShadowSplit2: + m_OverrideState: 0 + m_Value: 0.3 + cascadeShadowBorder0: + m_OverrideState: 0 + m_Value: 0 + cascadeShadowBorder1: + m_OverrideState: 0 + m_Value: 0 + cascadeShadowBorder2: + m_OverrideState: 0 + m_Value: 0 + cascadeShadowBorder3: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &2340290907100754200 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 24f077503be6ae942a1e1245dbd53ea9, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 0 + m_Value: 3 + threshold: + m_OverrideState: 0 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0.1 + scatter: + m_OverrideState: 0 + m_Value: 0.7 + tint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + dirtTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + dirtIntensity: + m_OverrideState: 0 + m_Value: 0 + anamorphic: + m_OverrideState: 0 + m_Value: 1 + m_Resolution: + m_OverrideState: 0 + m_Value: 2 + m_HighQualityPrefiltering: + m_OverrideState: 0 + m_Value: 0 + m_HighQualityFiltering: + m_OverrideState: 0 + m_Value: 1 +--- !u!114 &8761387877531654226 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f086a068d4c5889438831b3ae9afc11c, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 1 + toeStrength: + m_OverrideState: 0 + m_Value: 0 + toeLength: + m_OverrideState: 0 + m_Value: 0.5 + shoulderStrength: + m_OverrideState: 0 + m_Value: 0 + shoulderLength: + m_OverrideState: 0 + m_Value: 0.5 + shoulderAngle: + m_OverrideState: 0 + m_Value: 0 + gamma: + m_OverrideState: 0 + m_Value: 1 + lutTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + lutContribution: + m_OverrideState: 0 + m_Value: 1 diff --git a/Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset.meta b/Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset.meta new file mode 100644 index 0000000..d2ffa56 --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4594f4a3fb14247e192bcca6dc23c8ed +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset b/Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset new file mode 100644 index 0000000..e0f250d --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset @@ -0,0 +1,629 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7089757308646879465 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bcf384b154398e341b6b29969c078198, type: 3} + m_Name: MotionBlur + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 1 + m_Value: 1 + intensity: + m_OverrideState: 1 + m_Value: 0.5 + maximumVelocity: + m_OverrideState: 0 + m_Value: 200 + minimumVelocity: + m_OverrideState: 0 + m_Value: 2 + cameraMotionBlur: + m_OverrideState: 0 + m_Value: 1 + specialCameraClampMode: + m_OverrideState: 0 + m_Value: 0 + cameraVelocityClamp: + m_OverrideState: 0 + m_Value: 0.05 + cameraTranslationVelocityClamp: + m_OverrideState: 0 + m_Value: 0.05 + cameraRotationVelocityClamp: + m_OverrideState: 0 + m_Value: 0.03 + depthComparisonExtent: + m_OverrideState: 0 + m_Value: 1 + m_SampleCount: + m_OverrideState: 1 + m_Value: 8 +--- !u!114 &-1016694868962581565 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 56b145d2b9ee1ac4f846968484e7485a, type: 3} + m_Name: ContactShadows + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 0 + m_Value: 1 + enable: + m_OverrideState: 1 + m_Value: 1 + length: + m_OverrideState: 0 + m_Value: 0.15 + opacity: + m_OverrideState: 0 + m_Value: 1 + distanceScaleFactor: + m_OverrideState: 0 + m_Value: 0.5 + maxDistance: + m_OverrideState: 0 + m_Value: 50 + minDistance: + m_OverrideState: 0 + m_Value: 0 + fadeDistance: + m_OverrideState: 0 + m_Value: 5 + fadeInDistance: + m_OverrideState: 0 + m_Value: 0 + rayBias: + m_OverrideState: 0 + m_Value: 0.2 + thicknessScale: + m_OverrideState: 0 + m_Value: 0.15 + m_SampleCount: + m_OverrideState: 0 + m_Value: 10 +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: DefaultSettingsVolumeProfile + m_EditorClassIdentifier: + components: + - {fileID: 7686318427622180703} + - {fileID: -1016694868962581565} + - {fileID: 7502528774814404555} + - {fileID: 7542669330009093999} + - {fileID: 1501199423866068322} + - {fileID: 5315503232242033309} + - {fileID: 1932259527246508038} + - {fileID: 448115243408767295} + - {fileID: -7089757308646879465} +--- !u!114 &448115243408767295 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59b6606ef2548734bb6d11b9d160bc7e, type: 3} + m_Name: HDRISky + m_EditorClassIdentifier: + active: 1 + rotation: + m_OverrideState: 0 + m_Value: 0 + skyIntensityMode: + m_OverrideState: 0 + m_Value: 0 + exposure: + m_OverrideState: 1 + m_Value: 11 + multiplier: + m_OverrideState: 0 + m_Value: 1 + upperHemisphereLuxValue: + m_OverrideState: 0 + m_Value: 0.4660715 + upperHemisphereLuxColor: + m_OverrideState: 0 + m_Value: {x: 0.18750614, y: 0.29181972, z: 0.5} + desiredLuxValue: + m_OverrideState: 0 + m_Value: 20000 + updateMode: + m_OverrideState: 0 + m_Value: 0 + updatePeriod: + m_OverrideState: 0 + m_Value: 0 + includeSunInBaking: + m_OverrideState: 0 + m_Value: 0 + hdriSky: + m_OverrideState: 1 + m_Value: {fileID: 8900000, guid: 8253d41e6e8b11a4cbe77a4f8f82934d, type: 3} + distortionMode: + m_OverrideState: 0 + m_Value: 0 + flowmap: + m_OverrideState: 0 + m_Value: {fileID: 0} + upperHemisphereOnly: + m_OverrideState: 0 + m_Value: 1 + scrollOrientation: + m_OverrideState: 0 + m_Value: + mode: 1 + customValue: 0 + additiveValue: 0 + multiplyValue: 1 + scrollSpeed: + m_OverrideState: 0 + m_Value: + mode: 1 + customValue: 100 + additiveValue: 0 + multiplyValue: 1 + enableBackplate: + m_OverrideState: 0 + m_Value: 0 + backplateType: + m_OverrideState: 0 + m_Value: 0 + groundLevel: + m_OverrideState: 0 + m_Value: 0 + scale: + m_OverrideState: 0 + m_Value: {x: 32, y: 32} + projectionDistance: + m_OverrideState: 0 + m_Value: 16 + plateRotation: + m_OverrideState: 0 + m_Value: 0 + plateTexRotation: + m_OverrideState: 0 + m_Value: 0 + plateTexOffset: + m_OverrideState: 0 + m_Value: {x: 0, y: 0} + blendAmount: + m_OverrideState: 0 + m_Value: 0 + shadowTint: + m_OverrideState: 0 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + pointLightShadow: + m_OverrideState: 0 + m_Value: 0 + dirLightShadow: + m_OverrideState: 0 + m_Value: 0 + rectLightShadow: + m_OverrideState: 0 + m_Value: 0 + m_SkyVersion: 1 + enableDistortion: + m_OverrideState: 0 + m_Value: 0 + procedural: + m_OverrideState: 0 + m_Value: 1 + scrollDirection: + m_OverrideState: 0 + m_Value: 0 + m_ObsoleteScrollSpeed: + m_OverrideState: 0 + m_Value: 1 +--- !u!114 &1501199423866068322 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 24f077503be6ae942a1e1245dbd53ea9, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 1 + m_Value: 1 + threshold: + m_OverrideState: 0 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0.2 + scatter: + m_OverrideState: 0 + m_Value: 0.7 + tint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + dirtTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + dirtIntensity: + m_OverrideState: 0 + m_Value: 0 + anamorphic: + m_OverrideState: 0 + m_Value: 1 + m_Resolution: + m_OverrideState: 1 + m_Value: 2 + m_HighQualityPrefiltering: + m_OverrideState: 1 + m_Value: 0 + m_HighQualityFiltering: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &1932259527246508038 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d7593b3a9277ac4696b20006c21dde2, type: 3} + m_Name: VisualEnvironment + m_EditorClassIdentifier: + active: 1 + skyType: + m_OverrideState: 1 + m_Value: 1 + cloudType: + m_OverrideState: 0 + m_Value: 0 + skyAmbientMode: + m_OverrideState: 0 + m_Value: 0 + windOrientation: + m_OverrideState: 0 + m_Value: 0 + windSpeed: + m_OverrideState: 0 + m_Value: 100 + fogType: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &5315503232242033309 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2d08ce26990eb1a4a9177b860541e702, type: 3} + m_Name: Exposure + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 1 + meteringMode: + m_OverrideState: 0 + m_Value: 2 + luminanceSource: + m_OverrideState: 0 + m_Value: 1 + fixedExposure: + m_OverrideState: 0 + m_Value: 0 + compensation: + m_OverrideState: 0 + m_Value: 0 + limitMin: + m_OverrideState: 0 + m_Value: -10 + limitMax: + m_OverrideState: 0 + m_Value: 10 + curveMap: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: -10 + value: -10 + inSlope: 0 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 20 + value: 20 + inSlope: 1 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + limitMinCurveMap: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: -10 + value: -12 + inSlope: 0 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 20 + value: 18 + inSlope: 1 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + limitMaxCurveMap: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: -10 + value: -8 + inSlope: 0 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 20 + value: 22 + inSlope: 1 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + adaptationMode: + m_OverrideState: 0 + m_Value: 1 + adaptationSpeedDarkToLight: + m_OverrideState: 0 + m_Value: 3 + adaptationSpeedLightToDark: + m_OverrideState: 0 + m_Value: 1 + weightTextureMask: + m_OverrideState: 0 + m_Value: {fileID: 0} + histogramPercentages: + m_OverrideState: 0 + m_Value: {x: 40, y: 90} + histogramUseCurveRemapping: + m_OverrideState: 0 + m_Value: 0 + targetMidGray: + m_OverrideState: 0 + m_Value: 0 + centerAroundExposureTarget: + m_OverrideState: 0 + m_Value: 0 + proceduralCenter: + m_OverrideState: 0 + m_Value: {x: 0.5, y: 0.5} + proceduralRadii: + m_OverrideState: 0 + m_Value: {x: 0.3, y: 0.3} + maskMinIntensity: + m_OverrideState: 0 + m_Value: -30 + maskMaxIntensity: + m_OverrideState: 0 + m_Value: 30 + proceduralSoftness: + m_OverrideState: 0 + m_Value: 0.5 +--- !u!114 &7502528774814404555 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9008a067f4d626c4d8bc4bc48f04bb89, type: 3} + m_Name: AmbientOcclusion + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 0 + m_Value: 1 + rayTracing: + m_OverrideState: 0 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0.5 + directLightingStrength: + m_OverrideState: 0 + m_Value: 0 + radius: + m_OverrideState: 1 + m_Value: 1.5 + spatialBilateralAggressiveness: + m_OverrideState: 0 + m_Value: 0.15 + temporalAccumulation: + m_OverrideState: 0 + m_Value: 1 + ghostingReduction: + m_OverrideState: 0 + m_Value: 0.5 + blurSharpness: + m_OverrideState: 0 + m_Value: 0.1 + layerMask: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Bits: 4294967295 + occluderMotionRejection: + m_OverrideState: 0 + m_Value: 1 + receiverMotionRejection: + m_OverrideState: 0 + m_Value: 1 + m_StepCount: + m_OverrideState: 0 + m_Value: 6 + m_FullResolution: + m_OverrideState: 0 + m_Value: 0 + m_MaximumRadiusInPixels: + m_OverrideState: 0 + m_Value: 40 + m_BilateralUpsample: + m_OverrideState: 0 + m_Value: 1 + m_DirectionCount: + m_OverrideState: 0 + m_Value: 2 + m_RayLength: + m_OverrideState: 0 + m_Value: 3 + m_SampleCount: + m_OverrideState: 0 + m_Value: 2 + m_Denoise: + m_OverrideState: 0 + m_Value: 1 + m_DenoiserRadius: + m_OverrideState: 0 + m_Value: 0.5 +--- !u!114 &7542669330009093999 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f086a068d4c5889438831b3ae9afc11c, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 2 + toeStrength: + m_OverrideState: 0 + m_Value: 0 + toeLength: + m_OverrideState: 0 + m_Value: 0.5 + shoulderStrength: + m_OverrideState: 0 + m_Value: 0 + shoulderLength: + m_OverrideState: 0 + m_Value: 0.5 + shoulderAngle: + m_OverrideState: 0 + m_Value: 0 + gamma: + m_OverrideState: 0 + m_Value: 1 + lutTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + lutContribution: + m_OverrideState: 0 + m_Value: 1 +--- !u!114 &7686318427622180703 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7ddcec8a8eb2d684d833ac8f5d26aebd, type: 3} + m_Name: HDShadowSettings + m_EditorClassIdentifier: + active: 1 + maxShadowDistance: + m_OverrideState: 1 + m_Value: 150 + directionalTransmissionMultiplier: + m_OverrideState: 0 + m_Value: 1 + cascadeShadowSplitCount: + m_OverrideState: 0 + m_Value: 4 + cascadeShadowSplit0: + m_OverrideState: 0 + m_Value: 0.05 + cascadeShadowSplit1: + m_OverrideState: 0 + m_Value: 0.15 + cascadeShadowSplit2: + m_OverrideState: 0 + m_Value: 0.3 + cascadeShadowBorder0: + m_OverrideState: 1 + m_Value: 0.13333334 + cascadeShadowBorder1: + m_OverrideState: 1 + m_Value: 0.06666666 + cascadeShadowBorder2: + m_OverrideState: 0 + m_Value: 0 + cascadeShadowBorder3: + m_OverrideState: 0 + m_Value: 0 diff --git a/Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset.meta b/Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset.meta new file mode 100644 index 0000000..18b5dba --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 14b392ee213d25a48b1feddbd9f5a9be +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset b/Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset new file mode 100644 index 0000000..3e9a89f --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset @@ -0,0 +1,24 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b2686e09ec7aef44bad2843e4416f057, type: 3} + m_Name: FoliageDiffusionProfile + m_EditorClassIdentifier: + m_Version: 1 + profile: + scatteringDistance: {r: 0.7568628, g: 0.7019608, b: 0.24313727, a: 1} + transmissionTint: {r: 1, g: 1, b: 1, a: 1} + texturingMode: 0 + transmissionMode: 1 + thicknessRemap: {x: 0, y: 0.2873168} + worldScale: 1 + ior: 1.4 + hash: 1081692787 diff --git a/Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset.meta b/Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset.meta new file mode 100644 index 0000000..56cafea --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 879ffae44eefa4412bb327928f1a96dd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset new file mode 100644 index 0000000..6ee45f2 --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset @@ -0,0 +1,470 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cf1dab834d4ec34195b920ea7bbf9ec, type: 3} + m_Name: HDRenderPipelineAsset + m_EditorClassIdentifier: + m_RenderPipelineSettings: + supportShadowMask: 1 + supportSSR: 0 + supportSSRTransparent: 0 + supportSSAO: 1 + supportSSGI: 0 + supportSubsurfaceScattering: 1 + sssSampleBudget: + m_Values: 140000002800000050000000 + m_SchemaId: + m_Id: With3Levels + supportVolumetrics: 1 + supportVolumetricClouds: 0 + supportLightLayers: 0 + supportDistortion: 1 + supportTransparentBackface: 1 + supportTransparentDepthPrepass: 1 + supportTransparentDepthPostpass: 1 + colorBufferFormat: 74 + supportCustomPass: 1 + customBufferFormat: 12 + supportedLitShaderMode: 2 + planarReflectionResolution: + m_Values: 000100000004000000080000 + m_SchemaId: + m_Id: With3Levels + supportDecals: 1 + supportDecalLayers: 0 + supportSurfaceGradient: 0 + decalNormalBufferHP: 0 + msaaSampleCount: 1 + supportMotionVectors: 1 + supportRuntimeAOVAPI: 0 + supportDitheringCrossFade: 1 + supportTerrainHole: 0 + supportProbeVolume: 0 + probeVolumeMemoryBudget: 1024 + probeVolumeSHBands: 1 + supportRayTracing: 0 + supportedRayTracingMode: 3 + lightLoopSettings: + cookieAtlasSize: 2048 + cookieFormat: 74 + cookieAtlasLastValidMip: 0 + cookieTexArraySize: 1 + planarReflectionAtlasSize: 1024 + reflectionProbeCacheSize: 64 + reflectionCubemapSize: 256 + reflectionCacheCompressed: 0 + reflectionProbeFormat: 74 + skyReflectionSize: 256 + skyLightingOverrideLayerMask: + serializedVersion: 2 + m_Bits: 0 + supportFabricConvolution: 0 + maxDirectionalLightsOnScreen: 16 + maxPunctualLightsOnScreen: 512 + maxAreaLightsOnScreen: 64 + maxEnvLightsOnScreen: 64 + maxDecalsOnScreen: 512 + maxPlanarReflectionOnScreen: 16 + maxLightsPerClusterCell: 8 + maxLocalVolumetricFogSize: 32 + maxLocalVolumetricFogOnScreen: 64 + hdShadowInitParams: + maxShadowRequests: 128 + directionalShadowsDepthBits: 32 + shadowFilteringQuality: 1 + punctualLightShadowAtlas: + shadowAtlasResolution: 4096 + shadowAtlasDepthBits: 32 + useDynamicViewportRescale: 1 + areaLightShadowAtlas: + shadowAtlasResolution: 4096 + shadowAtlasDepthBits: 32 + useDynamicViewportRescale: 1 + cachedPunctualLightShadowAtlas: 2048 + cachedAreaLightShadowAtlas: 1024 + shadowResolutionDirectional: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionPunctual: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + shadowResolutionArea: + m_Values: 00010000000200000004000000080000 + m_SchemaId: + m_Id: With4Levels + maxDirectionalShadowMapResolution: 2048 + maxPunctualShadowMapResolution: 2048 + maxAreaShadowMapResolution: 2048 + supportScreenSpaceShadows: 0 + maxScreenSpaceShadowSlots: 4 + screenSpaceShadowBufferFormat: 48 + decalSettings: + drawDistance: 1000 + atlasWidth: 4096 + atlasHeight: 4096 + perChannelMask: 0 + postProcessSettings: + m_LutSize: 32 + lutFormat: 48 + bufferFormat: 74 + dynamicResolutionSettings: + enabled: 0 + useMipBias: 0 + enableDLSS: 0 + DLSSPerfQualitySetting: 0 + DLSSUseOptimalSettings: 0 + DLSSSharpness: 0 + maxPercentage: 100 + minPercentage: 100 + dynResType: 1 + upsampleFilter: 1 + forceResolution: 0 + forcedPercentage: 100 + lowResTransparencyMinimumThreshold: 0 + rayTracingHalfResThreshold: 50 + lowresTransparentSettings: + enabled: 1 + checkerboardDepthBuffer: 1 + upsampleType: 1 + xrSettings: + singlePass: 1 + occlusionMesh: 1 + cameraJitter: 0 + postProcessQualitySettings: + NearBlurSampleCount: 030000000500000008000000 + NearBlurMaxRadius: + - 2 + - 4 + - 7 + FarBlurSampleCount: 04000000070000000e000000 + FarBlurMaxRadius: + - 5 + - 8 + - 13 + DoFResolution: 040000000200000001000000 + DoFHighQualityFiltering: 000101 + DoFPhysicallyBased: 000000 + MotionBlurSampleCount: 04000000080000000c000000 + BloomRes: 040000000200000002000000 + BloomHighQualityFiltering: 000101 + BloomHighQualityPrefiltering: 000001 + ChromaticAberrationMaxSamples: 03000000060000000c000000 + lightSettings: + useContactShadow: + m_Values: 000001 + m_SchemaId: + m_Id: With3Levels + maximumLODLevel: + m_Values: 000000000000000000000000 + m_SchemaId: + m_Id: With3Levels + lodBias: + m_Values: + - 1 + - 1 + - 1 + m_SchemaId: + m_Id: With3Levels + lightingQualitySettings: + AOStepCount: 040000000600000010000000 + AOFullRes: 000001 + AOMaximumRadiusPixels: 200000002800000050000000 + AOBilateralUpsample: 000101 + AODirectionCount: 010000000200000004000000 + ContactShadowSampleCount: 060000000a00000010000000 + SSRMaxRaySteps: 100000002000000040000000 + SSGIRaySteps: 200000004000000080000000 + SSGIDenoise: 010101 + SSGIHalfResDenoise: 010000 + SSGIDenoiserRadius: + - 0.75 + - 0.5 + - 0.5 + SSGISecondDenoise: 010101 + RTAORayLength: + - 0.5 + - 3 + - 20 + RTAOSampleCount: 010000000200000008000000 + RTAODenoise: 010101 + RTAODenoiserRadius: + - 0.25 + - 0.5 + - 0.65 + RTGIRayLength: + - 50 + - 50 + - 50 + RTGIFullResolution: 000001 + RTGIClampValue: + - 0.5 + - 0.8 + - 1.5 + RTGIRaySteps: 200000003000000040000000 + RTGIDenoise: 010101 + RTGIHalfResDenoise: 010000 + RTGIDenoiserRadius: + - 0.75 + - 0.5 + - 0.25 + RTGISecondDenoise: 010101 + RTRMinSmoothness: + - 0.6 + - 0.4 + - 0 + RTRSmoothnessFadeStart: + - 0.7 + - 0.5 + - 0 + RTRRayLength: + - 50 + - 50 + - 50 + RTRClampValue: + - 0.8 + - 1 + - 1.2 + RTRFullResolution: 000001 + RTRRayMaxIterations: 200000003000000040000000 + RTRDenoise: 010101 + RTRDenoiserRadius: 080000000c00000010000000 + RTRSmoothDenoising: 010000 + Fog_ControlMode: 000000000000000000000000 + Fog_Budget: + - 0.166 + - 0.33 + - 0.666 + Fog_DepthRatio: + - 0.666 + - 0.666 + - 0.5 + m_ObsoleteLightLayerName0: + m_ObsoleteLightLayerName1: + m_ObsoleteLightLayerName2: + m_ObsoleteLightLayerName3: + m_ObsoleteLightLayerName4: + m_ObsoleteLightLayerName5: + m_ObsoleteLightLayerName6: + m_ObsoleteLightLayerName7: + m_ObsoleteDecalLayerName0: + m_ObsoleteDecalLayerName1: + m_ObsoleteDecalLayerName2: + m_ObsoleteDecalLayerName3: + m_ObsoleteDecalLayerName4: + m_ObsoleteDecalLayerName5: + m_ObsoleteDecalLayerName6: + m_ObsoleteDecalLayerName7: + m_ObsoleteSupportRuntimeDebugDisplay: 0 + allowShaderVariantStripping: 1 + enableSRPBatcher: 1 + availableMaterialQualityLevels: -1 + m_DefaultMaterialQualityLevel: 4 + diffusionProfileSettings: {fileID: 0} + virtualTexturingSettings: + streamingCpuCacheSizeInMegaBytes: 256 + streamingGpuCacheSettings: + - format: 0 + sizeInMegaBytes: 128 + m_UseRenderGraph: 1 + m_Version: 21 + m_ObsoleteFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteRealtimeReflectionFrameSettings: + overrides: 0 + enableShadow: 0 + enableContactShadows: 0 + enableShadowMask: 0 + enableSSR: 0 + enableSSAO: 0 + enableSubsurfaceScattering: 0 + enableTransmission: 0 + enableAtmosphericScattering: 0 + enableVolumetrics: 0 + enableReprojectionForVolumetrics: 0 + enableLightLayers: 0 + enableExposureControl: 1 + diffuseGlobalDimmer: 0 + specularGlobalDimmer: 0 + shaderLitMode: 0 + enableDepthPrepassWithDeferredRendering: 0 + enableTransparentPrepass: 0 + enableMotionVectors: 0 + enableObjectMotionVectors: 0 + enableDecals: 0 + enableRoughRefraction: 0 + enableTransparentPostpass: 0 + enableDistortion: 0 + enablePostprocess: 0 + enableOpaqueObjects: 0 + enableTransparentObjects: 0 + enableRealtimePlanarReflection: 0 + enableMSAA: 0 + enableAsyncCompute: 0 + runLightListAsync: 0 + runSSRAsync: 0 + runSSAOAsync: 0 + runContactShadowsAsync: 0 + runVolumeVoxelizationAsync: 0 + lightLoopSettings: + overrides: 0 + enableDeferredTileAndCluster: 0 + enableComputeLightEvaluation: 0 + enableComputeLightVariants: 0 + enableComputeMaterialVariants: 0 + enableFptlForForwardOpaque: 0 + enableBigTilePrepass: 0 + isFptlEnabled: 0 + m_ObsoleteDefaultVolumeProfile: {fileID: 0} + m_ObsoleteDefaultLookDevProfile: {fileID: 0} + m_ObsoleteFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 0 + data2: 0 + lodBias: 0 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 0 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteBakedOrCustomReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 0 + data2: 0 + lodBias: 0 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 0 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRealtimeReflectionFrameSettingsMovedToDefaultSettings: + bitDatas: + data1: 0 + data2: 0 + lodBias: 0 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 0 + msaaMode: 0 + materialQuality: 0 + m_ObsoleteRenderPipelineResources: {fileID: 0} + m_ObsoleteRenderPipelineRayTracingResources: {fileID: 0} + m_ObsoleteBeforeTransparentCustomPostProcesses: [] + m_ObsoleteBeforePostProcessCustomPostProcesses: [] + m_ObsoleteAfterPostProcessCustomPostProcesses: [] + m_ObsoleteBeforeTAACustomPostProcesses: [] + m_ObsoleteShaderVariantLogLevel: 0 + m_ObsoleteLensAttenuation: 0 + m_ObsoleteDiffusionProfileSettingsList: [] diff --git a/Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset.meta b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset.meta new file mode 100644 index 0000000..2b1a38d --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b9f3086da92434da0bc1518f19f0ce86 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset new file mode 100644 index 0000000..38aacbf --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset @@ -0,0 +1,104 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 781cc897cf8675041a751163b51f97dd, type: 3} + m_Name: HDRenderPipelineGlobalSettings + m_EditorClassIdentifier: + m_DefaultVolumeProfile: {fileID: 11400000, guid: 14b392ee213d25a48b1feddbd9f5a9be, + type: 2} + m_LookDevVolumeProfile: {fileID: 11400000, guid: 4594f4a3fb14247e192bcca6dc23c8ed, + type: 2} + m_RenderingPathDefaultCameraFrameSettings: + bitDatas: + data1: 72198260625768269 + data2: 13763000477350330392 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 1 + materialQuality: 0 + m_RenderingPathDefaultBakedOrCustomReflectionFrameSettings: + bitDatas: + data1: 135310754214733 + data2: 4539628428684460056 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 1 + materialQuality: 0 + m_RenderingPathDefaultRealtimeReflectionFrameSettings: + bitDatas: + data1: 139923391782733 + data2: 13763000465807638544 + lodBias: 1 + lodBiasMode: 0 + lodBiasQualityLevel: 0 + maximumLODLevel: 0 + maximumLODLevelMode: 0 + maximumLODLevelQualityLevel: 0 + sssQualityMode: 0 + sssQualityLevel: 0 + sssCustomSampleBudget: 20 + msaaMode: 1 + materialQuality: 0 + m_RenderPipelineResources: {fileID: 11400000, guid: 3ce144cff5783da45aa5d4fdc2da14b7, + type: 2} + m_RenderPipelineRayTracingResources: {fileID: 0} + beforeTransparentCustomPostProcesses: [] + beforePostProcessCustomPostProcesses: [] + afterPostProcessBlursCustomPostProcesses: [] + afterPostProcessCustomPostProcesses: [] + beforeTAACustomPostProcesses: [] + lightLayerName0: Light Layer default + lightLayerName1: Light Layer 1 + lightLayerName2: Light Layer 2 + lightLayerName3: Light Layer 3 + lightLayerName4: Light Layer 4 + lightLayerName5: Light Layer 5 + lightLayerName6: Light Layer 6 + lightLayerName7: Light Layer 7 + decalLayerName0: Decal Layer default + decalLayerName1: Decal Layer 1 + decalLayerName2: Decal Layer 2 + decalLayerName3: Decal Layer 3 + decalLayerName4: Decal Layer 4 + decalLayerName5: Decal Layer 5 + decalLayerName6: Decal Layer 6 + decalLayerName7: Decal Layer 7 + shaderVariantLogLevel: 0 + lensAttenuationMode: 0 + diffusionProfileSettingsList: + - {fileID: 11400000, guid: 48e911a1e337b44e2b85dbc65b47a594, type: 2} + - {fileID: 11400000, guid: 879ffae44eefa4412bb327928f1a96dd, type: 2} + rendererListCulling: 0 + DLSSProjectId: 000000 + useDLSSCustomProjectId: 0 + supportProbeVolumes: 0 + supportRuntimeDebugDisplay: 0 + apvScenesData: + serializedBounds: [] + serializedHasVolumes: [] + serializedProfiles: [] + serializedBakeSettings: [] + serializedBakingSets: [] + m_Version: 3 diff --git a/Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset.meta b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset.meta new file mode 100644 index 0000000..bdf3753 --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac0316ca287ba459492b669ff1317a6f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset b/Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset new file mode 100644 index 0000000..c0c5602 --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset @@ -0,0 +1,24 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b2686e09ec7aef44bad2843e4416f057, type: 3} + m_Name: SkinDiffusionProfile + m_EditorClassIdentifier: + m_Version: 1 + profile: + scatteringDistance: {r: 0.7568628, g: 0.32156864, b: 0.20000002, a: 1} + transmissionTint: {r: 0.7568628, g: 0.32156864, b: 0.20000002, a: 1} + texturingMode: 0 + transmissionMode: 0 + thicknessRemap: {x: 0, y: 8.152544} + worldScale: 1 + ior: 1.4 + hash: 1075477546 diff --git a/Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset.meta b/Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset.meta new file mode 100644 index 0000000..279b00e --- /dev/null +++ b/Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 48e911a1e337b44e2b85dbc65b47a594 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/SkyandFogSettingsProfile.asset b/Assets/Settings/SkyandFogSettingsProfile.asset new file mode 100644 index 0000000..2ab950a --- /dev/null +++ b/Assets/Settings/SkyandFogSettingsProfile.asset @@ -0,0 +1,423 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7724654706381055090 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d877ec3e844f2ca46830012e8e79319b, type: 3} + m_Name: + m_EditorClassIdentifier: + active: 1 + rotation: + m_OverrideState: 0 + m_Value: 0 + skyIntensityMode: + m_OverrideState: 0 + m_Value: 0 + exposure: + m_OverrideState: 0 + m_Value: 0 + multiplier: + m_OverrideState: 0 + m_Value: 1 + upperHemisphereLuxValue: + m_OverrideState: 0 + m_Value: 1 + upperHemisphereLuxColor: + m_OverrideState: 0 + m_Value: {x: 0, y: 0, z: 0} + desiredLuxValue: + m_OverrideState: 0 + m_Value: 20000 + updateMode: + m_OverrideState: 0 + m_Value: 0 + updatePeriod: + m_OverrideState: 0 + m_Value: 0 + includeSunInBaking: + m_OverrideState: 0 + m_Value: 0 + type: + m_OverrideState: 0 + m_Value: 1 + sphericalMode: + m_OverrideState: 0 + m_Value: 1 + seaLevel: + m_OverrideState: 0 + m_Value: 0 + planetaryRadius: + m_OverrideState: 0 + m_Value: 6378100 + planetCenterPosition: + m_OverrideState: 0 + m_Value: {x: 0, y: -6378100, z: 0} + airDensityR: + m_OverrideState: 0 + m_Value: 0.04534 + airDensityG: + m_OverrideState: 0 + m_Value: 0.10237241 + airDensityB: + m_OverrideState: 0 + m_Value: 0.23264056 + airTint: + m_OverrideState: 0 + m_Value: {r: 0.9, g: 0.9, b: 1, a: 1} + airMaximumAltitude: + m_OverrideState: 0 + m_Value: 55261.973 + aerosolDensity: + m_OverrideState: 0 + m_Value: 0.01192826 + aerosolTint: + m_OverrideState: 0 + m_Value: {r: 0.9, g: 0.9, b: 0.9, a: 1} + aerosolMaximumAltitude: + m_OverrideState: 0 + m_Value: 8289.296 + aerosolAnisotropy: + m_OverrideState: 0 + m_Value: 0 + numberOfBounces: + m_OverrideState: 0 + m_Value: 8 + groundTint: + m_OverrideState: 1 + m_Value: {r: 0.122641504, g: 0.1043775, b: 0.09313812, a: 1} + groundColorTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + groundEmissionTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + groundEmissionMultiplier: + m_OverrideState: 0 + m_Value: 1 + planetRotation: + m_OverrideState: 0 + m_Value: {x: 0, y: 0, z: 0} + spaceEmissionTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + spaceEmissionMultiplier: + m_OverrideState: 0 + m_Value: 1 + spaceRotation: + m_OverrideState: 0 + m_Value: {x: 0, y: 0, z: 0} + colorSaturation: + m_OverrideState: 0 + m_Value: 1 + alphaSaturation: + m_OverrideState: 0 + m_Value: 1 + alphaMultiplier: + m_OverrideState: 0 + m_Value: 1 + horizonTint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + zenithTint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + horizonZenithShift: + m_OverrideState: 0 + m_Value: 0 + m_SkyVersion: 1 + m_ObsoleteEarthPreset: + m_OverrideState: 0 + m_Value: 1 +--- !u!114 &-4151792930034644520 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 953beb541740ddc499d005ee80c9ff29, type: 3} + m_Name: + m_EditorClassIdentifier: + active: 1 + quality: + m_OverrideState: 0 + m_Value: 1 + enabled: + m_OverrideState: 1 + m_Value: 1 + colorMode: + m_OverrideState: 0 + m_Value: 1 + color: + m_OverrideState: 0 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + tint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + maxFogDistance: + m_OverrideState: 0 + m_Value: 5000 + mipFogMaxMip: + m_OverrideState: 0 + m_Value: 0.5 + mipFogNear: + m_OverrideState: 0 + m_Value: 0 + mipFogFar: + m_OverrideState: 0 + m_Value: 1000 + baseHeight: + m_OverrideState: 0 + m_Value: 0 + maximumHeight: + m_OverrideState: 0 + m_Value: 50 + meanFreePath: + m_OverrideState: 0 + m_Value: 400 + enableVolumetricFog: + m_OverrideState: 1 + m_Value: 1 + albedo: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + globalLightProbeDimmer: + m_OverrideState: 0 + m_Value: 1 + depthExtent: + m_OverrideState: 0 + m_Value: 64 + denoisingMode: + m_OverrideState: 0 + m_Value: 2 + anisotropy: + m_OverrideState: 1 + m_Value: 0.65 + sliceDistributionUniformity: + m_OverrideState: 0 + m_Value: 0.75 + m_FogControlMode: + m_OverrideState: 0 + m_Value: 0 + screenResolutionPercentage: + m_OverrideState: 0 + m_Value: 12.5 + volumeSliceCount: + m_OverrideState: 0 + m_Value: 64 + m_VolumetricFogBudget: + m_OverrideState: 0 + m_Value: 0.5 + m_ResolutionDepthRatio: + m_OverrideState: 0 + m_Value: 0.5 + directionalLightsOnly: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: SkyandFogSettingsProfile + m_EditorClassIdentifier: + components: + - {fileID: 1142777632297148762} + - {fileID: -7724654706381055090} + - {fileID: -4151792930034644520} + - {fileID: 7642060734654139733} +--- !u!114 &1142777632297148762 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d7593b3a9277ac4696b20006c21dde2, type: 3} + m_Name: + m_EditorClassIdentifier: + active: 1 + skyType: + m_OverrideState: 1 + m_Value: 4 + cloudType: + m_OverrideState: 0 + m_Value: 0 + skyAmbientMode: + m_OverrideState: 1 + m_Value: 0 + windOrientation: + m_OverrideState: 0 + m_Value: 0 + windSpeed: + m_OverrideState: 0 + m_Value: 100 + fogType: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &7642060734654139733 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2d08ce26990eb1a4a9177b860541e702, type: 3} + m_Name: Exposure + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 4 + meteringMode: + m_OverrideState: 0 + m_Value: 2 + luminanceSource: + m_OverrideState: 0 + m_Value: 1 + fixedExposure: + m_OverrideState: 0 + m_Value: 0 + compensation: + m_OverrideState: 0 + m_Value: 0 + limitMin: + m_OverrideState: 1 + m_Value: 2 + limitMax: + m_OverrideState: 1 + m_Value: 14 + curveMap: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: -10 + value: -10 + inSlope: 0 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 20 + value: 20 + inSlope: 1 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + limitMinCurveMap: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: -10 + value: -12 + inSlope: 0 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 20 + value: 18 + inSlope: 1 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + limitMaxCurveMap: + m_OverrideState: 0 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: -10 + value: -8 + inSlope: 0 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 20 + value: 22 + inSlope: 1 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + adaptationMode: + m_OverrideState: 0 + m_Value: 1 + adaptationSpeedDarkToLight: + m_OverrideState: 0 + m_Value: 3 + adaptationSpeedLightToDark: + m_OverrideState: 0 + m_Value: 1 + weightTextureMask: + m_OverrideState: 0 + m_Value: {fileID: 0} + histogramPercentages: + m_OverrideState: 0 + m_Value: {x: 40, y: 90} + histogramUseCurveRemapping: + m_OverrideState: 0 + m_Value: 0 + targetMidGray: + m_OverrideState: 0 + m_Value: 0 + centerAroundExposureTarget: + m_OverrideState: 0 + m_Value: 0 + proceduralCenter: + m_OverrideState: 0 + m_Value: {x: 0.5, y: 0.5} + proceduralRadii: + m_OverrideState: 0 + m_Value: {x: 0.15, y: 0.15} + maskMinIntensity: + m_OverrideState: 0 + m_Value: -30 + maskMaxIntensity: + m_OverrideState: 0 + m_Value: 30 + proceduralSoftness: + m_OverrideState: 0 + m_Value: 0.5 diff --git a/Assets/Settings/SkyandFogSettingsProfile.asset.meta b/Assets/Settings/SkyandFogSettingsProfile.asset.meta new file mode 100644 index 0000000..ab1b028 --- /dev/null +++ b/Assets/Settings/SkyandFogSettingsProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8ba92e2dd7f884a0f88b98fa2d235fe7 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code.meta b/Assets/_Code.meta new file mode 100644 index 0000000..dad1a83 --- /dev/null +++ b/Assets/_Code.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b5848c900b39ae34495b3a6883da6415 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/ATAR.asmdef b/Assets/_Code/ATAR.asmdef new file mode 100644 index 0000000..9dd878e --- /dev/null +++ b/Assets/_Code/ATAR.asmdef @@ -0,0 +1,28 @@ +{ + "name": "ATAR", + "rootNamespace": "Atar", + "references": [ + "GUID:46fe9b4ef94d76744bc0b97bc2f73d94", + "GUID:2de22fb7141426546b7382812669d73f", + "GUID:d1d3d8be6fb0d9248bc0df28e441c521", + "GUID:11912cc675165314485c904ed61b2e92", + "GUID:7a450cf7ca9694b5a8bfa3fd83ec635a", + "GUID:734d92eba21c94caba915361bd5ac177", + "GUID:a5baed0c9693541a5bd947d336ec7659", + "GUID:44dcb2bf5b9be4b5aa76674c8edcd1d5", + "GUID:5f3cf485eb0554709a8abbeace890c86", + "GUID:8819f35a0fc84499b990e90a4ca1911f", + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:2665a8d13d1b3f18800f46e256720795" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/_Code/ATAR.asmdef.meta b/Assets/_Code/ATAR.asmdef.meta new file mode 100644 index 0000000..d98ea92 --- /dev/null +++ b/Assets/_Code/ATAR.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f246cec838c2e6549be4d13de9573270 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring.meta b/Assets/_Code/Authoring.meta new file mode 100644 index 0000000..2e85775 --- /dev/null +++ b/Assets/_Code/Authoring.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 407269bf28cb15642baef6f69ad8285d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Combat.meta b/Assets/_Code/Authoring/Combat.meta new file mode 100644 index 0000000..5db33f2 --- /dev/null +++ b/Assets/_Code/Authoring/Combat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 78676ea484b839b43a61912ba6d17317 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Combat/Player.meta b/Assets/_Code/Authoring/Combat/Player.meta new file mode 100644 index 0000000..a402335 --- /dev/null +++ b/Assets/_Code/Authoring/Combat/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e1da5c981e5735043a21a3400a841e98 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Combat/Player/PlayerAuthoring.cs b/Assets/_Code/Authoring/Combat/Player/PlayerAuthoring.cs new file mode 100644 index 0000000..9c0e6c6 --- /dev/null +++ b/Assets/_Code/Authoring/Combat/Player/PlayerAuthoring.cs @@ -0,0 +1,15 @@ +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Atar.Authoring +{ + [DisallowMultipleComponent] + public class PlayerAuthoring : MonoBehaviour, IConvertGameObjectToEntity + { + public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) + { + } + } +} + diff --git a/Assets/_Code/Authoring/Combat/Player/PlayerAuthoring.cs.meta b/Assets/_Code/Authoring/Combat/Player/PlayerAuthoring.cs.meta new file mode 100644 index 0000000..51d1953 --- /dev/null +++ b/Assets/_Code/Authoring/Combat/Player/PlayerAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32dfaedde73630947bb45f74a8bfd9d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Fortification.meta b/Assets/_Code/Authoring/Fortification.meta new file mode 100644 index 0000000..fed5cd3 --- /dev/null +++ b/Assets/_Code/Authoring/Fortification.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9b433a8a835a58458334ccb8cf8d8c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Fortification/TowerListAuthoring.cs b/Assets/_Code/Authoring/Fortification/TowerListAuthoring.cs new file mode 100644 index 0000000..3e811e3 --- /dev/null +++ b/Assets/_Code/Authoring/Fortification/TowerListAuthoring.cs @@ -0,0 +1,15 @@ +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Atar.Authoring +{ + [DisallowMultipleComponent] + public class TowerListAuthoring : MonoBehaviour, IConvertGameObjectToEntity + { + public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) + { + } + } +} + diff --git a/Assets/_Code/Authoring/Fortification/TowerListAuthoring.cs.meta b/Assets/_Code/Authoring/Fortification/TowerListAuthoring.cs.meta new file mode 100644 index 0000000..2c42791 --- /dev/null +++ b/Assets/_Code/Authoring/Fortification/TowerListAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36399346536229548aef20907193aa29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Shared.meta b/Assets/_Code/Authoring/Shared.meta new file mode 100644 index 0000000..909e3f1 --- /dev/null +++ b/Assets/_Code/Authoring/Shared.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ae91c2920aa22b4f8e50734295abab3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/Shared/DebugResourcesAuthoring.cs b/Assets/_Code/Authoring/Shared/DebugResourcesAuthoring.cs new file mode 100644 index 0000000..d96fa75 --- /dev/null +++ b/Assets/_Code/Authoring/Shared/DebugResourcesAuthoring.cs @@ -0,0 +1,15 @@ +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Atar.Authoring +{ + [DisallowMultipleComponent] + public class DebugResourcesAuthoring : MonoBehaviour, IConvertGameObjectToEntity + { + public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) + { + } + } +} + diff --git a/Assets/_Code/Authoring/Shared/DebugResourcesAuthoring.cs.meta b/Assets/_Code/Authoring/Shared/DebugResourcesAuthoring.cs.meta new file mode 100644 index 0000000..235b6d5 --- /dev/null +++ b/Assets/_Code/Authoring/Shared/DebugResourcesAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 435514dba01657e47bc9aa31ac5fbddc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/TitleAndMenu.meta b/Assets/_Code/Authoring/TitleAndMenu.meta new file mode 100644 index 0000000..de35b03 --- /dev/null +++ b/Assets/_Code/Authoring/TitleAndMenu.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3dd590b30bb39634ebd1dd1d28b3818e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Authoring/TitleAndMenu/TitleAndMenuEntityPrefabsAuthoring.cs b/Assets/_Code/Authoring/TitleAndMenu/TitleAndMenuEntityPrefabsAuthoring.cs new file mode 100644 index 0000000..45a014f --- /dev/null +++ b/Assets/_Code/Authoring/TitleAndMenu/TitleAndMenuEntityPrefabsAuthoring.cs @@ -0,0 +1,15 @@ +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Atar.Authoring +{ + [DisallowMultipleComponent] + public class TitleAndMenuEntityPrefabsAuthoring : MonoBehaviour, IConvertGameObjectToEntity + { + public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) + { + } + } +} + diff --git a/Assets/_Code/Authoring/TitleAndMenu/TitleAndMenuEntityPrefabsAuthoring.cs.meta b/Assets/_Code/Authoring/TitleAndMenu/TitleAndMenuEntityPrefabsAuthoring.cs.meta new file mode 100644 index 0000000..5a05e56 --- /dev/null +++ b/Assets/_Code/Authoring/TitleAndMenu/TitleAndMenuEntityPrefabsAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6eec0ea1cb00d8149b94e2cf8e468642 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components.meta b/Assets/_Code/Components.meta new file mode 100644 index 0000000..f9f4255 --- /dev/null +++ b/Assets/_Code/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5aaa6975f8684944da46221df534e07b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Combat.meta b/Assets/_Code/Components/Combat.meta new file mode 100644 index 0000000..8f0c9b0 --- /dev/null +++ b/Assets/_Code/Components/Combat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 59f331aa9ea39b544a2bc2ee3e79523e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Combat/Player.meta b/Assets/_Code/Components/Combat/Player.meta new file mode 100644 index 0000000..7b4dc64 --- /dev/null +++ b/Assets/_Code/Components/Combat/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb7a542919a470641859a3578dfe4e52 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Combat/Player/PlayerComponents.cs b/Assets/_Code/Components/Combat/Player/PlayerComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/Combat/Player/PlayerComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/Combat/Player/PlayerComponents.cs.meta b/Assets/_Code/Components/Combat/Player/PlayerComponents.cs.meta new file mode 100644 index 0000000..e363cab --- /dev/null +++ b/Assets/_Code/Components/Combat/Player/PlayerComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d63e46cee0a6e94c9678ee7b26e614e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Fortification.meta b/Assets/_Code/Components/Fortification.meta new file mode 100644 index 0000000..2c710eb --- /dev/null +++ b/Assets/_Code/Components/Fortification.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6640d5f3645f6e4c85d66a0a9b7228e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Fortification/FortificationSpatialComponents.cs b/Assets/_Code/Components/Fortification/FortificationSpatialComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/Fortification/FortificationSpatialComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/Fortification/FortificationSpatialComponents.cs.meta b/Assets/_Code/Components/Fortification/FortificationSpatialComponents.cs.meta new file mode 100644 index 0000000..d0a61e4 --- /dev/null +++ b/Assets/_Code/Components/Fortification/FortificationSpatialComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3cfd0d04072ea01418410b03a9899926 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Fortification/PhoneFriendsComponents.cs b/Assets/_Code/Components/Fortification/PhoneFriendsComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/Fortification/PhoneFriendsComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/Fortification/PhoneFriendsComponents.cs.meta b/Assets/_Code/Components/Fortification/PhoneFriendsComponents.cs.meta new file mode 100644 index 0000000..0d7b1af --- /dev/null +++ b/Assets/_Code/Components/Fortification/PhoneFriendsComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4fbd56d3f6844b34296c1b55609bf6ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Shared.meta b/Assets/_Code/Components/Shared.meta new file mode 100644 index 0000000..c0554ea --- /dev/null +++ b/Assets/_Code/Components/Shared.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd8df01d0835ac94e9dc7fa304f54063 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Shared/DefenseConfigurationComponents.cs b/Assets/_Code/Components/Shared/DefenseConfigurationComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/Shared/DefenseConfigurationComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/Shared/DefenseConfigurationComponents.cs.meta b/Assets/_Code/Components/Shared/DefenseConfigurationComponents.cs.meta new file mode 100644 index 0000000..beb4f91 --- /dev/null +++ b/Assets/_Code/Components/Shared/DefenseConfigurationComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d9b6a924768d7b4aaf8bb20fbf8b80e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Shared/ResourcesComponents.cs b/Assets/_Code/Components/Shared/ResourcesComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/Shared/ResourcesComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/Shared/ResourcesComponents.cs.meta b/Assets/_Code/Components/Shared/ResourcesComponents.cs.meta new file mode 100644 index 0000000..16383d4 --- /dev/null +++ b/Assets/_Code/Components/Shared/ResourcesComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f1610fa7c4ff8c499e1a5bd8a6f0095 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/Shared/SettingsComponents.cs b/Assets/_Code/Components/Shared/SettingsComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/Shared/SettingsComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/Shared/SettingsComponents.cs.meta b/Assets/_Code/Components/Shared/SettingsComponents.cs.meta new file mode 100644 index 0000000..73bc5b2 --- /dev/null +++ b/Assets/_Code/Components/Shared/SettingsComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a2f8a0f3a77ab54985ec23421a8681c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/TitleAndMenu.meta b/Assets/_Code/Components/TitleAndMenu.meta new file mode 100644 index 0000000..59053fc --- /dev/null +++ b/Assets/_Code/Components/TitleAndMenu.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52f223933131e7d408a3875484d32fc7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Components/TitleAndMenu/TitleAndMenuComponents.cs b/Assets/_Code/Components/TitleAndMenu/TitleAndMenuComponents.cs new file mode 100644 index 0000000..a2c9ae4 --- /dev/null +++ b/Assets/_Code/Components/TitleAndMenu/TitleAndMenuComponents.cs @@ -0,0 +1,9 @@ +using Latios; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Atar +{ +} + diff --git a/Assets/_Code/Components/TitleAndMenu/TitleAndMenuComponents.cs.meta b/Assets/_Code/Components/TitleAndMenu/TitleAndMenuComponents.cs.meta new file mode 100644 index 0000000..70cd401 --- /dev/null +++ b/Assets/_Code/Components/TitleAndMenu/TitleAndMenuComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce2a02b06c4b66746a3f2ed8562c52b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/LatiosBootstrap.cs b/Assets/_Code/LatiosBootstrap.cs new file mode 100644 index 0000000..0f916f3 --- /dev/null +++ b/Assets/_Code/LatiosBootstrap.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Unity.Entities; + +namespace Atar.Authoring +{ + [UnityEngine.Scripting.Preserve] + public class LatiosConversionBootstrap : ICustomConversionBootstrap + { + public bool InitializeConversion(World conversionWorldWithGroupsAndMappingSystems, CustomConversionSettings settings, ref List filteredSystems) + { + var defaultGroup = conversionWorldWithGroupsAndMappingSystems.GetExistingSystem(); + BootstrapTools.InjectSystems(filteredSystems, conversionWorldWithGroupsAndMappingSystems, defaultGroup); + + Latios.Psyshock.Authoring.PsyshockConversionBootstrap.InstallLegacyColliderConversion(conversionWorldWithGroupsAndMappingSystems); + Latios.Kinemation.Authoring.KinemationConversionBootstrap.InstallKinemationConversion(conversionWorldWithGroupsAndMappingSystems); + return true; + } + } +} + +namespace Atar +{ + [UnityEngine.Scripting.Preserve] + public class LatiosBootstrap : ICustomBootstrap + { + public unsafe bool Initialize(string defaultWorldName) + { + var world = new LatiosWorld(defaultWorldName); + World.DefaultGameObjectInjectionWorld = world; + world.useExplicitSystemOrdering = true; + + var systems = new List(DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)); + + BootstrapTools.InjectUnitySystems(systems, world, world.simulationSystemGroup); + BootstrapTools.InjectRootSuperSystems(systems, world, world.simulationSystemGroup); + + world.GetExistingSystem().Enabled = false; // Leaks LocalToWorld query and generates ECB. + + CoreBootstrap.InstallSceneManager(world); + CoreBootstrap.InstallExtremeTransforms(world); + Latios.Myri.MyriBootstrap.InstallMyri(world); + Latios.Kinemation.KinemationBootstrap.InstallKinemation(world); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + BootstrapTools.AddWorldToCurrentPlayerLoopWithDelayedSimulation(world); + return true; + } + } +} + diff --git a/Assets/_Code/LatiosBootstrap.cs.meta b/Assets/_Code/LatiosBootstrap.cs.meta new file mode 100644 index 0000000..a7b2e85 --- /dev/null +++ b/Assets/_Code/LatiosBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c54c4f557e938842abb06bed932582c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/SuperSystems.meta b/Assets/_Code/SuperSystems.meta new file mode 100644 index 0000000..7abf98e --- /dev/null +++ b/Assets/_Code/SuperSystems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fb2bf52c3398af849a819539b34c1d76 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/SuperSystems/CombatSuperSystems.cs b/Assets/_Code/SuperSystems/CombatSuperSystems.cs new file mode 100644 index 0000000..291341e --- /dev/null +++ b/Assets/_Code/SuperSystems/CombatSuperSystems.cs @@ -0,0 +1,7 @@ +using Latios; +using Unity.Entities; + +namespace Atar +{ +} + diff --git a/Assets/_Code/SuperSystems/CombatSuperSystems.cs.meta b/Assets/_Code/SuperSystems/CombatSuperSystems.cs.meta new file mode 100644 index 0000000..3f4b3a3 --- /dev/null +++ b/Assets/_Code/SuperSystems/CombatSuperSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57865929ebfd7934188a203e4508a0d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/SuperSystems/FortificationSuperSystems.cs b/Assets/_Code/SuperSystems/FortificationSuperSystems.cs new file mode 100644 index 0000000..291341e --- /dev/null +++ b/Assets/_Code/SuperSystems/FortificationSuperSystems.cs @@ -0,0 +1,7 @@ +using Latios; +using Unity.Entities; + +namespace Atar +{ +} + diff --git a/Assets/_Code/SuperSystems/FortificationSuperSystems.cs.meta b/Assets/_Code/SuperSystems/FortificationSuperSystems.cs.meta new file mode 100644 index 0000000..1e8b727 --- /dev/null +++ b/Assets/_Code/SuperSystems/FortificationSuperSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bb9254a10f385348b5585b93bf8b459 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/SuperSystems/RootSuperSystems.cs b/Assets/_Code/SuperSystems/RootSuperSystems.cs new file mode 100644 index 0000000..289b50b --- /dev/null +++ b/Assets/_Code/SuperSystems/RootSuperSystems.cs @@ -0,0 +1,42 @@ +using Latios; +using Latios.Systems; +using Unity.Entities; + +namespace Atar +{ + [UpdateInGroup(typeof(LatiosWorldSyncGroup))] + public class SyncPointRootSuperSystem : RootSuperSystem + { + protected override void CreateSystems() + { + } + } + + [UpdateInGroup(typeof(PresentationSystemGroup))] + [UpdateBefore(typeof(Unity.Rendering.StructuralChangePresentationSystemGroup))] + public class PresentationRootSuperSystem : RootSuperSystem + { + protected override void CreateSystems() + { + } + } + + [UpdateInGroup(typeof(SimulationSystemGroup))] + [UpdateBefore(typeof(Unity.Transforms.TransformSystemGroup))] + public class PreTransformSimulationRootSuperSystem : RootSuperSystem + { + protected override void CreateSystems() + { + } + } + + [UpdateInGroup(typeof(SimulationSystemGroup))] + [UpdateAfter(typeof(Unity.Transforms.TransformSystemGroup))] + public class PostTransformSimulationRootSuperSystem : RootSuperSystem + { + protected override void CreateSystems() + { + } + } +} + diff --git a/Assets/_Code/SuperSystems/RootSuperSystems.cs.meta b/Assets/_Code/SuperSystems/RootSuperSystems.cs.meta new file mode 100644 index 0000000..356416e --- /dev/null +++ b/Assets/_Code/SuperSystems/RootSuperSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d83ff48909bee7543ac967fb3e7eed5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/SuperSystems/TitleAndMenuSuperSystems.cs b/Assets/_Code/SuperSystems/TitleAndMenuSuperSystems.cs new file mode 100644 index 0000000..291341e --- /dev/null +++ b/Assets/_Code/SuperSystems/TitleAndMenuSuperSystems.cs @@ -0,0 +1,7 @@ +using Latios; +using Unity.Entities; + +namespace Atar +{ +} + diff --git a/Assets/_Code/SuperSystems/TitleAndMenuSuperSystems.cs.meta b/Assets/_Code/SuperSystems/TitleAndMenuSuperSystems.cs.meta new file mode 100644 index 0000000..34f8b15 --- /dev/null +++ b/Assets/_Code/SuperSystems/TitleAndMenuSuperSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54bb2e7d005f47142b53a006a6f2eb5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems.meta b/Assets/_Code/Systems.meta new file mode 100644 index 0000000..4392972 --- /dev/null +++ b/Assets/_Code/Systems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 10bfad5c24fc8a049936537e6ac8cd38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Combat.meta b/Assets/_Code/Systems/Combat.meta new file mode 100644 index 0000000..6a0204c --- /dev/null +++ b/Assets/_Code/Systems/Combat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 91716422463bc7e429dbb36b7b1ef3e8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Combat/Player.meta b/Assets/_Code/Systems/Combat/Player.meta new file mode 100644 index 0000000..16f0e06 --- /dev/null +++ b/Assets/_Code/Systems/Combat/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 54fb0504ab5e23241a5fbd4e18e14bcd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Combat/Player/ReadPlayerInputSystem.cs b/Assets/_Code/Systems/Combat/Player/ReadPlayerInputSystem.cs new file mode 100644 index 0000000..5957ee3 --- /dev/null +++ b/Assets/_Code/Systems/Combat/Player/ReadPlayerInputSystem.cs @@ -0,0 +1,18 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Atar +{ + public partial class ReadPlayerInputSystem : SubSystem + { + protected override void OnUpdate() + { + + } + } +} diff --git a/Assets/_Code/Systems/Combat/Player/ReadPlayerInputSystem.cs.meta b/Assets/_Code/Systems/Combat/Player/ReadPlayerInputSystem.cs.meta new file mode 100644 index 0000000..92bab00 --- /dev/null +++ b/Assets/_Code/Systems/Combat/Player/ReadPlayerInputSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d74e6e095b1a7a408dbf65eeb179613 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Fortification.meta b/Assets/_Code/Systems/Fortification.meta new file mode 100644 index 0000000..86d91dc --- /dev/null +++ b/Assets/_Code/Systems/Fortification.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5d34ba5cb291bc2459642ba5af78cc89 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Fortification/CheckPhoneFriendsForUnlocksSystem.cs b/Assets/_Code/Systems/Fortification/CheckPhoneFriendsForUnlocksSystem.cs new file mode 100644 index 0000000..987dd7f --- /dev/null +++ b/Assets/_Code/Systems/Fortification/CheckPhoneFriendsForUnlocksSystem.cs @@ -0,0 +1,28 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Atar +{ + [BurstCompile] + public partial struct CheckPhoneFriendsForUnlocksSystem : ISystem + { + [BurstCompile] + public void OnCreate(ref SystemState state) + { + } + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + + } + } +} diff --git a/Assets/_Code/Systems/Fortification/CheckPhoneFriendsForUnlocksSystem.cs.meta b/Assets/_Code/Systems/Fortification/CheckPhoneFriendsForUnlocksSystem.cs.meta new file mode 100644 index 0000000..fb1f0f4 --- /dev/null +++ b/Assets/_Code/Systems/Fortification/CheckPhoneFriendsForUnlocksSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e335d4e63ed5e2445b74e5959cc9cc39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Shared.meta b/Assets/_Code/Systems/Shared.meta new file mode 100644 index 0000000..0c97214 --- /dev/null +++ b/Assets/_Code/Systems/Shared.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8097cdca708259f418c54baf2bdb533d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/Shared/LoadDefenseConfigurationSystem.cs b/Assets/_Code/Systems/Shared/LoadDefenseConfigurationSystem.cs new file mode 100644 index 0000000..577f98d --- /dev/null +++ b/Assets/_Code/Systems/Shared/LoadDefenseConfigurationSystem.cs @@ -0,0 +1,28 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Atar +{ + [BurstCompile] + public partial struct LoadDefenseConfigurationSystem : ISystem + { + [BurstCompile] + public void OnCreate(ref SystemState state) + { + } + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + + } + } +} diff --git a/Assets/_Code/Systems/Shared/LoadDefenseConfigurationSystem.cs.meta b/Assets/_Code/Systems/Shared/LoadDefenseConfigurationSystem.cs.meta new file mode 100644 index 0000000..c993636 --- /dev/null +++ b/Assets/_Code/Systems/Shared/LoadDefenseConfigurationSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfec21eea4c078e419108b4c70088db3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/TitleAndMenu.meta b/Assets/_Code/Systems/TitleAndMenu.meta new file mode 100644 index 0000000..7af997e --- /dev/null +++ b/Assets/_Code/Systems/TitleAndMenu.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d282c608785293c49b99683381a3843f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Code/Systems/TitleAndMenu/MenuNavigationSystem.cs b/Assets/_Code/Systems/TitleAndMenu/MenuNavigationSystem.cs new file mode 100644 index 0000000..37c7f77 --- /dev/null +++ b/Assets/_Code/Systems/TitleAndMenu/MenuNavigationSystem.cs @@ -0,0 +1,18 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Atar +{ + public partial class MenuNavigationSystem : SubSystem + { + protected override void OnUpdate() + { + + } + } +} diff --git a/Assets/_Code/Systems/TitleAndMenu/MenuNavigationSystem.cs.meta b/Assets/_Code/Systems/TitleAndMenu/MenuNavigationSystem.cs.meta new file mode 100644 index 0000000..94e0843 --- /dev/null +++ b/Assets/_Code/Systems/TitleAndMenu/MenuNavigationSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aed02af3d47889c4996b34b085779feb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ac567f0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Dreaming381 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Packages/com.latios.latios-framework/Core.meta b/Packages/com.latios.latios-framework/Core.meta new file mode 100644 index 0000000..e7cef13 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7a1353c0c1a73f64f85da4224620bb7f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor.meta b/Packages/com.latios.latios-framework/Core/Core.Editor.meta new file mode 100644 index 0000000..617867f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 27509ac9d52e0614a95b65e5c2aa5b00 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/Latios.Core.Editor.asmdef b/Packages/com.latios.latios-framework/Core/Core.Editor/Latios.Core.Editor.asmdef new file mode 100644 index 0000000..5488edf --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/Latios.Core.Editor.asmdef @@ -0,0 +1,24 @@ +{ + "name": "Latios.Core.Editor", + "rootNamespace": "Latios.Editor", + "references": [ + "Latios.Core" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.netcode", + "expression": "0.49", + "define": "NETCODE_PROJECT" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/Latios.Core.Editor.asmdef.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/Latios.Core.Editor.asmdef.meta new file mode 100644 index 0000000..cb6fad4 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/Latios.Core.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 42b38659db4a3c64590f6c8e31576389 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplateMenus.cs b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplateMenus.cs new file mode 100644 index 0000000..d52f1b6 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplateMenus.cs @@ -0,0 +1,110 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; + +internal class ScriptTemplateMenus +{ + public const string TemplatesRootSolo = "Packages/com.latios.core/Core.Editor/ScriptTemplates"; + public const string TemplatesRootFramework = "Packages/com.latios.latiosframework/Core/Core.Editor/ScriptTemplates"; + public const string TemplatesRootAssets = "Assets/_Code/Core.Editor/ScriptTemplates"; + + [MenuItem("Assets/Create/Latios/Bootstrap/Minimal - Injection Workflow")] + public static void CreateMinimalInjectionBootstrap() + { + CreateScriptFromTemplate("MinimalInjectionBootstrap.txt", "LatiosBootstrap.cs"); + } + + [MenuItem("Assets/Create/Latios/Bootstrap/Minimal - Explicit Workflow")] + public static void CreateMinimalExplicitBootstrap() + { + CreateScriptFromTemplate("MinimalExplicitBootstrap.txt", "LatiosBootstrap.cs"); + } + + [MenuItem("Assets/Create/Latios/Bootstrap/Standard - Injection Workflow")] + public static void CreateStandardInjectionBootstrap() + { + CreateScriptFromTemplate("StandardInjectionBootstrap.txt", "LatiosBootstrap.cs"); + } + + [MenuItem("Assets/Create/Latios/Bootstrap/Standard - Explicit Workflow")] + public static void CreateStandardExplicitBootstrap() + { + CreateScriptFromTemplate("StandardExplicitBootstrap.txt", "LatiosBootstrap.cs"); + } + + [MenuItem("Assets/Create/Latios/Bootstrap/Dreaming Specialized")] + public static void CreateDreamingBootstrap() + { + CreateScriptFromTemplate("DreamingBootstrap.txt", "LatiosBootstrap.cs"); + } + + [MenuItem("Assets/Create/Latios/SubSystem")] + public static void CreateSubSystem() + { + CreateScriptFromTemplate("SubSystem.txt", "NewSubSystem.cs"); + } + + [MenuItem("Assets/Create/Latios/ISystem")] + public static void CreateISystem() + { + CreateScriptFromTemplate("ISystem.txt", "NewBurstSystem.cs"); + } + +#if NETCODE_PROJECT + [MenuItem("Assets/Create/Latios/Bootstrap/NetCode Minimal - Injection Workflow")] + public static void CreateNetCodeMinimalInjectionBootstrap() + { + CreateScriptFromTemplate("NetCodeMinimalInjectionBootstrap.txt", "NetCodeLatiosBootstrap.cs"); + } + + [MenuItem("Assets/Create/Latios/Bootstrap/NetCode Standard - Injection Workflow")] + public static void CreateNetCodeStandardInjectionBootstrap() + { + CreateScriptFromTemplate("NetCodeStandardInjectionBootstrap.txt", "NetCodeLatiosBootstrap.cs"); + } +#endif + + public static void CreateScriptFromTemplate(string templateName, string defaultScriptName) + { + bool success = true; + try + { + ProjectWindowUtil.CreateScriptAssetFromTemplateFile( + $"{TemplatesRootFramework}/{templateName}", + defaultScriptName); + } + catch (System.IO.FileNotFoundException) + { + success = false; + } + if (!success) + { + success = true; + try + { + ProjectWindowUtil.CreateScriptAssetFromTemplateFile( + $"{TemplatesRootSolo}/{templateName}", + defaultScriptName); + } + catch (System.IO.FileNotFoundException) + { + success = false; + } + } + if (!success) + { + //success = true; + try + { + ProjectWindowUtil.CreateScriptAssetFromTemplateFile( + $"{TemplatesRootAssets}/{templateName}", + defaultScriptName); + } + catch (System.IO.FileNotFoundException) + { + //success = false; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplateMenus.cs.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplateMenus.cs.meta new file mode 100644 index 0000000..591f6ee --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplateMenus.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fed1495a1f4881e4a8221d66dde4ed35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates.meta new file mode 100644 index 0000000..0ad9cff --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6f5ea3953eff18e4094afd2a760cd5e3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/DreamingBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/DreamingBootstrap.txt new file mode 100644 index 0000000..1c7bcae --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/DreamingBootstrap.txt @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class LatiosConversionBootstrap : ICustomConversionBootstrap +{ + public bool InitializeConversion(World conversionWorldWithGroupsAndMappingSystems, CustomConversionSettings settings, ref List filteredSystems) + { + var defaultGroup = conversionWorldWithGroupsAndMappingSystems.GetExistingSystem(); + BootstrapTools.InjectSystems(filteredSystems, conversionWorldWithGroupsAndMappingSystems, defaultGroup); + + Latios.Psyshock.Authoring.PsyshockConversionBootstrap.InstallLegacyColliderConversion(conversionWorldWithGroupsAndMappingSystems); + Latios.Kinemation.Authoring.KinemationConversionBootstrap.InstallKinemationConversion(conversionWorldWithGroupsAndMappingSystems); + return true; + } +} + +[UnityEngine.Scripting.Preserve] +public class LatiosBootstrap : ICustomBootstrap +{ + public unsafe bool Initialize(string defaultWorldName) + { + var world = new LatiosWorld(defaultWorldName); + World.DefaultGameObjectInjectionWorld = world; + world.useExplicitSystemOrdering = true; + + var systems = new List(DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)); + + BootstrapTools.InjectUnitySystems(systems, world, world.simulationSystemGroup); + BootstrapTools.InjectRootSuperSystems(systems, world, world.simulationSystemGroup); + + world.GetExistingSystem().Enabled = false; // Leaks LocalToWorld query and generates ECB. + + CoreBootstrap.InstallSceneManager(world); + CoreBootstrap.InstallExtremeTransforms(world); + Latios.Myri.MyriBootstrap.InstallMyri(world); + Latios.Kinemation.KinemationBootstrap.InstallKinemation(world); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + BootstrapTools.AddWorldToCurrentPlayerLoopWithDelayedSimulation(world); + return true; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/DreamingBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/DreamingBootstrap.txt.meta new file mode 100644 index 0000000..d33794d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/DreamingBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 273242a018fdcb84fb1f540737135f32 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/ISystem.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/ISystem.txt new file mode 100644 index 0000000..4cd8cd8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/ISystem.txt @@ -0,0 +1,27 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + + #ROOTNAMESPACEBEGIN# +[BurstCompile] +public partial struct #SCRIPTNAME# : ISystem +{ + [BurstCompile] + public void OnCreate(ref SystemState state) + { + } + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + #NOTRIM# + } +} +#ROOTNAMESPACEEND# diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/ISystem.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/ISystem.txt.meta new file mode 100644 index 0000000..08f9e1e --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/ISystem.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0987aa66dbb0fef45b4e3c52f0b7d548 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalExplicitBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalExplicitBootstrap.txt new file mode 100644 index 0000000..48e1312 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalExplicitBootstrap.txt @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class LatiosBootstrap : ICustomBootstrap +{ + public unsafe bool Initialize(string defaultWorldName) + { + var world = new LatiosWorld(defaultWorldName); + World.DefaultGameObjectInjectionWorld = world; + world.useExplicitSystemOrdering = true; + + var systems = new List(DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)); + + BootstrapTools.InjectUnitySystems(systems, world, world.simulationSystemGroup); + BootstrapTools.InjectRootSuperSystems(systems, world, world.simulationSystemGroup); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(world); + return true; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalExplicitBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalExplicitBootstrap.txt.meta new file mode 100644 index 0000000..5107d68 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalExplicitBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3f8a3a0c2f139b84ba06b14a6ebf4075 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalInjectionBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalInjectionBootstrap.txt new file mode 100644 index 0000000..1136ef2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalInjectionBootstrap.txt @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class LatiosBootstrap : ICustomBootstrap +{ + public unsafe bool Initialize(string defaultWorldName) + { + var world = new LatiosWorld(defaultWorldName); + World.DefaultGameObjectInjectionWorld = world; + + var systems = new List(DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)); + BootstrapTools.InjectSystems(systems, world, world.simulationSystemGroup); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(world); + return true; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalInjectionBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalInjectionBootstrap.txt.meta new file mode 100644 index 0000000..978b07f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/MinimalInjectionBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 97e8bd8200314614fb9be9afbc172832 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeMinimalInjectionBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeMinimalInjectionBootstrap.txt new file mode 100644 index 0000000..d4c1b57 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeMinimalInjectionBootstrap.txt @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Latios.Compatibility.UnityNetCode; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class NetCodeLatiosBootstrap : LatiosClientServerBootstrapBase +{ + public override bool Initialize(string defaultWorldName) + { + AutoConnectPort = 7979; // Enable auto connect + return base.Initialize(defaultWorldName); + } + + public override World CreateCustomClientWorld(string worldName) + { + var world = new LatiosWorld(worldName, WorldFlags.Game, LatiosWorld.WorldRole.Client); + + BootstrapTools.InjectSystems(ClientSystems, world, world.simulationSystemGroup, ClientGroupRemap); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + return world; + } + + public override World CreateCustomServerWorld(string worldName) + { + var world = new LatiosWorld(worldName, WorldFlags.Game, LatiosWorld.WorldRole.Server); + + BootstrapTools.InjectSystems(ServerSystems, world, world.simulationSystemGroup, ServerGroupRemap); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + + return world; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeMinimalInjectionBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeMinimalInjectionBootstrap.txt.meta new file mode 100644 index 0000000..410cf59 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeMinimalInjectionBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c299216187c41c40b720fad0451919a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeStandardInjectionBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeStandardInjectionBootstrap.txt new file mode 100644 index 0000000..57fdb55 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeStandardInjectionBootstrap.txt @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Latios.Compatibility.UnityNetCode; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class LatiosConversionBootstrap : ICustomConversionBootstrap +{ + public bool InitializeConversion(World conversionWorldWithGroupsAndMappingSystems, CustomConversionSettings settings, ref List filteredSystems) + { + var defaultGroup = conversionWorldWithGroupsAndMappingSystems.GetExistingSystem(); + BootstrapTools.InjectSystems(filteredSystems, conversionWorldWithGroupsAndMappingSystems, defaultGroup); + + Latios.Psyshock.Authoring.PsyshockConversionBootstrap.InstallLegacyColliderConversion(conversionWorldWithGroupsAndMappingSystems); + Latios.Kinemation.Authoring.KinemationConversionBootstrap.InstallKinemationConversion(conversionWorldWithGroupsAndMappingSystems); + return true; + } +} + +[UnityEngine.Scripting.Preserve] +public class NetCodeLatiosBootstrap : LatiosClientServerBootstrapBase +{ + public override bool Initialize(string defaultWorldName) + { + AutoConnectPort = 7979; // Enable auto connect + return base.Initialize(defaultWorldName); + } + + public override World CreateCustomClientWorld(string worldName) + { + var world = new LatiosWorld(worldName, WorldFlags.Game, LatiosWorld.WorldRole.Client); + + BootstrapTools.InjectSystems(ClientSystems, world, world.simulationSystemGroup, ClientGroupRemap); + + CoreBootstrap.InstallImprovedTransforms(world); + Latios.Myri.MyriBootstrap.InstallMyri(world); + Latios.Kinemation.KinemationBootstrap.InstallKinemation(world); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + return world; + } + + public override World CreateCustomServerWorld(string worldName) + { + var world = new LatiosWorld(worldName, WorldFlags.Game, LatiosWorld.WorldRole.Server); + + BootstrapTools.InjectSystems(ServerSystems, world, world.simulationSystemGroup, ServerGroupRemap); + + CoreBootstrap.InstallImprovedTransforms(world); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + + return world; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeStandardInjectionBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeStandardInjectionBootstrap.txt.meta new file mode 100644 index 0000000..3acdfc0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/NetCodeStandardInjectionBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 29cbc0fe075a269428c3c9d477c8df50 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardExplicitBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardExplicitBootstrap.txt new file mode 100644 index 0000000..64568c0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardExplicitBootstrap.txt @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class LatiosConversionBootstrap : ICustomConversionBootstrap +{ + public bool InitializeConversion(World conversionWorldWithGroupsAndMappingSystems, CustomConversionSettings settings, ref List filteredSystems) + { + var defaultGroup = conversionWorldWithGroupsAndMappingSystems.GetExistingSystem(); + BootstrapTools.InjectSystems(filteredSystems, conversionWorldWithGroupsAndMappingSystems, defaultGroup); + + Latios.Psyshock.Authoring.PsyshockConversionBootstrap.InstallLegacyColliderConversion(conversionWorldWithGroupsAndMappingSystems); + Latios.Kinemation.Authoring.KinemationConversionBootstrap.InstallKinemationConversion(conversionWorldWithGroupsAndMappingSystems); + return true; + } +} + +[UnityEngine.Scripting.Preserve] +public class LatiosBootstrap : ICustomBootstrap +{ + public unsafe bool Initialize(string defaultWorldName) + { + var world = new LatiosWorld(defaultWorldName); + World.DefaultGameObjectInjectionWorld = world; + world.useExplicitSystemOrdering = true; + + var systems = new List(DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)); + + BootstrapTools.InjectUnitySystems(systems, world, world.simulationSystemGroup); + BootstrapTools.InjectRootSuperSystems(systems, world, world.simulationSystemGroup); + + CoreBootstrap.InstallImprovedTransforms(world); + Latios.Myri.MyriBootstrap.InstallMyri(world); + Latios.Kinemation.KinemationBootstrap.InstallKinemation(world); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(world); + return true; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardExplicitBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardExplicitBootstrap.txt.meta new file mode 100644 index 0000000..84f6d19 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardExplicitBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f3a6bdb0efda91a499ab1a0ff9b118ad +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardInjectionBootstrap.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardInjectionBootstrap.txt new file mode 100644 index 0000000..07181ac --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardInjectionBootstrap.txt @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Latios; +using Latios.Authoring; +using Unity.Entities; + +[UnityEngine.Scripting.Preserve] +public class LatiosConversionBootstrap : ICustomConversionBootstrap +{ + public bool InitializeConversion(World conversionWorldWithGroupsAndMappingSystems, CustomConversionSettings settings, ref List filteredSystems) + { + var defaultGroup = conversionWorldWithGroupsAndMappingSystems.GetExistingSystem(); + BootstrapTools.InjectSystems(filteredSystems, conversionWorldWithGroupsAndMappingSystems, defaultGroup); + + Latios.Psyshock.Authoring.PsyshockConversionBootstrap.InstallLegacyColliderConversion(conversionWorldWithGroupsAndMappingSystems); + Latios.Kinemation.Authoring.KinemationConversionBootstrap.InstallKinemationConversion(conversionWorldWithGroupsAndMappingSystems); + return true; + } +} + +[UnityEngine.Scripting.Preserve] +public class LatiosBootstrap : ICustomBootstrap +{ + public unsafe bool Initialize(string defaultWorldName) + { + var world = new LatiosWorld(defaultWorldName); + World.DefaultGameObjectInjectionWorld = world; + + var systems = new List(DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)); + BootstrapTools.InjectSystems(systems, world, world.simulationSystemGroup); + + CoreBootstrap.InstallImprovedTransforms(world); + Latios.Myri.MyriBootstrap.InstallMyri(world); + Latios.Kinemation.KinemationBootstrap.InstallKinemation(world); + + world.initializationSystemGroup.SortSystems(); + world.simulationSystemGroup.SortSystems(); + world.presentationSystemGroup.SortSystems(); + + ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(world); + return true; + } +} diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardInjectionBootstrap.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardInjectionBootstrap.txt.meta new file mode 100644 index 0000000..27a9e2d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/StandardInjectionBootstrap.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d6935aed2266b924aa8981f7b0a6f26e +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/SubSystem.txt b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/SubSystem.txt new file mode 100644 index 0000000..e4bb4d3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/SubSystem.txt @@ -0,0 +1,17 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + + #ROOTNAMESPACEBEGIN# +public partial class #SCRIPTNAME# : SubSystem +{ + protected override void OnUpdate() + { + #NOTRIM# + } +} +#ROOTNAMESPACEEND# diff --git a/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/SubSystem.txt.meta b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/SubSystem.txt.meta new file mode 100644 index 0000000..b1094e2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.Editor/ScriptTemplates/SubSystem.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e9110d8885c5b1244a3c222a79fd1952 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core.meta b/Packages/com.latios.latios-framework/Core/Core.meta new file mode 100644 index 0000000..5b3b176 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 950683d6629a3264380a792809c316da +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring.meta new file mode 100644 index 0000000..ccad7cb --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c1afdf318c17cf6438809012c77ed1b2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/BlackboardEntityDataAuthoring.cs b/Packages/com.latios.latios-framework/Core/Core/Authoring/BlackboardEntityDataAuthoring.cs new file mode 100644 index 0000000..e184e2d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/BlackboardEntityDataAuthoring.cs @@ -0,0 +1,25 @@ +using Unity.Entities; +using UnityEngine; + +//Based on the code generated from [GenerateAuthoringComponent] +namespace Latios.Authoring +{ + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Core/Blackboard Entity Data")] + [ConverterVersion("latios", 2)] + public class BlackboardEntityDataAuthoring : MonoBehaviour, IConvertGameObjectToEntity + { + public BlackboardScope blackboardScope; + + public MergeMethod mergeMethod; + + public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) + { + BlackboardEntityData componentData = default; + componentData.blackboardScope = blackboardScope; + componentData.mergeMethod = mergeMethod; + dstManager.AddComponentData(entity, componentData); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/BlackboardEntityDataAuthoring.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring/BlackboardEntityDataAuthoring.cs.meta new file mode 100644 index 0000000..a53d9b8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/BlackboardEntityDataAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d25dead4db6afd4a9a206ac77a2986f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/ICustomConversionBootstrap.cs b/Packages/com.latios.latios-framework/Core/Core/Authoring/ICustomConversionBootstrap.cs new file mode 100644 index 0000000..68312bc --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/ICustomConversionBootstrap.cs @@ -0,0 +1,306 @@ +using System.Collections.Generic; +using System.Reflection; +using static Unity.Entities.GameObjectConversionUtility; +using Unity.Entities; + +namespace Latios.Authoring +{ + public delegate void OnPostCreateConversionWorldDelegate(World world, CustomConversionSettings settings); + + /// + /// A subset of the settings provided to GameObject Conversion. + /// These settings are populated by Unity and some may not be populated at all. + /// + public struct CustomConversionSettings + { + public World destinationWorld; + public Hash128 sceneGUID; + public string debugConversionName; + public ConversionFlags conversionFlags; + public OnPostCreateConversionWorldEventWrapper OnPostCreateConversionWorldWrapper; + internal void InvokePostCreateConversionWorldEvent() => OnPostCreateConversionWorldWrapper.InvokePostCreateConversionWorldEvent(destinationWorld, this); +#if UNITY_EDITOR + public UnityEditor.GUID buildConfigurationGUID; + public Unity.Build.BuildConfiguration buildConfiguration; + public UnityEditor.AssetImporters.AssetImportContext assetImportContext; + public UnityEngine.GameObject prefabRoot; +#endif + } + + /// + /// Implement this interface in a bootstrap to customize the conversion world similar to runtime ICustomBootstrap. + /// + public interface ICustomConversionBootstrap + { + /// + /// This function behaves similarly to ICustomBootstrap in that you can customize the conversion process + /// at startup. However, unlike ICustomBootstrap, the World and top-level ComponentSystemGroups already + /// exist when this method is called. + /// + /// The auto-generated conversion world. + /// Settings generated by Unity for the conversion operation. Some values may not be populated. + /// + /// The list of filtered systems initially contains the systems Unity would have otherwise added. + /// Modify or replace the list to change the systems to add if returning false. + /// If returning true, the result of any modification is ignored. + /// + /// + /// Return true if this function created all the conversion world systems. + /// Return false if Unity should create them from the settings after this function returns. + /// + bool InitializeConversion(World conversionWorldWithGroupsAndMappingSystems, CustomConversionSettings settings, ref List filteredSystems); + } + + public static class GameObjectConversionGetSettingsExtension + { + /// + /// Retrieves a subset of the settings provided to GameObject Conversion + /// + public static CustomConversionSettings GetSettings(this GameObjectConversionSystem system) + { + return system.World.GetExistingSystem().customConversionSettings; + } + } + + public class OnPostCreateConversionWorldEventWrapper + { + public event OnPostCreateConversionWorldDelegate OnPostCreateConversionWorld; + internal void InvokePostCreateConversionWorldEvent(World destinationWorld, CustomConversionSettings settings) => OnPostCreateConversionWorld?.Invoke(destinationWorld, + settings); + } + +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoad] +#endif + internal static class ConversionBootstrapUtilities + { + static ConversionBootstrapUtilities() + { + RegisterConversionWorldAction(); + } + + static bool m_isRegistered = false; + + internal static void RegisterConversionWorldAction() + { + if (!m_isRegistered) + { + m_isRegistered = true; + Unity.Entities.Exposed.WorldExposedExtensions.OnWorldCreated += InitializeConversionWorld; + } + } + + static void InitializeConversionWorld(World conversionWorldWithoutSystems) + { + if (!conversionWorldWithoutSystems.Flags.HasFlag(WorldFlags.Conversion)) + return; + + conversionWorldWithoutSystems.GetOrCreateSystem(); + } + + [DisableAutoCreation] + class CustomConversionBootstrapDetectorSystem : ComponentSystem + { + bool bootstrapRan = false; + + protected override void OnCreate() + { + Unity.Entities.Exposed.WorldExposedExtensions.OnSystemCreated += CreateBootstrapSystem; + } + + protected override void OnDestroy() + { + if (!bootstrapRan) + Unity.Entities.Exposed.WorldExposedExtensions.OnSystemCreated -= CreateBootstrapSystem; + } + + protected override void OnUpdate() + { + } + + void CreateBootstrapSystem(World world, ComponentSystemBase system) + { + if (world != World) + return; + + if (system == this) + return; + + bootstrapRan = true; + Unity.Entities.Exposed.WorldExposedExtensions.OnSystemCreated -= CreateBootstrapSystem; + + world.CreateSystem(); + } + } + + [DisableAutoCreation] + public class CustomConversionBootstrapSystem : GameObjectConversionSystem + { + public CustomConversionSettings customConversionSettings { get; private set; } + + protected override void OnCreate() + { + base.OnCreate(); + + IEnumerable bootstrapTypes; +#if UNITY_EDITOR + bootstrapTypes = UnityEditor.TypeCache.GetTypesDerivedFrom(typeof(ICustomConversionBootstrap)); +#else + + var types = new List(); + var type = typeof(ICustomConversionBootstrap); + foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies()) + { + if (!BootstrapTools.IsAssemblyReferencingLatios(assembly)) + continue; + + try + { + var assemblyTypes = assembly.GetTypes(); + foreach (var t in assemblyTypes) + { + if (type.IsAssignableFrom(t)) + types.Add(t); + } + } + catch (ReflectionTypeLoadException e) + { + foreach (var t in e.Types) + { + if (t != null && type.IsAssignableFrom(t)) + types.Add(t); + } + + UnityEngine.Debug.LogWarning($"ConversionWorldBootstrap failed loading assembly: {(assembly.IsDynamic ? assembly.ToString() : assembly.Location)}"); + } + } + + bootstrapTypes = types; +#endif + + System.Type selectedType = null; + + foreach (var bootType in bootstrapTypes) + { + if (bootType.IsAbstract || bootType.ContainsGenericParameters) + continue; + + if (selectedType == null) + selectedType = bootType; + else if (selectedType.IsAssignableFrom(bootType)) + selectedType = bootType; + else if (!bootType.IsAssignableFrom(selectedType)) + UnityEngine.Debug.LogError("Multiple custom ICustomConversionBootstrap exist in the project, ignoring " + bootType); + } + if (selectedType == null) + return; + + ICustomConversionBootstrap bootstrap = System.Activator.CreateInstance(selectedType) as ICustomConversionBootstrap; + + var settingsProperty = GetType().GetProperty("Settings", BindingFlags.Instance | BindingFlags.NonPublic); + var settingsObject = settingsProperty.GetValue(this); + var settings = settingsObject as GameObjectConversionSettings; + var settingsType = settings.GetType(); + var conversionAssembly = settingsType.Assembly; + + var incremental = World.GetOrCreateSystem(conversionAssembly.GetType("Unity.Entities.ConversionSetupGroup")) as ComponentSystemGroup; + var declareConvert = World.GetOrCreateSystem(); + var earlyConvert = World.GetOrCreateSystem(); + var convert = World.GetOrCreateSystem(); + var lateConvert = World.GetOrCreateSystem(); + + var export = settings.SupportsExporting ? World.GetOrCreateSystem() : null; + + { + // for various reasons, this system needs to be present before any other system initializes + var system = World.GetOrCreateSystem(conversionAssembly.GetType("Unity.Entities.IncrementalChangesSystem")); + incremental.AddSystemToUpdateList(system); + } + + var baseSystemTypes = settings.Systems ?? DefaultWorldInitialization.GetAllSystems(settings.FilterFlags); + var filteredSystemsSet = new HashSet(); + foreach (var system in baseSystemTypes) + filteredSystemsSet.Add(system); + foreach (var system in settings.ExtraSystems) + filteredSystemsSet.Add(system); + + // Todo: Assuming the filter flags are not set to All so we don't have to remove the [DisableAutoCreation] systems we just created. + var filteredSystems = new List(filteredSystemsSet); + + CustomConversionSettings customSettings = new CustomConversionSettings + { + destinationWorld = settings.DestinationWorld, + sceneGUID = settings.SceneGUID, + debugConversionName = settings.DebugConversionName, + conversionFlags = settings.ConversionFlags, + OnPostCreateConversionWorldWrapper = new OnPostCreateConversionWorldEventWrapper(), + +#if UNITY_EDITOR + buildConfigurationGUID = settings.BuildConfigurationGUID, + buildConfiguration = settings.BuildConfiguration, + assetImportContext = settings.AssetImportContext, + prefabRoot = settings.PrefabRoot +#endif + }; + + customConversionSettings = customSettings; + bool createdSystems = bootstrap.InitializeConversion(World, customSettings, ref filteredSystems); + + if (createdSystems) + { + settings.Systems = new List(); + } + else + { + settings.Systems = filteredSystems; + } + settings.ExtraSystems = System.Array.Empty(); + settings.ConversionWorldCreated += OnConversionWorldCreationFinished; + m_settings = settings; + m_ranCleanup = false; + + foreach (var system in World.Systems) + { + if (!system.Enabled) + m_disableSet.Add(system); + } + } + + protected override void OnUpdate() + { + } + + GameObjectConversionSettings m_settings; + bool m_ranCleanup = true; + HashSet m_disableSet = new HashSet(); + + protected override void OnDestroy() + { + if (!m_ranCleanup) + m_settings.ConversionWorldCreated -= OnConversionWorldCreationFinished; + m_ranCleanup = true; + } + + void OnConversionWorldCreationFinished(World world) + { + if (world == World) + { + m_settings.ConversionWorldCreated -= OnConversionWorldCreationFinished; + m_ranCleanup = true; + + if (m_disableSet.Count != 0) + { + foreach (var system in World.Systems) + { + if (m_disableSet.Contains(system)) + system.Enabled = false; + } + } + + customConversionSettings.InvokePostCreateConversionWorldEvent(); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/ICustomConversionBootstrap.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring/ICustomConversionBootstrap.cs.meta new file mode 100644 index 0000000..069e762 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/ICustomConversionBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67bb66e8cb19da5408e4b6eab34ffea5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/ObjectAuthoringExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Authoring/ObjectAuthoringExtensions.cs new file mode 100644 index 0000000..701031d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/ObjectAuthoringExtensions.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +namespace Latios.Authoring +{ + public static class ObjectAuthoringExtensions + { + public static void DestroyDuringConversion(this Object unityEngineObject) + { +#if UNITY_EDITOR + if (Application.isPlaying) + { + Object.Destroy(unityEngineObject); + } + else + { + Object.DestroyImmediate(unityEngineObject); + } +#else + Object.Destroy(unityEngineObject); +#endif + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/ObjectAuthoringExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring/ObjectAuthoringExtensions.cs.meta new file mode 100644 index 0000000..ed8d158 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/ObjectAuthoringExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ee69ff71f7268c438aa02d6ac5b87f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionGroup.cs b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionGroup.cs new file mode 100644 index 0000000..214afe6 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionGroup.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Unity.Entities; +using UnityEngine; + +namespace Latios.Authoring.Systems +{ + /// + /// Conversion System Group when all the Smart Blobber Systems execute. + /// The UpdateInGroup attribute is inherited from the base classes. + /// + [UpdateInGroup(typeof(GameObjectBeforeConversionGroup), OrderLast = true)] + [WorldSystemFilter(WorldSystemFilterFlags.GameObjectConversion)] + public class SmartBlobberConversionGroup : ComponentSystemGroup + { + } + + /// + /// Conversion System used to invoke RequestBlobAssets after the DeclareReferencedPrefabs stage + /// but before SmartBlobberConversion. + /// + [UpdateInGroup(typeof(GameObjectBeforeConversionGroup))] + public class RequestSmartBlobAssetsConversionSystem : GameObjectConversionSystem + { + void Convert(Transform transform, List convertibles) + { + try + { + transform.GetComponents(convertibles); + + foreach (var c in convertibles) + { + var behaviour = c as Behaviour; + if (behaviour != null && !behaviour.enabled) + continue; + +#if UNITY_EDITOR + if (!ShouldRunConversionSystem(c.GetType())) + continue; +#endif + + var entity = GetPrimaryEntity((Component)c); + c.RequestBlobAssets(entity, DstEntityManager, this); + } + } + catch (Exception x) + { + Debug.LogException(x, transform); + } + } + + protected override void OnUpdate() + { + var convertibles = new List(); + + Entities.ForEach((Transform transform) => Convert(transform, convertibles)); + convertibles.Clear(); + + Entities.ForEach((RectTransform transform) => Convert(transform, convertibles)); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionGroup.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionGroup.cs.meta new file mode 100644 index 0000000..8257cf1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd147fe7ffdfe3940bd66563522575af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystem.cs new file mode 100644 index 0000000..777c21f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystem.cs @@ -0,0 +1,389 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Entities.LowLevel.Unsafe; +using Unity.Jobs; + +namespace Latios.Authoring +{ + /// + /// Implement this interface to request BlobAsset conversion from SmartBlobbers before + /// they execute. You can then retrieve the results using IConvertGameObjectToEntity. + /// + public interface IRequestBlobAssets + { + void RequestBlobAssets(Entity entity, EntityManager dstEntityManager, GameObjectConversionSystem conversionSystem); + } + + #region Handles + /// + /// A handle to a computed blob to be created by a smart blobber + /// + /// The top of blob to be created + public struct SmartBlobberHandle where TBlobType : unmanaged + { + internal ISmartBlobberHandleResolver m_resolver; + internal int m_index; + + /// + /// Retrieves the blob asset after the smart blobber has run. Throws an exception if called before the smart blobber has run. + /// + /// The blob asset generated by the smart blobber + public BlobAssetReference Resolve() + { + if (m_resolver == null) + throw new System.InvalidOperationException("This handle has not been initialized."); + + if (!m_resolver.HasProcessed(m_index)) + throw new System.InvalidOperationException( + $"The smart blobber of type {m_resolver.GetType().FullName} has not processed the blob yet. Please request the blob generation prior to smart blobber execution such as in DeclareReferencedPrefabs() and do not attempt to resolve the blob until hafter smart blobber execution such as in Convert()."); + + return m_resolver.Resolve(m_index); + } + + /// + /// Returns true if this handle was generated by a smart blobber. + /// + public bool IsValid => m_resolver != null; + } + + /// + /// A handle to a computed blob to be created by a smart blobber + /// + public struct SmartBlobberHandleUntyped + { + internal ISmartBlobberHandleResolverUntyped m_resolver; + internal int m_index; + + /// + /// Retrieves the blob asset after the smart blobber has run. Throws an exception if called before the smart blobber has run. + /// + /// The blob asset generated by the smart blobber + public UnsafeUntypedBlobAssetReference Resolve() + { + if (m_resolver == null) + throw new System.InvalidOperationException("This handle has not been initialized."); + + if (!m_resolver.HasProcessed(m_index)) + throw new System.InvalidOperationException( + $"The smart blobber of type {m_resolver.GetType().FullName} has not processed the blob yet. Please request the blob generation prior to smart blobber execution such as in DeclareReferencedPrefabs() and do not attempt to resolve the blob until hafter smart blobber execution such as in Convert()."); + + return m_resolver.ResolveUntyped(m_index); + } + + /// + /// Returns true if this handle was generated by a smart blobber. + /// + public bool IsValid => m_resolver != null; + } + #endregion +} + +namespace Latios.Authoring.Systems +{ + #region BuilderInterfaces + /// + /// Implement this interface on a struct for simple Smart Blobber Conversion. + /// This struct gets stored in a NativeArray and passed to a parallel job. + /// If you need dynamically sized arrays of data for the blob, use UnsafeList + /// allocated with World.UpdateAllocator.ToAllocator. You do not have to dispose + /// such lists. + /// + /// The root type of the Blob Asset + public interface ISmartBlobberSimpleBuilder where TBlobType : unmanaged + { + public BlobAssetReference BuildBlob(); + } + + /// + /// Implement this interface on a struct for advanced Smart Blobber Conversion. + /// This struct gets stored in a NativeArray and passed to a parallel job. + /// You can store shared data for all builders inside the context object. + /// This is especially useful for MeshDataArrays or NativeHashMaps. + /// + /// The root type of the Blob Asset + /// The type of context object. This object can store NativeContainers and parallel job attributes apply. + public interface ISmartBlobberContextBuilder where TBlobType : unmanaged where TContextType : struct + { + /// + /// Build a blob asset using a context + /// + /// Corresponds to the indices in FilterBlobberData + /// Corresponds to the indices in PostFilterBlobberData + /// The context object shared across all converters + /// + public BlobAssetReference BuildBlob(int prefilterIndex, int postfilterIndex, ref TContextType context); + } + + // Unity does not cache blobs and hashes between conversions, meaning hashes only help deduplicate identical assets. + // Smart blobbers have their own mechanism for deduplication at the input side, so using hashes only to deduplicate + // final blob assets is sufficient. Therefore, pre-hashing rarely offsets its own cost and complexity. + /*public interface ISmartBlobberHashBuilder where TBlobType : unmanaged where TContextType : struct + { + public Hash128 ComputeHash(int prefilterIndex, int postfilterIndex, ref TContextType context); + }*/ + #endregion + + #region BaseClasses + /// + /// This is the simplest smart blobber type. Subclass this to create a smart blobber that only reasons about each blob asset individually. + /// + /// The type of blob this smart blobber generates + /// A struct containing authoring data possibly including managed references + /// A struct which contains the necessary unmanaged data and functions for creating a BlobAsset. It's method will be executed in a Burst job. + /// + /// A Smart Blobber is a GameObjectConversionSystem which acts as a blob asset generation service for other conversion logic. + /// MonoBehaviours implementing IRequestBlobAssets and conversion systems updating in GameObjectBeforeConversionGroup can request blob asset conversion + /// for a given input. Requests are responded with a handle that can be resolved during GameObjectConversionGroup or IConvertGameObjectToEntity. + /// Smart Blobbers can also iterate authoring entities and add components to converted entities. + /// Smart blobbers will try to parallelize as much of the blob asset conversion as possible. They handle BlobAssetStore and BlobAssetComputationContext + /// internally. You do not need to worry about these things. Just implement the required functions and interfaces and you should be good to go. + /// + [UpdateInGroup(typeof(SmartBlobberConversionGroup))] + public abstract partial class SmartBlobberConversionSystem : GameObjectConversionSystem + where TBlobType : unmanaged + where TManagedInputType : struct + where TUnmanagedConversionType : unmanaged, ISmartBlobberSimpleBuilder + { + /// + /// Add an input to convert to a BlobAsset as well as the unityObject the blob should be associated with. Can only be called before the system is updated or during GatherInputs(). + /// + /// The converted GameObject to associate the blob with, throws if null. This object should have an entity associated with it. + /// Input data which can be used to gather everything needed to generate a blob + /// A handle which can be resolved into a BlobAssetReference after the smart blobber runs + public SmartBlobberHandle AddToConvert(UnityEngine.GameObject gameObject, in TManagedInputType input) + { + if (m_inputsAreLocked) + { + throw new System.InvalidOperationException("You cannot call AddToConvert() during Filter()"); + } + + if (gameObject == null) + { + throw new System.ArgumentNullException("unityObject cannot be null"); + } + + int index = m_inputs.Count + m_handleToOutputIndices.Count; + m_inputs.Add(new InputElement { gameObject = gameObject, input = input }); + return new SmartBlobberHandle { m_index = index, m_resolver = this }; + } + + /// + /// Add an input to convert to a BlobAsset as well as the unityObject the blob should be associated with. Can only be called before the system is updated or during GatherInputs(). + /// + /// The converted GameObject to associate the blob with, throws if null. This object should have an entity associated with it. + /// Input data which can be used to gather everything needed to generate a blob + /// A handle which can be resolved into a BlobAssetReference after the smart blobber runs + public SmartBlobberHandleUntyped AddToConvertUntyped(UnityEngine.GameObject gameObject, in TManagedInputType input) + { + if (m_inputsAreLocked) + { + throw new System.InvalidOperationException("You cannot call AddToConvertUntyped() during Filter()"); + } + + if (gameObject == null) + { + throw new System.ArgumentNullException("unityObject cannot be null"); + } + + int index = m_inputs.Count + m_handleToOutputIndices.Count; + m_inputs.Add(new InputElement { gameObject = gameObject, input = input }); + return new SmartBlobberHandleUntyped { m_index = index, m_resolver = this }; + } + + /// + /// Override this function to collect additional inputs using AddToConvert() and AddToConvertUntyped(). + /// + protected virtual void GatherInputs() + { + } + + /// + /// Generate a converter instance to generate a Blob Asset. You must override this function. Return false to skip the input. + /// + /// The managed input + /// The GameObject which is being converted. + /// The unmanaged data required to generate a Blob Asset. The converter will be executed in a Burst job. + /// Returns false if skipped and true if kept. + protected abstract bool Filter(in TManagedInputType input, UnityEngine.GameObject gameObject, out TUnmanagedConversionType converter); + + /// + /// Override this function to process any handles created during GatherInputs(). + /// + protected virtual void FinalizeOutputs() + { + } + } + + /// + /// This is a smart blobber type that provides a context object which can use NativeContainers and is accessible by all converters. + /// It also provides a more advanced input filtering API which can perform initial deduplication. + /// Subclass this when you need a smart blobber that can reason about more than one blob asset at a time. + /// + /// The type of blob this smart blobber generates + /// A struct containing authoring data possibly including managed references + /// A struct which contains the necessary unmanaged data and functions for creating a BlobAsset. It's method will be executed in a Burst job. + /// A struct which may use NativeContainers and is provided to all the converters. + [UpdateInGroup(typeof(SmartBlobberConversionGroup))] + public abstract partial class SmartBlobberConversionSystem : GameObjectConversionSystem + where TBlobType : unmanaged + where TManagedInputType : struct + where TUnmanagedConversionType : unmanaged, ISmartBlobberContextBuilder + where TContextType : struct, System.IDisposable + { + /// + /// Add an input to convert to a BlobAsset as well as the unityObject the blob should be associated with. Can only be called before the system is updated or during GatherInputs(). + /// + /// The converted GameObject to associate the blob with, throws if null. This object should have an entity associated with it. + /// Input data which can be used to gather everything needed to generate a blob + /// A handle which can be resolved into a BlobAssetReference after the smart blobber runs + public SmartBlobberHandle AddToConvert(UnityEngine.GameObject gameObject, in TManagedInputType input) + { + if (m_inputsAreLocked) + { + throw new System.InvalidOperationException("You cannot call AddToConvert() during Filter()"); + } + + if (gameObject == null) + { + throw new System.ArgumentNullException("unityObject cannot be null"); + } + + int index = m_inputs.Count + m_handleToOutputIndices.Count; + m_inputs.Add(new InputElement { gameObject = gameObject, input = input }); + return new SmartBlobberHandle { m_index = index, m_resolver = this }; + } + + /// + /// Add an input to convert to a BlobAsset as well as the unityObject the blob should be associated with. Can only be called before the system is updated or during GatherInputs(). + /// + /// The converted GameObject to associate the blob with, throws if null. This object should have an entity associated with it. + /// Input data which can be used to gather everything needed to generate a blob + /// A handle which can be resolved into a BlobAssetReference after the smart blobber runs + public SmartBlobberHandleUntyped AddToConvertUntyped(UnityEngine.GameObject gameObject, in TManagedInputType input) + { + if (m_inputsAreLocked) + { + throw new System.InvalidOperationException("You cannot call AddToConvertUntyped() during Filter()"); + } + + if (gameObject == null) + { + throw new System.ArgumentNullException("unityObject cannot be null"); + } + + int index = m_inputs.Count + m_handleToOutputIndices.Count; + m_inputs.Add(new InputElement { gameObject = gameObject, input = input }); + return new SmartBlobberHandleUntyped { m_index = index, m_resolver = this }; + } + + /// + /// Override this function to collect additional inputs using AddToConvert() and AddToConvertUntyped(). + /// + protected virtual void GatherInputs() + { + } + + /// + /// Indexer-based mutable access to the array. + /// + public partial struct InputAccess + { + public TManagedInputType this[int index] + { + get => GetInput(index); + set => SetInput(index, value); + } + } + + /// + /// Indexer-based readonly access to the GameObject instances associated with the inputs + /// + public partial struct GameObjectAccess + { + public UnityEngine.GameObject this[int index] + { + get => GetInput(index); + } + } + + /// + /// Contains access to inputs and converters during filtering + /// + public struct FilterBlobberData + { + /// + /// Read-write access to the inputs + /// + public InputAccess input { get; internal set; } + /// + /// Readonly access to the associated UnityEngine.GameObjects being converted + /// + public GameObjectAccess associatedObject { get; internal set; } + /// + /// Read-write access to the converters . Values are completely uninitialized to begin with. + /// The converters do not need to be initialized at this time. + /// + public NativeArray converters { get; internal set; } + + public int Count => converters.Length; + } + + /// + /// Setup the converters and context using the inputs. Use the to deduplicate or invalidate inputs. + /// + /// Access to the inputs and converters + /// Global context object accessible to converters + /// Set an element to a negative value to skip the corresponding input. + /// Otherwise, set it to the index of the input to use, which must be equal or lower to the element index. + /// Examples: + /// inputToFilteredMapping[2] = -1; - Discard the third input. + /// inputToFilteredMapping[3] = 3; - Use the fourth input as is. + /// inputTofilteredMapping[4] = 1; - Treat the fifth input as a duplicate of the second input. The fifth converter will not be executed. + /// inputToFilteredMapping[5] = 8; - Invalid. 8 is larger than 5. + /// inputToFilteredMapping[6] = 4; inputToFilteredMapping[7] = 6; - Invalid. Cannot mark duplicated of an input that is already marked duplicated. + /// + protected abstract void Filter(FilterBlobberData blobberData, ref TContextType context, NativeArray inputToFilteredMapping); + + /// + /// Contains access to inputs and converters after filtering but before the converters have been executed. + /// The arrays have been compacted at this point and filtered out elements are no longer present. + /// + public struct PostFilterBlobberData + { + /// + /// Read-write access to the inputs + /// + public InputAccess input { get; internal set; } + /// + /// Read-write access to the filtered converters . Values are completely uninitialized to begin with. + /// If the converters were not initialized before, they must be initialized now. + /// + public NativeArray converters { get; internal set; } + /// + /// Readonly access to the mapping from a filtered converter to the original input for manipulating the context object. + /// + public NativeArray.ReadOnly filteredToInputMapping; + + public int Count => converters.Length; + } + + /// + /// Override this function to perform any additional preparation to the filtered list of converters and context. After this, the blob assets will be generated. + /// + /// Access to the filtered subset of inputs and converters + /// Global context object accessible to converters + protected virtual void PostFilter(PostFilterBlobberData blobberData, ref TContextType context) + { + } + + /// + /// Override this function to process any handles created during GatherInputs(). + /// + protected virtual void FinalizeOutputs() + { + } + } + #endregion +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystem.cs.meta new file mode 100644 index 0000000..ff11220 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c43e70b907e7b064f8295a2c4ac5c77d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystemImpl.cs b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystemImpl.cs new file mode 100644 index 0000000..88ca42a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystemImpl.cs @@ -0,0 +1,764 @@ +using System.Collections.Generic; +using GameObject = UnityEngine.GameObject; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Entities.LowLevel.Unsafe; +using Unity.Jobs; + +namespace Latios.Authoring +{ + internal interface ISmartBlobberHandleResolver : ISmartBlobberHasProcessed where TBlobType : unmanaged + { + internal BlobAssetReference Resolve(int index); + } + + internal interface ISmartBlobberHandleResolverUntyped : ISmartBlobberHasProcessed + { + internal UnsafeUntypedBlobAssetReference ResolveUntyped(int index); + } + + internal interface ISmartBlobberHasProcessed + { + internal bool HasProcessed(int index); + } +} +namespace Latios.Authoring.Systems +{ + public abstract partial class SmartBlobberConversionSystem : GameObjectConversionSystem, + ISmartBlobberHandleResolver, + ISmartBlobberHandleResolverUntyped + where TBlobType : unmanaged + where TManagedInputType : struct + where TUnmanagedConversionType : unmanaged, ISmartBlobberSimpleBuilder + { + struct InputElement + { + public TManagedInputType input; + public GameObject gameObject; + } + + List m_inputs = new List(); + List m_handleToOutputIndices = new List(); + List > m_outputBlobs = new List >(); + bool m_inputsAreLocked = false; + + BlobAssetReference ISmartBlobberHandleResolver.Resolve(int index) + { + if (m_handleToOutputIndices[index] < 0) + return default; + + return m_outputBlobs[m_handleToOutputIndices[index]]; + } + + UnsafeUntypedBlobAssetReference ISmartBlobberHandleResolverUntyped.ResolveUntyped(int index) + { + if (m_handleToOutputIndices[index] < 0) + return default; + + var blob = m_outputBlobs[m_handleToOutputIndices[index]]; + return UnsafeUntypedBlobAssetReference.Create(blob); + } + + bool ISmartBlobberHasProcessed.HasProcessed(int index) => index < m_handleToOutputIndices.Count; + + protected sealed override void OnUpdate() + { + GatherInputs(); + + if (m_inputs.Count <= 0) + return; + + // Step 1: Process inputs + var converters = new NativeArray(m_inputs.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var indices = new NativeArray(m_inputs.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + try + { + m_inputsAreLocked = true; + for (int i = 0; i < m_inputs.Count; i++) + { + if (Filter(m_inputs[i].input, m_inputs[i].gameObject, out var converter)) + { + converters[i] = converter; + indices[i] = 1; + } + else + { + indices[i] = -1; + } + } + m_inputsAreLocked = false; + } + catch (System.Exception) + { + converters.Dispose(); + indices.Dispose(); + m_inputsAreLocked = false; + throw; + } + + // Step 2: Build blobs and hashes + var blobs = new NativeArray >(converters.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var hashes = new NativeArray(converters.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + new ConvertBlobsJob + { + converters = converters, + blobs = blobs, + hashes = hashes, + indices = indices, + }.ScheduleParallel(converters.Length, 1, default).Complete(); + + // Step 3: filter with BlobAssetComputationContext + var compData = new NativeList(Allocator.TempJob); + new MakeCompDataJob + { + indices = indices, + compData = compData + }.Run(); + + var computationContext = new BlobAssetComputationContext(BlobAssetStore, 128, Allocator.TempJob); + foreach (var cd in compData) + { + var hash = hashes[cd.index]; + computationContext.AssociateBlobAssetWithUnityObject(hash, m_inputs[cd.index].gameObject); + if (computationContext.NeedToComputeBlobAsset(hash)) + { + computationContext.AddBlobAssetToCompute(hash, cd); + } + } + + var filteredCompData = computationContext.GetSettings(Allocator.TempJob); + foreach (var cd in filteredCompData) + { + computationContext.AddComputedBlobAsset(hashes[cd.index], blobs[cd.index]); + } + + // Step 4: Dispose unused blobs + var disposeBlobMask = new NativeBitArray(converters.Length, Allocator.TempJob, NativeArrayOptions.ClearMemory); + new MakeDisposeMaskJob + { + compData = compData, + filteredCompData = filteredCompData, + disposeBlobMask = disposeBlobMask + }.Run(); + + new DisposeUnusedBlobsJob + { + blobs = blobs, + disposeBlobMask = disposeBlobMask + }.ScheduleParallel(converters.Length, 1, default).Complete(); + + // Step 5: Collect final blobs + foreach (var cd in compData) + { + computationContext.GetBlobAsset(hashes[cd.index], out var blob); + blobs[cd.index] = blob; + } + + var filteredBlobs = new NativeArray >(compData.Length, Allocator.TempJob); + new MakeFinalIndicesAndBlobsJob + { + indices = indices, + srcBlobs = blobs, + dstBlobs = filteredBlobs, + baseIndex = m_handleToOutputIndices.Count + }.Run(); + + m_handleToOutputIndices.AddRange(indices); + m_outputBlobs.AddRange(filteredBlobs); + + converters.Dispose(); + indices.Dispose(); + blobs.Dispose(); + hashes.Dispose(); + compData.Dispose(); + computationContext.Dispose(); + filteredCompData.Dispose(); + disposeBlobMask.Dispose(); + filteredBlobs.Dispose(); + + m_inputs.Clear(); + + FinalizeOutputs(); + } + + [BurstCompile] + struct ConvertBlobsJob : IJobFor + { + public NativeArray converters; + public NativeArray > blobs; + public NativeArray hashes; + public NativeArray indices; + + public unsafe void Execute(int i) + { + if (indices[i] < 0) + { + blobs[i] = default; + return; + } + + var blob = converters[i].BuildBlob(); + var length = blob.GetLength(); + if (length <= 0) + { + // The blob is null, so modify the index and return out. + indices[i] = -1; + return; + } + + blobs[i] = blob; + indices[i] = i; + var hash64 = xxHash3.Hash64(blob.GetUnsafePtr(), length); + hashes[i] = new Hash128(hash64.x, hash64.y, (uint)length, 0); + } + } + + struct CompData + { + public int index; + } + + [BurstCompile] + struct MakeCompDataJob : IJob + { + [ReadOnly] public NativeArray indices; + public NativeList compData; + + public void Execute() + { + for (int i = 0; i < indices.Length; i++) + { + if (indices[i] >= 0) + { + compData.Add(new CompData { index = i }); + } + } + } + } + + [BurstCompile] + struct MakeDisposeMaskJob : IJob + { + [ReadOnly] public NativeArray compData; + [ReadOnly] public NativeArray filteredCompData; + public NativeBitArray disposeBlobMask; + + public void Execute() + { + for (int i = 0; i < compData.Length; i++) + { + disposeBlobMask.Set(compData[i].index, true); + } + + for (int i = 0; i < filteredCompData.Length; i++) + { + disposeBlobMask.Set(filteredCompData[i].index, false); + } + } + } + + [BurstCompile] + struct DisposeUnusedBlobsJob : IJobFor + { + public NativeArray > blobs; + [ReadOnly] public NativeBitArray disposeBlobMask; + + public void Execute(int i) + { + if (disposeBlobMask.IsSet(i)) + { + var blob = blobs[i]; + blob.Dispose(); + blobs[i] = default; + } + } + } + + [BurstCompile] + struct MakeFinalIndicesAndBlobsJob : IJob + { + public NativeArray indices; + public NativeArray > dstBlobs; + [ReadOnly] public NativeArray > srcBlobs; + public int baseIndex; + + public void Execute() + { + int dst = 0; + for (int src = 0; src < indices.Length; src++) + { + if (indices[src] >= 0) + { + indices[src] = dst + baseIndex; + dstBlobs[dst] = srcBlobs[src]; + dst++; + } + } + } + } + } + + public abstract partial class SmartBlobberConversionSystem : GameObjectConversionSystem, + ISmartBlobberHandleResolver, + ISmartBlobberHandleResolverUntyped + where TBlobType : unmanaged + where TManagedInputType : struct + where TUnmanagedConversionType : unmanaged, ISmartBlobberContextBuilder + where TContextType : struct, System.IDisposable + { + internal struct InputElement + { + public TManagedInputType input; + public GameObject gameObject; + } + + List m_inputs = new List(); + List m_handleToOutputIndices = new List(); + List > m_outputBlobs = new List >(); + bool m_inputsAreLocked = false; + + BlobAssetReference ISmartBlobberHandleResolver.Resolve(int index) + { + if (m_handleToOutputIndices[index] < 0) + return default; + + return m_outputBlobs[m_handleToOutputIndices[index]]; + } + + UnsafeUntypedBlobAssetReference ISmartBlobberHandleResolverUntyped.ResolveUntyped(int index) + { + if (m_handleToOutputIndices[index] < 0) + return default; + + var blob = m_outputBlobs[m_handleToOutputIndices[index]]; + return UnsafeUntypedBlobAssetReference.Create(blob); + } + + bool ISmartBlobberHasProcessed.HasProcessed(int index) => index < m_handleToOutputIndices.Count; + + public partial struct InputAccess + { + internal List m_inputs; + internal NativeArray m_filteredToInputMapping; + + internal TManagedInputType GetInput(int index) + { + if (m_filteredToInputMapping.IsCreated) + return m_inputs[m_filteredToInputMapping[index]].input; + return m_inputs[index].input; + } + + internal void SetInput(int index, TManagedInputType input) + { + if (m_filteredToInputMapping.IsCreated) + index = m_filteredToInputMapping[index]; + var v = m_inputs[index]; + v.input = input; + m_inputs[index] = v; + } + } + + public partial struct GameObjectAccess + { + internal List m_inputs; + internal NativeArray m_filteredToInputMapping; + + internal GameObject GetInput(int index) + { + if (m_filteredToInputMapping.IsCreated) + return m_inputs[m_filteredToInputMapping[index]].gameObject; + return m_inputs[index].gameObject; + } + } + + protected sealed override void OnUpdate() + { + GatherInputs(); + + if (m_inputs.Count <= 0) + return; + + // Step 1: Filter inputs + m_inputsAreLocked = true; + + var inputConverters = new NativeArray(m_inputs.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var inputToFilteredMapping = new NativeArray(m_inputs.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + new CreateIncrementingArrayJob { array = inputToFilteredMapping }.Run(); + + TContextType context = default; + var filterBlobberData = new FilterBlobberData + { + associatedObject = new GameObjectAccess { m_inputs = m_inputs }, + input = new InputAccess { m_inputs = m_inputs }, + converters = inputConverters + }; + try + { + Filter(filterBlobberData, ref context, inputToFilteredMapping); + } + catch (System.Exception) + { + inputConverters.Dispose(); + inputToFilteredMapping.Dispose(); + context.Dispose(); + m_inputsAreLocked = false; + throw; + } + + // Step 2: Generate filtered list + var filteredConverters = new NativeList(1, Allocator.TempJob); + var filteredToInputMapping = new NativeList(1, Allocator.TempJob); + var filterError = new NativeReference(new FilterError { errorIndex = -1, reason = FilterError.Reason.None }, Allocator.TempJob); + new GenerateFilteredConvertersJob + { + inputConverters = inputConverters, + inputToFilteredMapping = inputToFilteredMapping, + filteredConverters = filteredConverters, + filteredToInputMapping = filteredToInputMapping, + filterError = filterError + }.Run(); + + // Don't need these anymore. + inputConverters.Dispose(); + + if (filterError.Value.reason != FilterError.Reason.None) + { + filteredConverters.Dispose(); + filteredToInputMapping.Dispose(); + + if (filterError.Value.reason == FilterError.Reason.ForwardIndex) + { + int offendingIndex = filterError.Value.errorIndex; + int offendingValue = inputToFilteredMapping[offendingIndex]; + inputToFilteredMapping.Dispose(); + filterError.Dispose(); + throw new System.InvalidOperationException($"inputToFilteredMapping was initialized with a forward index of {offendingValue} at position {offendingIndex}"); + } + if (filterError.Value.reason == FilterError.Reason.CopyOfCopy) + { + int offendingIndex = filterError.Value.errorIndex; + int offendingValue = inputToFilteredMapping[offendingIndex]; + int offendingValuesValue = inputToFilteredMapping[offendingValue]; + inputToFilteredMapping.Dispose(); + filterError.Dispose(); + throw new System.InvalidOperationException( + $"inputToFilteredMapping at position {offendingIndex} creates a deduplication reference to position {offendingValue} which is already a deduplication reference to position {offendingValuesValue}"); + } + } + filterError.Dispose(); + + //Step 3: PostFilter + var postFilter = new PostFilterBlobberData + { + converters = filteredConverters, + input = new InputAccess { m_inputs = m_inputs, m_filteredToInputMapping = filteredToInputMapping }, + filteredToInputMapping = filteredToInputMapping.AsArray().AsReadOnly() + }; + try + { + PostFilter(postFilter, ref context); + } + catch + { + inputToFilteredMapping.Dispose(); + context.Dispose(); + filteredConverters.Dispose(); + filteredToInputMapping.Dispose(); + throw; + } + + // Step 4: Build blobs and hashes + var blobs = new NativeArray >(filteredConverters.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var hashes = new NativeArray(filteredConverters.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + new ConvertBlobsJob + { + converters = filteredConverters, + blobs = blobs, + hashes = hashes, + filteredToInputMapping = filteredToInputMapping, + context = context + }.Run(filteredConverters.Length); //.ScheduleParallel(filteredConverters.Length, 1, default).Complete(); + context.Dispose(); + + // Step 5: filter with BlobAssetComputationContext + var compData = new NativeList(Allocator.TempJob); + new MakeCompDataJob + { + indices = filteredToInputMapping, + compData = compData + }.Run(); + filteredToInputMapping.Dispose(); + + var computationContext = new BlobAssetComputationContext(BlobAssetStore, 128, Allocator.TempJob); + for (int i = 0; i < inputToFilteredMapping.Length; i++) + { + var filteredIndex = inputToFilteredMapping[i]; + if (filteredIndex < 0) + continue; + + computationContext.AssociateBlobAssetWithUnityObject(hashes[filteredIndex], m_inputs[i].gameObject); + } + foreach (var cd in compData) + { + if (computationContext.NeedToComputeBlobAsset(hashes[cd.index])) + { + computationContext.AddBlobAssetToCompute(hashes[cd.index], cd); + } + } + + var filteredCompData = computationContext.GetSettings(Allocator.TempJob); + foreach (var cd in filteredCompData) + { + computationContext.AddComputedBlobAsset(hashes[cd.index], blobs[cd.index]); + } + + // Step 6: Dispose unused blobs + var disposeBlobMask = new NativeBitArray(filteredConverters.Length, Allocator.TempJob, NativeArrayOptions.ClearMemory); + new MakeDisposeMaskJob + { + compData = compData, + filteredCompData = filteredCompData, + disposeBlobMask = disposeBlobMask + }.Run(); + filteredCompData.Dispose(); + + new DisposeUnusedBlobs + { + blobs = blobs, + disposeBlobMask = disposeBlobMask + }.ScheduleParallel(filteredConverters.Length, 1, default).Complete(); + disposeBlobMask.Dispose(); + + // Step 7: Collect final blobs + foreach (var cd in compData) + { + computationContext.GetBlobAsset(hashes[cd.index], out var blob); + blobs[cd.index] = blob; + } + computationContext.Dispose(); + + var filteredBlobs = new NativeArray >(compData.Length, Allocator.TempJob); + compData.Dispose(); + + new MakeFinalIndicesAndBlobsJob + { + inputToFilteredMapping = inputToFilteredMapping, + srcBlobs = blobs, + dstBlobs = filteredBlobs, + baseIndex = m_handleToOutputIndices.Count + }.Run(); + blobs.Dispose(); + hashes.Dispose(); + filteredConverters.Dispose(); + + m_handleToOutputIndices.AddRange(inputToFilteredMapping); + m_outputBlobs.AddRange(filteredBlobs); + + inputToFilteredMapping.Dispose(); + filteredBlobs.Dispose(); + + FinalizeOutputs(); + } + + [BurstCompile] + struct CreateIncrementingArrayJob : IJob + { + public NativeArray array; + + public void Execute() + { + for (int i = 0; i < array.Length; i++) + array[i] = i; + } + } + + struct FilterError + { + public int errorIndex; + public Reason reason; + + public enum Reason + { + None, + ForwardIndex, + CopyOfCopy + } + } + + [BurstCompile] + struct GenerateFilteredConvertersJob : IJob + { + [ReadOnly] public NativeArray inputConverters; + public NativeArray inputToFilteredMapping; + public NativeList filteredConverters; + public NativeList filteredToInputMapping; + public NativeReference filterError; + + public void Execute() + { + int count = 0; + for (int i = 0; i < inputToFilteredMapping.Length; i++) + { + if (inputToFilteredMapping[i] == i) + count++; + else if (inputToFilteredMapping[i] > i) + { + filterError.Value = new FilterError { errorIndex = i, reason = FilterError.Reason.ForwardIndex }; + return; + } + else if (inputToFilteredMapping[i] >= 0) + { + int dedupIndex = inputToFilteredMapping[i]; + if (inputToFilteredMapping[dedupIndex] != dedupIndex) + { + filterError.Value = new FilterError { errorIndex = i, reason = FilterError.Reason.CopyOfCopy }; + return; + } + } + } + + filteredConverters.ResizeUninitialized(count); + filteredToInputMapping.ResizeUninitialized(count); + int dst = 0; + for (int src = 0; src < inputToFilteredMapping.Length; src++) + { + if (inputToFilteredMapping[src] == src) + { + filteredToInputMapping[dst] = src; + filteredConverters[dst] = inputConverters[src]; + inputToFilteredMapping[src] = dst; + dst++; + } + else if (inputToFilteredMapping[src] >= 0) + inputToFilteredMapping[src] = inputToFilteredMapping[inputToFilteredMapping[src]]; + } + } + } + + [BurstCompile] + struct ConvertBlobsJob : IJobFor + { + public NativeArray converters; + public NativeArray > blobs; + public NativeArray hashes; + public NativeArray filteredToInputMapping; + public TContextType context; + + public unsafe void Execute(int i) + { + if (filteredToInputMapping[i] < 0) + { + blobs[i] = default; + return; + } + + var blob = converters[i].BuildBlob(filteredToInputMapping[i], i, ref context); + var length = blob.GetLength(); + if (length <= 0) + { + // The blob is null, so modify the index and return out. + filteredToInputMapping[i] = -1; + return; + } + + blobs[i] = blob; + var hash64 = xxHash3.Hash64(blob.GetUnsafePtr(), length); + hashes[i] = new Hash128(hash64.x, hash64.y, (uint)length, 0); + } + } + + struct CompData + { + public int index; + } + + [BurstCompile] + struct MakeCompDataJob : IJob + { + [ReadOnly] public NativeArray indices; + public NativeList compData; + + public void Execute() + { + for (int i = 0; i < indices.Length; i++) + { + if (indices[i] >= 0) + { + compData.Add(new CompData { index = i }); + } + } + } + } + + [BurstCompile] + struct MakeDisposeMaskJob : IJob + { + [ReadOnly] public NativeArray compData; + [ReadOnly] public NativeArray filteredCompData; + public NativeBitArray disposeBlobMask; + + public void Execute() + { + for (int i = 0; i < compData.Length; i++) + { + disposeBlobMask.Set(compData[i].index, true); + } + + for (int i = 0; i < filteredCompData.Length; i++) + { + disposeBlobMask.Set(filteredCompData[i].index, false); + } + } + } + + [BurstCompile] + struct DisposeUnusedBlobs : IJobFor + { + public NativeArray > blobs; + [ReadOnly] public NativeBitArray disposeBlobMask; + + public void Execute(int i) + { + if (disposeBlobMask.IsSet(i)) + { + var blob = blobs[i]; + blob.Dispose(); + blobs[i] = default; + } + } + } + + [BurstCompile] + struct MakeFinalIndicesAndBlobsJob : IJob + { + public NativeArray inputToFilteredMapping; + public NativeArray > dstBlobs; + [ReadOnly] public NativeArray > srcBlobs; + public int baseIndex; + + public void Execute() + { + int dst = 0; + for (int src = 0; src < inputToFilteredMapping.Length; src++) + { + if (inputToFilteredMapping[src] == dst) + { + int filteredIndex = inputToFilteredMapping[src]; + inputToFilteredMapping[src] += baseIndex; + dstBlobs[dst] = srcBlobs[filteredIndex]; + dst++; + } + else if (inputToFilteredMapping[src] >= 0) + { + inputToFilteredMapping[src] += baseIndex; + } + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystemImpl.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystemImpl.cs.meta new file mode 100644 index 0000000..8af6fbd --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Authoring/SmartBlobberConversionSystemImpl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc15834880a0c3444956071e8177949c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Components.meta b/Packages/com.latios.latios-framework/Core/Core/Components.meta new file mode 100644 index 0000000..c39f2c0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c8c844d37f6a77448adf0880a302f10 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/BlackboardEntityData.cs b/Packages/com.latios.latios-framework/Core/Core/Components/BlackboardEntityData.cs new file mode 100644 index 0000000..325dfcb --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/BlackboardEntityData.cs @@ -0,0 +1,54 @@ +using System; +using Unity.Entities; + +namespace Latios +{ + /// + /// A tag component automatically added to the worldBlackboardEntity. Use it in queries if necessary. + /// + public struct WorldBlackboardTag : IComponentData { } + + /// + /// A tag component automatically added to the sceneBlackboardEntity. Use it in queries if necessary. + /// + public struct SceneBlackboardTag : IComponentData { } + + public enum BlackboardScope + { + /// + /// Apply to the worldBlackboardEntity + /// + World, + /// + /// Apply to the sceneBlackboardEntity + /// + Scene + } + public enum MergeMethod + { + /// + /// Any already existing components on the blackboard entity can have their data overwritten + /// by the new component values + /// + Overwrite, + /// + /// If the blackboard entity already has a given component, the new component value will be discarded + /// + KeepExisting, + /// + /// An exception is thrown if the blackboard entity already has a given component + /// + ErrorOnConflict + } + + /// + /// Attach this to an entity to have all its components copied over to a blackboard entity. + /// The entity will then be destroyed. + /// + public struct BlackboardEntityData : IComponentData + { + public BlackboardScope blackboardScope; + public MergeMethod mergeMethod; + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/BlackboardEntityData.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Components/BlackboardEntityData.cs.meta new file mode 100644 index 0000000..a0fb1cd --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/BlackboardEntityData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2a1c6def24f38545ac83721fed8b9a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/IManagedComponent.cs b/Packages/com.latios.latios-framework/Core/Core/Components/IManagedComponent.cs new file mode 100644 index 0000000..d8cec22 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/IManagedComponent.cs @@ -0,0 +1,34 @@ +using System; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// A pseudo-component that can be attached to entities. + /// It does not allocate GC but can store managed references. + /// + public interface IManagedComponent + { + Type AssociatedComponentType { get; } + } + + /// + /// A Pseduo-component that can be attached to entities. + /// It can store NativeContainers and automatically tracks their dependencies. + /// + public interface ICollectionComponent + { + JobHandle Dispose(JobHandle inputDeps); + Type AssociatedComponentType { get; } + } + + //public struct ManagedComponentTag : IComponentData where T : struct, IManagedComponent { } + + internal struct ManagedComponentSystemStateTag : ISystemStateComponentData where T : struct, IManagedComponent { } + + //public struct CollectionComponentTag : IComponentData where T : struct, ICollectionComponent { } + + internal struct CollectionComponentSystemStateTag : ISystemStateComponentData where T : struct, ICollectionComponent { } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/IManagedComponent.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Components/IManagedComponent.cs.meta new file mode 100644 index 0000000..0d06241 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/IManagedComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24b4211f45c7d8342b26627ac018402b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/SceneComponents.cs b/Packages/com.latios.latios-framework/Core/Core/Components/SceneComponents.cs new file mode 100644 index 0000000..7130a8f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/SceneComponents.cs @@ -0,0 +1,39 @@ +using System; +using Unity.Collections; +using Unity.Entities; + +//Todo: Async scene requests +namespace Latios +{ + /// + /// Prevents the entity this is attached to from being destroyed on scene change. + /// It has no effect if the SceneManager is not installed (it is not by default). + /// + public struct DontDestroyOnSceneChangeTag : IComponentData { } + + /// + /// Unlike Unity.Entities.RequestSceneLoaded, this requests a true scene (not a subscene) to be loaded synchronously. + /// Entities without the DontDestroyOnSceneChangeTag or the WorldGlobalTag will be deleted. + /// It has no effect if the SceneManager is not installed (it is not by default). + /// + public struct RequestLoadScene : IComponentData + { + public FixedString128Bytes newScene; + } + + /// + /// A component attached to the worldBlackboardEntity. It provides info about the current scene. + /// It is not added if the SceneManager is not installed (it is not by default). + /// + public struct CurrentScene : IComponentData + { + internal FixedString128Bytes currentScene; + internal FixedString128Bytes previousScene; + internal bool isSceneFirstFrame; + + public FixedString128Bytes current => currentScene; + public FixedString128Bytes previous => previousScene; + public bool isFirstFrame => isSceneFirstFrame; + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/SceneComponents.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Components/SceneComponents.cs.meta new file mode 100644 index 0000000..e76df8a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/SceneComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: edc600a10bedb1b49a3bb53bbb547208 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/TransformComponents.cs b/Packages/com.latios.latios-framework/Core/Core/Components/TransformComponents.cs new file mode 100644 index 0000000..ce8e38e --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/TransformComponents.cs @@ -0,0 +1,22 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios +{ + #region Extreme Hierarchy + [Serializable] + internal struct Depth : IComponentData + { + public byte depth; + } + + [Serializable] + internal struct ChunkDepthMask : IComponentData + { + public BitField32 chunkDepthMask; + } + #endregion +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Components/TransformComponents.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Components/TransformComponents.cs.meta new file mode 100644 index 0000000..2d64689 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Components/TransformComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c14b89cb0175f3d47b8b0ecd43abd50b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers.meta b/Packages/com.latios.latios-framework/Core/Core/Containers.meta new file mode 100644 index 0000000..2efe2e9 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 06b1446b65db9cd42b8814a057d05f92 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/DestroyCommandBuffer.cs b/Packages/com.latios.latios-framework/Core/Core/Containers/DestroyCommandBuffer.cs new file mode 100644 index 0000000..35b17ac --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/DestroyCommandBuffer.cs @@ -0,0 +1,162 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// A specialized variant of the EntityCommandBuffer exclusively for destroying entities. + /// Destroyed entities automatically account for LinkedEntityGroup at the time of playback. + /// + public unsafe struct DestroyCommandBuffer : INativeDisposable + { + #region Structure + private EntityOperationCommandBuffer m_entityOperationCommandBuffer; + private NativeReference m_playedBack; + #endregion + + #region CreateDestroy + /// + /// Create an DestroyCommandBuffer which can be used to destroy entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public DestroyCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + m_entityOperationCommandBuffer = new EntityOperationCommandBuffer(allocator); + m_playedBack = new NativeReference(allocator); + } + + /// + /// Disposes the DestroyCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this DestroyCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + var jh0 = m_entityOperationCommandBuffer.Dispose(inputDeps); + var jh1 = m_playedBack.Dispose(inputDeps); + return JobHandle.CombineDependencies(jh0, jh1); + } + + /// + /// Disposes the DestroyCommandBuffer + /// + public void Dispose() + { + m_entityOperationCommandBuffer.Dispose(); + m_playedBack.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the DestroyCommandBuffer which should be destroyed + /// + /// The entity to be destroyed, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, int sortKey = int.MaxValue) + { + CheckDidNotPlayback(); + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + + /// + /// Plays back the DestroyCommandBuffer. + /// + /// The EntityManager with which to play back the DestroyCommandBuffer + /// A ReadOnly accessor to the entities' LinkedEntityGroup + public void Playback(EntityManager entityManager) + { + CheckDidNotPlayback(); + bool ran = false; + NativeList entities = default; + RunPrepInJob(ref ran, ref entities); + if (ran) + { + entityManager.DestroyEntity(entities); + entities.Dispose(); + } + else + { + entityManager.DestroyEntity(m_entityOperationCommandBuffer.GetEntities(Allocator.Temp)); + } + m_playedBack.Value = true; + } + + /// + /// Get the number of entities stored in this DestroyCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this DestroyCommandBuffer + public int Count() => m_entityOperationCommandBuffer.Count(); + + /// + /// Gets the ParallelWriter for this DestroyCommandBuffer. + /// + /// The ParallelWriter which shares this DestroyCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + CheckDidNotPlayback(); + return new ParallelWriter(m_entityOperationCommandBuffer); + } + #endregion + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckDidNotPlayback() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_playedBack.Value == true) + throw new System.InvalidOperationException("The DestroyCommandBuffer has already been played back. You cannot write more commands to it or play it back again."); +#endif + } + + #region PlaybackJobs + [BurstDiscard] + private unsafe void RunPrepInJob(ref bool ran, ref NativeList entities) + { + ran = true; + entities = new NativeList(0, Allocator.TempJob); + new PrepJob { eocb = m_entityOperationCommandBuffer, entities = entities }.Run(); + } + + [BurstCompile] + private struct PrepJob : IJob + { + [ReadOnly] public EntityOperationCommandBuffer eocb; + public NativeList entities; + + public void Execute() + { + eocb.GetEntities(ref entities); + } + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of DestroyCommandBuffer. Use AsParallelWriter to obtain one from an DestroyCommandBuffer + /// + public struct ParallelWriter + { + private EntityOperationCommandBuffer.ParallelWriter m_entityOperationCommandBuffer; + + internal ParallelWriter(EntityOperationCommandBuffer eocb) + { + m_entityOperationCommandBuffer = eocb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the DestroyCommandBuffer which should be destroyed + /// + /// The entity to be destroyed, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback + public void Add(Entity entity, int sortKey) + { + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/DestroyCommandBuffer.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Containers/DestroyCommandBuffer.cs.meta new file mode 100644 index 0000000..977493d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/DestroyCommandBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a17d15d28313a4488f67b46da9cf54e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/DisableCommandBuffer.cs b/Packages/com.latios.latios-framework/Core/Core/Containers/DisableCommandBuffer.cs new file mode 100644 index 0000000..9631c6c --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/DisableCommandBuffer.cs @@ -0,0 +1,163 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// A specialized variant of the EntityCommandBuffer exclusively for disabling entities. + /// Disabled entities automatically account for LinkedEntityGroup at the time of playback. + /// + public unsafe struct DisableCommandBuffer : INativeDisposable + { + #region Structure + private EntityOperationCommandBuffer m_entityOperationCommandBuffer; + private NativeReference m_playedBack; + #endregion + + #region CreateDestroy + /// + /// Create an DisableCommandBuffer which can be used to disable entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public DisableCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + m_entityOperationCommandBuffer = new EntityOperationCommandBuffer(allocator); + m_playedBack = new NativeReference(allocator); + } + + /// + /// Disposes the DisableCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this DisableCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + var jh0 = m_entityOperationCommandBuffer.Dispose(inputDeps); + var jh1 = m_playedBack.Dispose(inputDeps); + return JobHandle.CombineDependencies(jh0, jh1); + } + + /// + /// Disposes the DisableCommandBuffer + /// + public void Dispose() + { + m_entityOperationCommandBuffer.Dispose(); + m_playedBack.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the DisableCommandBuffer which should be disabled + /// + /// The entity to be disabled, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, int sortKey = int.MaxValue) + { + CheckDidNotPlayback(); + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + + /// + /// Plays back the DisableCommandBuffer. + /// + /// The EntityManager with which to play back the DisableCommandBuffer + /// A ReadOnly accessor to the entities' LinkedEntityGroup + public void Playback(EntityManager entityManager, BufferFromEntity linkedFEReadOnly) + { + CheckDidNotPlayback(); + bool ran = false; + NativeList entities = default; + RunPrepInJob(linkedFEReadOnly, ref ran, ref entities); + if (ran) + { + entityManager.RemoveComponent(entities); + entities.Dispose(); + } + else + { + entityManager.RemoveComponent(m_entityOperationCommandBuffer.GetLinkedEntities(linkedFEReadOnly, Allocator.Temp)); + } + m_playedBack.Value = true; + } + + /// + /// Get the number of entities stored in this DisableCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this DisableCommandBuffer + public int Count() => m_entityOperationCommandBuffer.Count(); + + /// + /// Gets the ParallelWriter for this DisableCommandBuffer. + /// + /// The ParallelWriter which shares this DisableCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + CheckDidNotPlayback(); + return new ParallelWriter(m_entityOperationCommandBuffer); + } + #endregion + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckDidNotPlayback() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_playedBack.Value == true) + throw new System.InvalidOperationException("The DisableCommandBuffer has already been played back. You cannot write more commands to it or play it back again."); +#endif + } + + #region PlaybackJobs + [BurstDiscard] + private void RunPrepInJob(BufferFromEntity linkedFE, ref bool ran, ref NativeList entities) + { + ran = true; + entities = new NativeList(0, Allocator.TempJob); + new PrepJob { linkedFE = linkedFE, eocb = m_entityOperationCommandBuffer, entities = entities }.Run(); + } + + [BurstCompile] + private struct PrepJob : IJob + { + [ReadOnly] public BufferFromEntity linkedFE; + [ReadOnly] public EntityOperationCommandBuffer eocb; + public NativeList entities; + + public void Execute() + { + eocb.GetLinkedEntities(linkedFE, ref entities); + } + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of DisableCommandBuffer. Use AsParallelWriter to obtain one from an DisableCommandBuffer + /// + public struct ParallelWriter + { + private EntityOperationCommandBuffer.ParallelWriter m_entityOperationCommandBuffer; + + internal ParallelWriter(EntityOperationCommandBuffer eocb) + { + m_entityOperationCommandBuffer = eocb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the DisableCommandBuffer which should be disabled + /// + /// The entity to be disabled, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback + public void Add(Entity entity, int sortKey = int.MaxValue) + { + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/DisableCommandBuffer.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Containers/DisableCommandBuffer.cs.meta new file mode 100644 index 0000000..be4c10c --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/DisableCommandBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc504606e2082614f9b4b6f1dbb8334e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/EnableCommandBuffer.cs b/Packages/com.latios.latios-framework/Core/Core/Containers/EnableCommandBuffer.cs new file mode 100644 index 0000000..1ea93fe --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/EnableCommandBuffer.cs @@ -0,0 +1,163 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// A specialized variant of the EntityCommandBuffer exclusively for enabling entities. + /// Enabled entities automatically account for LinkedEntityGroup at the time of playback. + /// + public unsafe struct EnableCommandBuffer : INativeDisposable + { + #region Structure + private EntityOperationCommandBuffer m_entityOperationCommandBuffer; + private NativeReference m_playedBack; + #endregion + + #region CreateDestroy + /// + /// Create an EnableCommandBuffer which can be used to enable entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public EnableCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + m_entityOperationCommandBuffer = new EntityOperationCommandBuffer(allocator); + m_playedBack = new NativeReference(allocator); + } + + /// + /// Disposes the EnableCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this EnableCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + var jh0 = m_entityOperationCommandBuffer.Dispose(inputDeps); + var jh1 = m_playedBack.Dispose(inputDeps); + return JobHandle.CombineDependencies(jh0, jh1); + } + + /// + /// Disposes the EnableCommandBuffer + /// + public void Dispose() + { + m_entityOperationCommandBuffer.Dispose(); + m_playedBack.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the EnableCommandBuffer which should be enabled + /// + /// The entity to be enabled, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, int sortKey = int.MaxValue) + { + CheckDidNotPlayback(); + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + + /// + /// Plays back the EnableCommandBuffer. + /// + /// The EntityManager with which to play back the EnableCommandBuffer + /// A ReadOnly accessor to the entities' LinkedEntityGroup + public void Playback(EntityManager entityManager, BufferFromEntity linkedFEReadOnly) + { + CheckDidNotPlayback(); + bool ran = false; + NativeList entities = default; + RunPrepInJob(linkedFEReadOnly, ref ran, ref entities); + if (ran) + { + entityManager.RemoveComponent(entities); + entities.Dispose(); + } + else + { + entityManager.RemoveComponent(m_entityOperationCommandBuffer.GetLinkedEntities(linkedFEReadOnly, Allocator.Temp)); + } + m_playedBack.Value = true; + } + + /// + /// Get the number of entities stored in this EnableCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this EnableCommandBuffer + public int Count() => m_entityOperationCommandBuffer.Count(); + + /// + /// Gets the ParallelWriter for this EnableCommandBuffer. + /// + /// The ParallelWriter which shares this EnableCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + CheckDidNotPlayback(); + return new ParallelWriter(m_entityOperationCommandBuffer); + } + #endregion + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckDidNotPlayback() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_playedBack.Value == true) + throw new System.InvalidOperationException("The EnableCommandBuffer has already been played back. You cannot write more commands to it or play it back again."); +#endif + } + + #region PlaybackJobs + [BurstDiscard] + private void RunPrepInJob(BufferFromEntity linkedFE, ref bool ran, ref NativeList entities) + { + ran = true; + entities = new NativeList(0, Allocator.TempJob); + new PrepJob { linkedFE = linkedFE, eocb = m_entityOperationCommandBuffer, entities = entities }.Run(); + } + + [BurstCompile] + private struct PrepJob : IJob + { + [ReadOnly] public BufferFromEntity linkedFE; + [ReadOnly] public EntityOperationCommandBuffer eocb; + public NativeList entities; + + public void Execute() + { + eocb.GetLinkedEntities(linkedFE, ref entities); + } + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of EnableCommandBuffer. Use AsParallelWriter to obtain one from an EnableCommandBuffer + /// + public struct ParallelWriter + { + private EntityOperationCommandBuffer.ParallelWriter m_entityOperationCommandBuffer; + + internal ParallelWriter(EntityOperationCommandBuffer eocb) + { + m_entityOperationCommandBuffer = eocb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the EnableCommandBuffer which should be enabled + /// + /// The entity to be enabled, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback + public void Add(Entity entity, int sortKey = int.MaxValue) + { + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/EnableCommandBuffer.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Containers/EnableCommandBuffer.cs.meta new file mode 100644 index 0000000..998a472 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/EnableCommandBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5388bf7af7573ce45885b7e3999ce5d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/EntityOperationCommandBuffer.cs b/Packages/com.latios.latios-framework/Core/Core/Containers/EntityOperationCommandBuffer.cs new file mode 100644 index 0000000..7f3e304 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/EntityOperationCommandBuffer.cs @@ -0,0 +1,463 @@ +using System.Diagnostics; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios +{ + /// + /// This is a special container for writing a list of entities in parallel and retrieving a deterministic list of entities on a single thread. + /// This container is used internally by several of the specialized ECB variants. However, you can also use it directly to create your own operations. + /// + [NativeContainer] + public unsafe struct EntityOperationCommandBuffer : INativeDisposable + { + #region Structure + [NativeDisableUnsafePtrRestriction] + private UnsafeParallelBlockList* m_blockList; + + [NativeDisableUnsafePtrRestriction] + private State* m_state; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + //Unfortunately this name is hardcoded into Unity. No idea how EntityCommandBuffer gets away with multiple safety handles. + AtomicSafetyHandle m_Safety; + + static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); + + [NativeSetClassTypeToNullOnSchedule] + //Unfortunately this name is hardcoded into Unity. + DisposeSentinel m_DisposeSentinel; +#endif + + private struct State + { + public AllocatorManager.AllocatorHandle allocator; + } + + private struct EntityWithOperation : IRadixSortableInt, IRadixSortableInt3 + { + public Entity entity; + public int sortKey; + + public int GetKey() => sortKey; + + public int3 GetKey3() + { + return new int3(entity.Index, entity.Version, sortKey); + } + } + #endregion + + #region CreateDestroy + /// + /// Create an EntityOperationCommandBuffer which can be used to write entities from multiple threads and retrieve them in a deterministic order. + /// + /// The type of allocator to use for allocating the buffer + public EntityOperationCommandBuffer(AllocatorManager.AllocatorHandle allocator) : this(allocator, 1) + { + } + + internal EntityOperationCommandBuffer(AllocatorManager.AllocatorHandle allocator, int disposeSentinalStackDepth) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckAllocator(allocator); + + if (allocator.IsCustomAllocator) + { + m_Safety = AtomicSafetyHandle.Create(); + m_DisposeSentinel = null; + } + else + { + DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 2, allocator.ToAllocator); + } + + CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); + AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); +#endif + + m_blockList = AllocatorManager.Allocate(allocator, 1); + m_state = AllocatorManager.Allocate(allocator, 1); + *m_blockList = new UnsafeParallelBlockList(UnsafeUtility.SizeOf(), 256, allocator); + *m_state = new State + { + allocator = allocator, + }; + } + + [BurstCompile] + private struct DisposeJob : IJob + { + [NativeDisableUnsafePtrRestriction] + public State* state; + + [NativeDisableUnsafePtrRestriction] + public UnsafeParallelBlockList* blockList; + + public void Execute() + { + Deallocate(state, blockList); + } + } + + /// + /// Disposes the EntityOperationCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this EnableCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // [DeallocateOnJobCompletion] is not supported, but we want the deallocation + // to happen in a thread. DisposeSentinel needs to be cleared on main thread. + // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling + // will check that no jobs are writing to the container). + DisposeSentinel.Clear(ref m_DisposeSentinel); +#endif + var jobHandle = new DisposeJob { blockList = m_blockList, state = m_state }.Schedule(inputDeps); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.Release(m_Safety); +#endif + return jobHandle; + } + + /// + /// Disposes the EntityOperationCommandBuffer + /// + public void Dispose() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); +#endif + Deallocate(m_state, m_blockList); + } + + private static void Deallocate(State* state, UnsafeParallelBlockList* blockList) + { + var allocator = state->allocator; + blockList->Dispose(); + AllocatorManager.Free(allocator, blockList, 1); + AllocatorManager.Free(allocator, state, 1); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the EntityOperationCommandBuffer which should be operated on + /// + /// The entity to be operated on, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback if interleaving single and parallel writes + [WriteAccessRequired] + public void Add(Entity entity, int sortKey = int.MaxValue) + { + CheckWriteAccess(); + m_blockList->Write(new EntityWithOperation { entity = entity, sortKey = sortKey }, 0); + } + + /// + /// Get the number of entities stored in this EntityOperationCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this EntityOperationCommandBuffer + public int Count() + { + CheckReadAccess(); + return m_blockList->Count(); + } + + /// + /// Returns an array of entities stored in the EntityOperationCommandBuffer ordered by SortKey. + /// + /// The allocator to use for the returned NativeArray + /// The array of entities stored in the EntityOperationCommandBuffer ordered by SortKey + public NativeArray GetEntities(Allocator allocator) + { + CheckReadAccess(); + int count = m_blockList->Count(); + var entities = new NativeArray(count, allocator, NativeArrayOptions.UninitializedMemory); + + GetEntities(entities); + + return entities; + } + + /// + /// Returns an array of entities stored in the EntityOperationCommandBuffer ordered by Entity and then by SortKey. + /// + /// The allocator to use for the returned NativeArray + /// The array of entities stored in the EntityOperationCommandBuffer ordered by Entity and then by SortKey + public NativeArray GetEntitiesSortedByEntity(Allocator allocator) + { + CheckReadAccess(); + int count = m_blockList->Count(); + var entities = new NativeArray(count, allocator, NativeArrayOptions.UninitializedMemory); + + GetEntitiesSorted(entities); + + return entities; + } + + /// + /// Fills the native list with entities stored in the EntityOperationCommandBuffer sorted by SortKey + /// + /// The list to fill. The list will automatically be resized to fit the new entities. + /// If true, entities will be appended. If false, the list will be overwritten. + public void GetEntities(ref NativeList entities, bool append = false) + { + CheckReadAccess(); + int count = m_blockList->Count(); + + if (append) + { + int originalLength = entities.Length; + entities.ResizeUninitialized(originalLength + count); + var subArray = entities.AsArray().GetSubArray(originalLength, count); + GetEntities(subArray); + } + else + { + entities.ResizeUninitialized(count); + GetEntities(entities); + } + } + + /// + /// Returns an array of entities stored in the EntityOperationCommandBuffer ordered by SortKey and their LinkedEntityGroup entities if they have them. + /// + /// A ReadOnly accessor to the Entities' LinkedEntityGroup + /// The allocator to use for the returned NativeArray + /// The array of entities stored in the EntityOperationCommandBuffer ordered by SortKey and their LinkedEntityGroup entities if they have them. + public NativeArray GetLinkedEntities(BufferFromEntity linkedFEReadOnly, Allocator allocator) + { + GetLinkedEntitiesInternal(linkedFEReadOnly, out _, Allocator.Temp, out var entities, allocator); + return entities; + } + + /// + /// Returns an array of entities stored in the EntityOperationCommandBuffer ordered by SortKey and their LinkedEntityGroup entities if they have them. + /// This override also returns the root entities stored in the EntityOperationCommandBuffer as a separate array. + /// + /// A ReadOnly accessor to the Entities' LinkedEntityGroup + /// The allocator to use for the returned NativeArray + /// An array of entities in the EntityOperationCommandBuffer excluding their LinkedEntityGroup entities + /// The array of entities stored in the EntityOperationCommandBuffer ordered by SortKey and their LinkedEntityGroup entities if they have them. + public NativeArray GetLinkedEntities(BufferFromEntity linkedFEReadOnly, Allocator allocator, out NativeArray roots) + { + CheckReadAccess(); + GetLinkedEntitiesInternal(linkedFEReadOnly, out roots, allocator, out var entities, allocator); + return entities; + } + + /// + /// Fills the native list with entities stored in the EntityOperationCommandBuffer sorted by SortKey and their LinkedEntityGroup entities if they have them. + /// + /// A ReadOnly accessor to the Entities' LinkedEntityGroup + /// The list to fill. The list will automatically be resized to fit the new entities. + /// If true, entities will be appended. If false, the list will be overwritten. + public void GetLinkedEntities(BufferFromEntity linkedFEReadOnly, ref NativeList entities, bool append = false) + { + CheckReadAccess(); + var roots = GetEntities(Allocator.Temp); + int count = GetLinkedEntitiesCount(linkedFEReadOnly, roots); + if (append) + { + int originalLength = entities.Length; + entities.ResizeUninitialized(originalLength + count); + var subArray = entities.AsArray().GetSubArray(originalLength, count); + GetLinkedEntitiesFill(linkedFEReadOnly, roots, subArray); + } + else + { + entities.ResizeUninitialized(count); + GetLinkedEntitiesFill(linkedFEReadOnly, roots, entities); + } + } + + public ParallelWriter AsParallelWriter() + { + var writer = new ParallelWriter(m_blockList, m_state); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + writer.m_Safety = m_Safety; + CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data); +#endif + return writer; + } + #endregion + + #region Implementations + private void GetEntities(NativeArray entities) + { + var tempEntitiesWithOperation = new NativeArray(entities.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var ranks = new NativeArray(entities.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + m_blockList->GetElementValues(tempEntitiesWithOperation); + + //Radix sort + RadixSort.RankSortInt(ranks, tempEntitiesWithOperation); + + //Copy results + for (int i = 0; i < ranks.Length; i++) + { + entities[i] = tempEntitiesWithOperation[ranks[i]].entity; + } + } + + private void GetEntitiesSorted(NativeArray entities) + { + var tempEntitiesWithOperation = new NativeArray(entities.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var ranks = new NativeArray(entities.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + m_blockList->GetElementValues(tempEntitiesWithOperation); + + //Radix sort + RadixSort.RankSortInt3(ranks, tempEntitiesWithOperation); + + //Copy results + for (int i = 0; i < ranks.Length; i++) + { + entities[i] = tempEntitiesWithOperation[ranks[i]].entity; + } + } + + private int GetLinkedEntitiesCount(BufferFromEntity linkedFE, NativeArray roots) + { + int count = 0; + for (int i = 0; i < roots.Length; i++) + { + if (linkedFE.HasComponent(roots[i])) + { + count += linkedFE[roots[i]].Length; + } + else + { + count++; + } + } + return count; + } + + private void GetLinkedEntitiesFill(BufferFromEntity linkedFE, NativeArray roots, NativeArray entities) + { + int count = 0; + for (int i = 0; i < roots.Length; i++) + { + if (linkedFE.HasComponent(roots[i])) + { + var currentGroup = linkedFE[roots[i]]; + NativeArray.Copy(currentGroup.AsNativeArray().Reinterpret(), 0, entities, count, currentGroup.Length); + count += currentGroup.Length; + } + else + { + entities[count] = roots[i]; + count++; + } + } + } + + private void GetLinkedEntitiesInternal(BufferFromEntity linkedFE, + out NativeArray roots, + Allocator rootsAllocator, + out NativeArray linkedEntities, + Allocator linkedAllocator) + { + CheckReadAccess(); + roots = GetEntities(rootsAllocator); + int count = GetLinkedEntitiesCount(linkedFE, roots); + linkedEntities = new NativeArray(count, linkedAllocator, NativeArrayOptions.UninitializedMemory); + GetLinkedEntitiesFill(linkedFE, roots, linkedEntities); + } + #endregion + + #region Checks + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckWriteAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckReadAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckAllocator(AllocatorManager.AllocatorHandle allocator) + { + if (allocator.ToAllocator <= Allocator.None) +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new System.InvalidOperationException("Allocator cannot be Invalid or None"); +#endif + } + #endregion + + #region ParallelWriter + /// + /// Implements ParallelWriter of the EntityOperationCommandBuffer. Use AsParallelWriter to obtain one from the EntityOperationCommandBuffer. + /// + [NativeContainer] + [NativeContainerIsAtomicWriteOnly] + public struct ParallelWriter + { + [NativeDisableUnsafePtrRestriction] + private UnsafeParallelBlockList* m_blockList; + + [NativeDisableUnsafePtrRestriction] + private State* m_state; + + [NativeSetThreadIndex] + private int m_ThreadIndex; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + //More ugly Unity naming + internal AtomicSafetyHandle m_Safety; + internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); +#endif + + internal ParallelWriter(UnsafeParallelBlockList* blockList, void* state) + { + m_blockList = blockList; + m_state = (State*)state; + m_ThreadIndex = 0; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Safety = default; +#endif + } + + /// + /// Adds an Entity to the EntityOperationCommandBuffer which should be operated on + /// + /// The entity to be operated on, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback + public void Add(Entity entity, int sortKey) + { + CheckWriteAccess(); + m_blockList->Write(new EntityWithOperation { entity = entity, sortKey = sortKey }, m_ThreadIndex); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckWriteAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); +#endif + } + } + #endregion + } +} + + + + + diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/EntityOperationCommandBuffer.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Containers/EntityOperationCommandBuffer.cs.meta new file mode 100644 index 0000000..31dfae5 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/EntityOperationCommandBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c7e45e676849f44c9deb6c54456257c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/InstantiateCommandBuffer.cs b/Packages/com.latios.latios-framework/Core/Core/Containers/InstantiateCommandBuffer.cs new file mode 100644 index 0000000..2784698 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/InstantiateCommandBuffer.cs @@ -0,0 +1,865 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// A specialized variant of the EntityCommandBuffer exclusively for instantiating entities. + /// This variant does not perform any additional initialization after instantiation. + /// + public struct InstantiateCommandBuffer : INativeDisposable + { + #region Structure + private EntityOperationCommandBuffer m_entityOperationCommandBuffer; + private NativeReference m_playedBack; + #endregion + + #region CreateDestroy + /// + /// Create an InstantiateCommandBuffer which can be used to instantiate entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public InstantiateCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + m_entityOperationCommandBuffer = new EntityOperationCommandBuffer(allocator); + m_playedBack = new NativeReference(allocator); + } + + /// + /// Disposes the InstantiateCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this InstantiateCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + var jh0 = m_entityOperationCommandBuffer.Dispose(inputDeps); + var jh1 = m_playedBack.Dispose(inputDeps); + return JobHandle.CombineDependencies(jh0, jh1); + } + + /// + /// Disposes the InstantiateCommandBuffer + /// + public void Dispose() + { + m_entityOperationCommandBuffer.Dispose(); + m_playedBack.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, int sortKey = int.MaxValue) + { + CheckDidNotPlayback(); + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + + /// + /// Plays back the InstantiateCommandBuffer. + /// + /// The EntityManager with which to play back the InstantiateCommandBuffer + public void Playback(EntityManager entityManager) + { + CheckDidNotPlayback(); + var eet = entityManager.BeginExclusiveEntityTransaction(); + new PlaybackJob { eocb = m_entityOperationCommandBuffer, eet = eet }.Run(); + eet.EntityManager.EndExclusiveEntityTransaction(); + m_playedBack.Value = true; + } + + /// + /// Get the number of entities stored in this InstantiateCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this InstantiateCommandBuffer + public int Count() => m_entityOperationCommandBuffer.Count(); + + /// + /// Gets the ParallelWriter for this InstantiateCommandBuffer. + /// + /// The ParallelWriter which shares this InstantiateCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + CheckDidNotPlayback(); + return new ParallelWriter(m_entityOperationCommandBuffer); + } + #endregion + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckDidNotPlayback() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_playedBack.Value == true) + throw new System.InvalidOperationException("The InstantiateCommandBuffer has already been played back. You cannot write more commands to it or play it back again."); +#endif + } + + #region PlaybackJobs + [BurstCompile] + private struct PlaybackJob : IJob + { + [ReadOnly] public EntityOperationCommandBuffer eocb; + public ExclusiveEntityTransaction eet; + + public void Execute() + { + var prefabs = eocb.GetEntitiesSortedByEntity(Allocator.Temp); + int i = 0; + while (i < prefabs.Length) + { + var prefab = prefabs[i]; + i++; + int count = 1; + while (i < prefabs.Length && prefab == prefabs[i]) + { + i++; + count++; + } + eet.EntityManager.Instantiate(prefab, count, Allocator.Temp); + } + } + + public void RunOrExecute() + { + bool ran = false; + TryRun(ref ran); + if (!ran) + Execute(); + } + + [BurstDiscard] + void TryRun(ref bool ran) + { + this.Run(); + ran = true; + } + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of InstantiateCommandBuffer. Use AsParallelWriter to obtain one from an InstantiateCommandBuffer + /// + public struct ParallelWriter + { + private EntityOperationCommandBuffer.ParallelWriter m_entityOperationCommandBuffer; + + internal ParallelWriter(EntityOperationCommandBuffer eocb) + { + m_entityOperationCommandBuffer = eocb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The sort key for deterministic playback + public void Add(Entity entity, int sortKey) + { + m_entityOperationCommandBuffer.Add(entity, sortKey); + } + } + #endregion + } + + /// + /// A specialized variant of the EntityCommandBuffer exclusively for instantiating entities. + /// This variant initializes the specified component type with specified values per instance. + /// + public struct InstantiateCommandBuffer : INativeDisposable where T0 : unmanaged, IComponentData + { + #region Structure + internal InstantiateCommandBufferUntyped m_instantiateCommandBufferUntyped { get; private set; } + #endregion + + #region CreateDestroy + /// + /// Create an InstantiateCommandBuffer which can be used to instantiate entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public InstantiateCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + FixedList64Bytes types = new FixedList64Bytes(); + types.Add(ComponentType.ReadWrite()); + m_instantiateCommandBufferUntyped = new InstantiateCommandBufferUntyped(allocator, types); + } + + /// + /// Disposes the InstantiateCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this InstantiateCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + return m_instantiateCommandBufferUntyped.Dispose(inputDeps); + } + + /// + /// Disposes the InstantiateCommandBuffer + /// + public void Dispose() + { + m_instantiateCommandBufferUntyped.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, T0 c0, int sortKey = int.MaxValue) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, sortKey); + } + + /// + /// Plays back the InstantiateCommandBuffer. + /// + /// The EntityManager with which to play back the InstantiateCommandBuffer + public void Playback(EntityManager entityManager) + { + m_instantiateCommandBufferUntyped.Playback(entityManager); + } + + /// + /// Get the number of entities stored in this InstantiateCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this InstantiateCommandBuffer + public int Count() => m_instantiateCommandBufferUntyped.Count(); + + /// + /// Set additional component types to be added to the instantiated entities. These components will be default-initialized. + /// + /// The types to add to each instantiated entity + public void SetComponentTags(ComponentTypes tags) + { + m_instantiateCommandBufferUntyped.SetTags(tags); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag(ComponentType tag) + { + m_instantiateCommandBufferUntyped.AddTag(tag); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag() where T : struct, IComponentData + { + AddComponentTag(ComponentType.ReadOnly()); + } + + /// + /// Gets the ParallelWriter for this InstantiateCommandBuffer. + /// + /// The ParallelWriter which shares this InstantiateCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + return new ParallelWriter(m_instantiateCommandBufferUntyped); + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of InstantiateCommandBuffer. Use AsParallelWriter to obtain one from an InstantiateCommandBuffer + /// + public struct ParallelWriter + { + private InstantiateCommandBufferUntyped.ParallelWriter m_instantiateCommandBufferUntyped; + + internal ParallelWriter(InstantiateCommandBufferUntyped eocb) + { + m_instantiateCommandBufferUntyped = eocb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The sort key for deterministic playback + public void Add(Entity entity, T0 c0, int sortKey) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, sortKey); + } + } + #endregion + } + + /// + /// A specialized variant of the EntityCommandBuffer exclusively for instantiating entities. + /// This variant initializes both the specified component types with specified values per instance. + /// + public struct InstantiateCommandBuffer : INativeDisposable where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData + { + #region Structure + internal InstantiateCommandBufferUntyped m_instantiateCommandBufferUntyped { get; private set; } + #endregion + + #region CreateDestroy + /// + /// Create an InstantiateCommandBuffer which can be used to instantiate entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public InstantiateCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + FixedList64Bytes types = new FixedList64Bytes(); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + m_instantiateCommandBufferUntyped = new InstantiateCommandBufferUntyped(allocator, types); + } + + /// + /// Disposes the InstantiateCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this InstantiateCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + return m_instantiateCommandBufferUntyped.Dispose(inputDeps); + } + + /// + /// Disposes the InstantiateCommandBuffer + /// + public void Dispose() + { + m_instantiateCommandBufferUntyped.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, T0 c0, T1 c1, int sortKey = int.MaxValue) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, sortKey); + } + + /// + /// Plays back the InstantiateCommandBuffer. + /// + /// The EntityManager with which to play back the InstantiateCommandBuffer + public void Playback(EntityManager entityManager) + { + m_instantiateCommandBufferUntyped.Playback(entityManager); + } + + /// + /// Get the number of entities stored in this InstantiateCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this InstantiateCommandBuffer + public int Count() => m_instantiateCommandBufferUntyped.Count(); + + /// + /// Set additional component types to be added to the instantiated entities. These components will be default-initialized. + /// + /// The types to add to each instantiated entity + public void SetComponentTags(ComponentTypes tags) + { + m_instantiateCommandBufferUntyped.SetTags(tags); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag(ComponentType tag) + { + m_instantiateCommandBufferUntyped.AddTag(tag); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag() where T : struct, IComponentData + { + AddComponentTag(ComponentType.ReadOnly()); + } + + /// + /// Gets the ParallelWriter for this InstantiateCommandBuffer. + /// + /// The ParallelWriter which shares this InstantiateCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + return new ParallelWriter(m_instantiateCommandBufferUntyped); + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of InstantiateCommandBuffer. Use AsParallelWriter to obtain one from an InstantiateCommandBuffer + /// + public struct ParallelWriter + { + private InstantiateCommandBufferUntyped.ParallelWriter m_instantiateCommandBufferUntyped; + + internal ParallelWriter(InstantiateCommandBufferUntyped icb) + { + m_instantiateCommandBufferUntyped = icb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The sort key for deterministic playback + public void Add(Entity entity, T0 c0, T1 c1, int sortKey) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, sortKey); + } + } + #endregion + } + + /// + /// A specialized variant of the EntityCommandBuffer exclusively for instantiating entities. + /// This variant initializes the three specified component types with specified values per instance. + /// + public struct InstantiateCommandBuffer : INativeDisposable where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, + IComponentData + { + #region Structure + internal InstantiateCommandBufferUntyped m_instantiateCommandBufferUntyped { get; private set; } + #endregion + + #region CreateDestroy + /// + /// Create an InstantiateCommandBuffer which can be used to instantiate entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public InstantiateCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + FixedList64Bytes types = new FixedList64Bytes(); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + m_instantiateCommandBufferUntyped = new InstantiateCommandBufferUntyped(allocator, types); + } + + /// + /// Disposes the InstantiateCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this InstantiateCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + return m_instantiateCommandBufferUntyped.Dispose(inputDeps); + } + + /// + /// Disposes the InstantiateCommandBuffer + /// + public void Dispose() + { + m_instantiateCommandBufferUntyped.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The third component value to initialize for the instantiated entity + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, T0 c0, T1 c1, T2 c2, int sortKey = int.MaxValue) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, c2, sortKey); + } + + /// + /// Plays back the InstantiateCommandBuffer. + /// + /// The EntityManager with which to play back the InstantiateCommandBuffer + public void Playback(EntityManager entityManager) + { + m_instantiateCommandBufferUntyped.Playback(entityManager); + } + + /// + /// Get the number of entities stored in this InstantiateCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this InstantiateCommandBuffer + public int Count() => m_instantiateCommandBufferUntyped.Count(); + + /// + /// Set additional component types to be added to the instantiated entities. These components will be default-initialized. + /// + /// The types to add to each instantiated entity + public void SetComponentTags(ComponentTypes tags) + { + m_instantiateCommandBufferUntyped.SetTags(tags); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag(ComponentType tag) + { + m_instantiateCommandBufferUntyped.AddTag(tag); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag() where T : struct, IComponentData + { + AddComponentTag(ComponentType.ReadOnly()); + } + + /// + /// Gets the ParallelWriter for this InstantiateCommandBuffer. + /// + /// The ParallelWriter which shares this InstantiateCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + return new ParallelWriter(m_instantiateCommandBufferUntyped); + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of InstantiateCommandBuffer. Use AsParallelWriter to obtain one from an InstantiateCommandBuffer + /// + public struct ParallelWriter + { + private InstantiateCommandBufferUntyped.ParallelWriter m_instantiateCommandBufferUntyped; + + internal ParallelWriter(InstantiateCommandBufferUntyped icb) + { + m_instantiateCommandBufferUntyped = icb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The third component value to initialize for the instantiated entity + /// The sort key for deterministic playback + public void Add(Entity entity, T0 c0, T1 c1, T2 c2, int sortKey) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, c2, sortKey); + } + } + #endregion + } + + /// + /// A specialized variant of the EntityCommandBuffer exclusively for instantiating entities. + /// This variant initializes the four specified component types with specified values per instance. + /// + public struct InstantiateCommandBuffer : INativeDisposable where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, + IComponentData where T3 : unmanaged, IComponentData + { + #region Structure + internal InstantiateCommandBufferUntyped m_instantiateCommandBufferUntyped { get; private set; } + #endregion + + #region CreateDestroy + /// + /// Create an InstantiateCommandBuffer which can be used to instantiate entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public InstantiateCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + FixedList64Bytes types = new FixedList64Bytes(); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + m_instantiateCommandBufferUntyped = new InstantiateCommandBufferUntyped(allocator, types); + } + + /// + /// Disposes the InstantiateCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this InstantiateCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + return m_instantiateCommandBufferUntyped.Dispose(inputDeps); + } + + /// + /// Disposes the InstantiateCommandBuffer + /// + public void Dispose() + { + m_instantiateCommandBufferUntyped.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The third component value to initialize for the instantiated entity + /// The fourth component value to initialize for the instantiated entity + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, T0 c0, T1 c1, T2 c2, T3 c3, int sortKey = int.MaxValue) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, c2, c3, sortKey); + } + + /// + /// Plays back the InstantiateCommandBuffer. + /// + /// The EntityManager with which to play back the InstantiateCommandBuffer + public void Playback(EntityManager entityManager) + { + m_instantiateCommandBufferUntyped.Playback(entityManager); + } + + /// + /// Get the number of entities stored in this InstantiateCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this InstantiateCommandBuffer + public int Count() => m_instantiateCommandBufferUntyped.Count(); + + /// + /// Set additional component types to be added to the instantiated entities. These components will be default-initialized. + /// + /// The types to add to each instantiated entity + public void SetComponentTags(ComponentTypes tags) + { + m_instantiateCommandBufferUntyped.SetTags(tags); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag(ComponentType tag) + { + m_instantiateCommandBufferUntyped.AddTag(tag); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag() where T : struct, IComponentData + { + AddComponentTag(ComponentType.ReadOnly()); + } + + /// + /// Gets the ParallelWriter for this InstantiateCommandBuffer. + /// + /// The ParallelWriter which shares this InstantiateCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + return new ParallelWriter(m_instantiateCommandBufferUntyped); + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of InstantiateCommandBuffer. Use AsParallelWriter to obtain one from an InstantiateCommandBuffer + /// + public struct ParallelWriter + { + private InstantiateCommandBufferUntyped.ParallelWriter m_instantiateCommandBufferUntyped; + + internal ParallelWriter(InstantiateCommandBufferUntyped icb) + { + m_instantiateCommandBufferUntyped = icb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The third component value to initialize for the instantiated entity + /// The fourth component value to initialize for the instantiated entity + /// The sort key for deterministic playback + public void Add(Entity entity, T0 c0, T1 c1, T2 c2, T3 c3, int sortKey) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, c2, c3, sortKey); + } + } + #endregion + } + + /// + /// A specialized variant of the EntityCommandBuffer exclusively for instantiating entities. + /// This variant initializes the four specified component types with specified values per instance. + /// + public struct InstantiateCommandBuffer : INativeDisposable where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, + IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData + { + #region Structure + internal InstantiateCommandBufferUntyped m_instantiateCommandBufferUntyped { get; private set; } + #endregion + + #region CreateDestroy + /// + /// Create an InstantiateCommandBuffer which can be used to instantiate entities and play them back later. + /// + /// The type of allocator to use for allocating the buffer + public InstantiateCommandBuffer(AllocatorManager.AllocatorHandle allocator) + { + FixedList64Bytes types = new FixedList64Bytes(); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + types.Add(ComponentType.ReadWrite()); + m_instantiateCommandBufferUntyped = new InstantiateCommandBufferUntyped(allocator, types); + } + + /// + /// Disposes the InstantiateCommandBuffer after the jobs which use it have finished. + /// + /// The JobHandle for any jobs previously using this InstantiateCommandBuffer + /// + public JobHandle Dispose(JobHandle inputDeps) + { + return m_instantiateCommandBufferUntyped.Dispose(inputDeps); + } + + /// + /// Disposes the InstantiateCommandBuffer + /// + public void Dispose() + { + m_instantiateCommandBufferUntyped.Dispose(); + } + #endregion + + #region PublicAPI + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The third component value to initialize for the instantiated entity + /// The fourth component value to initialize for the instantiated entity + /// The fifth component value to initialize for the instantiated entity + /// The sort key for deterministic playback if interleaving single and parallel writes + public void Add(Entity entity, T0 c0, T1 c1, T2 c2, T3 c3, T4 c4, int sortKey = int.MaxValue) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, c2, c3, c4, sortKey); + } + + /// + /// Plays back the InstantiateCommandBuffer. + /// + /// The EntityManager with which to play back the InstantiateCommandBuffer + public void Playback(EntityManager entityManager) + { + m_instantiateCommandBufferUntyped.Playback(entityManager); + } + + /// + /// Get the number of entities stored in this InstantiateCommandBuffer. This method performs a summing operation on every invocation. + /// + /// The number of elements stored in this InstantiateCommandBuffer + public int Count() => m_instantiateCommandBufferUntyped.Count(); + + /// + /// Set additional component types to be added to the instantiated entities. These components will be default-initialized. + /// + /// The types to add to each instantiated entity + public void SetComponentTags(ComponentTypes tags) + { + m_instantiateCommandBufferUntyped.SetTags(tags); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag(ComponentType tag) + { + m_instantiateCommandBufferUntyped.AddTag(tag); + } + + /// + /// Adds an additional component type to the list of types to add to the instantiated entities. This type will be default-initialized. + /// + /// The type to add to each instantiated entity + public void AddComponentTag() where T : struct, IComponentData + { + AddComponentTag(ComponentType.ReadOnly()); + } + + /// + /// Gets the ParallelWriter for this InstantiateCommandBuffer. + /// + /// The ParallelWriter which shares this InstantiateCommandBuffer's backing storage. + public ParallelWriter AsParallelWriter() + { + return new ParallelWriter(m_instantiateCommandBufferUntyped); + } + #endregion + + #region ParallelWriter + /// + /// The parallelWriter implementation of InstantiateCommandBuffer. Use AsParallelWriter to obtain one from an InstantiateCommandBuffer + /// + public struct ParallelWriter + { + private InstantiateCommandBufferUntyped.ParallelWriter m_instantiateCommandBufferUntyped; + + internal ParallelWriter(InstantiateCommandBufferUntyped icb) + { + m_instantiateCommandBufferUntyped = icb.AsParallelWriter(); + } + + /// + /// Adds an Entity to the InstantiateCommandBuffer which should be instantiated + /// + /// The entity to be instantiated, including its LinkedEntityGroup at the time of playback if it has one + /// The first component value to initialize for the instantiated entity + /// The second component value to initialize for the instantiated entity + /// The third component value to initialize for the instantiated entity + /// The fourth component value to initialize for the instantiated entity + /// The fifth component value to initialize for the instantiated entity + /// The sort key for deterministic playback + public void Add(Entity entity, T0 c0, T1 c1, T2 c2, T3 c3, T4 c4, int sortKey) + { + m_instantiateCommandBufferUntyped.Add(entity, c0, c1, c2, c3, c4, sortKey); + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/InstantiateCommandBuffer.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Containers/InstantiateCommandBuffer.cs.meta new file mode 100644 index 0000000..d11da84 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/InstantiateCommandBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5c356fa86adec54aaf19d85da8fb073 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/UnsafeParallelBlockList.cs b/Packages/com.latios.latios-framework/Core/Core/Containers/UnsafeParallelBlockList.cs new file mode 100644 index 0000000..0152c00 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/UnsafeParallelBlockList.cs @@ -0,0 +1,382 @@ +using System.Runtime.InteropServices; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Latios.Unsafe +{ + /// + /// An unsafe container which can be written to by multiple threads and multiple jobs. + /// The container is lock-free and items never change addresses once written. + /// Items written in the same thread in the same job will be read back in the same order. + /// This container is type-erased, but all elements are expected to be of the same size. + /// Unlike Unity's Unsafe* containers, it is safe to copy this type by value. + /// Alignment is 1 byte aligned so any pointer must use UnsafeUtility.CopyStructureToPtr + /// and UnsafeUtility.CopyPtrToStructure when operating directly on pointers. + /// + public unsafe struct UnsafeParallelBlockList : INativeDisposable + { + public readonly int m_elementSize; + private readonly int m_blockSize; + private readonly int m_elementsPerBlock; + private AllocatorManager.AllocatorHandle m_allocator; + + [NativeDisableUnsafePtrRestriction] private PerThreadBlockList* m_perThreadBlockLists; + + /// + /// Construct a new UnsafeParallelBlockList using a UnityEngine allocator + /// + /// The size of each element in bytes that will be stored + /// + /// The number of elements stored per native thread index before needing to perform an additional allocation. + /// Higher values may allocate more memory that is left unused. Lower values may perform more allocations. + /// + /// The allocator to use for allocations + public UnsafeParallelBlockList(int elementSize, int elementsPerBlock, AllocatorManager.AllocatorHandle allocator) + { + m_elementSize = elementSize; + m_elementsPerBlock = elementsPerBlock; + m_blockSize = elementSize * elementsPerBlock; + m_allocator = allocator; + + m_perThreadBlockLists = AllocatorManager.Allocate(allocator, JobsUtility.MaxJobThreadCount); + for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++) + { + m_perThreadBlockLists[i].lastByteAddressInBlock = null; + m_perThreadBlockLists[i].nextWriteAddress = null; + m_perThreadBlockLists[i].nextWriteAddress++; + m_perThreadBlockLists[i].elementCount = 0; + } + } + + //The thread index is passed in because otherwise job reflection can't inject it through a pointer. + /// + /// Write an element for a given thread index + /// + /// It is assumed the size of T is the same as what was passed into elementSize during construction + /// The value to write + /// The thread index to use when writing. This should come from [NativeSetThreadIndex]. + public void Write(T value, int threadIndex) where T : unmanaged + { + var blockList = m_perThreadBlockLists + threadIndex; + if (blockList->nextWriteAddress > blockList->lastByteAddressInBlock) + { + if (blockList->elementCount == 0) + { + blockList->blocks = new UnsafeList(8, m_allocator); + } + BlockPtr newBlockPtr = new BlockPtr + { + ptr = AllocatorManager.Allocate(m_allocator, m_blockSize) + }; + blockList->nextWriteAddress = newBlockPtr.ptr; + blockList->lastByteAddressInBlock = newBlockPtr.ptr + m_blockSize - 1; + blockList->blocks.Add(newBlockPtr); + } + + UnsafeUtility.CopyStructureToPtr(ref value, blockList->nextWriteAddress); + blockList->nextWriteAddress += m_elementSize; + blockList->elementCount++; + } + + /// + /// Reserve memory for an element and return the fixed memory address. + /// + /// The thread index to use when allocating. This should come from [NativeSetThreadIndex]. + /// A pointer where an element can be copied to + public void* Allocate(int threadIndex) + { + var blockList = m_perThreadBlockLists + threadIndex; + if (blockList->nextWriteAddress > blockList->lastByteAddressInBlock) + { + if (blockList->elementCount == 0) + { + blockList->blocks = new UnsafeList(8, m_allocator); + } + BlockPtr newBlockPtr = new BlockPtr + { + ptr = AllocatorManager.Allocate(m_allocator, m_blockSize), + }; + blockList->nextWriteAddress = newBlockPtr.ptr; + blockList->lastByteAddressInBlock = newBlockPtr.ptr + m_blockSize - 1; + blockList->blocks.Add(newBlockPtr); + } + + var result = blockList->nextWriteAddress; + blockList->nextWriteAddress += m_elementSize; + blockList->elementCount++; + return result; + } + + /// + /// Count the number of elements. Do this once and cache the result. + /// + /// The number of elements stored + public int Count() + { + int result = 0; + for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++) + { + result += m_perThreadBlockLists[i].elementCount; + } + return result; + } + + /// + /// A pointer to an element stored + /// + public struct ElementPtr + { + public byte* ptr; + } + + /// + /// Gets all the pointers for all elements stored. + /// This does not actually traverse the memory but instead calculates memory addresses from metadata, + /// which is often faster, especially for large elements. + /// + /// An array in which the pointers should be stored. Its Length should be equal to Count(). + public void GetElementPtrs(NativeArray ptrs) + { + int dst = 0; + + for (int threadBlockId = 0; threadBlockId < JobsUtility.MaxJobThreadCount; threadBlockId++) + { + var blockList = m_perThreadBlockLists + threadBlockId; + if (blockList->elementCount > 0) + { + int src = 0; + for (int blockId = 0; blockId < blockList->blocks.Length - 1; blockId++) + { + var address = blockList->blocks[blockId].ptr; + for (int i = 0; i < m_elementsPerBlock; i++) + { + ptrs[dst] = new ElementPtr { ptr = address }; + address += m_elementSize; + src++; + dst++; + } + } + { + var address = blockList->blocks[blockList->blocks.Length - 1].ptr; + for (int i = src; i < blockList->elementCount; i++) + { + ptrs[dst] = new ElementPtr { ptr = address }; + address += m_elementSize; + dst++; + } + } + } + } + } + + /// + /// Copies all the elements stored into values. + /// + /// It is assumed the size of T is the same as what was passed into elementSize during construction + /// An array where the elements should be copied to. Its Length should be equal to Count(). + [Unity.Burst.CompilerServices.IgnoreWarning(1371)] + public void GetElementValues(NativeArray values) where T : struct + { + int dst = 0; + + for (int threadBlockId = 0; threadBlockId < JobsUtility.MaxJobThreadCount; threadBlockId++) + { + var blockList = m_perThreadBlockLists + threadBlockId; + if (blockList->elementCount > 0) + { + int src = 0; + CheckBlockCountMatchesCount(blockList->elementCount, blockList->blocks.Length); + for (int blockId = 0; blockId < blockList->blocks.Length - 1; blockId++) + { + var address = blockList->blocks[blockId].ptr; + for (int i = 0; i < m_elementsPerBlock; i++) + { + UnsafeUtility.CopyPtrToStructure(address, out T temp); + values[dst] = temp; + address += m_elementSize; + src++; + dst++; + } + } + { + var address = blockList->blocks[blockList->blocks.Length - 1].ptr; + for (int i = src; i < blockList->elementCount; i++) + { + UnsafeUtility.CopyPtrToStructure(address, out T temp); + values[dst] = temp; + address += m_elementSize; + dst++; + } + } + } + } + } + + //This catches race conditions if I accidentally pass in 0 for thread index in the parallel writer because copy and paste. + [BurstDiscard] + void CheckBlockCountMatchesCount(int count, int blockCount) + { + int expectedBlocks = count / m_elementsPerBlock; + if (count % m_elementsPerBlock > 0) + expectedBlocks++; + if (blockCount != expectedBlocks) + throw new System.InvalidOperationException($"Block count: {blockCount} does not match element count: {count}"); + } + + [BurstCompile] + struct DisposeJob : IJob + { + public UnsafeParallelBlockList upbl; + + public void Execute() + { + upbl.Dispose(); + } + } + + /// + /// Uses a job to dispose this container + /// + /// A JobHandle for all jobs which should finish before disposal. + /// A JobHandle for the disposal job. + public JobHandle Dispose(JobHandle inputDeps) + { + var jh = new DisposeJob { upbl = this }.Schedule(inputDeps); + m_perThreadBlockLists = null; + return jh; + } + + /// + /// Disposes the container immediately. It is legal to call this from within a job, + /// as long as no other jobs or threads are using it. + /// + public void Dispose() + { + for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++) + { + if (m_perThreadBlockLists[i].elementCount > 0) + { + for (int j = 0; j < m_perThreadBlockLists[i].blocks.Length; j++) + { + var block = m_perThreadBlockLists[i].blocks[j]; + AllocatorManager.Free(m_allocator, block.ptr, m_blockSize); + } + m_perThreadBlockLists[i].blocks.Dispose(); + } + } + AllocatorManager.Free(m_allocator, m_perThreadBlockLists, JobsUtility.MaxJobThreadCount); + } + + private struct BlockPtr + { + public byte* ptr; + } + + [StructLayout(LayoutKind.Sequential, Size = 64)] + private struct PerThreadBlockList + { + public UnsafeList blocks; + public byte* nextWriteAddress; + public byte* lastByteAddressInBlock; + public int elementCount; + } + + /// + /// Gets an enumerator for one of the thread indices in the job. + /// + /// + /// The thread index that was used when the elements were written. + /// This does not have to be the thread index of the reader. + /// In fact, you usually want to iterate through all threads. + /// + /// + public Enumerator GetEnumerator(int nativeThreadIndex) + { + return new Enumerator(m_perThreadBlockLists + nativeThreadIndex, m_elementSize, m_elementsPerBlock); + } + + /// + /// An enumerator which can be used for iterating over the elements written by a single thread index. + /// It is allowed to have multiple enumerators for the same thread index. + /// + public struct Enumerator + { + private PerThreadBlockList* m_perThreadBlockList; + private byte* m_readAddress; + private byte* m_lastByteAddressInBlock; + private int m_blockIndex; + private int m_elementSize; + private int m_elementsPerBlock; + + internal Enumerator(void* perThreadBlockList, int elementSize, int elementsPerBlock) + { + m_perThreadBlockList = (PerThreadBlockList*)perThreadBlockList; + m_readAddress = null; + m_readAddress++; + m_lastByteAddressInBlock = null; + m_blockIndex = -1; + m_elementSize = elementSize; + m_elementsPerBlock = elementsPerBlock; + } + + /// + /// Advance to the next element + /// + /// Returns false if the previous element was the last, true otherwise + public bool MoveNext() + { + m_readAddress += m_elementSize; + if (m_readAddress > m_lastByteAddressInBlock) + { + m_blockIndex++; + if (m_blockIndex >= m_perThreadBlockList->blocks.Length) + return false; + + int elementsInBlock = math.min(m_elementsPerBlock, m_perThreadBlockList->elementCount - m_blockIndex * m_elementsPerBlock); + var blocks = m_perThreadBlockList->blocks.Ptr; + m_lastByteAddressInBlock = blocks[m_blockIndex].ptr + elementsInBlock * m_elementSize - 1; + m_readAddress = blocks[m_blockIndex].ptr; + } + return true; + } + + /// + /// Retrieves the current element, copying it to a variable of the specified type. + /// + /// It is assumed the size of T is the same as what was passed into elementSize during construction + /// A value containing a copy of the element stored, reinterpreted with the strong type + public T GetCurrent() where T : struct + { + UnsafeUtility.CopyPtrToStructure(m_readAddress, out T t); + return t; + } + } + } + + // Schedule for 128 iterations + //[BurstCompile] + //struct ExampleReadJob : IJobFor + //{ + // [NativeDisableUnsafePtrRestriction] public UnsafeParallelBlockList blockList; + // + // public void Execute(int index) + // { + // var enumerator = blockList.GetEnumerator(index); + // + // while (enumerator.MoveNext()) + // { + // int number = enumerator.GetCurrent(); + // + // if (number == 381) + // UnityEngine.Debug.Log("You found me!"); + // else if (number == 380) + // UnityEngine.Debug.Log("Where?"); + // } + // } + //} +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Containers/UnsafeParallelBlockList.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Containers/UnsafeParallelBlockList.cs.meta new file mode 100644 index 0000000..55f1e36 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Containers/UnsafeParallelBlockList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb387e3dced4fed44af1f5b57dbc905a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework.meta b/Packages/com.latios.latios-framework/Core/Core/Framework.meta new file mode 100644 index 0000000..394c50f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c07d04c5a1683304a9f7072dc5638239 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/BlackboardEntity.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/BlackboardEntity.cs new file mode 100644 index 0000000..28f6dce --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/BlackboardEntity.cs @@ -0,0 +1,132 @@ +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// An entity and its associated EntityManager, which provides shorthands for manipulating the entity's components + /// + public struct BlackboardEntity + { + private Entity entity; + private EntityManager em; + + /// + /// Create a blackboard entity + /// + /// The existing entity to use + /// The entity's associated EntityManager + public BlackboardEntity(Entity entity, EntityManager entityManager) + { + this.entity = entity; + em = entityManager; + } + + /// + /// Implicitly fetch the entity of the BlackboardEntity + /// + public static implicit operator Entity(BlackboardEntity entity) + { + return entity.entity; + } + + public bool AddComponent() where T : struct, IComponentData + { + return em.AddComponent(entity); + } + + public bool AddComponentData(T data) where T : struct, IComponentData + { + return em.AddComponentData(entity, data); + } + + public bool AddComponentDataIfMissing(T data) where T : struct, IComponentData + { + if (em.HasComponent(entity)) + return false; + em.AddComponentData(entity, data); + return true; + } + + public void SetComponentData(T data) where T : struct, IComponentData + { + em.SetComponentData(entity, data); + } + + public T GetComponentData() where T : struct, IComponentData + { + return em.GetComponentData(entity); + } + + public bool HasComponent() where T : struct, IComponentData + { + return em.HasComponent(entity); + } + + public bool HasComponent(ComponentType componentType) + { + return em.HasComponent(entity, componentType); + } + + public bool AddSharedComponentData(T data) where T : struct, ISharedComponentData + { + return em.AddSharedComponentData(entity, data); + } + + public void SetSharedComponentData(T data) where T : struct, ISharedComponentData + { + em.SetSharedComponentData(entity, data); + } + + public T GetSharedComponentData() where T : struct, ISharedComponentData + { + return em.GetSharedComponentData(entity); + } + + public DynamicBuffer AddBuffer() where T : struct, IBufferElementData + { + return em.AddBuffer(entity); + } + + public DynamicBuffer GetBuffer(bool readOnly = false) where T : struct, IBufferElementData + { + return em.GetBuffer(entity, readOnly); + } + + public void AddCollectionComponent(T value, bool isInitialized = true) where T : struct, ICollectionComponent + { + em.AddCollectionComponent(entity, value, isInitialized); + } + + public T GetCollectionComponent(bool readOnly, out JobHandle handle) where T : struct, ICollectionComponent + { + return em.GetCollectionComponent(entity, readOnly, out handle); + } + + public T GetCollectionComponent(bool readOnly = false) where T : struct, ICollectionComponent + { + return em.GetCollectionComponent(entity, readOnly); + } + + public bool HasCollectionComponent() where T : struct, ICollectionComponent + { + return em.HasCollectionComponent(entity); + } + + public void RemoveCollectionComponentAndDispose() where T : struct, ICollectionComponent + { + em.RemoveCollectionComponentAndDispose(entity); + } + + public void SetCollectionComponentAndDisposeOld(T value) where T : struct, ICollectionComponent + { + em.SetCollectionComponentAndDisposeOld(entity, value); + } + + public void UpdateJobDependency(JobHandle handle, bool wasReadOnly) where T : struct, ICollectionComponent + { + em.UpdateCollectionComponentDependency(entity, handle, wasReadOnly); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/BlackboardEntity.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/BlackboardEntity.cs.meta new file mode 100644 index 0000000..61417ce --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/BlackboardEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47891b38cee465f43b4a89587b974332 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/BootstrapTools.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/BootstrapTools.cs new file mode 100644 index 0000000..63d7a47 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/BootstrapTools.cs @@ -0,0 +1,547 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using Debug = UnityEngine.Debug; +using Unity.Entities; +using UnityEngine.LowLevel; +using UnityEngine.PlayerLoop; + +using Latios.Systems; +using Unity.Collections; +using Unity.Entities.Exposed; + +namespace Latios +{ + /// + /// Add this attribute to a system to prevent the system from being injected into the default group. + /// Only works when using an injection method in BootstrapTools. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public class NoGroupInjectionAttribute : Attribute + { + } + + public static class BootstrapTools + { + #region SystemManipulation + /// + /// Injects all systems in which contain 'namespaceSubstring within their namespaces. + /// Automatically creates parent ComponentSystemGroups if necessary. + /// Use this for libraries which use traditional injection to inject into the default world. + /// + /// List of systems containing the namespaced systems to inject using world.GetOrCreateSystem + /// The namespace substring to query the systems' namespace against + /// The world to inject the systems into + /// If no UpdateInGroupAttributes exist on the type and this value is not null, the system is injected in this group + public static void InjectSystemsFromNamespace(List systems, string namespaceSubstring, World world, ComponentSystemGroup defaultGroup = null) + { + foreach (var type in systems) + { + if (type.Namespace == null) + { + Debug.LogWarning("No namespace for " + type.ToString()); + continue; + } + else if (!type.Namespace.Contains(namespaceSubstring)) + continue; + + InjectSystem(type, world, defaultGroup); + } + } + + /// + /// Injects all systems made by Unity (or systems that use "Unity" in their namespace or assembly). + /// Automatically creates parent ComponentSystemGroups if necessary. + /// Use this instead of InjectSystemsFromNamespace because Unity sometimes forgets to put namespaces on things. + /// + /// + /// + /// + public static void InjectUnitySystems(List systems, World world, ComponentSystemGroup defaultGroup = null, bool silenceWarnings = true) + { + var sysList = new List(); + foreach (var type in systems) + { + if (type.Namespace == null) + { + if (type.Assembly.FullName.Contains("Unity") && !silenceWarnings) + { + Debug.LogWarning("Hey Unity Devs! You forget a namespace for " + type.ToString()); + } + else + continue; + } + else if (!type.Namespace.Contains("Unity")) + continue; + + sysList.Add(type); + } + + InjectSystems(sysList, world, defaultGroup); + } + + /// + /// Injects all RootSuperSystems in the systems list into the world. + /// Automatically creates parent ComponentSystemGroups if necessary. + /// + /// List of systems containing the RootSuperSystems to inject using world.GetOrCreateSystem + /// The world to inject the systems into + /// If no UpdateInGroupAttributes exist on the type and this value is not null, the system is injected in this group + public static void InjectRootSuperSystems(List systems, World world, ComponentSystemGroup defaultGroup = null) + { + foreach (var type in systems) + { + if (typeof(RootSuperSystem).IsAssignableFrom(type)) + InjectSystem(type, world, defaultGroup); + } + } + + public struct ComponentSystemBaseSystemHandleUntypedUnion + { + public ComponentSystemBase systemManaged; + public SystemHandleUntyped systemHandle; + + public static implicit operator ComponentSystemBase(ComponentSystemBaseSystemHandleUntypedUnion me) => me.systemManaged; + public static implicit operator SystemHandleUntyped(ComponentSystemBaseSystemHandleUntypedUnion me) => me.systemHandle; + } + + //Copied and pasted from Entities package and then modified as needed. + /// + /// Injects the system into the world. Automatically creates parent ComponentSystemGroups if necessary. + /// + /// This function does nothing for unmanaged systems. + /// The type to inject. Uses world.GetOrCreateSystem + /// The world to inject the system into + /// If no UpdateInGroupAttributes exist on the type and this value is not null, the system is injected in this group + /// If a type in an UpdateInGroupAttribute matches a key in this dictionary, it will be swapped with the value + public static ComponentSystemBaseSystemHandleUntypedUnion InjectSystem(Type type, + World world, + ComponentSystemGroup defaultGroup = null, + IReadOnlyDictionary groupRemap = null) + { + bool isManaged = false; + if (typeof(ComponentSystemBase).IsAssignableFrom(type)) + { + isManaged = true; + } + else if (!typeof(ISystem).IsAssignableFrom(type)) + { + return default; + } + + var groups = TypeManager.GetSystemAttributes(type, typeof(UpdateInGroupAttribute)); + + ComponentSystemBaseSystemHandleUntypedUnion newSystem = default; + if (isManaged) + { + newSystem.systemManaged = world.GetOrCreateSystem(type); + newSystem.systemHandle = newSystem.systemManaged.SystemHandleUntyped; + } + else + { + newSystem.systemHandle = world.GetOrCreateUnmanagedSystem(type); + } + if (groups.Length == 0 && defaultGroup != null) + { + if (isManaged) + defaultGroup.AddSystemToUpdateList(newSystem); + else + defaultGroup.AddUnmanagedSystemToUpdateList(newSystem); + } + foreach (var g in groups) + { + if (TypeManager.GetSystemAttributes(newSystem.GetType(), typeof(NoGroupInjectionAttribute)).Length > 0) + break; + + var group = FindOrCreateGroup(world, type, g, defaultGroup, groupRemap); + if (group != null) + { + if (isManaged) + group.AddSystemToUpdateList(newSystem); + else + group.AddUnmanagedSystemToUpdateList(newSystem); + } + } + return newSystem; + } + + //Copied and pasted from Entities package and then modified as needed. + /// + /// Injects the systems into the world. Automatically creates parent ComponentSystemGroups if necessary. + /// GetExistingSystem is valid in OnCreate for all systems within types as well as previously added systems. + /// + /// This function does nothing for unmanaged systems. + /// The types to inject. + /// The world to inject the system into + /// If no UpdateInGroupAttributes exist on the type and this value is not null, the system is injected in this group + /// If a type in an UpdateInGroupAttribute matches a key in this dictionary, it will be swapped with the value + public static ComponentSystemBaseSystemHandleUntypedUnion[] InjectSystems(IReadOnlyList types, + World world, + ComponentSystemGroup defaultGroup = null, + IReadOnlyDictionary groupRemap = null) + { + var managedTypes = new List(); + var unmanagedTypes = new List(); + + foreach (var stype in types) + { + if (typeof(ComponentSystemBase).IsAssignableFrom(stype)) + managedTypes.Add(stype); + else if (typeof(ISystem).IsAssignableFrom(stype)) + unmanagedTypes.Add(stype); + else + throw new InvalidOperationException("Bad type"); + } + + var systems = world.GetOrCreateSystemsAndLogException(managedTypes.ToArray()); + + // Add systems to their groups, based on the [UpdateInGroup] attribute. + foreach (var system in systems) + { + if (system == null) + continue; + + // Skip the built-in root-level system groups + var type = system.GetType(); + + var noUpdateInGroupAttributes = TypeManager.GetSystemAttributes(system.GetType(), typeof(NoGroupInjectionAttribute)); + if (noUpdateInGroupAttributes.Length > 0) + continue; + + var updateInGroupAttributes = TypeManager.GetSystemAttributes(system.GetType(), typeof(UpdateInGroupAttribute)); + if (updateInGroupAttributes.Length == 0) + { + defaultGroup.AddSystemToUpdateList(system); + } + + foreach (var attr in updateInGroupAttributes) + { + var group = FindOrCreateGroup(world, type, attr, defaultGroup, groupRemap); + if (group != null) + { + group.AddSystemToUpdateList(system); + } + } + } + + // Create unmanaged systems in batch + NativeArray handles = world.CreateUnmanagedSystems(unmanagedTypes, Allocator.Temp); + + // Add systems to their groups, based on the [UpdateInGroup] attribute. + for (int i = 0; i < unmanagedTypes.Count; ++i) + { + var type = unmanagedTypes[i]; + SystemHandleUntyped sysHandle = handles[i]; + + var noUpdateInGroupAttributes = TypeManager.GetSystemAttributes(type, typeof(NoGroupInjectionAttribute)); + if (noUpdateInGroupAttributes.Length > 0) + continue; + + var updateInGroupAttributes = TypeManager.GetSystemAttributes(type, typeof(UpdateInGroupAttribute)); + if (updateInGroupAttributes.Length == 0) + { + defaultGroup.AddUnmanagedSystemToUpdateList(sysHandle); + } + + foreach (var attr in updateInGroupAttributes) + { + ComponentSystemGroup groupSys = FindOrCreateGroup(world, type, attr, defaultGroup, groupRemap); + + if (groupSys != null) + { + groupSys.AddUnmanagedSystemToUpdateList(sysHandle); + } + } + } + + var result = new ComponentSystemBaseSystemHandleUntypedUnion[systems.Length + handles.Length]; + for (int i = 0; i < systems.Length; i++) + { + result[i] = new ComponentSystemBaseSystemHandleUntypedUnion + { + systemManaged = systems[i], + systemHandle = systems[i].SystemHandleUntyped + }; + } + int b = systems.Length; + for (int i = 0; i < handles.Length; i++) + { + result[b + i] = new ComponentSystemBaseSystemHandleUntypedUnion + { + systemHandle = handles[i], + systemManaged = null + }; + } + + handles.Dispose(); + return result; + } + + private static ComponentSystemGroup FindOrCreateGroup(World world, Type systemType, Attribute attr, ComponentSystemGroup defaultGroup, IReadOnlyDictionary remap) + { + var uga = attr as UpdateInGroupAttribute; + + if (uga == null) + return null; + + var groupType = uga.GroupType; + if (remap != null && remap.TryGetValue(uga.GroupType, out var remapType)) + groupType = remapType; + if (groupType == null) + return null; + + if (!TypeManager.IsSystemAGroup(groupType)) + { + throw new InvalidOperationException($"Invalid [UpdateInGroup] attribute for {systemType}: {uga.GroupType} must be derived from ComponentSystemGroup."); + } + if (uga.OrderFirst && uga.OrderLast) + { + throw new InvalidOperationException($"The system {systemType} can not specify both OrderFirst=true and OrderLast=true in its [UpdateInGroup] attribute."); + } + + var groupSys = world.GetExistingSystem(groupType); + if (groupSys == null) + { + groupSys = InjectSystem(groupType, world, defaultGroup, remap); + } + + return groupSys as ComponentSystemGroup; + } + + /// + /// Builds a ComponentSystemGroup and auto-injects children systems in the list. + /// Systems without an UpdateInGroupAttribute are not added. + /// + /// + /// Systems that are allowed to be added to the Group hierarchy + /// The world in which the systems are built + /// + public static T BuildSystemGroup(List systems, World world) where T : ComponentSystemGroup + { + var groupsToCreate = new HashSet(); + var allGroups = new List(); + foreach (var system in systems) + { + if (IsGroup(system)) + allGroups.Add(system); + } + AddChildrenGroupsToHashsetRecursively(typeof(T), allGroups, groupsToCreate); + + var groupList = new List<(Type, ComponentSystemGroup)>(); + foreach (var system in groupsToCreate) + { + groupList.Add((system, world.CreateSystem(system) as ComponentSystemGroup)); + } + + foreach (var system in groupList) + { + foreach (var targetGroup in groupList) + { + if (IsInGroup(system.Item1, targetGroup.Item1)) + targetGroup.Item2.AddSystemToUpdateList(system.Item2); + } + } + + foreach (var system in systems) + { + if (IsGroup(system)) + continue; + if (!typeof(ComponentSystemBase).IsAssignableFrom(system)) + continue; + foreach (var targetGroup in groupList) + { + if (IsInGroup(system, targetGroup.Item1)) + targetGroup.Item2.AddSystemToUpdateList(world.GetOrCreateSystem(system)); + } + } + + foreach (var group in groupList) + { + if (group.Item1 == typeof(T)) + { + group.Item2.SortSystems(); + return group.Item2 as T; + } + } + + return null; + } + + /// + /// Is the system a type of ComponentSystemGroup? + /// + public static bool IsGroup(Type systemType) + { + return typeof(ComponentSystemGroup).IsAssignableFrom(systemType); + } + + /// + /// Does the system want to be injected in the group? + /// + /// The type of system to be injected + /// The type of group that would be specified in the UpdateInGroupAttribute + /// + public static bool IsInGroup(Type systemType, Type groupType) + { + var atts = systemType.GetCustomAttributes(typeof(UpdateInGroupAttribute), true); + foreach (var att in atts) + { + if (!(att is UpdateInGroupAttribute uig)) + continue; + if (uig.GroupType.IsAssignableFrom(groupType)) + { + return true; + } + } + return false; + } + + private static void AddChildrenGroupsToHashsetRecursively(Type startType, List componentSystemGroups, HashSet foundGroups) + { + if (!foundGroups.Contains(startType)) + { + foundGroups.Add(startType); + } + foreach (var system in componentSystemGroups) + { + if (!foundGroups.Contains(system) && IsInGroup(system, startType)) + AddChildrenGroupsToHashsetRecursively(system, componentSystemGroups, foundGroups); + } + } + #endregion + + #region PlayerLoop + + /// + /// Update the player loop with a world's root-level systems including FixedUpdate + /// + /// World with root-level systems that need insertion into the player loop + public static void AddWorldToCurrentPlayerLoopWithFixedUpdate(World world) + { + var playerLoop = PlayerLoop.GetCurrentPlayerLoop(); + + if (world != null) + { + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(Initialization)); + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(PostLateUpdate)); + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(PreLateUpdate)); + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(FixedUpdate)); + } + PlayerLoop.SetPlayerLoop(playerLoop); + } + + /// + /// Update the PlayerLoop to run simulation after rendering. + /// + /// World with root-level systems that need insertion into the player loop + public static void AddWorldToCurrentPlayerLoopWithDelayedSimulation(World world) + { + var playerLoop = PlayerLoop.GetCurrentPlayerLoop(); + + if (world != null) + { + InjectSystem(typeof(DeferredSimulationEndFrameControllerSystem), world); + + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(Initialization)); + // We add it here for visibility in tools. But really we don't update until EndOfFrame + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(PostLateUpdate)); + ScriptBehaviourUpdateOrder.AppendSystemToPlayerLoop(world.GetExistingSystem(), ref playerLoop, typeof(PreLateUpdate)); + } + PlayerLoop.SetPlayerLoop(playerLoop); + } + + #endregion + + #region TypeManager + private delegate void TmAddTypeInfoToTables(Type type, TypeManager.TypeInfo typeInfo, string name); + private delegate TypeManager.TypeInfo TmBuildComponentType(Type type); + + //Todo: Replace with codegen + public static void PopulateTypeManagerWithGenerics(Type genericWrapperIcdType, Type interfaceType) + { + if (!genericWrapperIcdType.IsValueType || !typeof(IComponentData).IsAssignableFrom(genericWrapperIcdType)) + throw new ArgumentException($"{genericWrapperIcdType} is not a valid struct IComponentData"); + + //Snag methods from TypeManager + //var tmAddTypeInfoToTables = GetStaticMethod("AddTypeInfoToTables", 2).CreateDelegate(typeof(TmAddTypeInfoToTables)) as TmAddTypeInfoToTables; + var tmAddTypeInfoToTables = GetStaticMethod("AddTypeInfoToTables", 3).CreateDelegate(typeof(TmAddTypeInfoToTables)) as TmAddTypeInfoToTables; + var tmBuildComponentType = GetStaticMethod("BuildComponentType", 1).CreateDelegate(typeof(TmBuildComponentType)) as TmBuildComponentType; + + //Create a hashset of all types so far so we don't dupe types. + HashSet typesHash = new HashSet(); + foreach (var t in TypeManager.GetAllTypes()) + { + typesHash.Add(t.Type); + } + + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + if (!IsAssemblyReferencingLatios(assembly)) + continue; + + foreach (var type in assembly.GetTypes()) + { + if (type.GetCustomAttribute(typeof(DisableAutoTypeRegistrationAttribute)) != null) + continue; + + if (type.IsInterface) + continue; + + if (interfaceType.IsAssignableFrom(type)) + { + if (!type.IsValueType) + throw new InvalidOperationException($"{type} implements {interfaceType} but is not a struct type"); + + var concrete = genericWrapperIcdType.MakeGenericType(type); + + if (typesHash.Contains(concrete)) + continue; + + var info = tmBuildComponentType(concrete); + tmAddTypeInfoToTables(concrete, info, concrete.FullName); + } + } + } + } + + private static MethodInfo GetMethod(string methodName, int numOfArgs) + { + var methods = typeof(TypeManager).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic); + foreach (var method in methods) + { + if (method.Name == methodName && method.GetParameters().Length == numOfArgs) + return method; + } + return null; + } + + private static MethodInfo GetStaticMethod(string methodName, int numOfArgs) + { + var methods = typeof(TypeManager).GetMethods(BindingFlags.Static | BindingFlags.NonPublic); + foreach (var method in methods) + { + if (method.Name == methodName && method.GetParameters().Length == numOfArgs) + return method; + } + return null; + } + + public static bool IsAssemblyReferencingLatios(Assembly assembly) + { + const string entitiesAssemblyName = "Latios"; + if (assembly.GetName().Name.Contains(entitiesAssemblyName)) + return true; + + var referencedAssemblies = assembly.GetReferencedAssemblies(); + foreach (var referenced in referencedAssemblies) + if (referenced.Name.Contains(entitiesAssemblyName)) + return true; + return false; + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/BootstrapTools.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/BootstrapTools.cs.meta new file mode 100644 index 0000000..fda3ebb --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/BootstrapTools.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b67ec745c443b741b2a20533f4572bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/CoreBootstrap.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/CoreBootstrap.cs new file mode 100644 index 0000000..c00940e --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/CoreBootstrap.cs @@ -0,0 +1,141 @@ +using Latios.Systems; +using Unity.Entities; +using Unity.Transforms; + +namespace Latios +{ + /// + /// Static class containing installers for optional runtime features in the Core module + /// + public static unsafe class CoreBootstrap + { + /// + /// Installs the Scene Management features into the World + /// + /// The World where systems should be installed. + public static void InstallSceneManager(World world) + { + if (world.Flags.HasFlag(WorldFlags.Conversion)) + throw new System.InvalidOperationException("Cannot install Scene Manager in a conversion world."); + + DisallowNetCode("Scene Manager"); + + BootstrapTools.InjectSystem(typeof(SceneManagerSystem), world); + BootstrapTools.InjectSystem(typeof(DestroyEntitiesOnSceneChangeSystem), world); + } + + /// + /// Installs Improved Transforms systems into the world and disables some existing transform systems. + /// Improved Transforms have been tested to be superior to Unity's transform systems in every way. + /// More code runs in Burst. More entities are culled with change filters. And fewer bugs exist. + /// Performance is always better, measured to a minimum improvement of 4%, often much higher. + /// + /// The World where systems should be installed. + public static void InstallImprovedTransforms(World world) + { + if (world.Flags.HasFlag(WorldFlags.Conversion)) + throw new System.InvalidOperationException("Cannot install Improved Transforms in a conversion world."); + + var unmanaged = world.Unmanaged; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + bool caughtException = false; + + try + { + unmanaged.GetExistingUnmanagedSystem(); + } + catch (System.InvalidOperationException) + { + // Failed to find the unmanaged system + caughtException = true; + } + + if (!caughtException) + throw new System.InvalidOperationException("Cannot install Improved Transforms when Extreme Transforms are already installed."); +#endif + + try + { + unmanaged.ResolveSystemState(unmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + } + catch (System.InvalidOperationException) + { + // Failed to find the unmanaged system + } + try + { + unmanaged.ResolveSystemState(unmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + } + catch (System.InvalidOperationException) + { + // Failed to find the unmanaged system + } + + BootstrapTools.InjectSystem(typeof(ImprovedParentSystem), world); + BootstrapTools.InjectSystem(typeof(ImprovedLocalToParentSystem), world); + } + + /// + /// Installs Extreme Transforms systems into the world and disables some existing transform systems. + /// Extreme Transforms is designed to make better use of L2 and L3 caches at extremely high entity counts. + /// It has a significant main thread cost and changes the archetypes of entities (though those changes are + /// batched with normal hierarchy archetype changes that Unity would normally make). + /// In extreme circumstances, it performs significantly better than Improved Transforms. But especially suffers + /// when the majority of entities undergo structural changes between updates. + /// + /// + public static void InstallExtremeTransforms(World world) + { + if (world.Flags.HasFlag(WorldFlags.Conversion)) + throw new System.InvalidOperationException("Cannot install Extreme Transforms in a conversion world."); + + var unmanaged = world.Unmanaged; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + bool caughtException = false; + + try + { + unmanaged.GetExistingUnmanagedSystem(); + } + catch (System.InvalidOperationException) + { + // Failed to find the unmanaged system + caughtException = true; + } + + if (!caughtException) + throw new System.InvalidOperationException("Cannot install Extreme Transforms when Improved Transforms are already installed"); +#endif + + try + { + unmanaged.ResolveSystemState(unmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + } + catch (System.InvalidOperationException) + { + // Failed to find the unmanaged system + } + try + { + unmanaged.ResolveSystemState(unmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + } + catch (System.InvalidOperationException) + { + // Failed to find the unmanaged system + } + + BootstrapTools.InjectSystem(typeof(ExtremeParentSystem), world); + BootstrapTools.InjectSystem(typeof(ExtremeChildDepthsSystem), world); + BootstrapTools.InjectSystem(typeof(ExtremeLocalToParentSystem), world); + } + + [System.Diagnostics.Conditional("NETCODE_PROJECT")] + private static void DisallowNetCode(string feature) + { + throw new System.InvalidOperationException($"{feature} cannot be used in a Unity NetCode project."); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/CoreBootstrap.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/CoreBootstrap.cs.meta new file mode 100644 index 0000000..df308d8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/CoreBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c31afcc1b16fda41925c6db1bf9c24e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/EntityDataCopyKit.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityDataCopyKit.cs new file mode 100644 index 0000000..d51cf89 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityDataCopyKit.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.Exposed; + +namespace Latios +{ + /// + /// A toolkit for copying components based on ComponentType. + /// + public unsafe class EntityDataCopyKit + { + private EntityManager m_em; + + private Dictionary m_typeTagsToTypesCache = new Dictionary(); + + public EntityDataCopyKit(EntityManager entityManager) + { + m_em = entityManager; + } + + /// + /// Copies the data stored in the componentType from the src entity to the dst entity.> + /// + /// The source entity + /// The destination entity + /// The type of data to be copied + /// Should collection components be shallow copied (true) or ignored (false)? + public void CopyData(Entity src, Entity dst, ComponentType componentType, bool copyCollections = false) + { + //Check to ensure dst has componentType + if (!m_em.HasComponent(dst, componentType)) + m_em.AddComponent(dst, componentType); + if (componentType.IsSharedComponent) + CopyScd(src, dst, componentType); + else if (componentType.IsBuffer) + CopyBuffer(src, dst, componentType); + else + { + if (copyCollections) + { + var type = componentType.GetManagedType(); + if (type.IsConstructedGenericType) + { + var genType = type.GetGenericTypeDefinition(); + if (genType == typeof(ManagedComponentSystemStateTag<>)) + { + if (!m_typeTagsToTypesCache.TryGetValue(type, out Type managedType)) + { + managedType = type.GenericTypeArguments[0]; + m_typeTagsToTypesCache[type] = managedType; + } + LatiosWorld lw = m_em.World as LatiosWorld; + lw.ManagedStructStorage.CopyComponent(src, dst, managedType); + } + else if (genType == typeof(CollectionComponentSystemStateTag<>)) + { + if (!m_typeTagsToTypesCache.TryGetValue(type, out Type managedType)) + { + managedType = type.GenericTypeArguments[0]; + m_typeTagsToTypesCache[type] = managedType; + } + LatiosWorld lw = m_em.World as LatiosWorld; + lw.CollectionComponentStorage.CopyComponent(src, dst, managedType); + } + } + } + CopyIcd(src, dst, componentType); + } + } + + private void CopyIcd(Entity src, Entity dst, ComponentType componentType) + { + m_em.CopyComponentData(src, dst, componentType); + } + + private void CopyScd(Entity src, Entity dst, ComponentType componentType) + { + m_em.CopySharedComponent(src, dst, componentType); + } + + private void CopyBuffer(Entity src, Entity dst, ComponentType componentType) + { + m_em.CopyDynamicBuffer(src, dst, componentType); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/EntityDataCopyKit.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityDataCopyKit.cs.meta new file mode 100644 index 0000000..3f9c936 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityDataCopyKit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3bd62473a5ffbc1448ab4ec7d3fcd5bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/EntityManagerCollectionsExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityManagerCollectionsExtensions.cs new file mode 100644 index 0000000..25edf81 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityManagerCollectionsExtensions.cs @@ -0,0 +1,245 @@ +using System; +using Unity.Entities; +using Unity.Jobs; +using UnityEngine.Profiling; + +namespace Latios +{ + public static class EntityManagerCollectionsExtensions + { + /// + /// Adds a managed component to the entity. This implicitly adds the managed component's AssociatedComponentType as well. + /// + /// The struct type implementing IManagedComponent + /// The entity to add the managed component to + /// The data for the managed component + public static void AddManagedComponent(this EntityManager em, Entity entity, T component) where T : struct, IManagedComponent + { + var storage = GetComponentStorage(em); + em.AddComponent( entity, component.AssociatedComponentType); + em.AddComponent >(entity); + storage.AddComponent(entity, component); + } + + /// + /// Removes a managed component from the entity. This implicitly removes the managed component's AssociatedComponentType as well. + /// + /// The struct type implementing IManagedComponent + /// The entity to remove the managed component from + public static void RemoveManagedComponent(this EntityManager em, Entity entity) where T : struct, IManagedComponent + { + var storage = GetComponentStorage(em); + if (storage.HasComponent(entity)) + { + em.RemoveComponent( entity, new T().AssociatedComponentType); + em.RemoveComponent >(entity); + storage.RemoveComponent(entity); + } + } + + /// + /// Gets the managed component instance from the entity + /// + /// The struct type implementing IManagedComponent + public static T GetManagedComponent(this EntityManager em, Entity entity) where T : struct, IManagedComponent + { + var storage = GetComponentStorage(em); + return storage.GetComponent(entity); + } + + /// + /// Sets the managed component instance for the entity. + /// Throws if the entity does not have the managed component + /// + /// The struct type implementing IManagedComponent + /// The entity which has the managed component to be replaced + /// The new managed component value + public static void SetManagedComponent(this EntityManager em, Entity entity, T component) where T : struct, IManagedComponent + { + var storage = GetComponentStorage(em); + storage.SetComponent(entity, component); + } + + /// + /// Returns true if the entity has the managed component. False otherwise. + /// + /// The struct type implementing IManagedComponent + public static bool HasManagedComponent(this EntityManager em, Entity entity) where T : struct, IManagedComponent + { + var storage = GetComponentStorage(em); + return storage.HasComponent(entity) && em.HasComponent(entity, storage.GetAssociatedType()); + } + + /// + /// Adds a collection component to the entity. This implicitly adds the collection component's AssociatedComponentType as well. + /// The collection component's dependency is automatically updated with the final Dependency of the currently running system. + /// + /// The struct type implementing ICollectionComponent + /// The entity in which the collection component should be added + /// The collection component value + /// If true, the collection component should be automatically disposed when replaced or removed + public static void AddCollectionComponent(this EntityManager em, Entity entity, T collectionComponent, bool isInitialized = true) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out LatiosWorld lw); + em.AddComponent( entity, collectionComponent.AssociatedComponentType); + em.AddComponent >(entity); + storage.AddCollectionComponent(entity, collectionComponent, !isInitialized); + lw.MarkCollectionDirty(entity, false); + } + + /// + /// Removes the collection component from the entity and disposes it. + /// This implicitly removes the collection component's AssociatedComponentType as well. + /// If a SubSystem is currently running, its Dependency property is combined with the disposal job. + /// Otherwise the disposal job is forced to complete. + /// + /// The struct type implementing ICollectionComponent + /// The entity in that has the collection component which should be removed + public static void RemoveCollectionComponentAndDispose(this EntityManager em, Entity entity) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out LatiosWorld lw); + if (storage.HasCollectionComponent(entity)) + { + em.RemoveComponent( entity, storage.GetAssociatedType()); + em.RemoveComponent >(entity); + bool isDisposable = storage.RemoveCollectionComponent(entity, out JobHandle readHandle, out JobHandle writeHandle, out T component); + if (isDisposable) + { + var jobHandle = component.Dispose(JobHandle.CombineDependencies(readHandle, writeHandle)); + lw.UpdateOrCompleteDependency(jobHandle); + lw.MarkCollectionClean(entity, false); + } + } + } + + /// + /// Removes the collection component from the entity and disposes it. + /// This implicitly removes the collection component's AssociatedComponentType as well. + /// + /// The struct type implementing ICollectionComponent + /// The entity in that has the collection component which should be removed + /// + public static void RemoveCollectionComponentAndDispose(this EntityManager em, Entity entity, out JobHandle jobHandle) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out LatiosWorld lw); + if (storage.HasCollectionComponent(entity)) + { + em.RemoveComponent( entity, storage.GetAssociatedType()); + em.RemoveComponent >(entity); + bool isDisposable = storage.RemoveCollectionComponent(entity, out JobHandle readHandle, out JobHandle writeHandle, out T component); + if (isDisposable) + { + jobHandle = component.Dispose(JobHandle.CombineDependencies(readHandle, writeHandle)); + lw.MarkCollectionClean(entity, false); + return; + } + } + jobHandle = default; + } + + /// + /// Gets the collection component and its dependency. + /// The collection component's dependency will be updated by the currently running SubSystem, + /// but the currently running SubSystem will not have its Dependency property updated. + /// + /// The struct type implementing ICollectionComponent + /// The entity that has the collection component + /// Specifies if the collection component will only be read by the system + /// The dependency of the collection component + /// The collection component instance + public static T GetCollectionComponent(this EntityManager em, Entity entity, bool readOnly, out JobHandle jobHandle) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out LatiosWorld lw); + lw.MarkCollectionDirty(entity, readOnly); + return storage.GetCollectionComponent(entity, readOnly, out jobHandle); + } + + /// + /// Gets the collection component and its dependency. + /// The collection component's dependency will be updated by the final Dependency of the currently running SubSystem, + /// and the currently running SubSystem will have its Dependency property updated by the collection component's dependency + /// at the time of retrieval. + /// + /// The struct type implementing ICollectionComponent + /// The entity that has the collection component + /// Specifies if the collection component will only be read by the system + /// The collection component instance + public static T GetCollectionComponent(this EntityManager em, Entity entity, bool readOnly = false) where T : struct, ICollectionComponent + { + var result = GetCollectionComponent(em, entity, readOnly, out JobHandle handle); + GetCollectionStorage(em, out LatiosWorld lw); + lw.UpdateOrCompleteDependency(handle); + return result; + } + + /// + /// Replaces the collection component's content with the new value, disposing the old instance. + /// If a SubSystem is currently running, its Dependency property is combined with the disposal job. + /// Otherwise the disposal job is forced to complete. + /// The new collection component's dependency will be updated by the final Dependency of the currently running SubSystem. + /// + /// The struct type implementing ICollectionComponent + /// The entity that has the collection component to be replaced + /// The new collection component value + public static void SetCollectionComponentAndDisposeOld(this EntityManager em, Entity entity, T collectionComponent) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out LatiosWorld lw); + bool isOldDisposable = storage.SetCollectionComponent(entity, collectionComponent, out JobHandle readHandle, out JobHandle writeHandle, out T oldComponent); + lw.UpdateOrCompleteDependency(readHandle, writeHandle); + if (isOldDisposable) + { + var jobHandle = oldComponent.Dispose(JobHandle.CombineDependencies(readHandle, writeHandle)); + lw.UpdateOrCompleteDependency(jobHandle); + } + lw.MarkCollectionDirty(entity, false); + } + + /// + /// Returns true if the entity has the collection component type + /// + /// The struct type implementing ICollectionComponent + public static bool HasCollectionComponent(this EntityManager em, Entity entity) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out _); + return storage.HasCollectionComponent(entity) && em.HasComponent(entity, storage.GetAssociatedType()); + } + + /// + /// Provides a dependency for the collection component attached to the entity. + /// The collection component will no longer be automatically updated with the final Dependency of the currently running SubSystem. + /// + /// The struct type implementing ICollectionComponent + /// The entity with the collection component whose dependency should be updated + /// The new dependency for the collection component + /// True if the dependency to update only read the collection component + public static void UpdateCollectionComponentDependency(this EntityManager em, Entity entity, JobHandle handle, bool wasReadOnly) where T : struct, ICollectionComponent + { + var storage = GetCollectionStorage(em, out LatiosWorld lw); + if (wasReadOnly) + { + storage.UpdateReadHandle(entity, typeof(T), handle); + } + else + { + storage.UpdateWriteHandle(entity, typeof(T), handle); + } + lw.MarkCollectionClean(entity, wasReadOnly); + } + + private static ManagedStructComponentStorage GetComponentStorage(EntityManager em) + { + if (!(em.World is LatiosWorld latiosWorld)) + throw new InvalidOperationException("The EntityManager must belong to a LatiosWorld in order to use IComponent"); + return latiosWorld.ManagedStructStorage; + } + + private static CollectionComponentStorage GetCollectionStorage(EntityManager em, out LatiosWorld latiosWorld) + { + if (!(em.World is LatiosWorld lw)) + throw new InvalidOperationException("The EntityManager must belong to a LatiosWorld in order to use ICollectionComponent"); + latiosWorld = lw; + return latiosWorld.CollectionComponentStorage; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/EntityManagerCollectionsExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityManagerCollectionsExtensions.cs.meta new file mode 100644 index 0000000..71865e0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/EntityManagerCollectionsExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3b8f31784ebe8e4d9c10afb9953d7c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/FluentQuery.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/FluentQuery.cs new file mode 100644 index 0000000..0579269 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/FluentQuery.cs @@ -0,0 +1,546 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; + +namespace Latios +{ + public static class FluentQueryStarters + { + /// + /// Starts the construction of an EntityQuery + /// + internal static FluentQuery Fluent(this ILatiosSystem system) + { + return new FluentQuery + { + m_all = new NativeList(Allocator.TempJob), + m_any = new NativeList(Allocator.TempJob), + m_none = new NativeList(Allocator.TempJob), + m_anyIfNotExcluded = new NativeList(Allocator.TempJob), + m_allWeak = new NativeList(Allocator.TempJob), + m_anyWeak = new NativeList(Allocator.TempJob), + m_anyIfNotExcludedWeak = new NativeList(Allocator.TempJob), + m_targetSystem = system, + m_targetState = default, + m_targetManager = default, + m_anyIsSatisfiedByAll = false, + m_sharedComponentFilterA = null, + m_sharedComponentFilterB = null, + m_changeFilters = new NativeList(Allocator.TempJob), + m_options = EntityQueryOptions.Default + }; + } + + /// + /// Starts the construction of an EntityQuery + /// + public static FluentQuery Fluent(this EntityManager em) + { + return new FluentQuery + { + m_all = new NativeList(Allocator.TempJob), + m_any = new NativeList(Allocator.TempJob), + m_none = new NativeList(Allocator.TempJob), + m_anyIfNotExcluded = new NativeList(Allocator.TempJob), + m_allWeak = new NativeList(Allocator.TempJob), + m_anyWeak = new NativeList(Allocator.TempJob), + m_anyIfNotExcludedWeak = new NativeList(Allocator.TempJob), + m_targetSystem = null, + m_targetState = default, + m_targetManager = em, + m_anyIsSatisfiedByAll = false, + m_sharedComponentFilterA = null, + m_sharedComponentFilterB = null, + m_changeFilters = new NativeList(Allocator.TempJob), + m_options = EntityQueryOptions.Default + }; + } + + /// + /// Starts the construction of an EntityQuery + /// + public static unsafe FluentQuery Fluent(this ref SystemState state) + { + return new FluentQuery + { + m_all = new NativeList(Allocator.TempJob), + m_any = new NativeList(Allocator.TempJob), + m_none = new NativeList(Allocator.TempJob), + m_anyIfNotExcluded = new NativeList(Allocator.TempJob), + m_allWeak = new NativeList(Allocator.TempJob), + m_anyWeak = new NativeList(Allocator.TempJob), + m_anyIfNotExcludedWeak = new NativeList(Allocator.TempJob), + m_targetSystem = null, + m_targetState = (SystemState*)UnsafeUtility.AddressOf(ref state), + m_targetManager = default, + m_anyIsSatisfiedByAll = false, + m_sharedComponentFilterA = null, + m_sharedComponentFilterB = null, + m_changeFilters = new NativeList(Allocator.TempJob), + m_options = EntityQueryOptions.Default + }; + } + } + + /// + /// A Fluent builder object for creating EntityQuery instances. + /// + public unsafe struct FluentQuery + { + internal NativeList m_all; + internal NativeList m_any; + internal NativeList m_none; + internal NativeList m_anyIfNotExcluded; + internal NativeList m_allWeak; + internal NativeList m_anyWeak; + internal NativeList m_anyIfNotExcludedWeak; + + internal ILatiosSystem m_targetSystem; + internal SystemState* m_targetState; + internal EntityManager m_targetManager; + + internal bool m_anyIsSatisfiedByAll; + + internal SharedComponentFilterBase m_sharedComponentFilterA; + internal SharedComponentFilterBase m_sharedComponentFilterB; + + internal NativeList m_changeFilters; + + internal EntityQueryOptions m_options; + + abstract internal class SharedComponentFilterBase + { + public abstract void ApplyFilter(EntityQuery query); + } + + internal class SharedComponentFilter : SharedComponentFilterBase where T : struct, ISharedComponentData + { + private T m_scd; + public SharedComponentFilter(T scd) + { + m_scd = scd; + } + + public override void ApplyFilter(EntityQuery query) + { + query.AddSharedComponentFilter(m_scd); + } + } + + /// + /// Adds a required component to the query with the specified access + /// + /// The type of component to add + /// Should the component be marked as ReadOnly? + /// Is the component a chunk component for the query? + public FluentQuery WithAll(bool readOnly = false, bool isChunkComponent = false) + { + if (isChunkComponent) + { + if (readOnly) + m_all.Add(ComponentType.ChunkComponentReadOnly()); + else + m_all.Add(ComponentType.ChunkComponent()); + } + else + { + if (readOnly) + m_all.Add(ComponentType.ReadOnly()); + else + m_all.Add(ComponentType.ReadWrite()); + } + return this; + } + + /// + /// Adds a required component to the query as ReadOnly unless the component has already been added (or added subsequently) + /// + /// The type of component to add + /// Is the component a chunk component for the query? + public FluentQuery WithAllWeak(bool isChunkComponent = false) + { + if (isChunkComponent) + m_allWeak.Add(ComponentType.ChunkComponentReadOnly()); + else + m_allWeak.Add(ComponentType.ReadOnly()); + return this; + } + + /// + /// Adds a component to the WithAny category of the query using the specified access unless the component has already been added to the All category (or added subsequently) in which case the WithAny category is dropped + /// + /// The type of component to add + /// Should the component be marked as ReadOnly? + /// Is the component a chunk component for the query? + public FluentQuery WithAny(bool readOnly = false, bool isChunkComponent = false) + { + if (isChunkComponent) + { + if (readOnly) + m_any.Add(ComponentType.ChunkComponentReadOnly()); + else + m_any.Add(ComponentType.ChunkComponent()); + } + else + { + if (readOnly) + m_any.Add(ComponentType.ReadOnly()); + else + m_any.Add(ComponentType.ReadWrite()); + } + return this; + } + + /// + /// Adds a component to the WithAny category of the query marked as ReadOnly unless the component has already been added (or added subsequently) with WithAny write access or added to the All category (or added subsequently) in which case the WithAny category is dropped + /// + /// The type of component to add + /// Is the component a chunk component of the query? + public FluentQuery WithAnyWeak(bool isChunkComponent = false) + { + if (isChunkComponent) + m_anyWeak.Add(ComponentType.ChunkComponentReadOnly()); + else + m_anyWeak.Add(ComponentType.ReadOnly()); + return this; + } + + /// + /// Same as WithAny except if the component was added using "Without" (or added subsequently) the component is not added + /// + /// The type of component to add + /// Should the component be marked as ReadOnly? + /// Is the component a chunk component of the query? + public FluentQuery WithAnyNotExcluded(bool readOnly = false, bool isChunkComponent = false) + { + if (isChunkComponent) + { + if (readOnly) + m_anyIfNotExcluded.Add(ComponentType.ChunkComponentReadOnly()); + else + m_anyIfNotExcluded.Add(ComponentType.ChunkComponent()); + } + else + { + if (readOnly) + m_anyIfNotExcluded.Add(ComponentType.ReadOnly()); + else + m_anyIfNotExcluded.Add(ComponentType.ReadWrite()); + } + return this; + } + + /// + /// Same as WithAnyWeak except if the component was added using "Without" (or added subsequently) the component is not added + /// + /// The type of component to add + /// Is the component a chunk component of the query? + public FluentQuery WithAnyNotExcludedWeak(bool isChunkComponent = false) + { + if (isChunkComponent) + m_anyIfNotExcludedWeak.Add(ComponentType.ChunkComponentReadOnly()); + else + m_anyIfNotExcludedWeak.Add(ComponentType.ReadOnly()); + return this; + } + + /// + /// Adds a component to be explicitly excluded from the query + /// + /// The type of component to exclude + /// Is the component excluded a chunk component? + public FluentQuery Without(bool isChunkComponent = false) + { + //m_none.Add(ComponentType.Exclude()); + if (isChunkComponent) + m_none.Add(ComponentType.ChunkComponentReadOnly()); + else + m_none.Add(ComponentType.ReadOnly()); + return this; + } + + /// + /// Adds a shared component value as a filter to the query + /// + /// The ISharedComponentData value that entities in the query are required to match + public FluentQuery WithSharedComponent(T value) where T : struct, ISharedComponentData + { + var em = m_targetManager; + if (em == default) + em = m_targetSystem.latiosWorld.EntityManager; + if (em == default) + throw new System.InvalidOperationException("Missing a system or entity manager reference to build an EntityQuery."); + var scf = new SharedComponentFilter(value); + if (m_sharedComponentFilterA == null) + m_sharedComponentFilterA = scf; + else if (m_sharedComponentFilterB == null) + m_sharedComponentFilterB = scf; + else + throw new System.InvalidOperationException("Only up to two Shared Components can be added to the EntityQuery"); + return this; + } + + /// + /// Applies a change filter to the component in the query + /// + /// The type of component to match the query only if it might have changed + /// + public FluentQuery WithChangeFilter() + { + m_changeFilters.Add(ComponentType.ReadOnly()); + return this; + } + + /// + /// Allows disabled entities to be included in the query + /// + /// + public FluentQuery IncludeDisabled() + { + m_options |= EntityQueryOptions.IncludeDisabled; + return this; + } + + /// + /// Allows prefab entities to be included in the query + /// + /// + public FluentQuery IncludePrefabs() + { + m_options |= EntityQueryOptions.IncludePrefab; + return this; + } + + /// + /// Turns on write group filtering for this query + /// + /// + public FluentQuery UseWriteGroups() + { + m_options |= EntityQueryOptions.FilterWriteGroup; + return this; + } + + public delegate void FluentDelegate(ref FluentQuery fluent); + + /// + /// Apply a custom function to the FluentQuery + /// + /// The custom function to apply + /// + public FluentQuery WithDelegate(FluentDelegate fluentDelegate) + { + fluentDelegate.Invoke(ref this); + return this; + } + + /// + /// Constructs the EntityQuery using the previous commands in the chain + /// + /// + public EntityQuery Build() + { + m_anyIsSatisfiedByAll = (m_any.Length + m_anyWeak.Length + m_anyIfNotExcluded.Length + m_anyIfNotExcludedWeak.Length) == 0; + + //Filter and merge "ifNotExcluded" + RemoveDuplicates(m_none); + RemoveDuplicates(m_anyIfNotExcluded); + RemoveDuplicates(m_anyIfNotExcludedWeak); + + RemoveIfInList(m_anyIfNotExcluded, m_none); + RemoveIfInList(m_anyIfNotExcludedWeak, m_none); + + m_any.AddRange(m_anyIfNotExcluded.AsArray()); + m_anyWeak.AddRange(m_anyIfNotExcludedWeak.AsArray()); + + //Cleanup + RemoveDuplicates(m_all); + RemoveDuplicates(m_any); + RemoveDuplicates(m_allWeak); + RemoveDuplicates(m_anyWeak); + + RemoveDuplicates(m_changeFilters); + + //throw cases: + //any is in all and access mismatch + + //If a component in the any group is also in the all group with the same permissions, upgrade it to all and mark the flag. + for (int i = 0; i < m_any.Length; i++) + { + for (int j = 0; j < m_all.Length; j++) + { + var a = m_any[i]; + var b = m_all[j]; + if (a.TypeIndex == b.TypeIndex && a.IsChunkComponent == b.IsChunkComponent) + { + if (a.AccessModeType != b.AccessModeType) + { + throw new System.InvalidOperationException($"Cannot build an EntityQuery with type {a} given as both {a.AccessModeType} and {b.AccessModeType}"); + } + else + { + m_anyIsSatisfiedByAll = true; + } + } + } + } + + //Merge allWeak + for (int i = 0; i < m_allWeak.Length; i++) + { + bool found = false; + for (int j = 0; j < m_all.Length; j++) + { + var a = m_allWeak[i]; + var b = m_all[j]; + if (a.TypeIndex == b.TypeIndex && a.IsChunkComponent == b.IsChunkComponent) + { + found = true; + } + } + if (!found) + { + for (int j = 0; j < m_any.Length; j++) + { + var a = m_allWeak[i]; + var b = m_any[j]; + if (a.TypeIndex == b.TypeIndex && a.IsChunkComponent == b.IsChunkComponent && b.AccessModeType == ComponentType.AccessMode.ReadWrite) + { + a.AccessModeType = ComponentType.AccessMode.ReadWrite; + m_allWeak[i] = a; + m_anyIsSatisfiedByAll = true; + } + } + m_all.Add(m_allWeak[i]); + } + } + + //Merge anyWeak + for (int i = 0; i < m_anyWeak.Length; i++) + { + bool found = false; + for (int j = 0; j < m_all.Length; j++) + { + var a = m_anyWeak[i]; + var b = m_all[j]; + if (a.TypeIndex == b.TypeIndex && a.IsChunkComponent == b.IsChunkComponent) + { + found = true; + m_anyIsSatisfiedByAll = true; + } + } + if (!found) + { + for (int j = 0; j < m_any.Length; j++) + { + var a = m_anyWeak[i]; + var b = m_any[j]; + if (a.TypeIndex == b.TypeIndex && a.IsChunkComponent == b.IsChunkComponent && b.AccessModeType == ComponentType.AccessMode.ReadWrite) + { + a.AccessModeType = ComponentType.AccessMode.ReadWrite; + m_anyWeak[i] = a; + } + } + m_any.Add(m_anyWeak[i]); + } + } + + if (m_anyIsSatisfiedByAll) + m_any.Clear(); + + //EntityQueryDescBuilder desc = new EntityQueryDescBuilder(Allocator.Temp); + //for (int i = 0; i < m_all.Length; i++) + // desc.AddAll(m_all[i]); + //for (int i = 0; i < m_any.Length; i++) + // desc.AddAny(m_any[i]); + //for (int i = 0; i < m_none.Length; i++) + // desc.AddNone(m_none[i]); + //desc.Options(m_options); + //desc.FinalizeQuery(); + + var desc = new EntityQueryDesc() + { + All = m_all.ToArray(), + Any = m_any.ToArray(), + None = m_none.ToArray(), + Options = m_options + }; + + DisposeArrays(); + EntityQuery query; + if (m_targetSystem != null) + { + query = m_targetSystem.GetEntityQuery(desc); + } + else if (m_targetState != null) + { + query = m_targetState->GetEntityQuery(desc); + } + else if (m_targetManager != default) + { + query = m_targetManager.CreateEntityQuery(desc); + } + else + throw new System.InvalidOperationException("Missing a system or entity manager reference to build an EntityQuery."); + m_sharedComponentFilterA?.ApplyFilter(query); + m_sharedComponentFilterB?.ApplyFilter(query); + for (int i = 0; i < m_changeFilters.Length; i++) + query.AddChangedVersionFilter(m_changeFilters[i]); + m_changeFilters.Dispose(); + return query; + } + + private void DisposeArrays() + { + m_all.Dispose(); + m_any.Dispose(); + m_none.Dispose(); + m_anyWeak.Dispose(); + m_allWeak.Dispose(); + m_anyIfNotExcluded.Dispose(); + m_anyIfNotExcludedWeak.Dispose(); + } + + private void RemoveDuplicates(NativeList list) + { + for (int i = 0; i < list.Length; i++) + { + for (int j = i + 1; j < list.Length; j++) + { + var a = list[i]; + var b = list[j]; + + if (a.TypeIndex == b.TypeIndex && a.IsChunkComponent == b.IsChunkComponent) + { + if (a.AccessModeType != b.AccessModeType) + { + throw new System.InvalidOperationException($"Cannot build an EntityQuery with type {a} given as both {a.AccessModeType} and {b.AccessModeType}"); + } + else + { + list.RemoveAtSwapBack(j); + j--; + } + } + } + } + } + + private void RemoveIfInList(NativeList listToFilter, NativeList typesToRemove) + { + for (int i = 0; i < listToFilter.Length; i++) + { + for (int j = 0; j < typesToRemove.Length; j++) + { + if (listToFilter[i].TypeIndex == typesToRemove[j].TypeIndex && listToFilter[i].IsChunkComponent == typesToRemove[j].IsChunkComponent) + { + listToFilter.RemoveAtSwapBack(i); + i--; + j = typesToRemove.Length; + } + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/FluentQuery.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/FluentQuery.cs.meta new file mode 100644 index 0000000..21dd3e8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/FluentQuery.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98afd96d626836c4d906e37720d21f06 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/ILatiosSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/ILatiosSystem.cs new file mode 100644 index 0000000..12a1e23 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/ILatiosSystem.cs @@ -0,0 +1,23 @@ +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + public interface ILatiosSystem + { + LatiosWorld latiosWorld { get; } + + BlackboardEntity worldBlackboardEntity { get; } + BlackboardEntity sceneBlackboardEntity { get; } + + bool ShouldUpdateSystem(); + void OnNewScene(); + + EntityQuery GetEntityQuery(EntityQueryDesc desc); + EntityQuery GetEntityQuery(EntityQueryDescBuilder desc); + + FluentQuery Fluent { get; } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/ILatiosSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/ILatiosSystem.cs.meta new file mode 100644 index 0000000..786b2bc --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/ILatiosSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6853d908f96aad140b5f98a4e80b3ef5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/LatiosWorld.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/LatiosWorld.cs new file mode 100644 index 0000000..023ae75 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/LatiosWorld.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using Debug = UnityEngine.Debug; +using Unity.Collections; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Jobs; + +using Latios.Systems; + +namespace Latios +{ + /// + /// A specialized runtime World which contains Latios Framework core functionality. + /// + public class LatiosWorld : World + { + /// + /// The worldBlackboardEntity associated with this world + /// + public BlackboardEntity worldBlackboardEntity { get; private set; } +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private BlackboardEntity m_sceneBlackboardEntity; + private bool m_sceneBlackboardSafetyOverride; + /// + /// The current sceneBlackboardEntity associated with this world + /// + public BlackboardEntity sceneBlackboardEntity + { + get + { + if (m_sceneBlackboardEntity == Entity.Null && !m_sceneBlackboardSafetyOverride) + { + throw new InvalidOperationException( + "The sceneBlackboardEntity has not been initialized yet. If you are trying to access this entity in OnCreate(), please use OnNewScene() or another callback instead."); + } + return m_sceneBlackboardEntity; + } + private set => m_sceneBlackboardEntity = value; + } +#else + public BlackboardEntity sceneBlackboardEntity { get; private set; } +#endif + /// + /// The main syncPoint system from which to get command buffers. + /// Command buffers retrieved from this property will have dependencies managed automatically + /// + public SyncPointPlaybackSystem syncPoint + { + get + { + m_activeSystemAccessedSyncPoint = true; + return m_syncPointPlaybackSystem; + } + set => m_syncPointPlaybackSystem = value; + } + /// + /// The InitializationSystemGroup of this world for convenience + /// + public InitializationSystemGroup initializationSystemGroup => m_initializationSystemGroup; + /// + /// The SimulationSystemGroup of this world for convenience + /// + public SimulationSystemGroup simulationSystemGroup => m_simulationSystemGroup; + /// + /// The PresentationsystemGroup of this world for convenience. It is null for NetCode server worlds. + /// + public PresentationSystemGroup presentationSystemGroup => m_presentationSystemGroup; + + /// + /// Specifies the default system ordering behavior for any newly created SuperSystems. + /// If true, automatic system ordering will by default be disabled for those SuperSystems. + /// + public bool useExplicitSystemOrdering = false; + + internal ManagedStructComponentStorage ManagedStructStorage { get { return m_componentStorage; } } + internal CollectionComponentStorage CollectionComponentStorage { get { return m_collectionsStorage; } } + internal UnmanagedExtraInterfacesDispatcher UnmanagedExtraInterfacesDispatcher { get { return m_interfacesDispatcher; } } + + private List m_collectionDependencies = new List(); + + //Todo: Disposal of storages is currently done in ManagedStructStorageCleanupSystems.cs. + //This is because overriding World doesn't give us an opportunity to override the Dispose method. + //In the future it may be worth creating a DisposeTool system that Dispose events can be registered to. + private ManagedStructComponentStorage m_componentStorage = new ManagedStructComponentStorage(); + private CollectionComponentStorage m_collectionsStorage = new CollectionComponentStorage(); + private UnmanagedExtraInterfacesDispatcher m_interfacesDispatcher; + + private InitializationSystemGroup m_initializationSystemGroup; + private SimulationSystemGroup m_simulationSystemGroup; + private PresentationSystemGroup m_presentationSystemGroup; + private SyncPointPlaybackSystem m_syncPointPlaybackSystem; + private SystemHandle m_blackboardUnmanagedSystem; + + private bool m_paused = false; + private bool m_resumeNextFrame = false; + + public enum WorldRole + { + Default, + Client, + Server + } + + /// + /// Creates a LatiosWorld + /// + /// The name of the world + /// Specifies world flags + /// The role of the world. Leave at default unless this is a NetCode project. + public LatiosWorld(string name, WorldFlags flags = WorldFlags.Simulation, WorldRole role = WorldRole.Default) : base(name, flags) + { + Authoring.ConversionBootstrapUtilities.RegisterConversionWorldAction(); + + //BootstrapTools.PopulateTypeManagerWithGenerics(typeof(ManagedComponentTag<>), typeof(IManagedComponent)); + BootstrapTools.PopulateTypeManagerWithGenerics(typeof(ManagedComponentSystemStateTag<>), typeof(IManagedComponent)); + //BootstrapTools.PopulateTypeManagerWithGenerics(typeof(CollectionComponentTag<>), typeof(ICollectionComponent)); + BootstrapTools.PopulateTypeManagerWithGenerics(typeof(CollectionComponentSystemStateTag<>), typeof(ICollectionComponent)); + m_interfacesDispatcher = new UnmanagedExtraInterfacesDispatcher(); + + var bus = this.GetOrCreateSystem(); + m_blackboardUnmanagedSystem = bus.Handle; + + worldBlackboardEntity = new BlackboardEntity(EntityManager.CreateEntity(), EntityManager); + worldBlackboardEntity.AddComponentData(new WorldBlackboardTag()); + EntityManager.SetName(worldBlackboardEntity, "World Blackboard Entity"); + bus.Struct.worldBlackboardEntity = (Entity)worldBlackboardEntity; + bus.Struct.sceneBlackboardEntity = default; + + if (role == WorldRole.Default) + { + m_initializationSystemGroup = GetOrCreateSystem(); + m_simulationSystemGroup = GetOrCreateSystem(); + m_presentationSystemGroup = GetOrCreateSystem(); + } + else if (role == WorldRole.Client) + { +#if NETCODE_PROJECT + m_initializationSystemGroup = GetOrCreateSystem(); + m_simulationSystemGroup = GetOrCreateSystem(); + m_presentationSystemGroup = GetOrCreateSystem(); +#endif + } + else if (role == WorldRole.Server) + { +#if NETCODE_PROJECT + m_initializationSystemGroup = GetOrCreateSystem(); + m_simulationSystemGroup = GetOrCreateSystem(); +#endif + } + + m_syncPointPlaybackSystem = GetExistingSystem(); + } + + /// + /// When the Scene Manager is not installed, call this function to destroy the old sceneBlackboardEntity, + /// create a new one, and call the OnNewScene() method for all systems which have it. + /// + /// + public BlackboardEntity ForceCreateNewSceneBlackboardEntityAndCallOnNewScene() + { + CreateNewSceneBlackboardEntity(true); + return sceneBlackboardEntity; + } + + //Todo: Make this API public in the future. + internal void Pause() => m_paused = true; + internal void ResumeNextFrame() => m_resumeNextFrame = true; + internal bool paused => m_paused; + internal bool willResumeNextFrame => m_resumeNextFrame; + internal bool autoGenerateSceneBlackboardEntity = true; + + internal void FrameStart() + { + if (m_resumeNextFrame) + { + m_paused = false; + m_resumeNextFrame = false; + } + + if (autoGenerateSceneBlackboardEntity) + { + CreateNewSceneBlackboardEntity(); + autoGenerateSceneBlackboardEntity = false; + } + } + + internal void CreateNewSceneBlackboardEntity(bool forceRecreate = false) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_sceneBlackboardSafetyOverride = true; +#endif + bool existsAndIsValid = EntityManager.Exists(sceneBlackboardEntity) && sceneBlackboardEntity.HasComponent(); + if (forceRecreate && existsAndIsValid) + { + EntityManager.DestroyEntity(sceneBlackboardEntity); + existsAndIsValid = false; + } + + if (!existsAndIsValid) + { + sceneBlackboardEntity = new BlackboardEntity(EntityManager.CreateEntity(), EntityManager); + sceneBlackboardEntity.AddComponentData(new SceneBlackboardTag()); + Unmanaged.ResolveSystem(m_blackboardUnmanagedSystem).sceneBlackboardEntity = (Entity)sceneBlackboardEntity; + EntityManager.SetName(sceneBlackboardEntity, "Scene Blackboard Entity"); + + foreach (var system in Systems) + { + if (system is ILatiosSystem latiosSystem) + { + latiosSystem.OnNewScene(); + } + } + + var unmanaged = Unmanaged; + var unmanagedStates = unmanaged.GetAllSystemStates(Allocator.TempJob); + for (int i = 0; i < unmanagedStates.Length; i++) + { + var dispatcher = m_interfacesDispatcher.GetDispatch(ref unmanagedStates.At(i)); + if (dispatcher != null) + dispatcher.OnNewScene(ref unmanagedStates.At(i)); + } + unmanagedStates.Dispose(); + } +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_sceneBlackboardSafetyOverride = false; +#endif + } + + #region AutoDependencies + + private SubSystemBase m_activeSystem; + private bool m_activeSystemAccessedSyncPoint; + + internal void UpdateOrCompleteDependency(JobHandle readHandle, JobHandle writeHandle) + { + if (m_activeSystem != null) + { + var jh = m_activeSystem.SystemBaseDependency; + jh = JobHandle.CombineDependencies(readHandle, writeHandle, jh); + m_activeSystem.SystemBaseDependency = jh; + } + else + { + JobHandle.CombineDependencies(readHandle, writeHandle).Complete(); + } + } + + internal void UpdateOrCompleteDependency(JobHandle jobHandle) + { + if (m_activeSystem != null) + { + var jh = m_activeSystem.SystemBaseDependency; + jh = JobHandle.CombineDependencies(jobHandle, jh); + m_activeSystem.SystemBaseDependency = jh; + } + else + { + jobHandle.Complete(); + } + } + + internal void MarkCollectionDirty(Entity entity, bool isReadOnly) where T : struct, ICollectionComponent + { + m_collectionDependencies.Add(new CollectionDependency(entity, typeof(T), isReadOnly)); + } + + internal void MarkCollectionClean(Entity entity, bool isReadOnly) where T : struct, ICollectionComponent + { + if (isReadOnly) + { + RemoveAllMatchingCollectionDependenciesReadOnly(entity, typeof(T)); + } + else + { + RemoveAllMatchingCollectionDependencies(entity, typeof(T)); + } + } + + internal void BeginDependencyTracking(SubSystemBase sys) + { + if (m_activeSystem != null) + { + throw new InvalidOperationException( + $"{sys.GetType().Name} has detected that the previously updated {m_activeSystem.GetType().Name} did not finish its update procedure properly. This is likely due to an exception thrown from within OnUpdate(), but please note that calling Update() on a SubSystem from within another SubSystem is not supported."); + } + m_activeSystem = sys; + m_activeSystemAccessedSyncPoint = false; + } + + internal void EndDependencyTracking(JobHandle outputDeps) + { + m_activeSystem = null; + if (outputDeps.IsCompleted) + { + //Todo: Is this necessary? And what are the performance impacts? Is there a better way to figure out that all jobs were using .Run()? + outputDeps.Complete(); + } + else + { + UpdateCollectionDependencies(outputDeps); + if (m_activeSystemAccessedSyncPoint) + { + m_syncPointPlaybackSystem.AddJobHandleForProducer(outputDeps); + } + } + } + + internal void CheckMissingDependenciesForCollections(SubSystemBase sys) + { + if (m_collectionDependencies.Count > 0) + Debug.LogWarning( + $"{sys} finished its update but there are ICollectionComponent instances, one of which was of type {m_collectionDependencies[0].type}, that were accessed but did not have their dependencies updated."); + } + + private struct CollectionDependency + { + public Entity entity; + public Type type; + public bool readOnly; + + public CollectionDependency(Entity entity, Type type, bool readOnly) + { + this.entity = entity; + this.type = type; + this.readOnly = readOnly; + } + } + + private void UpdateCollectionDependencies(JobHandle outputDeps) + { + foreach (var dep in m_collectionDependencies) + { + if (dep.readOnly) + { + m_collectionsStorage.UpdateReadHandle(dep.entity, dep.type, outputDeps); + } + else + { + m_collectionsStorage.UpdateWriteHandle(dep.entity, dep.type, outputDeps); + } + } + m_collectionDependencies.Clear(); + } + + private void RemoveAllMatchingCollectionDependencies(Entity entity, Type type) + { + for (int i = 0; i < m_collectionDependencies.Count; i++) + { + if (m_collectionDependencies[i].entity == entity && m_collectionDependencies[i].type == type) + { + m_collectionDependencies.RemoveAtSwapBack(i); + i--; + } + } + } + + private void RemoveAllMatchingCollectionDependenciesReadOnly(Entity entity, Type type) + { + for (int i = 0; i < m_collectionDependencies.Count; i++) + { + if (m_collectionDependencies[i].entity == entity && m_collectionDependencies[i].type == type && m_collectionDependencies[i].readOnly == true) + { + m_collectionDependencies.RemoveAtSwapBack(i); + i--; + } + } + } + #endregion + } + +namespace Systems +{ + /// + /// The SimulationSystemGroup for a LatiosWorld created with WorldRole.Default + /// + [DisableAutoCreation, NoGroupInjection] + public class LatiosSimulationSystemGroup : SimulationSystemGroup + { + SystemSortingTracker m_tracker; + internal bool skipInDeferred = false; + + protected override void OnUpdate() + { + if (!skipInDeferred) + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } + + /// + /// The PresentationSystemGroup for a LatiosWorld created with WorldRole.Default + /// + [DisableAutoCreation, NoGroupInjection] + public class LatiosPresentationSystemGroup : PresentationSystemGroup + { + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } +} +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/LatiosWorld.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/LatiosWorld.cs.meta new file mode 100644 index 0000000..0aff2a8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/LatiosWorld.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fff6874a8b561e4baf1a30941fb7fa9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/SubSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/SubSystem.cs new file mode 100644 index 0000000..6ff5265 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/SubSystem.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios +{ + /// + /// This is an internal base class for SubSystem + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public abstract partial class SubSystemBase : SystemBase, ILatiosSystem + { + /// + /// The latiosWorld of this system + /// + public LatiosWorld latiosWorld { get; private set; } + + /// + /// The scene blackboard entity for the LatiosWorld of this system + /// + public BlackboardEntity sceneBlackboardEntity => latiosWorld.sceneBlackboardEntity; + /// + /// The world blackboard entity for the LatiosWorld of this system + /// + public BlackboardEntity worldBlackboardEntity => latiosWorld.worldBlackboardEntity; + + /// + /// Begins a Fluent query chain + /// + public FluentQuery Fluent => this.Fluent(); + + /// + /// Override this method to perform additional filtering to decide if this system should run. + /// + /// true if this system should run + public virtual bool ShouldUpdateSystem() + { + return Enabled; + } + + protected sealed override void OnCreate() + { + if (World is LatiosWorld lWorld) + { + latiosWorld = lWorld; + } + else + { + UnityEngine.Debug.LogError($"Potentially missing DisableAutoCreationAttribute for {GetType()}"); + throw new InvalidOperationException( + "The current world is not of type LatiosWorld required for Latios framework functionality. Did you forget to create a Bootstrap?"); + } + OnCreateInternal(); + } + + protected sealed override void OnUpdate() + { + latiosWorld.BeginDependencyTracking(this); + OnUpdateInternal(); + latiosWorld.EndDependencyTracking(Dependency); + } + + protected sealed override void OnDestroy() + { + OnDestroyInternal(); + } + + internal abstract void OnCreateInternal(); + + internal abstract void OnDestroyInternal(); + + internal abstract void OnUpdateInternal(); + + public EntityQuery GetEntityQuery(EntityQueryDesc desc) => GetEntityQuery(new EntityQueryDesc[] { desc }); + public EntityQuery GetEntityQuery(EntityQueryDescBuilder desc) => GetEntityQuery(desc); + + public abstract void OnNewScene(); + + internal JobHandle SystemBaseDependency + { + get => Dependency; + set => Dependency = value; + } + } + + /// + /// A base class system which subclasses SystemBase and provides Latios Framework Core features + /// + public abstract partial class SubSystem : SubSystemBase + { + protected new virtual void OnCreate() + { + } + + protected new virtual void OnDestroy() + { + } + + protected new abstract void OnUpdate(); + + /// + /// Override to get alerted whenever a new sceneBlackboardEntity is created + /// + public override void OnNewScene() + { + } + + internal override void OnCreateInternal() + { + OnCreate(); + } + + internal override void OnDestroyInternal() + { + OnDestroy(); + } + + internal override void OnUpdateInternal() + { + OnUpdate(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/SubSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/SubSystem.cs.meta new file mode 100644 index 0000000..fca3814 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/SubSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d92a52552a06c364d97be179d06bf415 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/SuperSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/SuperSystem.cs new file mode 100644 index 0000000..d7eda0c --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/SuperSystem.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Entities.Exposed.Dangerous; + +namespace Latios +{ + /// + /// A SuperSystem that does not require its parent ComponentSystemGroup to support ShouldUpdateSystem(). + /// Use this for custom SuperSystems that need to be injected into raw ComponentSystemGroup types. + /// Nearly all Latios Framework ComponentSystemGroup types already support ShouldUpdateSystem(). + /// + public abstract class RootSuperSystem : SuperSystem + { + bool m_recursiveContext = false; + + protected override void OnUpdate() + { + if (m_recursiveContext) + return; + + bool shouldUpdate = ShouldUpdateSystem(); + if (!shouldUpdate) + { + Enabled = false; + m_recursiveContext = true; + Update(); + m_recursiveContext = false; + Enabled = true; + } + else + { + base.OnUpdate(); + } + } + } + + /// + /// A subclass of ComponentSystemGroup which provides Latios Framework Core features. + /// + public abstract class SuperSystem : ComponentSystemGroup, ILatiosSystem + { + /// + /// The latiosWorld of this system + /// + public LatiosWorld latiosWorld { get; private set; } + + /// + /// The scene blackboard entity for the LatiosWorld of this system + /// + public BlackboardEntity sceneBlackboardEntity => latiosWorld.sceneBlackboardEntity; + /// + /// The world blackboard entity for the LatiosWorld of this system + /// + public BlackboardEntity worldBlackboardEntity => latiosWorld.worldBlackboardEntity; + + /// + /// Begins a Fluent query chain + /// + public FluentQuery Fluent => this.Fluent(); + + /// + /// Override this method to perform additional filtering to decide if this system should run. + /// If it does not run, none of its child systems will run either. + /// + /// true if this system should run + public virtual bool ShouldUpdateSystem() + { + return Enabled; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + protected sealed override void OnCreate() + { + base.OnCreate(); + if (World is LatiosWorld lWorld) + { + latiosWorld = lWorld; + } + else + { + throw new InvalidOperationException("The current world is not of type LatiosWorld required for Latios framework functionality."); + } + CreateSystems(); + + EnableSystemSorting &= !latiosWorld.useExplicitSystemOrdering; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + protected override void OnUpdate() + { + DoSuperSystemUpdate(this, ref m_systemSortingTracker); + } + + public EntityQuery GetEntityQuery(EntityQueryDesc desc) => GetEntityQuery(new EntityQueryDesc[] { desc }); + public EntityQuery GetEntityQuery(EntityQueryDescBuilder desc) => GetEntityQuery(desc); + + #region API + /// + /// Define the systems to be added to this SuperSystem using GetOrCreateAndAddSystem<>() + /// or GetOrCreateAndAddUnmanagedSystem<>(). Leave empty for injection workflows. + /// + protected abstract void CreateSystems(); + + /// + /// Override to get alerted whenever a new sceneBlackboardEntity is created + /// + public virtual void OnNewScene() + { + } + + /// + /// Creates a new system from the given type and adds it to this SuperSystem's update list. + /// If system sorting is disabled, the update order is based on the order the systems are added. + /// If the system already exists in the world, that system is added to the update list and returned instead. + /// + /// The type of system to add. It can be managed or unmanaged. + /// A union object that contains the created managed or unmanaged system added + public BootstrapTools.ComponentSystemBaseSystemHandleUntypedUnion GetOrCreateAndAddSystem(Type type) + { + if (typeof(ComponentSystemBase).IsAssignableFrom(type)) + { + var system = World.GetOrCreateSystem(type); + AddSystemToUpdateList(system); + return new BootstrapTools.ComponentSystemBaseSystemHandleUntypedUnion + { + systemHandle = system.SystemHandleUntyped, + systemManaged = system + }; + } + if (typeof(ISystem).IsAssignableFrom(type)) + { + var system = World.GetOrCreateUnmanagedSystem(type); + AddUnmanagedSystemToUpdateList(system); + return new BootstrapTools.ComponentSystemBaseSystemHandleUntypedUnion + { + systemHandle = system, + systemManaged = null + }; + } + return default; + } + + /// + /// Creates a new managed system and adds it to this SuperSystem's update list. + /// If system sorting is disabled, the update order is based on the order the systems are added. + /// If the system already exists in the world, that system is added to the update list and returned instead. + /// + /// The type of managed system to create + /// The managed system added + public T GetOrCreateAndAddSystem() where T : ComponentSystemBase + { + var system = World.GetOrCreateSystem(); + AddSystemToUpdateList(system); + return system; + } + + /// + /// Creates a new unmanaged system and adds it to this SuperSystem's update list. + /// If system sorting is disabled, the update order is based on the order the systems are added. + /// If the system already exists in the world, that system is added to the update list and returned instead. + /// + /// The type of unmanaged system to create + /// The unmanaged system added + public SystemRef GetOrCreateAndAddUnmanagedSystem() where T : unmanaged, ISystem + { + var system = World.GetOrCreateSystem(); + AddUnmanagedSystemToUpdateList(system.Handle); + return system; + } + + public void SortSystemsUsingAttributes(bool enableSortingAlways = true) + { + EnableSystemSorting = true; + SortSystems(); + EnableSystemSorting = enableSortingAlways; + } + + /// + /// Updates a system while supporting full Latios Framework features + /// + /// The world containing the system + /// The system's handle + new public static unsafe void UpdateSystem(ref WorldUnmanaged world, SystemHandleUntyped system) + { + var managed = world.AsManagedSystem(system); + if (managed != null) + { + UpdateManagedSystem(managed); + } + else + { + var wu = world; + ref var state = ref UnsafeUtility.AsRef(wu.ResolveSystemState(system)); + if(state.World is LatiosWorld lw) + { + var dispatcher = lw.UnmanagedExtraInterfacesDispatcher.GetDispatch(ref state); + if (dispatcher != null) + { + if (!dispatcher.ShouldUpdateSystem(ref state)) + { + state.Enabled = false; + return; + } + else + state.Enabled = true; + } + } + + ComponentSystemGroup.UpdateSystem(ref wu, system); + } + } + + #endregion API + + internal static void UpdateManagedSystem(ComponentSystemBase system, bool propagateError = false) + { + try + { + if (system is ILatiosSystem latiosSys) + { + if (latiosSys.ShouldUpdateSystem()) + { + system.Enabled = true; + system.Update(); + } + else if (system.Enabled) + { + system.Enabled = false; + //Update to invoke OnStopRunning(). + system.Update(); + } + else + { + system.Enabled = false; + } + } + else + { + system.Update(); + } + } + catch (Exception e) + { + if (propagateError) + throw; + + UnityEngine.Debug.LogException(e); + } + } + + SystemSortingTracker m_systemSortingTracker; + + internal static void UpdateAllSystems(ComponentSystemGroup group, ref SystemSortingTracker tracker) + { + tracker.CheckAndSortSystems(group); + + LatiosWorld lw = group.World as LatiosWorld; + + // Update all unmanaged and managed systems together, in the correct sort order. + var world = group.World.Unmanaged; + var previouslyExecutingSystem = world.ExecutingSystemHandle(); + var enumerator = group.GetSystemEnumerator(); + while (enumerator.MoveNext()) + { + if (lw.paused) + break; + + try + { + if (!enumerator.IsCurrentManaged) + { + // Update unmanaged (burstable) code. + var handle = enumerator.current; + group.SetExecutingSystem(ref world, handle); + UpdateSystem(ref world, handle); + } + else + { + // Update managed code. + UpdateManagedSystem(enumerator.currentManaged, true); + } + } + catch (Exception e) + { + UnityEngine.Debug.LogException(e); +#if UNITY_DOTSRUNTIME + // When in a DOTS Runtime build, throw this upstream -- continuing after silently eating an exception + // is not what you'll want, except maybe once we have LiveLink. If you're looking at this code + // because your LiveLink dots runtime build is exiting when you don't want it to, feel free + // to remove this block, or guard it with something to indicate the player is not for live link. + throw; +#endif + } + finally + { + group.SetExecutingSystem(ref world, previouslyExecutingSystem); + } + + if (group.World.QuitUpdate) + break; + } + + group.DestroyPendingSystemsInWorld(ref world); + } + + internal static void DoSuperSystemUpdate(ComponentSystemGroup group, ref SystemSortingTracker tracker) + { + if (!group.Created) + throw new InvalidOperationException( + $"Group of type {group.GetType()} has not been created, either the derived class forgot to call base.OnCreate(), or it has been destroyed"); + + if (group.RateManager == null) + { + UpdateAllSystems(group, ref tracker); + } + else + { + while (group.RateManager.ShouldGroupUpdate(group)) + { + UpdateAllSystems(group, ref tracker); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/SuperSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/SuperSystem.cs.meta new file mode 100644 index 0000000..f53dc99 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/SuperSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e003eb5bdee62a748aeb81dfceff2d6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/UnmanagedSystemInterfaces.cs b/Packages/com.latios.latios-framework/Core/Core/Framework/UnmanagedSystemInterfaces.cs new file mode 100644 index 0000000..eb96f1f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/UnmanagedSystemInterfaces.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Entities.Exposed.Dangerous; +using Unity.Jobs; + +namespace Latios +{ + /// + /// Implement this interface to get a callback whenever a new sceneBlackboardEntity is created + /// + public interface ISystemNewScene + { + public void OnNewScene(ref SystemState state); + } + + /// + /// Implement this interface to get a callback for determining if this system should run + /// + public interface ISystemShouldUpdate + { + public bool ShouldUpdateSystem(ref SystemState state); + } + + internal class UnmanagedExtraInterfacesDispatcher : IDisposable + { + List m_dispatches; + + UnsafeParallelHashMap m_metaId2DispatchMapping; + + public DispatchBase GetDispatch(ref SystemState state) + { + if (BurstLookupUtility.BurstLookup(in m_metaId2DispatchMapping, state.UnmanagedMetaIndex, out int dispatchIndex)) + { + return m_dispatches[dispatchIndex]; + } + return null; + } + + public void Dispose() + { + m_metaId2DispatchMapping.Dispose(); + } + + public UnmanagedExtraInterfacesDispatcher() + { + var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.All); + var filteredIndices = new NativeList(Allocator.Temp); + for (int i = 0; i < systems.Count; i++) + { + var type = systems[i]; + if (typeof(ISystem).IsAssignableFrom(type)) + { + if (typeof(ISystemNewScene).IsAssignableFrom(type) || typeof(ISystemShouldUpdate).IsAssignableFrom(type)) + { + filteredIndices.Add(i); + } + } + } + + m_dispatches = new List(filteredIndices.Length); + m_metaId2DispatchMapping = new UnsafeParallelHashMap(filteredIndices.Length, Allocator.Persistent); + + var NewSceneType = typeof(DispatchNewScene<>); + var ShouldUpdateType = typeof(DispatchShouldUpdate<>); + var NewSceneShouldUpdateType = typeof(DispatchNewSceneShouldUpdate<>); + + for (int i = 0; i < filteredIndices.Length; i++) + { + var systemType = systems[filteredIndices[i]]; + bool isNewScene = typeof(ISystemNewScene).IsAssignableFrom(systemType); + bool isShouldUpdate = typeof(ISystemShouldUpdate).IsAssignableFrom(systemType); + + int metaId = -1; + + try + { + metaId = WorldExposedExtensions.GetMetaIdForType(systemType); + } + catch (ArgumentException e) + { + UnityEngine.Debug.LogWarning( + $"A meta ID was not found for system of type {systemType.Name}. OnNewScene and ShouldUpdateSystem may not work correctly. Unity.Entities Error: {e.Message}"); + continue; + } + + DispatchBase dispatcher; + if (isNewScene && isShouldUpdate) + dispatcher = Activator.CreateInstance(NewSceneShouldUpdateType.MakeGenericType(systemType)) as DispatchBase; + else if (isNewScene) + dispatcher = Activator.CreateInstance(NewSceneType.MakeGenericType(systemType)) as DispatchBase; + else if (isShouldUpdate) + dispatcher = Activator.CreateInstance(ShouldUpdateType.MakeGenericType(systemType)) as DispatchBase; + else + continue; + + m_metaId2DispatchMapping.Add(metaId, m_dispatches.Count); + m_dispatches.Add(dispatcher); + } + } + + public abstract class DispatchBase + { + public abstract void OnNewScene(ref SystemState state); + public abstract bool ShouldUpdateSystem(ref SystemState state); + } + + public class DispatchNewScene : DispatchBase where T : unmanaged, ISystem, ISystemNewScene + { + public override void OnNewScene(ref SystemState state) + { + state.GetStronglyTypedUnmanagedSystem().Struct.OnNewScene(ref state); + } + + public override bool ShouldUpdateSystem(ref SystemState state) + { + return state.Enabled; + } + } + + public class DispatchShouldUpdate : DispatchBase where T : unmanaged, ISystem, ISystemShouldUpdate + { + public override void OnNewScene(ref SystemState state) + { + } + + public override bool ShouldUpdateSystem(ref SystemState state) + { + return state.GetStronglyTypedUnmanagedSystem().Struct.ShouldUpdateSystem(ref state); + } + } + + public class DispatchNewSceneShouldUpdate : DispatchBase where T : unmanaged, ISystem, ISystemNewScene, ISystemShouldUpdate + { + public override void OnNewScene(ref SystemState state) + { + state.GetStronglyTypedUnmanagedSystem().Struct.OnNewScene(ref state); + } + + public override bool ShouldUpdateSystem(ref SystemState state) + { + return state.GetStronglyTypedUnmanagedSystem().Struct.ShouldUpdateSystem(ref state); + } + } + } + + [BurstCompile] + internal static class BurstLookupUtility + { + [BurstCompile] + public static bool BurstLookup(in UnsafeParallelHashMap map, int key, out int value) + { + return map.TryGetValue(key, out value); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Framework/UnmanagedSystemInterfaces.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Framework/UnmanagedSystemInterfaces.cs.meta new file mode 100644 index 0000000..ac14ad0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Framework/UnmanagedSystemInterfaces.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fee6167c28196434b8b975465e056c68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit.meta b/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit.meta new file mode 100644 index 0000000..a4971ec --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 551a0d786ff9f104e90ba1fff6b11302 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit/EntityWith.cs b/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit/EntityWith.cs new file mode 100644 index 0000000..39f30a0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit/EntityWith.cs @@ -0,0 +1,49 @@ +using Unity.Entities; + +namespace Latios +{ + public struct EntityWith where T : struct, IComponentData + { + public Entity entity; + + public EntityWith(Entity entity) + { + this.entity = entity; + } + + public T this[ComponentDataFromEntity cdfe] + { + get => cdfe[entity]; + set => cdfe[entity] = value; + } + + public bool IsValid(ComponentDataFromEntity cdfe) => cdfe.HasComponent(entity); + + public bool DidChange(ComponentDataFromEntity cdfe, uint version) => cdfe.DidChange(entity, version); + + public static implicit operator Entity(EntityWith entityWith) => entityWith.entity; + + public static implicit operator EntityWith(Entity entity) => new EntityWith(entity); + } + + public struct EntityWithBuffer where T : struct, IBufferElementData + { + public Entity entity; + + public EntityWithBuffer(Entity entity) + { + this.entity = entity; + } + + public DynamicBuffer this[BufferFromEntity bfe] => bfe[entity]; + + public bool IsValid(BufferFromEntity bfe) => bfe.HasComponent(entity); + + public bool DidChange(BufferFromEntity bfe, uint version) => bfe.DidChange(entity, version); + + public static implicit operator Entity(EntityWithBuffer entityWithBuffer) => entityWithBuffer.entity; + + public static implicit operator EntityWithBuffer(Entity entity) => new EntityWithBuffer(entity); + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit/EntityWith.cs.meta b/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit/EntityWith.cs.meta new file mode 100644 index 0000000..f70d2ca --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/GameplayToolkit/EntityWith.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 644d9c4d5168b9f42a6cab6fbe3addfc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility.meta b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility.meta new file mode 100644 index 0000000..ba5d553 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5cc13ef3ccca46242b573ece2af1d992 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode.meta b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode.meta new file mode 100644 index 0000000..255c7d0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b8a3bd2dd4392be46b2ff067278bf9a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/BaseSystems.cs b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/BaseSystems.cs new file mode 100644 index 0000000..e3d906f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/BaseSystems.cs @@ -0,0 +1,151 @@ +#if NETCODE_PROJECT + +using Latios.Systems; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Entities.Exposed.Dangerous; +using Unity.NetCode; + +namespace Latios.Compatibility.UnityNetCode +{ + [DisableAutoCreation, NoGroupInjection, AlwaysUpdateSystem] + public class LatiosServerInitializationSystemGroup : ServerInitializationSystemGroup + { + private SyncPointPlaybackSystem m_syncPlayback; + private MergeBlackboardsSystem m_mergeGlobals; + private ManagedComponentsReactiveSystemGroup m_cleanupGroup; + private LatiosWorldSyncGroup m_syncGroup; + private PreSyncPointGroup m_preSyncGroup; + + protected override void OnCreate() + { + base.OnCreate(); + m_syncPlayback = World.CreateSystem(); + m_mergeGlobals = World.CreateSystem(); + m_cleanupGroup = World.CreateSystem(); + m_syncGroup = World.GetOrCreateSystem(); + m_preSyncGroup = World.GetOrCreateSystem(); + AddSystemToUpdateList(m_syncPlayback); + AddSystemToUpdateList(m_syncGroup); + AddSystemToUpdateList(m_preSyncGroup); + m_syncGroup.AddSystemToUpdateList(m_mergeGlobals); + m_syncGroup.AddSystemToUpdateList(m_cleanupGroup); + } + + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME + this.ClearSystemIds(); +#endif + LatiosWorld lw = World as LatiosWorld; + lw.FrameStart(); + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } + + [DisableAutoCreation, NoGroupInjection, AlwaysUpdateSystem] + public class LatiosClientInitializationSystemGroup : ClientInitializationSystemGroup + { + private SyncPointPlaybackSystem m_syncPlayback; + private MergeBlackboardsSystem m_mergeGlobals; + private ManagedComponentsReactiveSystemGroup m_cleanupGroup; + private LatiosWorldSyncGroup m_syncGroup; + private PreSyncPointGroup m_preSyncGroup; + + protected override void OnCreate() + { + base.OnCreate(); + m_syncPlayback = World.CreateSystem(); + m_mergeGlobals = World.CreateSystem(); + m_cleanupGroup = World.CreateSystem(); + m_syncGroup = World.GetOrCreateSystem(); + m_preSyncGroup = World.GetOrCreateSystem(); + AddSystemToUpdateList(m_syncPlayback); + AddSystemToUpdateList(m_syncGroup); + AddSystemToUpdateList(m_preSyncGroup); + + m_syncGroup.AddSystemToUpdateList(m_cleanupGroup); + m_syncGroup.AddSystemToUpdateList(m_mergeGlobals); + } + + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME + this.ClearSystemIds(); +#endif + LatiosWorld lw = World as LatiosWorld; + lw.FrameStart(); + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } + + [DisableAutoCreation, NoGroupInjection] + public class LatiosServerSimulationSystemGroup : ServerSimulationSystemGroup + { + InsideOutRateManager m_insideOutHook = new InsideOutRateManager(); + + protected override void OnUpdate() + { + // This conditional check shouldn't be necessary, but serves as a cheap self-cleansing. + m_insideOutHook.m_realRateManager = RateManager != m_insideOutHook ? RateManager : null; + RateManager = m_insideOutHook; + base.OnUpdate(); + RateManager = m_insideOutHook.m_realRateManager; + } + } + + [DisableAutoCreation, NoGroupInjection] + public class LatiosClientSimulationSystemGroup : ClientSimulationSystemGroup + { + InsideOutRateManager m_insideOutHook = new InsideOutRateManager(); + + protected override void OnUpdate() + { + // This conditional check shouldn't be necessary, but serves as a cheap self-cleansing. + m_insideOutHook.m_realRateManager = RateManager != m_insideOutHook ? RateManager : null; + RateManager = m_insideOutHook; + base.OnUpdate(); + RateManager = m_insideOutHook.m_realRateManager; + } + } + + [DisableAutoCreation, NoGroupInjection] + public class LatiosClientPresentationSystemGroup : ClientPresentationSystemGroup + { + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { + if (HasSingleton()) + return; + + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } + + internal class InsideOutRateManager : IRateManager + { + SystemSortingTracker m_tracker; + internal IRateManager m_realRateManager; + + public float Timestep { get; set; } + + public bool ShouldGroupUpdate(ComponentSystemGroup group) + { + group.RateManager = m_realRateManager; + SuperSystem.DoSuperSystemUpdate(group, ref m_tracker); + + // If the group's rate manager changed, capture it. + m_realRateManager = group.RateManager; + + group.RateManager = this; + return false; + } + } +} +#endif + diff --git a/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/BaseSystems.cs.meta b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/BaseSystems.cs.meta new file mode 100644 index 0000000..2f18c10 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/BaseSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9026b3817eba0cc4687d0960a6e2c14b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/LatiosClientServerBootstrapBase.cs b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/LatiosClientServerBootstrapBase.cs new file mode 100644 index 0000000..c77bdc0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/LatiosClientServerBootstrapBase.cs @@ -0,0 +1,274 @@ +#if NETCODE_PROJECT +using System; +using System.Collections.Generic; +using System.Reflection; +using Unity.Entities; +using Unity.NetCode; +using Unity.Networking.Transport; + +namespace Latios.Compatibility.UnityNetCode +{ + public abstract class LatiosClientServerBootstrapBase : ClientServerBootstrap + { + public abstract World CreateCustomServerWorld(string worldName); + public abstract World CreateCustomClientWorld(string worldName); + + public IReadOnlyList ServerSystems + { + get + { + if (s_serverSystems == null) + BuildSystemsCache(); + return s_serverSystems; + } + } + public IReadOnlyList ClientSystems + { + get + { + if (s_clientSystems == null) + BuildSystemsCache(); + return s_clientSystems; + } + } + public IReadOnlyDictionary ServerGroupRemap + { + get + { + if (s_serverRemap == null) + BuildRemapCache(); + return s_serverRemap; + } + } + public IReadOnlyDictionary ClientGroupRemap + { + get + { + if (s_clientRemap == null) + BuildRemapCache(); + return s_clientRemap; + } + } + + public override void CreateDefaultClientServerWorlds(World defaultWorld) + { + PlayType playModeType = RequestedPlayType; + int numClientWorlds = 1; + int totalNumClients = numClientWorlds; + + if (playModeType == PlayType.Server || playModeType == PlayType.ClientAndServer) + { + CreateAndWrapServerWorld(defaultWorld, "ServerWorld"); + } + + if (playModeType != PlayType.Server) + { +#if UNITY_EDITOR + int numThinClients = RequestedNumThinClients; + totalNumClients += numThinClients; +#endif + for (int i = 0; i < numClientWorlds; ++i) + { + CreateAndWrapClientWorld(defaultWorld, "ClientWorld" + i); + } +#if UNITY_EDITOR + for (int i = numClientWorlds; i < totalNumClients; ++i) + { + var clientWorld = CreateAndWrapClientWorld(defaultWorld, "ClientWorld" + i); + clientWorld.EntityManager.CreateEntity(typeof(ThinClientComponent)); + } +#endif + } + } + + private static List s_clientSystems; + private static List s_serverSystems; + + private static Dictionary s_clientRemap; + private static Dictionary s_serverRemap; + + private void BuildSystemsCache() + { + var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default); + + // We call this for now because it performs error checking. + GenerateSystemLists(systems); + + s_clientSystems = new List(); + s_serverSystems = new List(); + + foreach (var s in systems) + { + var mask = GetTopLevelWorldMask(s); + if ((mask & WorldType.ServerWorld) != 0) + s_serverSystems.Add(s); + if ((mask & WorldType.ClientWorld) != 0) + s_clientSystems.Add(s); + } + } + + private void BuildRemapCache() + { + s_clientRemap = new Dictionary(); + s_serverRemap = new Dictionary(); + + s_clientRemap.Add(typeof(Systems.LatiosInitializationSystemGroup), typeof(LatiosClientInitializationSystemGroup)); + s_clientRemap.Add(typeof(ClientAndServerInitializationSystemGroup), typeof(LatiosClientInitializationSystemGroup)); + s_clientRemap.Add(typeof(Systems.LatiosSimulationSystemGroup), typeof(LatiosClientSimulationSystemGroup)); + s_clientRemap.Add(typeof(ClientAndServerSimulationSystemGroup), typeof(LatiosClientSimulationSystemGroup)); + s_clientRemap.Add(typeof(ServerInitializationSystemGroup), null); + s_clientRemap.Add(typeof(LatiosServerInitializationSystemGroup), null); + s_clientRemap.Add(typeof(ServerSimulationSystemGroup), null); + s_clientRemap.Add(typeof(LatiosServerSimulationSystemGroup), null); + + s_serverRemap.Add(typeof(Systems.LatiosInitializationSystemGroup), typeof(LatiosServerInitializationSystemGroup)); + s_serverRemap.Add(typeof(ClientAndServerInitializationSystemGroup), typeof(LatiosServerInitializationSystemGroup)); + s_serverRemap.Add(typeof(Systems.LatiosSimulationSystemGroup), typeof(LatiosServerSimulationSystemGroup)); + s_serverRemap.Add(typeof(ClientAndServerSimulationSystemGroup), typeof(LatiosServerSimulationSystemGroup)); + s_serverRemap.Add(typeof(ClientInitializationSystemGroup), null); + s_serverRemap.Add(typeof(LatiosClientInitializationSystemGroup), null); + s_serverRemap.Add(typeof(ClientSimulationSystemGroup), null); + s_serverRemap.Add(typeof(LatiosClientSimulationSystemGroup), null); + } + + [Flags] + private enum WorldType + { + NoWorld = 0, + DefaultWorld = 1, + ClientWorld = 2, + ServerWorld = 4, + ExplicitWorld = 8 + } + + static WorldType GetTopLevelWorldMask(Type type) + { + var targetWorld = GetSystemAttribute(type); + if (targetWorld != null) + { + if (targetWorld.World == TargetWorld.Default) + return WorldType.DefaultWorld | WorldType.ExplicitWorld; + if (targetWorld.World == TargetWorld.Client) + return WorldType.ClientWorld; + if (targetWorld.World == TargetWorld.Server) + return WorldType.ServerWorld; + return WorldType.ClientWorld | WorldType.ServerWorld; + } + + var groups = TypeManager.GetSystemAttributes(type, typeof(UpdateInGroupAttribute)); + if (groups.Length == 0) + { + if (typeof(ClientAndServerSimulationSystemGroup).IsAssignableFrom(type) || + typeof(ClientAndServerInitializationSystemGroup).IsAssignableFrom(type)) + return WorldType.ClientWorld | WorldType.ServerWorld; + if (typeof(ServerSimulationSystemGroup).IsAssignableFrom(type) || typeof(ServerInitializationSystemGroup).IsAssignableFrom(type)) + return WorldType.ServerWorld; + if (typeof(ClientSimulationSystemGroup).IsAssignableFrom(type) || + typeof(ClientInitializationSystemGroup).IsAssignableFrom(type)) + return WorldType.ClientWorld; + if (typeof(SimulationSystemGroup).IsAssignableFrom(type) || typeof(InitializationSystemGroup).IsAssignableFrom(type)) + return WorldType.DefaultWorld | WorldType.ClientWorld | WorldType.ServerWorld; + if (typeof(PresentationSystemGroup).IsAssignableFrom(type)) + return WorldType.DefaultWorld | WorldType.ClientWorld; + // Empty means the same thing as SimulationSystemGroup + return WorldType.DefaultWorld | WorldType.ClientWorld | WorldType.ServerWorld; + } + + WorldType mask = WorldType.NoWorld; + foreach (var grp in groups) + { + var group = grp as UpdateInGroupAttribute; + mask |= GetTopLevelWorldMask(group.GroupType); + } + + return mask; + } + + private World CreateAndWrapServerWorld(World defaultWorld, string serverWorldName) + { +#if UNITY_CLIENT && !UNITY_SERVER && !UNITY_EDITOR + throw new NotImplementedException(); +#else + var serverWorld = CreateCustomServerWorld(serverWorldName); + + // Todo: Make users install into PlayerLoop? Would allow for N-1 loops. + ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(serverWorld); + + if (AutoConnectPort != 0) + serverWorld.GetExistingSystem().Listen(DefaultListenAddress.WithPort(AutoConnectPort)); + + return serverWorld; +#endif + } + + private World CreateAndWrapClientWorld(World defaultWorld, string clientWorldName) + { +#if UNITY_SERVER + throw new NotImplementedException(); +#else + + var clientWorld = CreateCustomClientWorld(clientWorldName); + + // Todo: Make users install into PlayerLoop? Would allow for N-1 loops. + ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(clientWorld); + + if (AutoConnectPort != 0 && DefaultConnectAddress != NetworkEndPoint.AnyIpv4) + { + NetworkEndPoint ep; +#if UNITY_EDITOR + var addr = RequestedAutoConnect; + if (!NetworkEndPoint.TryParse(addr, AutoConnectPort, out ep)) +#endif + ep = DefaultConnectAddress.WithPort(AutoConnectPort); + clientWorld.GetExistingSystem().Connect(ep); + } + return clientWorld; +#endif + } + + static class ReflectionHelper + { + // From: + // http://dotnetfollower.com/wordpress/2012/12/c-how-to-set-or-get-value-of-a-private-or-internal-property-through-the-reflection/ + + private static FieldInfo GetFieldInfo(Type type, string fieldName) + { + FieldInfo fieldInfo; + do + { + fieldInfo = type.GetField(fieldName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } + while (fieldInfo == null && type != null); + return fieldInfo; + } + + public static object GetFieldValue(object obj, string fieldName) + { + if (obj == null) + throw new ArgumentNullException("obj"); + Type objType = obj.GetType(); + FieldInfo fieldInfo = GetFieldInfo(objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException("fieldName", + string.Format("Couldn't find field {0} in type {1}", fieldName, objType.FullName)); + return fieldInfo.GetValue(obj); + } + + public static void SetFieldValue(object obj, string fieldName, object val) + { + if (obj == null) + throw new ArgumentNullException("obj"); + Type objType = obj.GetType(); + FieldInfo fieldInfo = GetFieldInfo(objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException("fieldName", + string.Format("Couldn't find field {0} in type {1}", fieldName, objType.FullName)); + fieldInfo.SetValue(obj, val); + } + } + } +} +#endif + diff --git a/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/LatiosClientServerBootstrapBase.cs.meta b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/LatiosClientServerBootstrapBase.cs.meta new file mode 100644 index 0000000..064941b --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/IntegrationCompatibility/UnityNetCode/LatiosClientServerBootstrapBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f66a74e8ff53927478d4a06335bd4917 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal.meta b/Packages/com.latios.latios-framework/Core/Core/Internal.meta new file mode 100644 index 0000000..4670f45 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: afd5907839d20eb4eaf69543494becb3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/DeferredSimulationEndFrameController.cs b/Packages/com.latios.latios-framework/Core/Core/Internal/DeferredSimulationEndFrameController.cs new file mode 100644 index 0000000..754cd20 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/DeferredSimulationEndFrameController.cs @@ -0,0 +1,55 @@ +using System.Collections; +using System.Collections.Generic; +using Unity.Entities; +using UnityEngine; + +namespace Latios +{ + internal class DeferredSimulationEndFrameController : MonoBehaviour + { + internal bool done = false; + internal Systems.LatiosSimulationSystemGroup simGroup; + + IEnumerator Start() + { + var endOfFrame = new WaitForEndOfFrame(); + + while (!done) + { + simGroup.skipInDeferred = false; + simGroup.Update(); + simGroup.skipInDeferred = true; + yield return endOfFrame; + } + } + } + + [UpdateInGroup(typeof(PresentationSystemGroup))] + [DisableAutoCreation] + internal partial class DeferredSimulationEndFrameControllerSystem : SubSystem + { + GameObject m_controllerObject = null; + + protected override void OnUpdate() + { + if (m_controllerObject == null) + { + m_controllerObject = new GameObject(); + var controller = m_controllerObject.AddComponent(); + controller.simGroup = World.GetExistingSystem(); + controller.simGroup.skipInDeferred = true; + m_controllerObject.hideFlags = HideFlags.HideAndDontSave; + } + } + + protected override void OnDestroy() + { + if (m_controllerObject != null) + { + m_controllerObject.GetComponent().done = true; + GameObject.Destroy(m_controllerObject); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/DeferredSimulationEndFrameController.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Internal/DeferredSimulationEndFrameController.cs.meta new file mode 100644 index 0000000..84f0178 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/DeferredSimulationEndFrameController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 671a14bd996cc7d4bb7bd0a55eb8cb92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/InstantiateCommandBufferUntyped.cs b/Packages/com.latios.latios-framework/Core/Core/Internal/InstantiateCommandBufferUntyped.cs new file mode 100644 index 0000000..1e6ed53 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/InstantiateCommandBufferUntyped.cs @@ -0,0 +1,879 @@ +using System; +using System.Diagnostics; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios +{ + [NativeContainer] + internal unsafe struct InstantiateCommandBufferUntyped : INativeDisposable + { + #region Structure + [NativeDisableUnsafePtrRestriction] + private UnsafeParallelBlockList* m_prefabSortkeyBlockList; + [NativeDisableUnsafePtrRestriction] + private UnsafeParallelBlockList* m_componentDataBlockList; + + [NativeDisableUnsafePtrRestriction] + private State* m_state; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + //Unfortunately this name is hardcoded into Unity. No idea how EntityCommandBuffer gets away with multiple safety handles. + AtomicSafetyHandle m_Safety; + + static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); + + [NativeSetClassTypeToNullOnSchedule] + //Unfortunately this name is hardcoded into Unity. + DisposeSentinel m_DisposeSentinel; +#endif + + private struct State + { + public ComponentTypes tagsToAdd; + public FixedList64Bytes typesWithData; + public FixedList64Bytes typesSizes; + public AllocatorManager.AllocatorHandle allocator; + public bool playedBack; + } + + private struct PrefabSortkey : IRadixSortableInt3 + { + public Entity prefab; + public int sortKey; + + public int3 GetKey3() + { + return new int3(prefab.Index, prefab.Version, sortKey); + } + } + #endregion + + #region CreateDestroy + public InstantiateCommandBufferUntyped(AllocatorManager.AllocatorHandle allocator, FixedList64Bytes typesWithData) : this(allocator, typesWithData, 1) + { + } + + internal InstantiateCommandBufferUntyped(AllocatorManager.AllocatorHandle allocator, FixedList64Bytes componentTypesWithData, int disposeSentinalStackDepth) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckAllocator(allocator); + + if (allocator.IsCustomAllocator) + { + m_Safety = AtomicSafetyHandle.Create(); + m_DisposeSentinel = null; + } + else + { + DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 2, allocator.ToAllocator); + } + + CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); + AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(m_Safety, true); +#endif + + int dataPayloadSize = 0; + FixedList64Bytes typesSizes = new FixedList64Bytes(); + FixedList64Bytes typesWithData = new FixedList64Bytes(); + for (int i = 0; i < componentTypesWithData.Length; i++) + { + var size = TypeManager.GetTypeInfo(componentTypesWithData[i].TypeIndex).ElementSize; + dataPayloadSize += size; + typesSizes.Add(size); + typesWithData.Add(componentTypesWithData[i].TypeIndex); + } + CheckComponentTypesValid(BuildComponentTypesFromFixedList(typesWithData)); + m_prefabSortkeyBlockList = AllocatorManager.Allocate(allocator, 1); + m_componentDataBlockList = AllocatorManager.Allocate(allocator, 1); + m_state = AllocatorManager.Allocate(allocator, 1); + *m_prefabSortkeyBlockList = new UnsafeParallelBlockList(UnsafeUtility.SizeOf(), 256, allocator); + *m_componentDataBlockList = new UnsafeParallelBlockList(dataPayloadSize, 256, allocator); + *m_state = new State + { + typesWithData = typesWithData, + tagsToAdd = default, + typesSizes = typesSizes, + allocator = allocator, + playedBack = false + }; + } + + [BurstCompile] + private struct DisposeJob : IJob + { + [NativeDisableUnsafePtrRestriction] + public State* state; + + [NativeDisableUnsafePtrRestriction] + public UnsafeParallelBlockList* prefabSortkeyBlockList; + [NativeDisableUnsafePtrRestriction] + public UnsafeParallelBlockList* componentDataBlockList; + + public void Execute() + { + Deallocate(state, prefabSortkeyBlockList, componentDataBlockList); + } + } + + public JobHandle Dispose(JobHandle inputDeps) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // [DeallocateOnJobCompletion] is not supported, but we want the deallocation + // to happen in a thread. DisposeSentinel needs to be cleared on main thread. + // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling + // will check that no jobs are writing to the container). + DisposeSentinel.Clear(ref m_DisposeSentinel); +#endif + var jobHandle = new DisposeJob + { + prefabSortkeyBlockList = m_prefabSortkeyBlockList, + componentDataBlockList = m_componentDataBlockList, + state = m_state + }.Schedule(inputDeps); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.Release(m_Safety); +#endif + return jobHandle; + } + + public void Dispose() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); +#endif + Deallocate(m_state, m_prefabSortkeyBlockList, m_componentDataBlockList); + } + + private static void Deallocate(State* state, UnsafeParallelBlockList* prefabSortkeyBlockList, UnsafeParallelBlockList* componentDataBlockList) + { + var allocator = state->allocator; + prefabSortkeyBlockList->Dispose(); + componentDataBlockList->Dispose(); + AllocatorManager.Free(allocator, prefabSortkeyBlockList, 1); + AllocatorManager.Free(allocator, componentDataBlockList, 1); + AllocatorManager.Free(allocator, state, 1); + //UnsafeUtility.Free(prefabSortkeyBlockList, allocator); + //UnsafeUtility.Free(componentDataBlockList, allocator); + //UnsafeUtility.Free(state, allocator); + } + #endregion + + #region API + [WriteAccessRequired] + public void Add(Entity prefab, T0 c0, int sortKey = int.MaxValue) where T0 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, 0); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(0); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + } + [WriteAccessRequired] + public void Add(Entity prefab, T0 c0, T1 c1, int sortKey = int.MaxValue) where T0 : unmanaged where T1 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, 0); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(0); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + } + [WriteAccessRequired] + public void Add(Entity prefab, T0 c0, T1 c1, T2 c2, int sortKey = int.MaxValue) where T0 : unmanaged where T1 : unmanaged where T2 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, 0); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(0); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + ptr += m_state->typesSizes[1]; + UnsafeUtility.CopyStructureToPtr(ref c2, ptr); + } + [WriteAccessRequired] + public void Add(Entity prefab, T0 c0, T1 c1, T2 c2, T3 c3, + int sortKey = int.MaxValue) where T0 : unmanaged where T1 : unmanaged where T2 : unmanaged where T3 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, 0); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(0); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + ptr += m_state->typesSizes[1]; + UnsafeUtility.CopyStructureToPtr(ref c2, ptr); + ptr += m_state->typesSizes[2]; + UnsafeUtility.CopyStructureToPtr(ref c3, ptr); + } + [WriteAccessRequired] + public void Add(Entity prefab, T0 c0, T1 c1, T2 c2, T3 c3, T4 c4, + int sortKey = int.MaxValue) where T0 : unmanaged where T1 : unmanaged where T2 : unmanaged where T3 : unmanaged where T4 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, 0); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(0); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + ptr += m_state->typesSizes[1]; + UnsafeUtility.CopyStructureToPtr(ref c2, ptr); + ptr += m_state->typesSizes[2]; + UnsafeUtility.CopyStructureToPtr(ref c3, ptr); + ptr += m_state->typesSizes[3]; + UnsafeUtility.CopyStructureToPtr(ref c4, ptr); + } + + public int Count() + { + CheckReadAccess(); + return m_prefabSortkeyBlockList->Count(); + } + + public void Playback(EntityManager entityManager) + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + var chunkRanges = new NativeList(Allocator.TempJob); + var chunks = new NativeList(Allocator.TempJob); + var indicesInChunks = new NativeList(Allocator.TempJob); + var componentDataPtrs = new NativeList(Allocator.TempJob); + entityManager.CompleteAllJobs(); + //var eet = entityManager.BeginExclusiveEntityTransaction(); + //Run job that instantiates entities and populates hashmap + var job0 = new InstantiateAndBuildListsJob + { + icb = this, + //eet = eet, + em = entityManager, + chunks = chunks, + chunkRanges = chunkRanges, + indicesInChunks = indicesInChunks, + componentDataPtrs = componentDataPtrs + }; + job0.RunOrExecute(); + //entityManager.EndExclusiveEntityTransaction(); + //Schedule parallel job to populate data + var chunkJob = new WriteComponentDataJob + { + icb = this, + chunks = chunks, + chunkRanges = chunkRanges, + indicesInChunks = indicesInChunks, + componentDataPtrs = componentDataPtrs, + entityHandle = entityManager.GetEntityTypeHandle(), + t0 = entityManager.GetDynamicComponentTypeHandle(ComponentType.ReadWrite(m_state->typesWithData[0])) + }; + if (m_state->typesWithData.Length > 1) + chunkJob.t1 = entityManager.GetDynamicComponentTypeHandle(ComponentType.ReadWrite(m_state->typesWithData[1])); + if (m_state->typesWithData.Length > 2) + chunkJob.t2 = entityManager.GetDynamicComponentTypeHandle(ComponentType.ReadWrite(m_state->typesWithData[2])); + if (m_state->typesWithData.Length > 3) + chunkJob.t3 = entityManager.GetDynamicComponentTypeHandle(ComponentType.ReadWrite(m_state->typesWithData[3])); + if (m_state->typesWithData.Length > 4) + chunkJob.t4 = entityManager.GetDynamicComponentTypeHandle(ComponentType.ReadWrite(m_state->typesWithData[4])); + //The remaining types apparently need to be initialized. So set them to the dummy types. + if (m_state->typesWithData.Length <= 1) + chunkJob.t1 = entityManager.GetDynamicComponentTypeHandle(typeof(DummyTypeT1)); + if (m_state->typesWithData.Length <= 2) + chunkJob.t2 = entityManager.GetDynamicComponentTypeHandle(typeof(DummyTypeT2)); + if (m_state->typesWithData.Length <= 3) + chunkJob.t3 = entityManager.GetDynamicComponentTypeHandle(typeof(DummyTypeT3)); + if (m_state->typesWithData.Length <= 4) + chunkJob.t4 = entityManager.GetDynamicComponentTypeHandle(typeof(DummyTypeT4)); + //chunkJob.ScheduleParallel(chunks.Length, 1, default).Complete(); + chunkJob.RunOrExecute(chunks.Length); + m_state->playedBack = true; + chunks.Dispose(); + chunkRanges.Dispose(); + indicesInChunks.Dispose(); + componentDataPtrs.Dispose(); + } + + public void SetTags(ComponentTypes types) + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + m_state->tagsToAdd = types; + } + + public void AddTag(ComponentType type) + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + switch (m_state->tagsToAdd.Length) + { + case 0: m_state->tagsToAdd = new ComponentTypes(type); break; + case 1: m_state->tagsToAdd = new ComponentTypes(m_state->tagsToAdd.GetComponentType(0), type); break; + case 2: m_state->tagsToAdd = new ComponentTypes(m_state->tagsToAdd.GetComponentType(0), m_state->tagsToAdd.GetComponentType(1), type); break; + case 3: m_state->tagsToAdd = + new ComponentTypes(m_state->tagsToAdd.GetComponentType(0), m_state->tagsToAdd.GetComponentType(1), m_state->tagsToAdd.GetComponentType(2), type); break; + case 4: m_state->tagsToAdd = + new ComponentTypes(m_state->tagsToAdd.GetComponentType(0), + m_state->tagsToAdd.GetComponentType(1), + m_state->tagsToAdd.GetComponentType(2), + m_state->tagsToAdd.GetComponentType(3), + type); break; + case var n when n >= 5 && n < 15: + { + FixedList128Bytes types = default; + for (int i = 0; i < m_state->tagsToAdd.Length; i++) + types.Add(m_state->tagsToAdd.GetComponentType(i)); + types.Add(type); + m_state->tagsToAdd = new ComponentTypes(types); + break; + } + default: ThrowTooManyTags(); break; + } + } + + public ParallelWriter AsParallelWriter() + { + var writer = new ParallelWriter(m_prefabSortkeyBlockList, m_componentDataBlockList, m_state); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + writer.m_Safety = m_Safety; + CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data); +#endif + return writer; + } + #endregion + + #region Implementation + [BurstCompile] + private struct InstantiateAndBuildListsJob : IJob + { + [ReadOnly] public InstantiateCommandBufferUntyped icb; + //public ExclusiveEntityTransaction eet; + public EntityManager em; + + public NativeList chunks; + public NativeList chunkRanges; + public NativeList indicesInChunks; + public NativeList componentDataPtrs; + + public void Execute() + { + //Step 1: Get the prefabs and sort keys + int count = icb.Count(); + var prefabSortkeyArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + icb.m_prefabSortkeyBlockList->GetElementValues(prefabSortkeyArray); + //Step 2: Get the componentData pointers + var unsortedComponentDataPtrs = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + icb.m_componentDataBlockList->GetElementPtrs(unsortedComponentDataPtrs); + //Step 3: Sort the arrays by sort key and collapse unique entities + var ranks = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + RadixSort.RankSortInt3(ranks, prefabSortkeyArray); + var sortedPrefabs = new NativeList(count, Allocator.Temp); + var sortedPrefabCounts = new NativeList(count, Allocator.Temp); + var sortedComponentDataPtrs = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + Entity lastEntity = Entity.Null; + for (int i = 0; i < count; i++) + { + var entity = prefabSortkeyArray[ranks[i]].prefab; + sortedComponentDataPtrs[i] = unsortedComponentDataPtrs[ranks[i]]; + if (entity != lastEntity) + { + sortedPrefabs.AddNoResize(entity); + sortedPrefabCounts.AddNoResize(1); + lastEntity = entity; + } + else + { + ref var c = ref sortedPrefabCounts.ElementAt(sortedPrefabCounts.Length - 1); + c++; + } + } + //Step 4: Instantiate the prefabs + var instantiatedEntities = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var typesWithDataToAdd = BuildComponentTypesFromFixedList(icb.m_state->typesWithData); + int startIndex = 0; + for (int i = 0; i < sortedPrefabs.Length; i++) + { + //var firstEntity = eet.Instantiate(sortedPrefabs[i]); + //eet.EntityManager.AddComponents(firstEntity, typesWithDataToAdd); + //eet.EntityManager.AddComponents(firstEntity, icb.m_state->tagsToAdd); + var firstEntity = em.Instantiate(sortedPrefabs[i]); + em.AddComponents(firstEntity, typesWithDataToAdd); + em.AddComponents(firstEntity, icb.m_state->tagsToAdd); + instantiatedEntities[startIndex] = firstEntity; + startIndex++; + + if (sortedPrefabCounts[i] - 1 > 0) + { + var subArray = instantiatedEntities.GetSubArray(startIndex, sortedPrefabCounts[i] - 1); + //eet.Instantiate(firstEntity, subArray); + em.Instantiate(firstEntity, subArray); + startIndex += subArray.Length; + } + } + //Step 5: Get locations of new entities + var locations = new NativeArray(count, Allocator.Temp); + for (int i = 0; i < count; i++) + { + //locations[i] = eet.EntityManager.GetEntityLocationInChunk(instantiatedEntities[i]); + locations[i] = em.GetEntityLocationInChunk(instantiatedEntities[i]); + } + //Step 6: Sort chunks and build final lists + RadixSort.RankSortInt3(ranks, locations.Reinterpret()); + chunks.Capacity = count; + chunkRanges.Capacity = count; + indicesInChunks.ResizeUninitialized(count); + componentDataPtrs.ResizeUninitialized(count); + ArchetypeChunk lastChunk = default; + for (int i = 0; i < count; i++) + { + var loc = locations[ranks[i]]; + indicesInChunks[i] = loc.indexInChunk; + componentDataPtrs[i] = sortedComponentDataPtrs[ranks[i]]; + if (loc.chunk != lastChunk) + { + chunks.AddNoResize(loc.chunk); + chunkRanges.AddNoResize(new int2(i, 1)); + lastChunk = loc.chunk; + } + else + { + ref var c = ref chunkRanges.ElementAt(chunkRanges.Length - 1); + c.y++; + } + } + } + + struct WrappedEntityLocationInChunk : IRadixSortableInt3 + { + public EntityLocationInChunk elic; + + public int3 GetKey3() + { + var c = elic.ChunkAddressAsUlong; + int x = (int)(c >> 32); + int y = (int)(c & 0xFFFFFFFF); + int z = elic.indexInChunk; + return new int3(x, y, z); + } + } + + public void RunOrExecute() + { + bool ran = false; + TryRun(ref ran); + if (!ran) + Execute(); + } + + [BurstDiscard] + void TryRun(ref bool ran) + { + this.Run(); + ran = true; + } + } + + [BurstCompile] + private struct WriteComponentDataJob : IJobFor + { + [ReadOnly] public InstantiateCommandBufferUntyped icb; + [ReadOnly] public NativeArray chunks; + [ReadOnly] public NativeArray chunkRanges; + [ReadOnly] public NativeArray indicesInChunks; + [ReadOnly] public NativeArray componentDataPtrs; + [ReadOnly] public EntityTypeHandle entityHandle; + public DynamicComponentTypeHandle t0; + public DynamicComponentTypeHandle t1; + public DynamicComponentTypeHandle t2; + public DynamicComponentTypeHandle t3; + public DynamicComponentTypeHandle t4; + + public void Execute(int i) + { + var chunk = chunks[i]; + var range = chunkRanges[i]; + var indices = indicesInChunks.GetSubArray(range.x, range.y); + var ptrs = componentDataPtrs.GetSubArray(range.x, range.y); + switch(icb.m_state->typesSizes.Length) + { + case 1: DoT0(chunk, indices, ptrs); return; + case 2: DoT1(chunk, indices, ptrs); return; + case 3: DoT2(chunk, indices, ptrs); return; + case 4: DoT3(chunk, indices, ptrs); return; + case 5: DoT4(chunk, indices, ptrs); return; + } + } + + void DoT0(ArchetypeChunk chunk, NativeArray indices, NativeArray dataPtrs) + { + var entities = chunk.GetNativeArray(entityHandle); + var t0Size = icb.m_state->typesSizes[0]; + var t0Array = chunk.GetDynamicComponentDataArrayReinterpret(t0, t0Size); + byte* t0Ptr = (byte*)t0Array.GetUnsafePtr(); + for (int i = 0; i < indices.Length; i++) + { + var index = indices[i]; + var dataPtr = dataPtrs[i].ptr; + UnsafeUtility.MemCpy(t0Ptr + index * t0Size, dataPtr, t0Size); + } + } + + void DoT1(ArchetypeChunk chunk, NativeArray indices, NativeArray dataPtrs) + { + var entities = chunk.GetNativeArray(entityHandle); + var t0Size = icb.m_state->typesSizes[0]; + var t1Size = icb.m_state->typesSizes[1]; + var t0Array = chunk.GetDynamicComponentDataArrayReinterpret(t0, t0Size); + var t1Array = chunk.GetDynamicComponentDataArrayReinterpret(t1, t1Size); + byte* t0Ptr = (byte*)t0Array.GetUnsafePtr(); + byte* t1Ptr = (byte*)t1Array.GetUnsafePtr(); + for (int i = 0; i < indices.Length; i++) + { + var index = indices[i]; + var dataPtr = dataPtrs[i].ptr; + UnsafeUtility.MemCpy(t0Ptr + index * t0Size, dataPtr, t0Size); + dataPtr += t0Size; + UnsafeUtility.MemCpy(t1Ptr + index * t1Size, dataPtr, t1Size); + } + } + + void DoT2(ArchetypeChunk chunk, NativeArray indices, NativeArray dataPtrs) + { + var entities = chunk.GetNativeArray(entityHandle); + var t0Size = icb.m_state->typesSizes[0]; + var t1Size = icb.m_state->typesSizes[1]; + var t2Size = icb.m_state->typesSizes[2]; + var t0Array = chunk.GetDynamicComponentDataArrayReinterpret(t0, t0Size); + var t1Array = chunk.GetDynamicComponentDataArrayReinterpret(t1, t1Size); + var t2Array = chunk.GetDynamicComponentDataArrayReinterpret(t2, t2Size); + byte* t0Ptr = (byte*)t0Array.GetUnsafePtr(); + byte* t1Ptr = (byte*)t1Array.GetUnsafePtr(); + byte* t2Ptr = (byte*)t2Array.GetUnsafePtr(); + + for (int i = 0; i < indices.Length; i++) + { + var index = indices[i]; + var dataPtr = dataPtrs[i].ptr; + UnsafeUtility.MemCpy(t0Ptr + index * t0Size, dataPtr, t0Size); + dataPtr += t0Size; + UnsafeUtility.MemCpy(t1Ptr + index * t1Size, dataPtr, t1Size); + dataPtr += t1Size; + UnsafeUtility.MemCpy(t2Ptr + index * t2Size, dataPtr, t2Size); + } + } + + void DoT3(ArchetypeChunk chunk, NativeArray indices, NativeArray dataPtrs) + { + var entities = chunk.GetNativeArray(entityHandle); + var t0Size = icb.m_state->typesSizes[0]; + var t1Size = icb.m_state->typesSizes[1]; + var t2Size = icb.m_state->typesSizes[2]; + var t3Size = icb.m_state->typesSizes[3]; + var t0Array = chunk.GetDynamicComponentDataArrayReinterpret(t0, t0Size); + var t1Array = chunk.GetDynamicComponentDataArrayReinterpret(t1, t1Size); + var t2Array = chunk.GetDynamicComponentDataArrayReinterpret(t2, t2Size); + var t3Array = chunk.GetDynamicComponentDataArrayReinterpret(t3, t3Size); + byte* t0Ptr = (byte*)t0Array.GetUnsafePtr(); + byte* t1Ptr = (byte*)t1Array.GetUnsafePtr(); + byte* t2Ptr = (byte*)t2Array.GetUnsafePtr(); + byte* t3Ptr = (byte*)t3Array.GetUnsafePtr(); + for (int i = 0; i < indices.Length; i++) + { + var index = indices[i]; + var dataPtr = dataPtrs[i].ptr; + UnsafeUtility.MemCpy(t0Ptr + index * t0Size, dataPtr, t0Size); + dataPtr += t0Size; + UnsafeUtility.MemCpy(t1Ptr + index * t1Size, dataPtr, t1Size); + dataPtr += t1Size; + UnsafeUtility.MemCpy(t2Ptr + index * t2Size, dataPtr, t2Size); + dataPtr += t2Size; + UnsafeUtility.MemCpy(t3Ptr + index * t3Size, dataPtr, t3Size); + } + } + + void DoT4(ArchetypeChunk chunk, NativeArray indices, NativeArray dataPtrs) + { + var entities = chunk.GetNativeArray(entityHandle); + var t0Size = icb.m_state->typesSizes[0]; + var t1Size = icb.m_state->typesSizes[1]; + var t2Size = icb.m_state->typesSizes[2]; + var t3Size = icb.m_state->typesSizes[3]; + var t4Size = icb.m_state->typesSizes[4]; + var t0Array = chunk.GetDynamicComponentDataArrayReinterpret(t0, t0Size); + var t1Array = chunk.GetDynamicComponentDataArrayReinterpret(t1, t1Size); + var t2Array = chunk.GetDynamicComponentDataArrayReinterpret(t2, t2Size); + var t3Array = chunk.GetDynamicComponentDataArrayReinterpret(t3, t3Size); + var t4Array = chunk.GetDynamicComponentDataArrayReinterpret(t4, t4Size); + byte* t0Ptr = (byte*)t0Array.GetUnsafePtr(); + byte* t1Ptr = (byte*)t1Array.GetUnsafePtr(); + byte* t2Ptr = (byte*)t2Array.GetUnsafePtr(); + byte* t3Ptr = (byte*)t3Array.GetUnsafePtr(); + byte* t4Ptr = (byte*)t4Array.GetUnsafePtr(); + for (int i = 0; i < indices.Length; i++) + { + var index = indices[i]; + var dataPtr = dataPtrs[i].ptr; + UnsafeUtility.MemCpy(t0Ptr + index * t0Size, dataPtr, t0Size); + dataPtr += t0Size; + UnsafeUtility.MemCpy(t1Ptr + index * t1Size, dataPtr, t1Size); + dataPtr += t1Size; + UnsafeUtility.MemCpy(t2Ptr + index * t2Size, dataPtr, t2Size); + dataPtr += t2Size; + UnsafeUtility.MemCpy(t3Ptr + index * t3Size, dataPtr, t3Size); + dataPtr += t3Size; + UnsafeUtility.MemCpy(t4Ptr + index * t4Size, dataPtr, t4Size); + } + } + + public void RunOrExecute(int length) + { + bool ran = false; + TryRun(length, ref ran); + if (!ran) + { + for (int i = 0; i < length; i++) + Execute(i); + } + } + + [BurstDiscard] + void TryRun(int length, ref bool ran) + { + this.Run(length); + ran = true; + } + } + +#pragma warning disable CS0649 + private struct DummyTypeT1 : IComponentData { public int dummy; } + private struct DummyTypeT2 : IComponentData { public int dummy; } + private struct DummyTypeT3 : IComponentData { public int dummy; } + private struct DummyTypeT4 : IComponentData { public int dummy; } +#pragma warning restore CS0649 + + static ComponentTypes BuildComponentTypesFromFixedList(FixedList64Bytes types) + { + switch (types.Length) + { + case 1: return new ComponentTypes(ComponentType.ReadWrite(types[0])); + case 2: return new ComponentTypes(ComponentType.ReadWrite(types[0]), ComponentType.ReadWrite(types[1])); + case 3: return new ComponentTypes(ComponentType.ReadWrite(types[0]), ComponentType.ReadWrite(types[1]), ComponentType.ReadWrite(types[2])); + case 4: return new ComponentTypes(ComponentType.ReadWrite(types[0]), ComponentType.ReadWrite(types[1]), ComponentType.ReadWrite(types[2]), + ComponentType.ReadWrite(types[3])); + case 5: return new ComponentTypes(ComponentType.ReadWrite(types[0]), ComponentType.ReadWrite(types[1]), ComponentType.ReadWrite(types[2]), + ComponentType.ReadWrite(types[3]), ComponentType.ReadWrite(types[4])); + default: return default; + } + } + #endregion + + #region Checks + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckWriteAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckReadAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckComponentTypesValid(ComponentTypes types) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (types.m_masks.m_ZeroSizedMask != 0) + throw new InvalidOperationException( + "InstantiateCommandBuffer cannot be created with zero-sized component types. You can instead add such types using AddTagComponent() after creation."); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckEntityValid(Entity entity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (entity == Entity.Null) + throw new InvalidOperationException("A null entity was added to the InstantiateCommandBuffer. This is not currently supported."); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckHasNotPlayedBack() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_state->playedBack) + throw new InvalidOperationException( + "InstantiateCommandBuffer has already been played back."); +#endif + } + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void ThrowTooManyTags() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException( + "At least 15 tags have already been added and adding more is not supported."); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckAllocator(AllocatorManager.AllocatorHandle allocator) + { + if (allocator.ToAllocator <= Allocator.None) +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new System.InvalidOperationException("Allocator cannot be Invalid or None"); +#endif + } + #endregion + + #region ParallelWriter + [NativeContainer] + [NativeContainerIsAtomicWriteOnly] + public struct ParallelWriter + { + [NativeDisableUnsafePtrRestriction] + private UnsafeParallelBlockList* m_prefabSortkeyBlockList; + [NativeDisableUnsafePtrRestriction] + private UnsafeParallelBlockList* m_componentDataBlockList; + + [NativeDisableUnsafePtrRestriction] + private State* m_state; + + [NativeSetThreadIndex] + private int m_ThreadIndex; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + //More ugly Unity naming + internal AtomicSafetyHandle m_Safety; + internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); +#endif + + internal ParallelWriter(UnsafeParallelBlockList* prefabSortkeyBlockList, UnsafeParallelBlockList* componentDataBlockList, void* state) + { + m_prefabSortkeyBlockList = prefabSortkeyBlockList; + m_componentDataBlockList = componentDataBlockList; + m_state = (State*)state; + m_ThreadIndex = 0; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_Safety = default; +#endif + } + + public void Add(Entity prefab, T0 c0, int sortKey) where T0 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, m_ThreadIndex); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(m_ThreadIndex); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + } + public void Add(Entity prefab, T0 c0, T1 c1, int sortKey) where T0 : unmanaged where T1 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, m_ThreadIndex); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(m_ThreadIndex); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + } + public void Add(Entity prefab, T0 c0, T1 c1, T2 c2, int sortKey) where T0 : unmanaged where T1 : unmanaged where T2 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, m_ThreadIndex); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(m_ThreadIndex); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + ptr += m_state->typesSizes[1]; + UnsafeUtility.CopyStructureToPtr(ref c2, ptr); + } + public void Add(Entity prefab, T0 c0, T1 c1, T2 c2, T3 c3, + int sortKey) where T0 : unmanaged where T1 : unmanaged where T2 : unmanaged where T3 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, m_ThreadIndex); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(m_ThreadIndex); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + ptr += m_state->typesSizes[1]; + UnsafeUtility.CopyStructureToPtr(ref c2, ptr); + ptr += m_state->typesSizes[2]; + UnsafeUtility.CopyStructureToPtr(ref c3, ptr); + } + public void Add(Entity prefab, T0 c0, T1 c1, T2 c2, T3 c3, T4 c4, + int sortKey) where T0 : unmanaged where T1 : unmanaged where T2 : unmanaged where T3 : unmanaged where T4 : unmanaged + { + CheckWriteAccess(); + CheckHasNotPlayedBack(); + CheckEntityValid(prefab); + m_prefabSortkeyBlockList->Write(new PrefabSortkey { prefab = prefab, sortKey = sortKey }, m_ThreadIndex); + byte* ptr = (byte*)m_componentDataBlockList->Allocate(m_ThreadIndex); + UnsafeUtility.CopyStructureToPtr(ref c0, ptr); + ptr += m_state->typesSizes[0]; + UnsafeUtility.CopyStructureToPtr(ref c1, ptr); + ptr += m_state->typesSizes[1]; + UnsafeUtility.CopyStructureToPtr(ref c2, ptr); + ptr += m_state->typesSizes[2]; + UnsafeUtility.CopyStructureToPtr(ref c3, ptr); + ptr += m_state->typesSizes[3]; + UnsafeUtility.CopyStructureToPtr(ref c4, ptr); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckWriteAccess() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckEntityValid(Entity entity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (entity == Entity.Null) + throw new InvalidOperationException("A null entity was added to the InstantiateCommandBuffer. This is not currently supported."); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckHasNotPlayedBack() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (m_state->playedBack) + throw new InvalidOperationException( + "InstantiateCommandBuffer has already been played back."); +#endif + } + } + #endregion + } +} + + + diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/InstantiateCommandBufferUntyped.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Internal/InstantiateCommandBufferUntyped.cs.meta new file mode 100644 index 0000000..54ed3f3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/InstantiateCommandBufferUntyped.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e482c911db260974a804e622d4557432 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/ManagedStructStorage.cs b/Packages/com.latios.latios-framework/Core/Core/Internal/ManagedStructStorage.cs new file mode 100644 index 0000000..6ee1ad7 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/ManagedStructStorage.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using UnityEngine.Assertions; + +namespace Latios +{ + internal class ManagedStructComponentStorage + { + private Dictionary m_typeMap = new Dictionary(); + private Dictionary m_associateMap = new Dictionary(); + + public ComponentType GetAssociatedType() where T : struct, IManagedComponent + { + if (!m_associateMap.TryGetValue(typeof(T), out var result)) + { + result = new T().AssociatedComponentType; + m_associateMap.Add(typeof(T), result); + } + return result; + } + + public void AddComponent(Entity entity, T value) where T : struct, IManagedComponent + { + var tmss = GetTypedManagedStructStorage(); + + Assert.IsFalse(HasComponent(entity)); + tmss.storage.Add(entity, value); + } + + public T GetComponent(Entity entity) where T : struct, IManagedComponent + { + var tmss = GetTypedManagedStructStorage(); + + if (tmss.storage.TryGetValue(entity, out T component)) + { + return component; + } + else + { + throw new InvalidOperationException($"Entity {entity} does not have a component of type: {typeof(T)}"); + } + } + + public bool HasComponent(Entity entity) where T : struct, IManagedComponent + { + var tmss = GetTypedManagedStructStorage(); + return tmss.storage.ContainsKey(entity); + } + + public void RemoveComponent(Entity entity) where T : struct, IManagedComponent + { + var tmss = GetTypedManagedStructStorage(); + + Assert.IsTrue(HasComponent(entity)); + tmss.storage.Remove(entity); + } + + public void SetComponent(Entity entity, T value) where T : struct, IManagedComponent + { + var tmss = GetTypedManagedStructStorage(); + + Assert.IsTrue(HasComponent(entity)); + tmss.storage[entity] = value; + } + + public void CopyComponent(Entity src, Entity dst, Type type) + { + bool success = m_typeMap.TryGetValue(type, out TypedManagedStructStorageBase baseStorage); + if (success) + { + baseStorage.CopyComponent(src, dst); + } + } + + private TypedManagedStructStorage GetTypedManagedStructStorage() where T : struct, IManagedComponent + { + var ttype = typeof(T); + if (!m_typeMap.ContainsKey(ttype)) + { + m_typeMap.Add(ttype, new TypedManagedStructStorage()); + } + return m_typeMap[ttype] as TypedManagedStructStorage; + } + + private abstract class TypedManagedStructStorageBase + { + public abstract void CopyComponent(Entity src, Entity dst); + } + + private class TypedManagedStructStorage : TypedManagedStructStorageBase where T : struct, IManagedComponent + { + public Dictionary storage = new Dictionary(); + + public override void CopyComponent(Entity src, Entity dst) + { + bool success = storage.TryGetValue(src, out T srcVal); + if (success) + storage[dst] = srcVal; + } + } + } + + //Todo: Combine Read and Write handles into single struct with single dictionary. + //Todo: Explore idea of using NativeHashmap for JobHandles. + internal class CollectionComponentStorage : IDisposable + { + private Dictionary m_typeMap = new Dictionary(); + private Dictionary m_associateMap = new Dictionary(); + + public ComponentType GetAssociatedType() where T : struct, ICollectionComponent + { + if (!m_associateMap.TryGetValue(typeof(T), out var result)) + { + result = new T().AssociatedComponentType; + m_associateMap.Add(typeof(T), result); + } + return result; + } + + public void AddCollectionComponent(Entity entity, T value, bool isNotInitialized = false) where T : struct, ICollectionComponent + { + var tcs = GetTypedCollectionStorage(); + + Assert.IsFalse(HasCollectionComponent(entity)); + tcs.storage.Add(entity, value); + tcs.writeHandles.Add(entity, new JobHandle()); + tcs.readHandles.Add(entity, new JobHandle()); + tcs.isNotInitialized.Add(entity, isNotInitialized); + } + + public T GetCollectionComponent(Entity entity, bool readOnly, out JobHandle handle) where T : struct, ICollectionComponent + { + var tcs = GetTypedCollectionStorage(); + + if (readOnly) + { + if (tcs.storage.TryGetValue(entity, out T component)) + { + handle = tcs.writeHandles[entity]; + return component; + } + else + { + throw new InvalidOperationException($"Entity {entity} does not have a component of type: {typeof(T)}"); + } + } + else + { + if (tcs.storage.TryGetValue(entity, out T component)) + { + var rHandle = tcs.readHandles[entity]; + var wHandle = tcs.writeHandles[entity]; + handle = JobHandle.CombineDependencies(rHandle, wHandle); + return component; + } + else + { + throw new InvalidOperationException("Entity " + entity + " does not have a component of type: " + typeof(T)); + } + } + } + + public bool HasCollectionComponent(Entity entity) where T : struct, ICollectionComponent + { + var tcs = GetTypedCollectionStorage(); + return tcs.storage.ContainsKey(entity); + } + + //Returns true if the component can be safely disposed. + public bool RemoveCollectionComponent(Entity entity, out JobHandle oldReadHandle, out JobHandle oldWriteHandle, out T component) where T : struct, ICollectionComponent + { + var tcs = GetTypedCollectionStorage(); + + if (!tcs.storage.TryGetValue(entity, out component)) + { + throw new InvalidOperationException($"Entity {entity} does not have a component of type: {typeof(T)}"); + } + + oldReadHandle = tcs.readHandles[entity]; + oldWriteHandle = tcs.writeHandles[entity]; + bool canBeDisposed = !tcs.isNotInitialized[entity]; + tcs.storage.Remove(entity); + tcs.readHandles.Remove(entity); + tcs.writeHandles.Remove(entity); + tcs.isNotInitialized.Remove(entity); + return canBeDisposed; + } + + //Returns true if the old component can be safely disposed. + public bool SetCollectionComponent(Entity entity, T value, out JobHandle oldReadHandle, out JobHandle oldWriteHandle, out T oldComponent) where T : struct, + ICollectionComponent + { + var tcs = GetTypedCollectionStorage(); + + if (!tcs.storage.TryGetValue(entity, out oldComponent)) + { + throw new InvalidOperationException($"Entity {entity} does not have a component of type: {typeof(T)}"); + } + + oldReadHandle = tcs.readHandles[entity]; + oldWriteHandle = tcs.writeHandles[entity]; + bool canBeDisposed = !tcs.isNotInitialized[entity]; + tcs.storage[entity] = value; + tcs.writeHandles[entity] = new JobHandle(); + tcs.readHandles[entity] = new JobHandle(); + tcs.isNotInitialized[entity] = false; + return canBeDisposed; + } + + public void UpdateReadHandle(Entity entity, Type type, JobHandle readHandle) + { + var t = m_typeMap[type]; + t.readHandles[entity] = JobHandle.CombineDependencies(readHandle, t.readHandles[entity]); + } + + public void UpdateWriteHandle(Entity entity, Type type, JobHandle writeHandle) + { + var t = m_typeMap[type]; + var jh = JobHandle.CombineDependencies(writeHandle, t.readHandles[entity], t.writeHandles[entity]); + t.readHandles[entity] = jh; + t.writeHandles[entity] = jh; + } + + public void CopyComponent(Entity src, Entity dst, Type type) + { + bool success = m_typeMap.TryGetValue(type, out TypedCollectionStorageBase baseStorage); + if (success) + { + baseStorage.CopyComponent(src, dst); + } + } + + private TypedCollectionStorage GetTypedCollectionStorage() where T : struct, ICollectionComponent + { + Type ttype = typeof(T); + if (!m_typeMap.ContainsKey(ttype)) + { + m_typeMap.Add(ttype, new TypedCollectionStorage()); + } + return m_typeMap[ttype] as TypedCollectionStorage; + } + + public void Dispose() + { + foreach (var storage in m_typeMap.Values) + { + storage.Dispose(); + } + } + + private abstract class TypedCollectionStorageBase : IDisposable + { + public Dictionary readHandles = new Dictionary(); + public Dictionary writeHandles = new Dictionary(); + public Dictionary isNotInitialized = new Dictionary(); + + public abstract void Dispose(); + public abstract void CopyComponent(Entity src, Entity dst); + } + + private class TypedCollectionStorage : TypedCollectionStorageBase where T : struct, ICollectionComponent + { + public Dictionary storage = new Dictionary(); + + public override void CopyComponent(Entity src, Entity dst) + { + bool success = storage.TryGetValue(src, out T srcVal); + if (success) + { + storage[dst] = srcVal; + readHandles[dst] = readHandles[src]; + writeHandles[dst] = writeHandles[src]; + isNotInitialized[dst] = isNotInitialized[src]; + } + } + + public override void Dispose() + { + var jhs = new NativeArray(readHandles.Values.Count + writeHandles.Values.Count, Allocator.TempJob); + int i = 0; + foreach (var h in readHandles.Values) + { + jhs[i] = h; + i++; + } + foreach (var h in writeHandles.Values) + { + jhs[i] = h; + i++; + } + JobHandle.CompleteAll(jhs); + jhs.Dispose(); + + var djhs = new NativeList(isNotInitialized.Count, Allocator.TempJob); + + foreach (var pair in isNotInitialized) + { + if (!pair.Value) + { + djhs.AddNoResize(storage[pair.Key].Dispose(default)); + } + } + JobHandle.CompleteAll(djhs.AsArray()); + djhs.Dispose(); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/ManagedStructStorage.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Internal/ManagedStructStorage.cs.meta new file mode 100644 index 0000000..bda36da --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/ManagedStructStorage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: faf26fb735fc4ac44be44a8715509201 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/RadixSort.cs b/Packages/com.latios.latios-framework/Core/Core/Internal/RadixSort.cs new file mode 100644 index 0000000..8784120 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/RadixSort.cs @@ -0,0 +1,329 @@ +using Unity.Collections; +using Unity.Mathematics; + +namespace Latios +{ + internal interface IRadixSortableInt + { + int GetKey(); + } + + //X is MSB + internal interface IRadixSortableInt3 + { + int3 GetKey3(); + } + + internal static class RadixSort + { + #region Int + public static void RankSortInt(NativeArray ranks, NativeArray src) where T : struct, IRadixSortableInt + { + int count = src.Length; + + NativeArray counts1 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts2 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts3 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts4 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray prefixSum1 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum2 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum3 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum4 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + NativeArray frontArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray backArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + //Counts + for (int i = 0; i < count; i++) + { + var keys = Keys(src[i].GetKey()); + counts1[keys.byte1] = counts1[keys.byte1] + 1; + counts2[keys.byte2] = counts2[keys.byte2] + 1; + counts3[keys.byte3] = counts3[keys.byte3] + 1; + counts4[keys.byte4] = counts4[keys.byte4] + 1; + frontArray[i] = new Indexer32 { key = keys, index = i }; + } + + //Sums + calculatePrefixSum(counts1, prefixSum1); + calculatePrefixSum(counts2, prefixSum2); + calculatePrefixSum(counts3, prefixSum3); + calculatePrefixSum(counts4, prefixSum4); + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].key.byte1; + int dest = prefixSum1[key]; + backArray[dest] = frontArray[i]; + prefixSum1[key] = prefixSum1[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].key.byte2; + int dest = prefixSum2[key]; + frontArray[dest] = backArray[i]; + prefixSum2[key] = prefixSum2[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].key.byte3; + int dest = prefixSum3[key]; + backArray[dest] = frontArray[i]; + prefixSum3[key] = prefixSum3[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].key.byte4; + int dest = prefixSum4[key]; + ranks[dest] = backArray[i].index; + prefixSum4[key] = prefixSum4[key] + 1; + } + } + + private struct Indexer32 + { + public UintAsBytes key; + public int index; + } + + private struct UintAsBytes + { + public byte byte1; + public byte byte2; + public byte byte3; + public byte byte4; + } + + private static UintAsBytes Keys(int val) + { + uint key = math.asuint(val ^ 0x80000000); + UintAsBytes result; + result.byte1 = (byte)(key & 0x000000FF); + key = key >> 8; + result.byte2 = (byte)(key & 0x000000FF); + key = key >> 8; + result.byte3 = (byte)(key & 0x000000FF); + key = key >> 8; + result.byte4 = (byte)(key & 0x000000FF); + return result; + } + #endregion + + #region Int3 + public static void RankSortInt3(NativeArray ranks, NativeArray src) where T : struct, IRadixSortableInt3 + { + int count = src.Length; + + NativeArray counts1 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts2 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts3 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts4 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts5 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts6 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts7 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts8 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts9 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts10 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts11 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts12 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray prefixSum1 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum2 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum3 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum4 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum5 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum6 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum7 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum8 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum9 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum10 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum11 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum12 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + NativeArray frontArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray backArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + //Counts + for (int i = 0; i < count; i++) + { + var keysInt3 = src[i].GetKey3(); + var keysUint3 = math.asuint(keysInt3) ^ 0x80000000; + IndexerInt3 keys; + keys.index = i; + var lsb = keysUint3 & 0x000000FF; + var slsb = (keysUint3 >> 8) & 0x000000FF; + var smsb = (keysUint3 >> 16) & 0x000000FF; + var msb = (keysUint3 >> 24) & 0x000000FF; + keys.byte1 = (byte)lsb.z; + keys.byte2 = (byte)slsb.z; + keys.byte3 = (byte)smsb.z; + keys.byte4 = (byte)msb.z; + keys.byte5 = (byte)lsb.y; + keys.byte6 = (byte)slsb.y; + keys.byte7 = (byte)smsb.y; + keys.byte8 = (byte)msb.y; + keys.byte9 = (byte)lsb.z; + keys.byte10 = (byte)slsb.z; + keys.byte11 = (byte)smsb.z; + keys.byte12 = (byte)msb.z; + + counts1[keys.byte1] = counts1[keys.byte1] + 1; + counts2[keys.byte2] = counts2[keys.byte2] + 1; + counts3[keys.byte3] = counts3[keys.byte3] + 1; + counts4[keys.byte4] = counts4[keys.byte4] + 1; + counts5 [keys.byte5 ] = counts5 [keys.byte5 ] + 1; + counts6 [keys.byte6 ] = counts6 [keys.byte6 ] + 1; + counts7 [keys.byte7 ] = counts7 [keys.byte7 ] + 1; + counts8 [keys.byte8 ] = counts8 [keys.byte8 ] + 1; + counts9 [keys.byte9 ] = counts9 [keys.byte9 ] + 1; + counts10[keys.byte10] = counts10[keys.byte10] + 1; + counts11[keys.byte11] = counts11[keys.byte11] + 1; + counts12[keys.byte12] = counts12[keys.byte12] + 1; + frontArray[i] = keys; + } + + //Sums + calculatePrefixSum(counts1, prefixSum1); + calculatePrefixSum(counts2, prefixSum2); + calculatePrefixSum(counts3, prefixSum3); + calculatePrefixSum(counts4, prefixSum4); + calculatePrefixSum(counts5, prefixSum5 ); + calculatePrefixSum(counts6, prefixSum6 ); + calculatePrefixSum(counts7, prefixSum7 ); + calculatePrefixSum(counts8, prefixSum8 ); + calculatePrefixSum(counts9, prefixSum9 ); + calculatePrefixSum(counts10, prefixSum10); + calculatePrefixSum(counts11, prefixSum11); + calculatePrefixSum(counts12, prefixSum12); + + //Z + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].byte1; + int dest = prefixSum1[key]; + backArray[dest] = frontArray[i]; + prefixSum1[key] = prefixSum1[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].byte2; + int dest = prefixSum2[key]; + frontArray[dest] = backArray[i]; + prefixSum2[key] = prefixSum2[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].byte3; + int dest = prefixSum3[key]; + backArray[dest] = frontArray[i]; + prefixSum3[key] = prefixSum3[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].byte4; + int dest = prefixSum4[key]; + frontArray[dest] = backArray[i]; + prefixSum4[key] = prefixSum4[key] + 1; + } + + //Y + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].byte5; + int dest = prefixSum5[key]; + backArray[dest] = frontArray[i]; + prefixSum5[key] = prefixSum5[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].byte6; + int dest = prefixSum6[key]; + frontArray[dest] = backArray[i]; + prefixSum6[key] = prefixSum6[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].byte7; + int dest = prefixSum7[key]; + backArray[dest] = frontArray[i]; + prefixSum7[key] = prefixSum7[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].byte8; + int dest = prefixSum8[key]; + frontArray[dest] = backArray[i]; + prefixSum8[key] = prefixSum8[key] + 1; + } + + //X + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].byte9; + int dest = prefixSum9[key]; + backArray[dest] = frontArray[i]; + prefixSum9[key] = prefixSum9[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].byte10; + int dest = prefixSum10[key]; + frontArray[dest] = backArray[i]; + prefixSum10[key] = prefixSum10[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].byte11; + int dest = prefixSum11[key]; + backArray[dest] = frontArray[i]; + prefixSum11[key] = prefixSum11[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].byte12; + int dest = prefixSum12[key]; + ranks[dest] = backArray[i].index; + prefixSum12[key] = prefixSum12[key] + 1; + } + } + + struct IndexerInt3 + { + public int index; + public byte byte1; + public byte byte2; + public byte byte3; + public byte byte4; + public byte byte5; + public byte byte6; + public byte byte7; + public byte byte8; + public byte byte9; + public byte byte10; + public byte byte11; + public byte byte12; + } + #endregion + + private static void calculatePrefixSum(NativeArray counts, NativeArray sums) + { + sums[0] = 0; + for (int i = 0; i < counts.Length - 1; i++) + { + sums[i + 1] = sums[i] + counts[i]; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Internal/RadixSort.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Internal/RadixSort.cs.meta new file mode 100644 index 0000000..8bbc839 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Internal/RadixSort.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c87dd752089b2484783fe69494d6c8a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Latios.Core.asmdef b/Packages/com.latios.latios-framework/Core/Core/Latios.Core.asmdef new file mode 100644 index 0000000..71b6808 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Latios.Core.asmdef @@ -0,0 +1,33 @@ +{ + "name": "Latios.Core", + "rootNamespace": "Latios", + "references": [ + "Unity.Entities", + "Unity.Jobs", + "Unity.Mathematics", + "Unity.Rendering.Hybrid", + "Unity.Transforms", + "Unity.Collections", + "Unity.Entities.Hybrid", + "Unity.Burst", + "Unity.Scenes", + "Unity.Build", + "Unity.NetCode", + "Unity.Networking.Transport" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.netcode", + "expression": "0.49", + "define": "NETCODE_PROJECT" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Core/Core/Latios.Core.asmdef.meta b/Packages/com.latios.latios-framework/Core/Core/Latios.Core.asmdef.meta new file mode 100644 index 0000000..02b68ef --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Latios.Core.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 46fe9b4ef94d76744bc0b97bc2f73d94 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math.meta b/Packages/com.latios.latios-framework/Core/Core/Math.meta new file mode 100644 index 0000000..a2fcf3c --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3a481c75ed689534797424a626122fc4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/LatiosMath.cs b/Packages/com.latios.latios-framework/Core/Core/Math/LatiosMath.cs new file mode 100644 index 0000000..1504789 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/LatiosMath.cs @@ -0,0 +1,131 @@ +using Unity.Mathematics; + +namespace Latios +{ + public static class LatiosMath + { + #region Easing + + public static float SmoothStart(float t) + { + return t * t; + } + + public static float2 SmoothStart(float2 t) + { + return t * t; + } + + public static float SmoothStop(float t) + { + float mt = 1f - t; + return 1f - mt * mt; + } + + public static float2 SmoothStop(float2 t) + { + float2 mt = 1f - t; + return 1f - mt * mt; + } + + public static float SmoothStep(float t) + { + //return math.lerp(SmoothStart(t), SmoothStop(t), t); + var t2 = t * t; + return 3 * t2 - 2 * t2 * t; + } + + //Inserts a linear section in the middle of a SmoothStep and then renormalizes both the input and output + public static float SmoothSlide(float linearStart, float linearStop, float t) + { + float l = linearStart; + float h = linearStop; + float a, b; + + if (l >= h) + return SmoothStep(t); + if (l <= 0f && h >= 1f) + return t; + + if (l <= 0f) + { + a = 0; + b = -2f * h / (h - 3f); + } + else if (h >= 1f) + { + a = 3 * l / (l + 2f); + b = 1f; + } + else + { + a = 3 * l / (l - h + 3f); + b = (l + 2 * h) / (l - h + 3f); + } + + if (t < a) + { + float modTime = t * 0.5f / a; + return (l / 0.5f) * SmoothStep(modTime); + } + else if (t > b) + { + float modTime = (t - b) * 0.5f / (1 - b) + 0.5f; + return (SmoothStep(modTime) - 0.5f) * (1f - h) / h + h; + } + else + { + float modTime = math.unlerp(a, b, t); + return math.lerp(l, h, modTime); + } + } + + #endregion Easing + + #region Transformations + + //From Unity.Rendering.Hybrid/AABB.cs + public static float3 RotateExtents(float3 extents, float3 m0, float3 m1, float3 m2) + { + return math.abs(m0 * extents.x) + math.abs(m1 * extents.y) + math.abs(m2 * extents.z); + } + + public static float3 RotateExtents(float extents, float3 m0, float3 m1, float3 m2) + { + return math.abs(m0 * extents) + math.abs(m1 * extents) + math.abs(m2 * extents); + } + + public static float3 RotateExtents(float3 extents, float3x3 rotationMatrix) + { + return RotateExtents(extents, rotationMatrix.c0, rotationMatrix.c1, rotationMatrix.c2); + } + + public static float2 ComplexMul(float2 a, float2 b) + { + return new float2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); + } + + #endregion Transformations + + #region NumberTricks + + // Source: https://stackoverflow.com/questions/3154454/what-is-the-most-efficient-way-to-calculate-the-least-common-multiple-of-two-int + public static int gcd(int a, int b) + { + if (b == 0) + return a; + return gcd(b, a % b); + } + + public static int lcm(int a, int b) + { + if (a > b) + return (a / gcd(a, b)) * b; + else + return (b / gcd(a, b)) * a; + } + + #endregion NumberTricks + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/LatiosMath.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/LatiosMath.cs.meta new file mode 100644 index 0000000..65dd434 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/LatiosMath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c2433fcf44dcc24a9c4e3d7da308014 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/Rng.cs b/Packages/com.latios.latios-framework/Core/Core/Math/Rng.cs new file mode 100644 index 0000000..42a118d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/Rng.cs @@ -0,0 +1,99 @@ +using Unity.Mathematics; + +namespace Latios +{ + public struct Rng + { + uint m_state; + + public Rng(uint seed) + { + m_state = seed; + } + + public Rng(string seedString) + { + m_state = math.asuint(seedString.GetHashCode()); + } + + public Rng Shuffle() + { + var sequence = new RngSequence(new uint2(m_state, math.asuint(int.MinValue))); + m_state = sequence.NextUInt(); + return this; + } + + public RngSequence GetSequence(int index) + { + return new RngSequence(new uint2(math.asuint(index), m_state)); + } + + public struct RngSequence + { + uint2 m_state; + + public RngSequence(uint2 initialState) + { + m_state = initialState; + } + + uint NextState() + { + // From https://www.youtube.com/watch?v=LWFzPP8ZbdU + // This version is SquirrelNoise5, which was posted on the author's Twitter: https://twitter.com/SquirrelTweets/status/1421251894274625536. + // This is a Unity C# adaptation of SquirrelNoise5 - Squirrel's Raw Noise utilities (version 5). + // The following code within this scope is licensed by Squirrel Eiserloh under the Creative Commons Attribution 3.0 license (CC-BY-3.0 US). + var val = m_state.x * 0xd2a80a3f; + val += m_state.y; + val ^= (val >> 9); + val += 0xa884f197; + val ^= (val >> 11); + val *= 0x6c736f4b; + val ^= (val >> 13); + val += 0xb79f3abb; + val ^= (val >> 15); + val += 0x1b56c4f5; + val ^= (val >> 17); + m_state.x = val; + return val; + } + + public bool NextBool() => RngToolkit.AsBool(NextState()); + public bool2 NextBool2() => RngToolkit.AsBool2(NextState()); + public bool3 NextBool3() => RngToolkit.AsBool3(NextState()); + public bool4 NextBool4() => RngToolkit.AsBool4(NextState()); + + public uint NextUInt() => NextState(); + public uint2 NextUInt2() => new uint2(NextState(), NextState()); + public uint3 NextUInt3() => new uint3(NextState(), NextState(), NextState()); + public uint4 NextUInt4() => new uint4(NextState(), NextState(), NextState(), NextState()); + public uint NextUInt (uint min, uint max) => RngToolkit.AsUInt(NextState(), min, max); + public uint2 NextUInt2(uint2 min, uint2 max) => RngToolkit.AsUInt2(NextUInt2(), min, max); + public uint3 NextUInt3(uint3 min, uint3 max) => RngToolkit.AsUInt3(NextUInt3(), min, max); + public uint4 NextUInt4(uint4 min, uint4 max) => RngToolkit.AsUInt4(NextUInt4(), min, max); + + public int NextInt() => RngToolkit.AsInt(NextState()); + public int2 NextInt2() => RngToolkit.AsInt2(NextUInt2()); + public int3 NextInt3() => RngToolkit.AsInt3(NextUInt3()); + public int4 NextInt4() => RngToolkit.AsInt4(NextUInt4()); + public int NextInt(int min, int max) => RngToolkit.AsInt(NextState(), min, max); + public int2 NextInt2(int2 min, int2 max) => RngToolkit.AsInt2(NextUInt2(), min, max); + public int3 NextInt3(int3 min, int3 max) => RngToolkit.AsInt3(NextUInt3(), min, max); + public int4 NextInt4(int4 min, int4 max) => RngToolkit.AsInt4(NextUInt4(), min, max); + + public float NextFloat() => RngToolkit.AsFloat(NextState()); + public float2 NextFloat2() => RngToolkit.AsFloat2(NextUInt2()); + public float3 NextFloat3() => RngToolkit.AsFloat3(NextUInt3()); + public float4 NextFloat4() => RngToolkit.AsFloat4(NextUInt4()); + public float NextFloat(float min, float max) => RngToolkit.AsFloat(NextState(), min, max); + public float2 NextFloat2(float2 min, float2 max) => RngToolkit.AsFloat2(NextUInt2(), min, max); + public float3 NextFloat3(float3 min, float3 max) => RngToolkit.AsFloat3(NextUInt3(), min, max); + public float4 NextFloat4(float4 min, float4 max) => RngToolkit.AsFloat4(NextUInt4(), min, max); + + public float2 NextFloat2Direction() => RngToolkit.AsFloat2Direction(NextState()); + public float3 NextFloat3Direction() => RngToolkit.AsFloat3Direction(NextUInt2()); + public quaternion NextQuaternionRotation() => RngToolkit.AsQuaternionRotation(NextUInt3()); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/Rng.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/Rng.cs.meta new file mode 100644 index 0000000..3f76498 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/Rng.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d39a910719bf3a94d8edd58cb14ae82a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/RngToolkit.cs b/Packages/com.latios.latios-framework/Core/Core/Math/RngToolkit.cs new file mode 100644 index 0000000..7a6f3e1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/RngToolkit.cs @@ -0,0 +1,225 @@ +using System.Diagnostics; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios +{ + public static class RngToolkit + { + public static bool AsBool(uint u) + { + return (u & 0x1) == 0; + } + + public static bool2 AsBool2(uint u) + { + return (new uint2(u) & new uint2(0x1, 0x2)) == 0; + } + + public static bool3 AsBool3(uint u) + { + return (new uint3(u) & new uint3(0x1, 0x2, 0x4)) == 0; + } + + public static bool4 AsBool4(uint u) + { + return (new uint4(u) & new uint4(0x1, 0x2, 0x4, 0x8)) == 0; + } + + public static int AsInt(uint u) + { + return math.asint(u); + } + + public static int2 AsInt2(uint2 u) + { + return math.asint(u); + } + + public static int3 AsInt3(uint3 u) + { + return math.asint(u); + } + + public static int4 AsInt4(uint4 u) + { + return math.asint(u); + } + + public static int AsInt(uint u, int min, int max) + { + CheckIntMinMax(min, max); + uint range = (uint)(max - min); + return (int)(u * (ulong)range >> 32) + min; + } + + public static int2 AsInt2(uint2 u, int2 min, int2 max) + { + CheckIntMinMax(min.xyxy, max.xyxy); + uint2 range = (uint2)(max - min); + return new int2((int)(u.x * (ulong)range.x >> 32), + (int)(u.y * (ulong)range.y >> 32)) + min; + } + + public static int3 AsInt3(uint3 u, int3 min, int3 max) + { + CheckIntMinMax(min.xyzx, max.xyzx); + uint3 range = (uint3)(max - min); + return new int3((int)(u.x * (ulong)range.x >> 32), + (int)(u.y * (ulong)range.y >> 32), + (int)(u.z * (ulong)range.z >> 32)) + min; + } + + public static int4 AsInt4(uint4 u, int4 min, int4 max) + { + CheckIntMinMax(min, max); + uint4 range = (uint4)(max - min); + return new int4((int)(u.x * (ulong)range.x >> 32), + (int)(u.y * (ulong)range.y >> 32), + (int)(u.z * (ulong)range.z >> 32), + (int)(u.w * (ulong)range.w >> 32)) + min; + } + + public static uint AsUInt(uint u, uint min, uint max) + { + CheckUIntMinMax(min, max); + uint range = max - min; + return (uint)(u * (ulong)range >> 32) + min; + } + + public static uint2 AsUInt2(uint2 u, uint2 min, uint2 max) + { + CheckUIntMinMax(min.xyxy, max.xyxy); + uint2 range = max - min; + return new uint2((uint)(u.x * (ulong)range.x >> 32), + (uint)(u.y * (ulong)range.y >> 32)) + min; + } + + public static uint3 AsUInt3(uint3 u, uint3 min, uint3 max) + { + CheckUIntMinMax(min.xyzx, max.xyzx); + uint3 range = max - min; + return new uint3((uint)(u.x * (ulong)range.x >> 32), + (uint)(u.y * (ulong)range.y >> 32), + (uint)(u.z * (ulong)range.z >> 32)) + min; + } + + public static uint4 AsUInt4(uint4 u, uint4 min, uint4 max) + { + CheckUIntMinMax(min, max); + uint4 range = max - min; + return new uint4((uint)(u.x * (ulong)range.x >> 32), + (uint)(u.y * (ulong)range.y >> 32), + (uint)(u.z * (ulong)range.z >> 32), + (uint)(u.w * (ulong)range.w >> 32)) + min; + } + + public static float AsFloat(uint u) + { + return math.asfloat(0x3f800000 | (u >> 9)) - 1.0f; + } + + public static float2 AsFloat2(uint2 u) + { + return math.asfloat(0x3f800000 | (u >> 9)) - 1.0f; + } + + public static float3 AsFloat3(uint3 u) + { + return math.asfloat(0x3f800000 | (u >> 9)) - 1.0f; + } + + public static float4 AsFloat4(uint4 u) + { + return math.asfloat(0x3f800000 | (u >> 9)) - 1.0f; + } + + public static float AsFloat(uint u, float min, float max) + { + CheckFloatMinMax(min, max); + return AsFloat(u) * (max - min) + min; + } + + public static float2 AsFloat2(uint2 u, float2 min, float2 max) + { + CheckFloatMinMax(min.xyxy, max.xyxy); + return AsFloat2(u) * (max - min) + min; + } + + public static float3 AsFloat3(uint3 u, float3 min, float3 max) + { + CheckFloatMinMax(min.xyzx, max.xyzx); + return AsFloat3(u) * (max - min) + min; + } + + public static float4 AsFloat4(uint4 u, float4 min, float4 max) + { + CheckFloatMinMax(min, max); + return AsFloat4(u) * (max - min) + min; + } + + // Todo: There has to be a way to avoid trig in these + public static float2 AsFloat2Direction(uint u) + { + float angle = AsFloat(u) * math.PI * 2.0f; + math.sincos(angle, out float s, out float c); + return new float2(c, s); + } + + public static float3 AsFloat3Direction(uint2 u) + { + float2 rnd = AsFloat2(u); + float z = rnd.x * 2.0f - 1.0f; + float r = math.sqrt(math.max(1.0f - z * z, 0.0f)); + float angle = rnd.y * math.PI * 2.0f; + math.sincos(angle, out float s, out float c); + return new float3(c * r, s * r, z); + } + + public static quaternion AsQuaternionRotation(uint3 u) + { + float3 rnd = AsFloat3(u, 0f, new float3(2.0f * math.PI, 2.0f * math.PI, 1.0f)); + float u1 = rnd.z; + float2 theta_rho = rnd.xy; + + float i = math.sqrt(1.0f - u1); + float j = math.sqrt(u1); + + float2 sin_theta_rho; + float2 cos_theta_rho; + math.sincos(theta_rho, out sin_theta_rho, out cos_theta_rho); + + quaternion q = new quaternion(i * sin_theta_rho.x, i * cos_theta_rho.x, j * sin_theta_rho.y, j * cos_theta_rho.y); + return new quaternion(math.select(q.value, -q.value, q.value.w < 0.0f)); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void CheckIntMinMax(int4 min, int4 max) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (math.any(min > max)) + throw new System.ArgumentException("min must be less than or equal to max"); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void CheckUIntMinMax(uint4 min, uint4 max) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (math.any(min > max)) + throw new System.ArgumentException("min must be less than or equal to max"); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void CheckFloatMinMax(float4 min, float4 max) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (math.any(min > max)) + throw new System.ArgumentException("min must be less than or equal to max"); +#endif + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/RngToolkit.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/RngToolkit.cs.meta new file mode 100644 index 0000000..727320e --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/RngToolkit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 922acae3ec139e642807862140ffc8a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/mathExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Math/mathExtensions.cs new file mode 100644 index 0000000..5077da1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/mathExtensions.cs @@ -0,0 +1,10 @@ +using Unity.Mathematics; + +public static class MathExtensions +{ + public static float4 xyz1(this float3 value) + { + return new float4(value, 1f); + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/mathExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/mathExtensions.cs.meta new file mode 100644 index 0000000..269745d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/mathExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba8ddb8a79bbddc4ba530da41911061d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simd.math.cs b/Packages/com.latios.latios-framework/Core/Core/Math/simd.math.cs new file mode 100644 index 0000000..86eb39d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simd.math.cs @@ -0,0 +1,171 @@ +using Unity.Mathematics; + +namespace Latios +{ + public static partial class simd + { + public static float4 dot(simdFloat3 a, float3 b) => a.x * b.x + a.y * b.y + a.z * b.z; + public static float4 dot(float3 a, simdFloat3 b) => a.x * b.x + a.y * b.y + a.z * b.z; + public static float4 dot(simdFloat3 a, simdFloat3 b) => a.x * b.x + a.y * b.y + a.z * b.z; + + public static simdFloat3 cross(simdFloat3 a, float3 b) + { + return new simdFloat3 { m_float3s = new float4x3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x) }; + } + public static simdFloat3 cross(float3 a, simdFloat3 b) + { + return new simdFloat3 { m_float3s = new float4x3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x) }; + } + public static simdFloat3 cross(simdFloat3 a, simdFloat3 b) + { + return new simdFloat3 { m_float3s = new float4x3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x) }; + } + + public static float4 distancesq(simdFloat3 a, float3 b) + { + var t = a - b; + return dot(t, t); + } + public static float4 distancesq(float3 a, simdFloat3 b) + { + var t = a - b; + return dot(t, t); + } + public static float4 distancesq(simdFloat3 a, simdFloat3 b) + { + var t = a - b; + return dot(t, t); + } + + public static float4 lengthsq(simdFloat3 a) => dot(a, a); + + public static float4 length(simdFloat3 a) => math.sqrt(dot(a, a)); + + public static simdFloat3 select(simdFloat3 a, simdFloat3 b, bool4 c) + { + simdFloat3 result = default; + result.x = math.select(a.x, b.x, c); + result.y = math.select(a.y, b.y, c); + result.z = math.select(a.z, b.z, c); + return result; + } + + public static simdFloat3 select(simdFloat3 a, simdFloat3 b, bool4 xSelect, bool4 ySelect, bool4 zSelect) + { + simdFloat3 result = default; + result.x = math.select(a.x, b.x, xSelect); + result.y = math.select(a.y, b.y, ySelect); + result.z = math.select(a.z, b.z, zSelect); + return result; + } + + /*public static float3 shuffle(simdFloat3 left, simdFloat3 right, math.ShuffleComponent shuffleA) + { + float3 result = default; + result.x = math.shuffle(left.x, right.x, shuffleA); + result.y = math.shuffle(left.y, right.y, shuffleA); + result.z = math.shuffle(left.z, right.z, shuffleA); + return result; + } + + public static simdFloat3 shuffle(simdFloat3 left, + simdFloat3 right, + math.ShuffleComponent shuffleA, + math.ShuffleComponent shuffleB, + math.ShuffleComponent shuffleC, + math.ShuffleComponent shuffleD) + { + simdFloat3 result = default; + result.x = math.shuffle(left.x, right.x, shuffleA, shuffleB, shuffleC, shuffleD); + result.y = math.shuffle(left.y, right.y, shuffleA, shuffleB, shuffleC, shuffleD); + result.z = math.shuffle(left.z, right.z, shuffleA, shuffleB, shuffleC, shuffleD); + return result; + }*/ + + public static float3 shuffle(simdFloat3 left, simdFloat3 right, math.ShuffleComponent shuffleA) + { + int code = (int)shuffleA; + bool useRight = code > 3; + int index = code & 3; + + float3 result = default; + result.x = math.select(left.x, right.x, useRight)[index]; + result.y = math.select(left.y, right.y, useRight)[index]; + result.z = math.select(left.z, right.z, useRight)[index]; + return result; + } + + public static simdFloat3 shuffle(simdFloat3 left, + simdFloat3 right, + math.ShuffleComponent shuffleA, + math.ShuffleComponent shuffleB, + math.ShuffleComponent shuffleC, + math.ShuffleComponent shuffleD) + { + int4 code = new int4((int)shuffleA, (int)shuffleB, (int)shuffleC, (int)shuffleD); + bool4 useRight = code > 3; + int4 index = code & 3; + + simdFloat3 result = default; + float4 l = new float4(left.x[index.x], left.x[index.y], left.x[index.z], left.x[index.w]); + float4 r = new float4(right.x[index.x], right.x[index.y], right.x[index.z], right.x[index.w]); + result.x = math.select(l, r, useRight); + l = new float4(left.y[index.x], left.y[index.y], left.y[index.z], left.y[index.w]); + r = new float4(right.y[index.x], right.y[index.y], right.y[index.z], right.y[index.w]); + result.y = math.select(l, r, useRight); + l = new float4(left.z[index.x], left.z[index.y], left.z[index.z], left.z[index.w]); + r = new float4(right.z[index.x], right.z[index.y], right.z[index.z], right.z[index.w]); + result.z = math.select(l, r, useRight); + return result; + } + + public static simdFloat3 transform(RigidTransform transform, simdFloat3 positions) + { + simdFloat3 t = 2 * cross(transform.rot.value.xyz, positions); + var rotated = positions + transform.rot.value.w * t + cross(transform.rot.value.xyz, t); + return rotated + transform.pos; + } + + public static simdFloat3 mul(quaternion rotation, simdFloat3 directions) + { + simdFloat3 t = 2 * cross(rotation.value.xyz, directions); + return directions + rotation.value.w * t + cross(rotation.value.xyz, t); + } + + public static float3 cminabcd(simdFloat3 s) + { + return new float3(math.cmin(s.x), math.cmin(s.y), math.cmin(s.z)); + } + + public static float3 cmaxabcd(simdFloat3 s) + { + return new float3(math.cmax(s.x), math.cmax(s.y), math.cmax(s.z)); + } + + public static float4 cminxyz(simdFloat3 s) + { + return math.min(math.min(s.x, s.y), s.z); + } + + public static float4 cmaxxyz(simdFloat3 s) + { + return math.max(math.max(s.x, s.y), s.z); + } + + public static simdFloat3 abs(simdFloat3 s) + { + return new simdFloat3(math.abs(s.x), math.abs(s.y), math.abs(s.z)); + } + + public static simdFloat3 project(simdFloat3 a, float3 target) + { + return (dot(a, target) / math.dot(target, target)) * new simdFloat3(target); + } + + public static bool4 isfiniteallxyz(simdFloat3 s) + { + return math.isfinite(s.x) & math.isfinite(s.y) & math.isfinite(s.z); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simd.math.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/simd.math.cs.meta new file mode 100644 index 0000000..a741b13 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simd.math.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab99e1e13ff42774a9b4019552dae56e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.cs b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.cs new file mode 100644 index 0000000..fa95150 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.cs @@ -0,0 +1,278 @@ +using System; +using System.Diagnostics; +using Unity.Mathematics; + +namespace Latios +{ + public partial struct simdFloat3 : IEquatable + { + internal float4x3 m_float3s; + + public float4 x + { + get { return m_float3s.c0; } + set { m_float3s.c0 = value; } + } + public float4 y + { + get { return m_float3s.c1; } + set { m_float3s.c1 = value; } + } + public float4 z + { + get { return m_float3s.c2; } + set { m_float3s.c2 = value; } + } + + public float3 a + { + get { return new float3(m_float3s.c0.x, m_float3s.c1.x, m_float3s.c2.x); } + set { m_float3s.c0.x = value.x; m_float3s.c1.x = value.y; m_float3s.c2.x = value.z; } + } + public float3 b + { + get { return new float3(m_float3s.c0.y, m_float3s.c1.y, m_float3s.c2.y); } + set { m_float3s.c0.y = value.x; m_float3s.c1.y = value.y; m_float3s.c2.y = value.z; } + } + public float3 c + { + get { return new float3(m_float3s.c0.z, m_float3s.c1.z, m_float3s.c2.z); } + set { m_float3s.c0.z = value.x; m_float3s.c1.z = value.y; m_float3s.c2.z = value.z; } + } + public float3 d + { + get { return new float3(m_float3s.c0.w, m_float3s.c1.w, m_float3s.c2.w); } + set { m_float3s.c0.w = value.x; m_float3s.c1.w = value.y; m_float3s.c2.w = value.z; } + } + + public simdFloat3(float3 value) + { + m_float3s.c0 = value.xxxx; + m_float3s.c1 = value.yyyy; + m_float3s.c2 = value.zzzz; + } + + public simdFloat3(float3 a, float3 b, float3 c, float3 d) + { + m_float3s.c0 = new float4(a.x, b.x, c.x, d.x); + m_float3s.c1 = new float4(a.y, b.y, c.y, d.y); + m_float3s.c2 = new float4(a.z, b.z, c.z, d.z); + } + + public simdFloat3(float4 x, float4 y, float4 z) + { + m_float3s.c0 = x; + m_float3s.c1 = y; + m_float3s.c2 = z; + } + + public static simdFloat3 operator -(simdFloat3 value) + { + value.m_float3s = -value.m_float3s; + return value; + } + + public static simdFloat3 operator +(simdFloat3 lhs, float rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s + rhs }; + } + + public static simdFloat3 operator +(float lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs + rhs.m_float3s }; + } + + public static simdFloat3 operator +(simdFloat3 lhs, float3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z) }; + } + + public static simdFloat3 operator +(float3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z) }; + } + + public static simdFloat3 operator +(simdFloat3 lhs, float4 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x + rhs, lhs.y + rhs, lhs.z + rhs) }; + } + + public static simdFloat3 operator +(float4 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs + rhs.x, lhs + rhs.y, lhs + rhs.z) }; + } + + public static simdFloat3 operator +(simdFloat3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s + rhs.m_float3s }; + } + + public static simdFloat3 operator -(simdFloat3 lhs, float rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s - rhs }; + } + + public static simdFloat3 operator -(float lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs - rhs.m_float3s }; + } + + public static simdFloat3 operator -(simdFloat3 lhs, float3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z) }; + } + + public static simdFloat3 operator -(float3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z) }; + } + + public static simdFloat3 operator -(simdFloat3 lhs, float4 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x - rhs, lhs.y - rhs, lhs.z - rhs) }; + } + + public static simdFloat3 operator -(float4 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs - rhs.x, lhs - rhs.y, lhs - rhs.z) }; + } + + public static simdFloat3 operator -(simdFloat3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s - rhs.m_float3s }; + } + + public static simdFloat3 operator *(simdFloat3 lhs, float rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s * rhs }; + } + + public static simdFloat3 operator *(float lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs * rhs.m_float3s }; + } + + public static simdFloat3 operator *(simdFloat3 lhs, float3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z) }; + } + + public static simdFloat3 operator *(float3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z) }; + } + + public static simdFloat3 operator *(simdFloat3 lhs, float4 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs) }; + } + + public static simdFloat3 operator *(float4 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z) }; + } + + public static simdFloat3 operator *(simdFloat3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s * rhs.m_float3s }; + } + + public static simdFloat3 operator /(simdFloat3 lhs, float rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s / rhs }; + } + + public static simdFloat3 operator /(float lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs / rhs.m_float3s }; + } + + public static simdFloat3 operator /(simdFloat3 lhs, float3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z) }; + } + + public static simdFloat3 operator /(float3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z) }; + } + + public static simdFloat3 operator /(simdFloat3 lhs, float4 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs) }; + } + + public static simdFloat3 operator /(float4 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = new float4x3(lhs / rhs.x, lhs / rhs.y, lhs / rhs.z) }; + } + + public static simdFloat3 operator /(simdFloat3 lhs, simdFloat3 rhs) + { + return new simdFloat3 { m_float3s = lhs.m_float3s / rhs.m_float3s }; + } + + public static bool4 operator ==(simdFloat3 lhs, float3 rhs) + { + return (lhs.x == rhs.x) & (lhs.y == rhs.y) & (lhs.z == rhs.z); + } + + public static bool4 operator ==(float3 lhs, simdFloat3 rhs) + { + return (lhs.x == rhs.x) & (lhs.y == rhs.y) & (lhs.z == rhs.z); + } + + public static bool4 operator ==(simdFloat3 lhs, simdFloat3 rhs) + { + return (lhs.x == rhs.x) & (lhs.y == rhs.y) & (lhs.z == rhs.z); + } + + public static bool4 operator !=(simdFloat3 lhs, float3 rhs) + { + return (lhs.x != rhs.x) | (lhs.y != rhs.y) | (lhs.z != rhs.z); + } + + public static bool4 operator !=(float3 lhs, simdFloat3 rhs) + { + return (lhs.x != rhs.x) | (lhs.y != rhs.y) | (lhs.z != rhs.z); + } + + public static bool4 operator !=(simdFloat3 lhs, simdFloat3 rhs) + { + return (lhs.x != rhs.x) | (lhs.y != rhs.y) | (lhs.z != rhs.z); + } + + public float3 this[int i] + { + get + { + CheckIndex(i); + return new float3(x[i], y[i], z[i]); + } + } + + public bool Equals(simdFloat3 other) + { + return math.all(this == other); + } + + public override bool Equals(object obj) + { + return obj is simdFloat3 other && + m_float3s.Equals(other.m_float3s); + } + + public override int GetHashCode() + { + return 407164411 + m_float3s.GetHashCode(); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void CheckIndex(int i) + { + if (i > 3 || i < 0) + throw new System.ArgumentException($"simdFloat3 indexer must be in the range of 0 - 3. Was {i}"); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.cs.meta new file mode 100644 index 0000000..fa0c335 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1641e0f18d2dabd40b9659817ced392d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.gen.cs b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.gen.cs new file mode 100644 index 0000000..0e44178 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.gen.cs @@ -0,0 +1,1920 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform simdFloat3.swizzles.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.ComponentModel; +using Unity.Mathematics; + +namespace Latios +{ + public partial struct simdFloat3 + { + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aaaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxxx, y.xxxx, z.xxxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aaab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxxy, y.xxxy, z.xxxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aaac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxxz, y.xxxz, z.xxxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aaad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxxw, y.xxxw, z.xxxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aaba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxyx, y.xxyx, z.xxyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aabb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxyy, y.xxyy, z.xxyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aabc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxyz, y.xxyz, z.xxyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aabd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxyw, y.xxyw, z.xxyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aaca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxzx, y.xxzx, z.xxzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aacb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxzy, y.xxzy, z.xxzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aacc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxzz, y.xxzz, z.xxzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aacd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxzw, y.xxzw, z.xxzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aada + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxwx, y.xxwx, z.xxwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aadb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxwy, y.xxwy, z.xxwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aadc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxwz, y.xxwz, z.xxwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 aadd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xxww, y.xxww, z.xxww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyxx, y.xyxx, z.xyxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyxy, y.xyxy, z.xyxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyxz, y.xyxz, z.xyxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyxw, y.xyxw, z.xyxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyyx, y.xyyx, z.xyyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyyy, y.xyyy, z.xyyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyyz, y.xyyz, z.xyyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyyw, y.xyyw, z.xyyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyzx, y.xyzx, z.xyzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyzy, y.xyzy, z.xyzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyzz, y.xyzz, z.xyzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyzw, y.xyzw, z.xyzw) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.xyzw = value.x; + newY.xyzw = value.y; + newZ.xyzw = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xywx, y.xywx, z.xywx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xywy, y.xywy, z.xywy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xywz, y.xywz, z.xywz) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.xywz = value.x; + newY.xywz = value.y; + newZ.xywz = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 abdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xyww, y.xyww, z.xyww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzxx, y.xzxx, z.xzxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzxy, y.xzxy, z.xzxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzxz, y.xzxz, z.xzxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzxw, y.xzxw, z.xzxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzyx, y.xzyx, z.xzyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzyy, y.xzyy, z.xzyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzyz, y.xzyz, z.xzyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzyw, y.xzyw, z.xzyw) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.xzyw = value.x; + newY.xzyw = value.y; + newZ.xzyw = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzzx, y.xzzx, z.xzzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 accb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzzy, y.xzzy, z.xzzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 accc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzzz, y.xzzz, z.xzzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 accd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzzw, y.xzzw, z.xzzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzwx, y.xzwx, z.xzwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzwy, y.xzwy, z.xzwy) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.xzwy = value.x; + newY.xzwy = value.y; + newZ.xzwy = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzwz, y.xzwz, z.xzwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 acdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xzww, y.xzww, z.xzww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwxx, y.xwxx, z.xwxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwxy, y.xwxy, z.xwxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwxz, y.xwxz, z.xwxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwxw, y.xwxw, z.xwxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwyx, y.xwyx, z.xwyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwyy, y.xwyy, z.xwyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwyz, y.xwyz, z.xwyz) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.xwyz = value.x; + newY.xwyz = value.y; + newZ.xwyz = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwyw, y.xwyw, z.xwyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwzx, y.xwzx, z.xwzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwzy, y.xwzy, z.xwzy) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.xwzy = value.x; + newY.xwzy = value.y; + newZ.xwzy = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwzz, y.xwzz, z.xwzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwzw, y.xwzw, z.xwzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 adda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwwx, y.xwwx, z.xwwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 addb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwwy, y.xwwy, z.xwwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 addc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwwz, y.xwwz, z.xwwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 addd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.xwww, y.xwww, z.xwww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 baaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxxx, y.yxxx, z.yxxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 baab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxxy, y.yxxy, z.yxxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 baac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxxz, y.yxxz, z.yxxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 baad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxxw, y.yxxw, z.yxxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 baba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxyx, y.yxyx, z.yxyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 babb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxyy, y.yxyy, z.yxyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 babc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxyz, y.yxyz, z.yxyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 babd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxyw, y.yxyw, z.yxyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 baca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxzx, y.yxzx, z.yxzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bacb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxzy, y.yxzy, z.yxzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bacc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxzz, y.yxzz, z.yxzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bacd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxzw, y.yxzw, z.yxzw) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.yxzw = value.x; + newY.yxzw = value.y; + newZ.yxzw = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bada + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxwx, y.yxwx, z.yxwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 badb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxwy, y.yxwy, z.yxwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 badc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxwz, y.yxwz, z.yxwz) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.yxwz = value.x; + newY.yxwz = value.y; + newZ.yxwz = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 badd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yxww, y.yxww, z.yxww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyxx, y.yyxx, z.yyxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyxy, y.yyxy, z.yyxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyxz, y.yyxz, z.yyxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyxw, y.yyxw, z.yyxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyyx, y.yyyx, z.yyyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyyy, y.yyyy, z.yyyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyyz, y.yyyz, z.yyyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyyw, y.yyyw, z.yyyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyzx, y.yyzx, z.yyzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyzy, y.yyzy, z.yyzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyzz, y.yyzz, z.yyzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyzw, y.yyzw, z.yyzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yywx, y.yywx, z.yywx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yywy, y.yywy, z.yywy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yywz, y.yywz, z.yywz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bbdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yyww, y.yyww, z.yyww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzxx, y.yzxx, z.yzxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzxy, y.yzxy, z.yzxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzxz, y.yzxz, z.yzxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzxw, y.yzxw, z.yzxw) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.yzxw = value.x; + newY.yzxw = value.y; + newZ.yzxw = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzyx, y.yzyx, z.yzyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzyy, y.yzyy, z.yzyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzyz, y.yzyz, z.yzyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzyw, y.yzyw, z.yzyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzzx, y.yzzx, z.yzzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bccb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzzy, y.yzzy, z.yzzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bccc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzzz, y.yzzz, z.yzzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bccd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzzw, y.yzzw, z.yzzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzwx, y.yzwx, z.yzwx) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.yzwx = value.x; + newY.yzwx = value.y; + newZ.yzwx = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzwy, y.yzwy, z.yzwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzwz, y.yzwz, z.yzwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bcdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.yzww, y.yzww, z.yzww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywxx, y.ywxx, z.ywxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywxy, y.ywxy, z.ywxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywxz, y.ywxz, z.ywxz) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.ywxz = value.x; + newY.ywxz = value.y; + newZ.ywxz = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywxw, y.ywxw, z.ywxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywyx, y.ywyx, z.ywyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywyy, y.ywyy, z.ywyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywyz, y.ywyz, z.ywyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywyw, y.ywyw, z.ywyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywzx, y.ywzx, z.ywzx) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.ywzx = value.x; + newY.ywzx = value.y; + newZ.ywzx = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywzy, y.ywzy, z.ywzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywzz, y.ywzz, z.ywzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywzw, y.ywzw, z.ywzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bdda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywwx, y.ywwx, z.ywwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bddb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywwy, y.ywwy, z.ywwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bddc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywwz, y.ywwz, z.ywwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 bddd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.ywww, y.ywww, z.ywww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 caaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxxx, y.zxxx, z.zxxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 caab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxxy, y.zxxy, z.zxxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 caac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxxz, y.zxxz, z.zxxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 caad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxxw, y.zxxw, z.zxxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 caba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxyx, y.zxyx, z.zxyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cabb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxyy, y.zxyy, z.zxyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cabc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxyz, y.zxyz, z.zxyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cabd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxyw, y.zxyw, z.zxyw) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.zxyw = value.x; + newY.zxyw = value.y; + newZ.zxyw = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 caca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxzx, y.zxzx, z.zxzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cacb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxzy, y.zxzy, z.zxzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cacc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxzz, y.zxzz, z.zxzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cacd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxzw, y.zxzw, z.zxzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cada + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxwx, y.zxwx, z.zxwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cadb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxwy, y.zxwy, z.zxwy) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.zxwy = value.x; + newY.zxwy = value.y; + newZ.zxwy = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cadc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxwz, y.zxwz, z.zxwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cadd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zxww, y.zxww, z.zxww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyxx, y.zyxx, z.zyxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyxy, y.zyxy, z.zyxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyxz, y.zyxz, z.zyxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyxw, y.zyxw, z.zyxw) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.zyxw = value.x; + newY.zyxw = value.y; + newZ.zyxw = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyyx, y.zyyx, z.zyyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyyy, y.zyyy, z.zyyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyyz, y.zyyz, z.zyyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyyw, y.zyyw, z.zyyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyzx, y.zyzx, z.zyzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyzy, y.zyzy, z.zyzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyzz, y.zyzz, z.zyzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyzw, y.zyzw, z.zyzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zywx, y.zywx, z.zywx) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.zywx = value.x; + newY.zywx = value.y; + newZ.zywx = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zywy, y.zywy, z.zywy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zywz, y.zywz, z.zywz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cbdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zyww, y.zyww, z.zyww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzxx, y.zzxx, z.zzxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzxy, y.zzxy, z.zzxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzxz, y.zzxz, z.zzxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzxw, y.zzxw, z.zzxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzyx, y.zzyx, z.zzyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzyy, y.zzyy, z.zzyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzyz, y.zzyz, z.zzyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzyw, y.zzyw, z.zzyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzzx, y.zzzx, z.zzzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cccb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzzy, y.zzzy, z.zzzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cccc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzzz, y.zzzz, z.zzzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cccd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzzw, y.zzzw, z.zzzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzwx, y.zzwx, z.zzwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzwy, y.zzwy, z.zzwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzwz, y.zzwz, z.zzwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ccdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zzww, y.zzww, z.zzww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwxx, y.zwxx, z.zwxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwxy, y.zwxy, z.zwxy) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.zwxy = value.x; + newY.zwxy = value.y; + newZ.zwxy = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwxz, y.zwxz, z.zwxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwxw, y.zwxw, z.zwxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwyx, y.zwyx, z.zwyx) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.zwyx = value.x; + newY.zwyx = value.y; + newZ.zwyx = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwyy, y.zwyy, z.zwyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwyz, y.zwyz, z.zwyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwyw, y.zwyw, z.zwyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwzx, y.zwzx, z.zwzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwzy, y.zwzy, z.zwzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwzz, y.zwzz, z.zwzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwzw, y.zwzw, z.zwzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cdda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwwx, y.zwwx, z.zwwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cddb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwwy, y.zwwy, z.zwwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cddc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwwz, y.zwwz, z.zwwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 cddd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.zwww, y.zwww, z.zwww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 daaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxxx, y.wxxx, z.wxxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 daab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxxy, y.wxxy, z.wxxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 daac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxxz, y.wxxz, z.wxxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 daad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxxw, y.wxxw, z.wxxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 daba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxyx, y.wxyx, z.wxyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dabb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxyy, y.wxyy, z.wxyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dabc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxyz, y.wxyz, z.wxyz) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.wxyz = value.x; + newY.wxyz = value.y; + newZ.wxyz = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dabd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxyw, y.wxyw, z.wxyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 daca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxzx, y.wxzx, z.wxzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dacb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxzy, y.wxzy, z.wxzy) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.wxzy = value.x; + newY.wxzy = value.y; + newZ.wxzy = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dacc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxzz, y.wxzz, z.wxzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dacd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxzw, y.wxzw, z.wxzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dada + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxwx, y.wxwx, z.wxwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dadb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxwy, y.wxwy, z.wxwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dadc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxwz, y.wxwz, z.wxwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dadd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wxww, y.wxww, z.wxww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyxx, y.wyxx, z.wyxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyxy, y.wyxy, z.wyxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyxz, y.wyxz, z.wyxz) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.wyxz = value.x; + newY.wyxz = value.y; + newZ.wyxz = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyxw, y.wyxw, z.wyxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyyx, y.wyyx, z.wyyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyyy, y.wyyy, z.wyyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyyz, y.wyyz, z.wyyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyyw, y.wyyw, z.wyyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyzx, y.wyzx, z.wyzx) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.wyzx = value.x; + newY.wyzx = value.y; + newZ.wyzx = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyzy, y.wyzy, z.wyzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyzz, y.wyzz, z.wyzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyzw, y.wyzw, z.wyzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wywx, y.wywx, z.wywx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wywy, y.wywy, z.wywy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wywz, y.wywz, z.wywz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dbdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wyww, y.wyww, z.wyww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzxx, y.wzxx, z.wzxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzxy, y.wzxy, z.wzxy) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.wzxy = value.x; + newY.wzxy = value.y; + newZ.wzxy = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzxz, y.wzxz, z.wzxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzxw, y.wzxw, z.wzxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzyx, y.wzyx, z.wzyx) }; } + set + { + float4 newX = default, newY = default, newZ = default; + newX.wzyx = value.x; + newY.wzyx = value.y; + newZ.wzyx = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzyy, y.wzyy, z.wzyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzyz, y.wzyz, z.wzyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzyw, y.wzyw, z.wzyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzzx, y.wzzx, z.wzzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dccb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzzy, y.wzzy, z.wzzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dccc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzzz, y.wzzz, z.wzzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dccd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzzw, y.wzzw, z.wzzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzwx, y.wzwx, z.wzwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcdb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzwy, y.wzwy, z.wzwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcdc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzwz, y.wzwz, z.wzwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dcdd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wzww, y.wzww, z.wzww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddaa + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwxx, y.wwxx, z.wwxx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddab + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwxy, y.wwxy, z.wwxy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddac + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwxz, y.wwxz, z.wwxz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddad + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwxw, y.wwxw, z.wwxw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddba + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwyx, y.wwyx, z.wwyx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddbb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwyy, y.wwyy, z.wwyy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddbc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwyz, y.wwyz, z.wwyz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddbd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwyw, y.wwyw, z.wwyw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddca + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwzx, y.wwzx, z.wwzx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddcb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwzy, y.wwzy, z.wwzy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddcc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwzz, y.wwzz, z.wwzz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddcd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwzw, y.wwzw, z.wwzw) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 ddda + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwwx, y.wwwx, z.wwwx) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dddb + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwwy, y.wwwy, z.wwwy) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dddc + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwwz, y.wwwz, z.wwwz) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 dddd + { + get { return new simdFloat3 { m_float3s = new float4x3(x.wwww, y.wwww, z.wwww) }; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xxx + { + get { return new simdFloat3 { m_float3s = new float4x3(x, x, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xxy + { + get { return new simdFloat3 { m_float3s = new float4x3(x, x, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xxz + { + get { return new simdFloat3 { m_float3s = new float4x3(x, x, z) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xyx + { + get { return new simdFloat3 { m_float3s = new float4x3(x, y, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xyy + { + get { return new simdFloat3 { m_float3s = new float4x3(x, y, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xyz + { + get { return new simdFloat3 { m_float3s = new float4x3(x, y, z) }; } + set + { + x = value.x; + y = value.y; + z = value.z; + } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xzx + { + get { return new simdFloat3 { m_float3s = new float4x3(x, z, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xzy + { + get { return new simdFloat3 { m_float3s = new float4x3(x, z, y) }; } + set + { + x = value.x; + z = value.y; + y = value.z; + } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 xzz + { + get { return new simdFloat3 { m_float3s = new float4x3(x, z, z) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yxx + { + get { return new simdFloat3 { m_float3s = new float4x3(y, x, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yxy + { + get { return new simdFloat3 { m_float3s = new float4x3(y, x, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yxz + { + get { return new simdFloat3 { m_float3s = new float4x3(y, x, z) }; } + set + { + y = value.x; + x = value.y; + z = value.z; + } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yyx + { + get { return new simdFloat3 { m_float3s = new float4x3(y, y, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yyy + { + get { return new simdFloat3 { m_float3s = new float4x3(y, y, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yyz + { + get { return new simdFloat3 { m_float3s = new float4x3(y, y, z) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yzx + { + get { return new simdFloat3 { m_float3s = new float4x3(y, z, x) }; } + set + { + y = value.x; + z = value.y; + x = value.z; + } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yzy + { + get { return new simdFloat3 { m_float3s = new float4x3(y, z, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 yzz + { + get { return new simdFloat3 { m_float3s = new float4x3(y, z, z) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zxx + { + get { return new simdFloat3 { m_float3s = new float4x3(z, x, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zxy + { + get { return new simdFloat3 { m_float3s = new float4x3(z, x, y) }; } + set + { + z = value.x; + x = value.y; + y = value.z; + } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zxz + { + get { return new simdFloat3 { m_float3s = new float4x3(z, x, z) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zyx + { + get { return new simdFloat3 { m_float3s = new float4x3(z, y, x) }; } + set + { + z = value.x; + y = value.y; + x = value.z; + } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zyy + { + get { return new simdFloat3 { m_float3s = new float4x3(z, y, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zyz + { + get { return new simdFloat3 { m_float3s = new float4x3(z, y, z) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zzx + { + get { return new simdFloat3 { m_float3s = new float4x3(z, z, x) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zzy + { + get { return new simdFloat3 { m_float3s = new float4x3(z, z, y) }; } + } + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 zzz + { + get { return new simdFloat3 { m_float3s = new float4x3(z, z, z) }; } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.gen.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.gen.cs.meta new file mode 100644 index 0000000..2b19e1d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be7da14480b956c4d8c5f2e3c85c922f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.tt b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.tt new file mode 100644 index 0000000..546a0a6 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.tt @@ -0,0 +1,132 @@ +<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#> +<#@ output extension=".gen.cs" #> +<#@ assembly name="System.Collections" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Collections.Generic" #> +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform simdFloat3.swizzles.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.ComponentModel; +using Unity.Mathematics; + +namespace Latios +{ + public partial struct simdFloat3 + { +<# +{ + var abcdComponents = new char[] + { + 'a', + 'b', + 'c', + 'd' + }; + + char getXYZW(char abcd) + { + switch (abcd) + { + case 'a': return 'x'; + case 'b': return 'y'; + case 'c': return 'z'; + case 'd': return 'w'; + default: return '#'; + } + } + + string CharCombine(char c0, char c1, char c2, char c3) + { + // Combine chars into array + char[] arr = new char[4]; + arr[0] = c0; + arr[1] = c1; + arr[2] = c2; + arr[3] = c3; + // Return new string key + return new string(arr); + } + + foreach (char first in abcdComponents) + { + foreach (char second in abcdComponents) + { + foreach (char third in abcdComponents) + { + foreach (char fourth in abcdComponents) + { + bool isSettable = first != second && first != third && first != fourth && second != third && second != fourth && third != fourth; + var abcd = CharCombine(first, second, third, fourth); + var xyzw = CharCombine(getXYZW(first), getXYZW(second), getXYZW(third), getXYZW(fourth)); +#> + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 <#=abcd#> + { + get { return new simdFloat3 { m_float3s = new float4x3(x.<#=xyzw#>, y.<#=xyzw#>, z.<#=xyzw#>) }; } +<# + if (isSettable) + { +#> + set + { + float4 newX = default, newY = default, newZ = default; + newX.<#=xyzw#> = value.x; + newY.<#=xyzw#> = value.y; + newZ.<#=xyzw#> = value.z; + m_float3s = new float4x3(newX, newY, newZ); + } +<# + } +#> + } + +<# + } + } + } + } + + var xyzComponents = new char[] {'x', 'y', 'z'}; + foreach (var first in xyzComponents) + { + foreach (var second in xyzComponents) + { + foreach (var third in xyzComponents) + { + bool isSettable = first != second && first != third && second != third; +#> + [EditorBrowsable(EditorBrowsableState.Never)] + public simdFloat3 <#=first#><#=second#><#=third#> + { + get { return new simdFloat3 { m_float3s = new float4x3(<#=first#>, <#=second#>, <#=third#>) }; } +<# + if (isSettable) + { +#> + set + { + <#=first#> = value.x; + <#=second#> = value.y; + <#=third#> = value.z; + } +<# + } + +#> + } +<# + } + } + } +} +#> + } +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.tt.meta b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.tt.meta new file mode 100644 index 0000000..c667509 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Math/simdFloat3.swizzles.tt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c5a4ba3e6043634ab2de8569b862c48 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems.meta b/Packages/com.latios.latios-framework/Core/Core/Systems.meta new file mode 100644 index 0000000..2b0657a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2c541126880ef3a49a66b3f9ebe9afaa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes.meta new file mode 100644 index 0000000..ad19758 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3b7592780ed583f45a4d804294cac298 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/DestroyEntitiesOnSceneChangeSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/DestroyEntitiesOnSceneChangeSystem.cs new file mode 100644 index 0000000..b3c1c8f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/DestroyEntitiesOnSceneChangeSystem.cs @@ -0,0 +1,45 @@ +using Debug = UnityEngine.Debug; +using Unity.Entities; +using UnityEngine.SceneManagement; + +namespace Latios.Systems +{ + internal struct LatiosSceneChangeDummyTag : IComponentData { } + + [DisableAutoCreation] + [AlwaysUpdateSystem] + [UpdateInGroup(typeof(LatiosInitializationSystemGroup), OrderLast = true)] //Doesn't matter, but good for visualization + public partial class DestroyEntitiesOnSceneChangeSystem : SubSystem + { + private EntityQuery m_destroyQuery = default; + + protected override void OnCreate() + { + m_destroyQuery = Fluent.WithAll().Without().Without().Without() + .IncludePrefabs().IncludeDisabled().Build(); + SceneManager.activeSceneChanged += RealUpdateOnSceneChange; + } + + protected override void OnDestroy() + { + SceneManager.activeSceneChanged -= RealUpdateOnSceneChange; + } + + protected override void OnUpdate() + { + } + + private void RealUpdateOnSceneChange(Scene unloaded, Scene loaded) + { + if (!Enabled) + return; + + latiosWorld.ResumeNextFrame(); + EntityManager.AddComponent(EntityManager.UniversalQuery); + EntityManager.DestroyEntity(m_destroyQuery); + EntityManager.RemoveComponent(EntityManager.UniversalQuery); + latiosWorld.CreateNewSceneBlackboardEntity(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/DestroyEntitiesOnSceneChangeSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/DestroyEntitiesOnSceneChangeSystem.cs.meta new file mode 100644 index 0000000..baaffd2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/DestroyEntitiesOnSceneChangeSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5b6979af3682c94491c3a46488226fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/SceneManagerSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/SceneManagerSystem.cs new file mode 100644 index 0000000..e76a4ae --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/SceneManagerSystem.cs @@ -0,0 +1,91 @@ +using System.Diagnostics; +using Unity.Collections; +using Unity.Entities; +using UnityEngine.SceneManagement; + +namespace Latios.Systems +{ + [DisableAutoCreation] + [AlwaysUpdateSystem] + [UpdateInGroup(typeof(LatiosInitializationSystemGroup), OrderFirst = true)] + [UpdateAfter(typeof(SyncPointPlaybackSystem))] + public partial class SceneManagerSystem : SubSystem + { + private EntityQuery m_rlsQuery; + private bool m_paused = false; + + protected override void OnCreate() + { + CurrentScene curr = new CurrentScene + { + currentScene = new FixedString128Bytes(), + previousScene = new FixedString128Bytes(), + isSceneFirstFrame = false + }; + worldBlackboardEntity.AddComponentData(curr); + + latiosWorld.autoGenerateSceneBlackboardEntity = false; + } + + protected override void OnUpdate() + { + if (m_rlsQuery.CalculateChunkCount() > 0) + { + FixedString128Bytes targetScene = new FixedString128Bytes(); + bool isInvalid = false; + + Entities.WithStoreEntityQueryInField(ref m_rlsQuery).ForEach((ref RequestLoadScene rls) => + { + if (rls.newScene.Length == 0) + return; + if (targetScene.Length == 0) + targetScene = rls.newScene; + else if (rls.newScene != targetScene) + isInvalid = true; + }).Run(); + + if (targetScene.Length > 0) + { + if (isInvalid) + { + UnityEngine.Debug.LogError("Multiple scenes were requested to load during the previous frame."); + EntityManager.RemoveComponent(m_rlsQuery); + } + else + { + var curr = worldBlackboardEntity.GetComponentData(); + curr.previousScene = curr.currentScene; + UnityEngine.Debug.Log("Loading scene: " + targetScene); + SceneManager.LoadScene(targetScene.ToString()); + latiosWorld.Pause(); + m_paused = true; + curr.currentScene = targetScene; + curr.isSceneFirstFrame = true; + worldBlackboardEntity.SetComponentData(curr); + EntityManager.RemoveComponent(m_rlsQuery); + return; + } + } + } + + //Handle case where initial scene loads or set firstFrame to false + var currentScene = worldBlackboardEntity.GetComponentData(); + if (currentScene.currentScene.Length == 0) + { + latiosWorld.CreateNewSceneBlackboardEntity(); + currentScene.currentScene = SceneManager.GetActiveScene().name; + currentScene.isSceneFirstFrame = true; + } + else if (m_paused) + { + m_paused = false; + } + else + { + currentScene.isSceneFirstFrame = false; + } + worldBlackboardEntity.SetComponentData(currentScene); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/SceneManagerSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/SceneManagerSystem.cs.meta new file mode 100644 index 0000000..6b5001a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Scenes/SceneManagerSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2437ed84ed6c8614d8dcf82376808e6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms.meta new file mode 100644 index 0000000..b7348b1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2e6656434469b8045850b74ebb7f7b0b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy.meta new file mode 100644 index 0000000..52e1f8a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7aa20b03c2efb4b4d833b45316465cc2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeChildDepthsSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeChildDepthsSystem.cs new file mode 100644 index 0000000..55e88be --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeChildDepthsSystem.cs @@ -0,0 +1,169 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +// This system uses PreviousParent in all cases because it is guaranteed to be updated +// (ParentSystem just ran) and it is updated when the entity is enabled so change filters +// work correctly. +namespace Latios.Systems +{ + [UpdateInGroup(typeof(TransformSystemGroup))] + [UpdateAfter(typeof(ExtremeParentSystem))] + [UpdateBefore(typeof(ExtremeLocalToParentSystem))] + [DisableAutoCreation] + [BurstCompile] + public partial struct ExtremeChildDepthsSystem : ISystem + { + EntityQuery m_query; + + // For a 32-bit depth mask, the upper 16 bits are used as a scratch list if updates are needed. + const int kMaxDepthIterations = 16; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(true).WithAll(false).WithAll(false, true).Build(); + } + + [BurstCompile] public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.Dependency = new UpdateDepthsJob + { + parentHandle = state.GetComponentTypeHandle(true), + parentCdfe = state.GetComponentDataFromEntity(true), + childHandle = state.GetBufferTypeHandle(true), + childBfe = state.GetBufferFromEntity(true), + depthCdfe = state.GetComponentDataFromEntity(false), + depthHandle = state.GetComponentTypeHandle(false), + lastSystemVersion = state.LastSystemVersion + }.ScheduleParallel(m_query, state.Dependency); + + state.Dependency = new UpdateChunkDepthMasksJob + { + depthHandle = state.GetComponentTypeHandle(true), + chunkDepthMaskHandle = state.GetComponentTypeHandle(false), + lastSystemVersion = state.LastSystemVersion + }.ScheduleParallel(m_query, state.Dependency); + } + + // The way this job works is for each child with a dirty parent chunk, + // it walks up its ancestry to see if any ancestor has a dirty parent chunk. + // If so, the child is skipped as the ancestor will update it. + // If not, then it is responsible for walking all the way down the hierarchy + // and updating all depths after capturing the depth from its walk upward. + // In the case of new hierarchies, all but the first-level children will see + // a dirty ancestry just one level up and stop walking upwards. This is as + // efficient as it can get for chunk-granular change tracking. + // + // Todo: We could however capture the list of changed entities from ExtremeParentSystem + // and using either a bit array or a hashset run this algorithm with entity granularity. + [BurstCompile] + struct UpdateDepthsJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle parentHandle; + [ReadOnly] public ComponentDataFromEntity parentCdfe; + [ReadOnly] public BufferTypeHandle childHandle; + [ReadOnly] public BufferFromEntity childBfe; + [NativeDisableContainerSafetyRestriction] public ComponentDataFromEntity depthCdfe; + public ComponentTypeHandle depthHandle; + + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (!batchInChunk.DidChange(parentHandle, lastSystemVersion)) + return; + + var parents = batchInChunk.GetNativeArray(parentHandle); + + BufferAccessor childAccess = default; + bool hasChildrenToUpdate = batchInChunk.Has(childHandle); + if (hasChildrenToUpdate) + childAccess = batchInChunk.GetBufferAccessor(childHandle); + NativeArray depths = default; + + for (int i = 0; i < batchInChunk.Count; i++) + { + if (IsDepthChangeRoot(parents[i].Value, out var depth)) + { + if (!depths.IsCreated) + depths = batchInChunk.GetNativeArray(depthHandle); + + var startDepth = new Depth { depth = depth }; + depths[i] = startDepth; + startDepth.depth++; + + if (hasChildrenToUpdate) + { + foreach (var child in childAccess[i]) + { + WriteDepthAndRecurse(child.Value, startDepth); + } + } + } + } + } + + bool IsDepthChangeRoot(Entity parent, out byte depth) + { + var current = parent; + depth = 0; + while (parentCdfe.HasComponent(current)) + { + if (parentCdfe.DidChange(current, lastSystemVersion)) + { + return false; + } + depth++; + current = parentCdfe[current].Value; + } + return true; + } + + void WriteDepthAndRecurse(Entity child, Depth depth) + { + depthCdfe[child] = depth; + depth.depth++; + if (childBfe.HasComponent(child)) + { + foreach (var c in childBfe[child]) + { + WriteDepthAndRecurse(c.Value, depth); + } + } + } + } + + [BurstCompile] + struct UpdateChunkDepthMasksJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle depthHandle; + public ComponentTypeHandle chunkDepthMaskHandle; + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (batchInChunk.DidChange(depthHandle, lastSystemVersion) || batchInChunk.DidOrderChange(lastSystemVersion)) + { + BitField32 depthMask = default; + var depths = batchInChunk.GetNativeArray(depthHandle); + for (int i = 0; i < batchInChunk.Count; i++) + { + if (depths[i].depth < kMaxDepthIterations) + depthMask.SetBits(depths[i].depth, true); + } + + batchInChunk.SetChunkComponentData(chunkDepthMaskHandle, new ChunkDepthMask { chunkDepthMask = depthMask }); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeChildDepthsSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeChildDepthsSystem.cs.meta new file mode 100644 index 0000000..21f6e5d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeChildDepthsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9a2775b7108782478bd1e80a100f0b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeLocalToParentSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeLocalToParentSystem.cs new file mode 100644 index 0000000..2ea19e5 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeLocalToParentSystem.cs @@ -0,0 +1,457 @@ +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +// This system uses PreviousParent in all cases because it is guaranteed to be updated +// (ParentSystem just ran) and it is updated when the entity is enabled so change filters +// work correctly. +namespace Latios.Systems +{ + [DisableAutoCreation] + [UpdateInGroup(typeof(TransformSystemGroup))] + [UpdateAfter(typeof(TRSToLocalToParentSystem))] + [UpdateAfter(typeof(TRSToLocalToWorldSystem))] + [UpdateBefore(typeof(LocalToParentSystem))] + [BurstCompile] + public unsafe partial struct ExtremeLocalToParentSystem : ISystem, ISystemShouldUpdate + { + EntityQuery m_childWithParentDependencyQuery; + EntityQueryMask m_childWithParentDependencyMask; + + EntityQuery m_childQuery; + EntityQuery m_metaQuery; + + // For a 32-bit depth mask, the upper 16 bits are used as a scratch list if updates are needed. + const int kMaxDepthIterations = 16; + + ComponentTypeHandle m_headerHandle; + ComponentTypeHandle m_depthMaskHandle; + ComponentTypeHandle m_depthHandle; + ComponentTypeHandle m_ltpHandle; + ComponentTypeHandle m_parentHandle; + ComponentTypeHandle m_ltwHandle; + + public void OnCreate(ref SystemState state) + { + m_childWithParentDependencyQuery = state.Fluent().WithAll(false).WithAll(true).WithAll(true).UseWriteGroups().Build(); + m_childWithParentDependencyMask = m_childWithParentDependencyQuery.GetEntityQueryMask(); + m_childQuery = state.Fluent().WithAll(true).WithAll(true).Build(); + m_metaQuery = state.Fluent().WithAll(true).WithAll().Build(); + + m_headerHandle = state.GetComponentTypeHandle(true); + m_depthMaskHandle = state.GetComponentTypeHandle(false); + m_depthHandle = state.GetComponentTypeHandle(true); + m_ltpHandle = state.GetComponentTypeHandle(true); + m_parentHandle = state.GetComponentTypeHandle(true); + m_ltwHandle = state.GetComponentTypeHandle(false); + } + + public bool ShouldUpdateSystem(ref SystemState state) + { + return !m_childWithParentDependencyQuery.IsEmptyIgnoreFilter; + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + var unmanaged = state.WorldUnmanaged; + var chunkListArray = new ChunkListArray(ref unmanaged); + var blockLists = unmanaged.UpdateAllocator.AllocateNativeArray(kMaxDepthIterations); + + m_headerHandle.Update(ref state); + m_depthMaskHandle.Update(ref state); + m_depthHandle.Update(ref state); + var entityHandle = state.GetEntityTypeHandle(); + m_ltpHandle.Update(ref state); + m_parentHandle.Update(ref state); + m_ltwHandle.Update(ref state); + var ltwCdfe = state.GetComponentDataFromEntity(false); + + state.Dependency = new AllocateBlockListsJob + { + chunkBlockLists = blockLists + }.Schedule(kMaxDepthIterations, 1, state.Dependency); + + state.Dependency = new ClassifyChunksAndResetMasksJob + { + headerHandle = m_headerHandle, + depthMaskHandle = m_depthMaskHandle, + chunkBlockLists = blockLists + }.ScheduleParallel(m_metaQuery, state.Dependency); + + state.Dependency = new FlattenBlocklistsJob + { + chunkBlockLists = blockLists, + chunkListArray = chunkListArray + }.Schedule(kMaxDepthIterations, 1, state.Dependency); + + for (int i = 0; i < kMaxDepthIterations; i++) + { + var chunkList = chunkListArray[i]; + state.Dependency = new CheckIfMatricesShouldUpdateForSingleDepthLevelJob + { + chunkList = chunkList.AsDeferredJobArray(), + depth = i, + depthHandle = m_depthHandle, + depthMaskHandle = m_depthMaskHandle, + entityHandle = entityHandle, + lastSystemVersion = state.LastSystemVersion, + ltpHandle = m_ltpHandle, + ltwCdfe = ltwCdfe, + parentHandle = m_parentHandle, + shouldUpdateMask = m_childWithParentDependencyMask + }.Schedule(chunkList, 1, state.Dependency); + + state.Dependency = new UpdateMatricesOfSingleDepthLevelJob + { + chunkList = chunkList.AsDeferredJobArray(), + depth = i, + depthHandle = m_depthHandle, + depthMaskHandle = m_depthMaskHandle, + ltpHandle = m_ltpHandle, + ltwCdfe = ltwCdfe, + ltwHandle = m_ltwHandle, + parentHandle = m_parentHandle + }.Schedule(chunkList, 1, state.Dependency); + } + + var finalChunkList = chunkListArray[kMaxDepthIterations - 1]; + + state.Dependency = new UpdateMatricesOfDeepChildrenJob + { + childBfe = state.GetBufferFromEntity(true), + childHandle = state.GetBufferTypeHandle(true), + chunkList = finalChunkList.AsDeferredJobArray(), + depthHandle = m_depthHandle, + depthLevel = kMaxDepthIterations - 1, + lastSystemVersion = state.LastSystemVersion, + ltpCdfe = state.GetComponentDataFromEntity(true), + ltwCdfe = ltwCdfe, + ltwHandle = m_ltwHandle, + ltwWriteGroupMask = m_childWithParentDependencyMask, + parentCdfe = state.GetComponentDataFromEntity(true) + }.Schedule(finalChunkList, 1, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + struct ChunkListArray + { + [NativeDisableParallelForRestriction] NativeList l0; + [NativeDisableParallelForRestriction] NativeList l1; + [NativeDisableParallelForRestriction] NativeList l2; + [NativeDisableParallelForRestriction] NativeList l3; + [NativeDisableParallelForRestriction] NativeList l4; + [NativeDisableParallelForRestriction] NativeList l5; + [NativeDisableParallelForRestriction] NativeList l6; + [NativeDisableParallelForRestriction] NativeList l7; + [NativeDisableParallelForRestriction] NativeList l8; + [NativeDisableParallelForRestriction] NativeList l9; + [NativeDisableParallelForRestriction] NativeList l10; + [NativeDisableParallelForRestriction] NativeList l11; + [NativeDisableParallelForRestriction] NativeList l12; + [NativeDisableParallelForRestriction] NativeList l13; + [NativeDisableParallelForRestriction] NativeList l14; + [NativeDisableParallelForRestriction] NativeList l15; + + public ChunkListArray(ref WorldUnmanaged world) + { + var allocator = world.UpdateAllocator.ToAllocator; + l0 = new NativeList(allocator); + l1 = new NativeList(allocator); + l2 = new NativeList(allocator); + l3 = new NativeList(allocator); + l4 = new NativeList(allocator); + l5 = new NativeList(allocator); + l6 = new NativeList(allocator); + l7 = new NativeList(allocator); + l8 = new NativeList(allocator); + l9 = new NativeList(allocator); + l10 = new NativeList(allocator); + l11 = new NativeList(allocator); + l12 = new NativeList(allocator); + l13 = new NativeList(allocator); + l14 = new NativeList(allocator); + l15 = new NativeList(allocator); + } + + public NativeList this[int index] + { + get + { + return index switch + { + 0 => l0, + 1 => l1, + 2 => l2, + 3 => l3, + 4 => l4, + 5 => l5, + 6 => l6, + 7 => l7, + 8 => l8, + 9 => l9, + 10 => l10, + 11 => l11, + 12 => l12, + 13 => l13, + 14 => l14, + _ => l15, + }; + } + } + } + + [BurstCompile] + struct AllocateBlockListsJob : IJobParallelForBurstSchedulable + { + public NativeArray chunkBlockLists; + + public void Execute(int i) + { + chunkBlockLists[i] = new UnsafeParallelBlockList(sizeof(ArchetypeChunk), 64, Allocator.TempJob); + } + } + + [BurstCompile] + struct ClassifyChunksAndResetMasksJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle headerHandle; + public ComponentTypeHandle depthMaskHandle; + + [NativeDisableParallelForRestriction] + public NativeArray chunkBlockLists; + + [NativeSetThreadIndex] + int threadIndex; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var headers = batchInChunk.GetNativeArray(headerHandle); + var depthMasks = batchInChunk.GetNativeArray(depthMaskHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var mask = depthMasks[i]; + mask.chunkDepthMask.Value &= 0xffff; + depthMasks[i] = mask; + + var dynamicMask = mask.chunkDepthMask; + int j; + while ((j = dynamicMask.CountTrailingZeros()) < kMaxDepthIterations) + { + chunkBlockLists[j].Write(headers[i].ArchetypeChunk, threadIndex); + dynamicMask.SetBits(j, false); + } + } + } + } + + [BurstCompile] + struct FlattenBlocklistsJob : IJobParallelForBurstSchedulable + { + public NativeArray chunkBlockLists; + [NativeDisableParallelForRestriction] public ChunkListArray chunkListArray; + + public void Execute(int index) + { + var list = chunkListArray[index]; + list.ResizeUninitialized(chunkBlockLists[index].Count()); + chunkBlockLists[index].GetElementValues(list.AsArray()); + chunkBlockLists[index].Dispose(); + } + } + + [BurstCompile] + struct CheckIfMatricesShouldUpdateForSingleDepthLevelJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray chunkList; + + [ReadOnly] public ComponentTypeHandle ltpHandle; + [ReadOnly] public ComponentTypeHandle parentHandle; + [ReadOnly] public ComponentTypeHandle depthHandle; + [ReadOnly] public EntityTypeHandle entityHandle; + [ReadOnly] public ComponentDataFromEntity ltwCdfe; + + public ComponentTypeHandle depthMaskHandle; + + public EntityQueryMask shouldUpdateMask; + public int depth; + public uint lastSystemVersion; + + public void Execute(int index) + { + var chunk = chunkList[index]; + + if (!shouldUpdateMask.Matches(chunk.GetNativeArray(entityHandle)[0])) + { + return; + } + + var parents = chunk.GetNativeArray(parentHandle); + var depths = chunk.GetNativeArray(depthHandle); + + if (chunk.DidChange(parentHandle, lastSystemVersion) || chunk.DidChange(ltpHandle, lastSystemVersion)) + { + // Fast path. No need to check for changes on parent. + SetNeedsUpdate(chunk); + } + else + { + for (int i = 0; i < chunk.Count; i++) + { + if (depth == depths[i].depth) + { + var parent = parents[i].Value; + if (ltwCdfe.DidChange(parent, lastSystemVersion)) + { + SetNeedsUpdate(chunk); + return; + } + } + } + } + } + + void SetNeedsUpdate(ArchetypeChunk chunk) + { + var depthMask = chunk.GetChunkComponentData(depthMaskHandle); + depthMask.chunkDepthMask.SetBits(depth + kMaxDepthIterations, true); + chunk.SetChunkComponentData(depthMaskHandle, depthMask); + } + } + + [BurstCompile] + struct UpdateMatricesOfSingleDepthLevelJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray chunkList; + [NativeDisableContainerSafetyRestriction] public ComponentTypeHandle ltwHandle; + + [ReadOnly] public ComponentTypeHandle ltpHandle; + [ReadOnly] public ComponentTypeHandle parentHandle; + [ReadOnly] public ComponentTypeHandle depthHandle; + [ReadOnly] public ComponentDataFromEntity ltwCdfe; + + [ReadOnly] public ComponentTypeHandle depthMaskHandle; + + public int depth; + + public void Execute(int index) + { + var chunk = chunkList[index]; + if (!chunk.GetChunkComponentData(depthMaskHandle).chunkDepthMask.IsSet(depth + kMaxDepthIterations)) + return; + + var parents = chunk.GetNativeArray(parentHandle); + var depths = chunk.GetNativeArray(depthHandle); + var ltps = chunk.GetNativeArray(ltpHandle); + var ltws = chunk.GetNativeArray(ltwHandle); + + for (int i = 0; i < chunk.Count; i++) + { + if (depth == depths[i].depth) + { + ltws[i] = new LocalToWorld { Value = math.mul(ltwCdfe[parents[i].Value].Value, ltps[i].Value) }; + } + } + } + } + + [BurstCompile] + struct UpdateMatricesOfDeepChildrenJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray chunkList; + [ReadOnly] public ComponentTypeHandle ltwHandle; + [ReadOnly] public ComponentTypeHandle depthHandle; + [ReadOnly] public BufferTypeHandle childHandle; + [ReadOnly] public BufferFromEntity childBfe; + [ReadOnly] public ComponentDataFromEntity ltpCdfe; + [ReadOnly] public ComponentDataFromEntity parentCdfe; + [ReadOnly] public EntityQueryMask ltwWriteGroupMask; + public uint lastSystemVersion; + public int depthLevel; + + [NativeDisableContainerSafetyRestriction] + public ComponentDataFromEntity ltwCdfe; + + void ChildLocalToWorld(ref float4x4 parentLocalToWorld, + Entity entity, + bool updateChildrenTransform, + Entity parent, + ref bool parentLtwValid, + bool parentsChildBufferChanged) + { + updateChildrenTransform = updateChildrenTransform || ltpCdfe.DidChange(entity, lastSystemVersion); + updateChildrenTransform = updateChildrenTransform || (parentsChildBufferChanged && parentCdfe.DidChange(entity, lastSystemVersion)); + + float4x4 localToWorldMatrix = default; + bool ltwIsValid = false; + + bool isDependent = ltwWriteGroupMask.Matches(entity); + if (updateChildrenTransform && isDependent) + { + if (!parentLtwValid) + { + parentLocalToWorld = ltwCdfe[parent].Value; + parentLtwValid = true; + } + var localToParent = ltpCdfe[entity]; + localToWorldMatrix = math.mul(parentLocalToWorld, localToParent.Value); + ltwIsValid = true; + ltwCdfe[entity] = new LocalToWorld { Value = localToWorldMatrix }; + } + else if (!isDependent) //This entity has a component with the WriteGroup(LocalToWorld) + { + updateChildrenTransform = updateChildrenTransform || ltwCdfe.DidChange(entity, lastSystemVersion); + } + if (childBfe.HasComponent(entity)) + { + var children = childBfe[entity]; + var childrenChanged = updateChildrenTransform || childBfe.DidChange(entity, lastSystemVersion); + for (int i = 0; i < children.Length; i++) + { + ChildLocalToWorld(ref localToWorldMatrix, children[i].Value, updateChildrenTransform, entity, ref ltwIsValid, childrenChanged); + } + } + } + + public void Execute(int index) + { + var batchInChunk = chunkList[index]; + + if (!batchInChunk.Has(childHandle)) + return; + + bool updateChildrenTransform = + batchInChunk.DidChange(ltwHandle, lastSystemVersion) || + batchInChunk.DidChange(childHandle, lastSystemVersion); + + var chunkLocalToWorld = batchInChunk.GetNativeArray(ltwHandle); + var depths = batchInChunk.GetNativeArray(depthHandle); + var chunkChildren = batchInChunk.GetBufferAccessor(childHandle); + bool ltwIsValid = true; + for (int i = 0; i < batchInChunk.Count; i++) + { + if (depths[i].depth == depthLevel) + { + var localToWorldMatrix = chunkLocalToWorld[i].Value; + var children = chunkChildren[i]; + for (int j = 0; j < children.Length; j++) + { + ChildLocalToWorld(ref localToWorldMatrix, children[j].Value, updateChildrenTransform, Entity.Null, ref ltwIsValid, + batchInChunk.DidChange(childHandle, lastSystemVersion)); + } + } + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeLocalToParentSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeLocalToParentSystem.cs.meta new file mode 100644 index 0000000..9cf8a31 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeLocalToParentSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42f65355746ba44419f31c754c5dd897 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeParentSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeParentSystem.cs new file mode 100644 index 0000000..6dfdca1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeParentSystem.cs @@ -0,0 +1,468 @@ +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Profiling; +using Unity.Transforms; + +namespace Latios.Systems +{ + [UpdateInGroup(typeof(TransformSystemGroup))] + [UpdateBefore(typeof(ParentSystem))] + [DisableAutoCreation] + [BurstCompile] + public partial struct ExtremeParentSystem : ISystem + { + EntityQuery m_NewParentsQuery; + EntityQuery m_RemovedParentsQuery; + EntityQuery m_ExistingParentsQuery; + EntityQuery m_DeletedParentsQuery; + + ComponentTypes m_NewParentComponents; + + static readonly ProfilerMarker k_ProfileDeletedParents = new ProfilerMarker("ExtremeParentSystem.DeletedParents"); + static readonly ProfilerMarker k_ProfileRemoveParents = new ProfilerMarker("ExtremeParentSystem.RemoveParents"); + static readonly ProfilerMarker k_ProfileChangeParents = new ProfilerMarker("ExtremeParentSystem.ChangeParents"); + static readonly ProfilerMarker k_ProfileNewParents = new ProfilerMarker("ExtremeParentSystem.NewParents"); + + int FindChildIndex(DynamicBuffer children, Entity entity) + { + for (int i = 0; i < children.Length; i++) + { + if (children[i].Value == entity) + return i; + } + + throw new InvalidOperationException("Child entity not in parent"); + } + + void RemoveChildFromParent(ref SystemState state, Entity childEntity, Entity parentEntity) + { + if (!state.EntityManager.HasComponent(parentEntity)) + return; + + var children = state.EntityManager.GetBuffer(parentEntity); + var childIndex = FindChildIndex(children, childEntity); + children.RemoveAtSwapBack(childIndex); + if (children.Length == 0) + { + state.EntityManager.RemoveComponent(parentEntity, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + } + } + + [BurstCompile] + struct GatherChangedParents : IJobEntityBatch + { + public NativeParallelMultiHashMap.ParallelWriter ParentChildrenToAdd; + public NativeParallelMultiHashMap.ParallelWriter ParentChildrenToRemove; + public NativeParallelHashMap.ParallelWriter UniqueParents; + public ComponentTypeHandle PreviousParentTypeHandle; + + [ReadOnly] public ComponentTypeHandle ParentTypeHandle; + [ReadOnly] public EntityTypeHandle EntityTypeHandle; + [ReadOnly] public BufferFromEntity ChildFromEntity; + public uint LastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (batchInChunk.DidChange(ParentTypeHandle, LastSystemVersion) || + batchInChunk.DidChange(PreviousParentTypeHandle, LastSystemVersion)) + { + var chunkPreviousParents = batchInChunk.GetNativeArray(PreviousParentTypeHandle); + var chunkParents = batchInChunk.GetNativeArray(ParentTypeHandle); + var chunkEntities = batchInChunk.GetNativeArray(EntityTypeHandle); + + for (int j = 0; j < batchInChunk.Count; j++) + { + if (chunkParents[j].Value != chunkPreviousParents[j].Value) + { + var childEntity = chunkEntities[j]; + var parentEntity = chunkParents[j].Value; + var previousParentEntity = chunkPreviousParents[j].Value; + + ParentChildrenToAdd.Add(parentEntity, childEntity); + UniqueParents.TryAdd(parentEntity, 0); + + if (ChildFromEntity.HasComponent(previousParentEntity)) + { + ParentChildrenToRemove.Add(previousParentEntity, childEntity); + UniqueParents.TryAdd(previousParentEntity, 0); + } + + chunkPreviousParents[j] = new PreviousParent + { + Value = parentEntity + }; + } + } + } + } + } + + [BurstCompile] + struct FindMissingChild : IJob + { + [ReadOnly] public NativeParallelHashMap UniqueParents; + [ReadOnly] public BufferFromEntity ChildFromEntity; + public NativeList ParentsMissingChild; + + public void Execute() + { + var parents = UniqueParents.GetKeyArray(Allocator.Temp); + for (int i = 0; i < parents.Length; i++) + { + var parent = parents[i]; + if (!ChildFromEntity.HasComponent(parent)) + { + ParentsMissingChild.Add(parent); + } + } + } + } + + [BurstCompile] + struct FixupChangedChildren : IJob + { + [ReadOnly] public NativeParallelMultiHashMap ParentChildrenToAdd; + [ReadOnly] public NativeParallelMultiHashMap ParentChildrenToRemove; + [ReadOnly] public NativeParallelHashMap UniqueParents; + + public BufferFromEntity ChildFromEntity; + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ThrowChildEntityNotInParent() + { + throw new InvalidOperationException("Child entity not in parent"); + } + + int FindChildIndex(DynamicBuffer children, Entity entity) + { + for (int i = 0; i < children.Length; i++) + { + if (children[i].Value == entity) + return i; + } + + ThrowChildEntityNotInParent(); + return -1; + } + + void RemoveChildrenFromParent(Entity parent, DynamicBuffer children, NativeList entityCache) + { + if (ParentChildrenToRemove.TryGetFirstValue(parent, out var child, out var it)) + { + entityCache.Clear(); + do + { + entityCache.Add(child); + } + while (ParentChildrenToRemove.TryGetNextValue(out child, ref it)); + + entityCache.Sort(); + foreach (var entity in entityCache) + { + var childIndex = FindChildIndex(children, entity); + children.RemoveAtSwapBack(childIndex); + } + } + } + + void AddChildrenToParent(Entity parent, DynamicBuffer children, NativeList entityCache) + { + if (ParentChildrenToAdd.TryGetFirstValue(parent, out var child, out var it)) + { + entityCache.Clear(); + do + { + entityCache.Add(child); + } + while (ParentChildrenToAdd.TryGetNextValue(out child, ref it)); + + entityCache.Sort(); + foreach (var entity in entityCache) + { + children.Add(new Child() { + Value = entity + }); + } + } + } + + public void Execute() + { + var parents = UniqueParents.GetKeyArray(Allocator.Temp); + var entityCache = new NativeList(128, Allocator.Temp); + for (int i = 0; i < parents.Length; i++) + { + var parent = parents[i]; + // Todo: Until the minimum Entities is 0.51, need to use two separate queries since + // TryGetBuffer is broken. + if (ChildFromEntity.HasComponent(parent)) + { + var children = ChildFromEntity[parent]; + + RemoveChildrenFromParent(parent, children, entityCache); + AddChildrenToParent(parent, children, entityCache); + } + } + } + } + + //burst disabled pending burstable EntityQueryDesc + //[BurstCompile] + public unsafe void OnCreate(ref SystemState state) + { + state.WorldUnmanaged.ResolveSystemState(state.WorldUnmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + + m_NewParentComponents = new ComponentTypes(ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ChunkComponent()); + + m_NewParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + }, + None = new ComponentType[] + { + typeof(PreviousParent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + m_RemovedParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + typeof(PreviousParent) + }, + None = new ComponentType[] + { + typeof(Parent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + m_ExistingParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + typeof(PreviousParent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + m_ExistingParentsQuery.SetChangedVersionFilter(new ComponentType[] { typeof(Parent), typeof(PreviousParent) }); + m_DeletedParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + typeof(Child) + }, + None = new ComponentType[] + { + typeof(LocalToWorld) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + + void UpdateNewParents(ref SystemState state) + { + if (m_NewParentsQuery.IsEmptyIgnoreFilter) + return; + + state.CompleteDependency(); + + state.EntityManager.AddComponent(m_NewParentsQuery, m_NewParentComponents); + } + + void UpdateRemoveParents(ref SystemState state) + { + if (m_RemovedParentsQuery.IsEmptyIgnoreFilter) + return; + + state.CompleteDependency(); + + var childEntities = m_RemovedParentsQuery.ToEntityArray(Allocator.TempJob); + var previousParents = m_RemovedParentsQuery.ToComponentDataArray(Allocator.TempJob); + + for (int i = 0; i < childEntities.Length; i++) + { + var childEntity = childEntities[i]; + var previousParentEntity = previousParents[i].Value; + + RemoveChildFromParent(ref state, childEntity, previousParentEntity); + } + + state.EntityManager.RemoveComponent(m_RemovedParentsQuery, m_NewParentComponents); + childEntities.Dispose(); + previousParents.Dispose(); + } + + void UpdateChangeParents(ref SystemState state) + { + if (m_ExistingParentsQuery.IsEmptyIgnoreFilter) + return; + + var count = m_ExistingParentsQuery.CalculateEntityCount() * 2; // Potentially 2x changed: current and previous + if (count == 0) + return; + + state.CompleteDependency(); + + // 1. Get (Parent,Child) to remove + // 2. Get (Parent,Child) to add + // 3. Get unique Parent change list + // 4. Set PreviousParent to new Parent + var parentChildrenToAdd = new NativeParallelMultiHashMap(count, Allocator.TempJob); + var parentChildrenToRemove = new NativeParallelMultiHashMap(count, Allocator.TempJob); + var uniqueParents = new NativeParallelHashMap(count, Allocator.TempJob); + var gatherChangedParentsJob = new GatherChangedParents + { + ParentChildrenToAdd = parentChildrenToAdd.AsParallelWriter(), + ParentChildrenToRemove = parentChildrenToRemove.AsParallelWriter(), + UniqueParents = uniqueParents.AsParallelWriter(), + PreviousParentTypeHandle = state.GetComponentTypeHandle(false), + ChildFromEntity = state.GetBufferFromEntity(), + ParentTypeHandle = state.GetComponentTypeHandle(true), + EntityTypeHandle = state.GetEntityTypeHandle(), + LastSystemVersion = state.LastSystemVersion + }; + var gatherChangedParentsJobHandle = gatherChangedParentsJob.ScheduleParallel(m_ExistingParentsQuery); + gatherChangedParentsJobHandle.Complete(); + + // 5. (Structural change) Add any missing Child to Parents + var parentsMissingChild = new NativeList(Allocator.TempJob); + var findMissingChildJob = new FindMissingChild + { + UniqueParents = uniqueParents, + ChildFromEntity = state.GetBufferFromEntity(true), + ParentsMissingChild = parentsMissingChild + }; + //var findMissingChildJobHandle = findMissingChildJob.Schedule(); + //findMissingChildJobHandle.Complete(); + findMissingChildJob.Execute(); + + state.EntityManager.AddComponent(parentsMissingChild.AsArray(), ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + + // 6. Get Child[] for each unique Parent + // 7. Update Child[] for each unique Parent + var fixupChangedChildrenJob = new FixupChangedChildren + { + ParentChildrenToAdd = parentChildrenToAdd, + ParentChildrenToRemove = parentChildrenToRemove, + UniqueParents = uniqueParents, + ChildFromEntity = state.GetBufferFromEntity() + }; + + //var fixupChangedChildrenJobHandle = fixupChangedChildrenJob.Schedule(); + //fixupChangedChildrenJobHandle.Complete(); + fixupChangedChildrenJob.Execute(); + + parentChildrenToAdd.Dispose(); + parentChildrenToRemove.Dispose(); + uniqueParents.Dispose(); + parentsMissingChild.Dispose(); + } + + [BurstCompile] + struct GatherChildEntities : IJob + { + [ReadOnly] public NativeArray Parents; + public NativeList Children; + [ReadOnly] public BufferFromEntity ChildFromEntity; + [ReadOnly] public ComponentDataFromEntity ParentFromEntity; + + public void Execute() + { + for (int i = 0; i < Parents.Length; i++) + { + var parentEntity = Parents[i]; + var childEntitiesSource = ChildFromEntity[parentEntity].AsNativeArray(); + for (int j = 0; j < childEntitiesSource.Length; j++) + { + var childEntity = childEntitiesSource[j].Value; + if (ParentFromEntity.HasComponent(childEntity) && ParentFromEntity[childEntity].Value == parentEntity) + { + Children.Add(childEntitiesSource[j].Value); + } + } + } + } + } + + void UpdateDeletedParents(ref SystemState state) + { + if (m_DeletedParentsQuery.IsEmptyIgnoreFilter) + return; + + state.CompleteDependency(); + + var previousParents = m_DeletedParentsQuery.ToEntityArray(Allocator.TempJob); + var childEntities = new NativeList(Allocator.TempJob); + var gatherChildEntitiesJob = new GatherChildEntities + { + Parents = previousParents, + Children = childEntities, + ChildFromEntity = state.GetBufferFromEntity(true), + ParentFromEntity = state.GetComponentDataFromEntity(true), + }; + //var gatherChildEntitiesJobHandle = gatherChildEntitiesJob.Schedule(); + //gatherChildEntitiesJobHandle.Complete(); + gatherChildEntitiesJob.Execute(); + + // Todo: Need batch remove components for list of entities so that we don't have to do these repeated structural changes. + // Right now we just leave Depth and ChunkDepthMask to avoid additional structural change costs. + state.EntityManager.RemoveComponent( + childEntities, + ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + state.EntityManager.RemoveComponent(childEntities, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + state.EntityManager.RemoveComponent(childEntities, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + state.EntityManager.RemoveComponent(m_DeletedParentsQuery, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + + childEntities.Dispose(); + previousParents.Dispose(); + } + + //burst disabled pending IJobBurstSchedulable not requiring hardcoded calls to init + //for every distinct job + [BurstCompile] + public void OnUpdate(ref SystemState state) //JobHandle inputDeps) + { + //inputDeps.Complete(); // #todo + //state.Dependency.Complete(); + + k_ProfileDeletedParents.Begin(); + UpdateDeletedParents(ref state); + k_ProfileDeletedParents.End(); + + k_ProfileRemoveParents.Begin(); + UpdateRemoveParents(ref state); + k_ProfileRemoveParents.End(); + + k_ProfileNewParents.Begin(); + UpdateNewParents(ref state); + k_ProfileNewParents.End(); + + k_ProfileChangeParents.Begin(); + UpdateChangeParents(ref state); + k_ProfileChangeParents.End(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeParentSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeParentSystem.cs.meta new file mode 100644 index 0000000..0891a72 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ExtremeHierarchy/ExtremeParentSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f93ceb3324deb1c49bc1a28e8c30228c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms.meta new file mode 100644 index 0000000..1df3463 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a15076902f56c1d4c889892f0accb1bf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedLocalToParentSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedLocalToParentSystem.cs new file mode 100644 index 0000000..949f2d9 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedLocalToParentSystem.cs @@ -0,0 +1,169 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +// This system uses PreviousParent in all cases because it is guaranteed to be updated +// (ParentSystem just ran) and it is updated when the entity is enabled so change filters +// work correctly. +namespace Latios.Systems +{ + [DisableAutoCreation] + [UpdateInGroup(typeof(TransformSystemGroup))] + [UpdateAfter(typeof(TRSToLocalToParentSystem))] + [UpdateAfter(typeof(TRSToLocalToWorldSystem))] + [UpdateBefore(typeof(LocalToParentSystem))] + [BurstCompile] + public partial struct ImprovedLocalToParentSystem : ISystem + { + private EntityQuery m_RootsQuery; + private EntityQueryMask m_LocalToWorldWriteGroupMask; + + // LocalToWorld = Parent.LocalToWorld * LocalToParent + [BurstCompile] + struct UpdateHierarchy : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle LocalToWorldTypeHandle; + [ReadOnly] public BufferTypeHandle ChildTypeHandle; + [ReadOnly] public BufferFromEntity ChildFromEntity; + [ReadOnly] public ComponentDataFromEntity ParentFromEntity; + [ReadOnly] public ComponentDataFromEntity LocalToParentFromEntity; + [ReadOnly] public EntityQueryMask LocalToWorldWriteGroupMask; + public uint LastSystemVersion; + + [NativeDisableContainerSafetyRestriction] + public ComponentDataFromEntity LocalToWorldFromEntity; + + void ChildLocalToWorld(ref float4x4 parentLocalToWorld, + Entity entity, + bool updateChildrenTransform, + Entity parent, + ref bool parentLtwValid, + bool parentsChildBufferChanged) + { + updateChildrenTransform = updateChildrenTransform || LocalToParentFromEntity.DidChange(entity, LastSystemVersion); + updateChildrenTransform = updateChildrenTransform || (parentsChildBufferChanged && ParentFromEntity.DidChange(entity, LastSystemVersion)); + + float4x4 localToWorldMatrix = default; + bool ltwIsValid = false; + + bool isDependent = LocalToWorldWriteGroupMask.Matches(entity); + if (updateChildrenTransform && isDependent) + { + if (!parentLtwValid) + { + parentLocalToWorld = LocalToWorldFromEntity[parent].Value; + parentLtwValid = true; + } + var localToParent = LocalToParentFromEntity[entity]; + localToWorldMatrix = math.mul(parentLocalToWorld, localToParent.Value); + ltwIsValid = true; + LocalToWorldFromEntity[entity] = new LocalToWorld { Value = localToWorldMatrix }; + } + else if (!isDependent) //This entity has a component with the WriteGroup(LocalToWorld) + { + updateChildrenTransform = updateChildrenTransform || LocalToWorldFromEntity.DidChange(entity, LastSystemVersion); + } + if (ChildFromEntity.HasComponent(entity)) + { + var children = ChildFromEntity[entity]; + var childrenChanged = updateChildrenTransform || ChildFromEntity.DidChange(entity, LastSystemVersion); + for (int i = 0; i < children.Length; i++) + { + ChildLocalToWorld(ref localToWorldMatrix, children[i].Value, updateChildrenTransform, entity, ref ltwIsValid, childrenChanged); + } + } + } + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + bool updateChildrenTransform = + batchInChunk.DidChange(LocalToWorldTypeHandle, LastSystemVersion) || + batchInChunk.DidChange(ChildTypeHandle, LastSystemVersion); + + var chunkLocalToWorld = batchInChunk.GetNativeArray(LocalToWorldTypeHandle); + var chunkChildren = batchInChunk.GetBufferAccessor(ChildTypeHandle); + bool ltwIsValid = true; + for (int i = 0; i < batchInChunk.Count; i++) + { + var localToWorldMatrix = chunkLocalToWorld[i].Value; + var children = chunkChildren[i]; + for (int j = 0; j < children.Length; j++) + { + ChildLocalToWorld(ref localToWorldMatrix, children[j].Value, updateChildrenTransform, Entity.Null, ref ltwIsValid, + batchInChunk.DidChange(ChildTypeHandle, LastSystemVersion)); + } + } + } + } + + //burst disabled pending burstable entityquerydesc + //[BurstCompile] + public unsafe void OnCreate(ref SystemState state) + { + state.WorldUnmanaged.ResolveSystemState(state.WorldUnmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + + m_RootsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + }, + None = new ComponentType[] + { + typeof(Parent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + + m_LocalToWorldWriteGroupMask = state.EntityManager.GetEntityQueryMask(state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + typeof(LocalToWorld), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + }, + Options = EntityQueryOptions.FilterWriteGroup + })); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + + //disabling burst in dotsrt until burstable scheduling works +#if !UNITY_DOTSRUNTIME + [BurstCompile] +#endif + public void OnUpdate(ref SystemState state) + { + var localToWorldType = state.GetComponentTypeHandle(true); + var childType = state.GetBufferTypeHandle(true); + var childFromEntity = state.GetBufferFromEntity(true); + var parentFromEntity = state.GetComponentDataFromEntity(true); + var localToParentFromEntity = state.GetComponentDataFromEntity(true); + var localToWorldFromEntity = state.GetComponentDataFromEntity(); + + var updateHierarchyJob = new UpdateHierarchy + { + LocalToWorldTypeHandle = localToWorldType, + ChildTypeHandle = childType, + ChildFromEntity = childFromEntity, + ParentFromEntity = parentFromEntity, + LocalToParentFromEntity = localToParentFromEntity, + LocalToWorldFromEntity = localToWorldFromEntity, + LocalToWorldWriteGroupMask = m_LocalToWorldWriteGroupMask, + LastSystemVersion = state.LastSystemVersion + }; + state.Dependency = updateHierarchyJob.ScheduleParallel(m_RootsQuery, state.Dependency); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedLocalToParentSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedLocalToParentSystem.cs.meta new file mode 100644 index 0000000..00b5ca2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedLocalToParentSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe8b077f36964fe4fa9e0882f0e0e02e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedParentSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedParentSystem.cs new file mode 100644 index 0000000..c3815e0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedParentSystem.cs @@ -0,0 +1,464 @@ +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Profiling; +using Unity.Transforms; + +namespace Latios.Systems +{ + [UpdateInGroup(typeof(TransformSystemGroup))] + [UpdateBefore(typeof(ParentSystem))] + [DisableAutoCreation] + [BurstCompile] + public partial struct ImprovedParentSystem : ISystem + { + EntityQuery m_NewParentsQuery; + EntityQuery m_RemovedParentsQuery; + EntityQuery m_ExistingParentsQuery; + EntityQuery m_DeletedParentsQuery; + + static readonly ProfilerMarker k_ProfileDeletedParents = new ProfilerMarker("ImprovedParentSystem.DeletedParents"); + static readonly ProfilerMarker k_ProfileRemoveParents = new ProfilerMarker("ImprovedParentSystem.RemoveParents"); + static readonly ProfilerMarker k_ProfileChangeParents = new ProfilerMarker("ImprovedParentSystem.ChangeParents"); + static readonly ProfilerMarker k_ProfileNewParents = new ProfilerMarker("ImprovedParentSystem.NewParents"); + + int FindChildIndex(DynamicBuffer children, Entity entity) + { + for (int i = 0; i < children.Length; i++) + { + if (children[i].Value == entity) + return i; + } + + throw new InvalidOperationException("Child entity not in parent"); + } + + void RemoveChildFromParent(ref SystemState state, Entity childEntity, Entity parentEntity) + { + if (!state.EntityManager.HasComponent(parentEntity)) + return; + + var children = state.EntityManager.GetBuffer(parentEntity); + var childIndex = FindChildIndex(children, childEntity); + children.RemoveAtSwapBack(childIndex); + if (children.Length == 0) + { + state.EntityManager.RemoveComponent(parentEntity, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + } + } + + [BurstCompile] + struct GatherChangedParents : IJobEntityBatch + { + public NativeParallelMultiHashMap.ParallelWriter ParentChildrenToAdd; + public NativeParallelMultiHashMap.ParallelWriter ParentChildrenToRemove; + public NativeParallelHashMap.ParallelWriter UniqueParents; + public ComponentTypeHandle PreviousParentTypeHandle; + + [ReadOnly] public ComponentTypeHandle ParentTypeHandle; + [ReadOnly] public EntityTypeHandle EntityTypeHandle; + [ReadOnly] public BufferFromEntity ChildFromEntity; + public uint LastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (batchInChunk.DidChange(ParentTypeHandle, LastSystemVersion) || + batchInChunk.DidChange(PreviousParentTypeHandle, LastSystemVersion)) + { + var chunkPreviousParents = batchInChunk.GetNativeArray(PreviousParentTypeHandle); + var chunkParents = batchInChunk.GetNativeArray(ParentTypeHandle); + var chunkEntities = batchInChunk.GetNativeArray(EntityTypeHandle); + + for (int j = 0; j < batchInChunk.Count; j++) + { + if (chunkParents[j].Value != chunkPreviousParents[j].Value) + { + var childEntity = chunkEntities[j]; + var parentEntity = chunkParents[j].Value; + var previousParentEntity = chunkPreviousParents[j].Value; + + ParentChildrenToAdd.Add(parentEntity, childEntity); + UniqueParents.TryAdd(parentEntity, 0); + + if (ChildFromEntity.HasComponent(previousParentEntity)) + { + ParentChildrenToRemove.Add(previousParentEntity, childEntity); + UniqueParents.TryAdd(previousParentEntity, 0); + } + + chunkPreviousParents[j] = new PreviousParent + { + Value = parentEntity + }; + } + } + } + } + } + + [BurstCompile] + struct FindMissingChild : IJob + { + [ReadOnly] public NativeParallelHashMap UniqueParents; + [ReadOnly] public BufferFromEntity ChildFromEntity; + public NativeList ParentsMissingChild; + + public void Execute() + { + var parents = UniqueParents.GetKeyArray(Allocator.Temp); + for (int i = 0; i < parents.Length; i++) + { + var parent = parents[i]; + if (!ChildFromEntity.HasComponent(parent)) + { + ParentsMissingChild.Add(parent); + } + } + } + } + + [BurstCompile] + struct FixupChangedChildren : IJob + { + [ReadOnly] public NativeParallelMultiHashMap ParentChildrenToAdd; + [ReadOnly] public NativeParallelMultiHashMap ParentChildrenToRemove; + [ReadOnly] public NativeParallelHashMap UniqueParents; + + public BufferFromEntity ChildFromEntity; + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ThrowChildEntityNotInParent() + { + throw new InvalidOperationException("Child entity not in parent"); + } + + int FindChildIndex(DynamicBuffer children, Entity entity) + { + for (int i = 0; i < children.Length; i++) + { + if (children[i].Value == entity) + return i; + } + + ThrowChildEntityNotInParent(); + return -1; + } + + void RemoveChildrenFromParent(Entity parent, DynamicBuffer children, NativeList entityCache) + { + if (ParentChildrenToRemove.TryGetFirstValue(parent, out var child, out var it)) + { + entityCache.Clear(); + do + { + entityCache.Add(child); + } + while (ParentChildrenToRemove.TryGetNextValue(out child, ref it)); + + entityCache.Sort(); + foreach (var entity in entityCache) + { + var childIndex = FindChildIndex(children, entity); + children.RemoveAtSwapBack(childIndex); + } + } + } + + void AddChildrenToParent(Entity parent, DynamicBuffer children, NativeList entityCache) + { + if (ParentChildrenToAdd.TryGetFirstValue(parent, out var child, out var it)) + { + entityCache.Clear(); + do + { + entityCache.Add(child); + } + while (ParentChildrenToAdd.TryGetNextValue(out child, ref it)); + + entityCache.Sort(); + foreach (var entity in entityCache) + { + children.Add(new Child() { + Value = entity + }); + } + } + } + + public void Execute() + { + var parents = UniqueParents.GetKeyArray(Allocator.Temp); + var entityCache = new NativeList(128, Allocator.Temp); + for (int i = 0; i < parents.Length; i++) + { + var parent = parents[i]; + // Todo: Until the minimum Entities is 0.51, need to use two separate queries since + // TryGetBuffer is broken. + if (ChildFromEntity.HasComponent(parent)) + { + var children = ChildFromEntity[parent]; + + RemoveChildrenFromParent(parent, children, entityCache); + AddChildrenToParent(parent, children, entityCache); + } + } + } + } + + //burst disabled pending burstable EntityQueryDesc + //[BurstCompile] + public unsafe void OnCreate(ref SystemState state) + { + state.WorldUnmanaged.ResolveSystemState(state.WorldUnmanaged.GetExistingUnmanagedSystem().Handle)->Enabled = false; + + m_NewParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + }, + None = new ComponentType[] + { + typeof(PreviousParent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + m_RemovedParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + typeof(PreviousParent) + }, + None = new ComponentType[] + { + typeof(Parent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + m_ExistingParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + typeof(PreviousParent) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + m_ExistingParentsQuery.SetChangedVersionFilter(new ComponentType[] { typeof(Parent), typeof(PreviousParent) }); + m_DeletedParentsQuery = state.GetEntityQuery(new EntityQueryDesc + { + All = new ComponentType[] + { + typeof(Child) + }, + None = new ComponentType[] + { + typeof(LocalToWorld) + }, + Options = EntityQueryOptions.FilterWriteGroup + }); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + + void UpdateNewParents(ref SystemState state) + { + if (m_NewParentsQuery.IsEmptyIgnoreFilter) + return; + + state.CompleteDependency(); + + state.EntityManager.AddComponent(m_NewParentsQuery, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + } + + void UpdateRemoveParents(ref SystemState state) + { + if (m_RemovedParentsQuery.IsEmptyIgnoreFilter) + return; + + state.CompleteDependency(); + + var childEntities = m_RemovedParentsQuery.ToEntityArray(Allocator.TempJob); + var previousParents = m_RemovedParentsQuery.ToComponentDataArray(Allocator.TempJob); + + for (int i = 0; i < childEntities.Length; i++) + { + var childEntity = childEntities[i]; + var previousParentEntity = previousParents[i].Value; + + RemoveChildFromParent(ref state, childEntity, previousParentEntity); + } + + state.EntityManager.RemoveComponent(m_RemovedParentsQuery, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + childEntities.Dispose(); + previousParents.Dispose(); + } + + void UpdateChangeParents(ref SystemState state) + { + if (m_ExistingParentsQuery.IsEmptyIgnoreFilter) + return; + + var count = m_ExistingParentsQuery.CalculateEntityCount() * 2; // Potentially 2x changed: current and previous + if (count == 0) + return; + + state.CompleteDependency(); + + // 1. Get (Parent,Child) to remove + // 2. Get (Parent,Child) to add + // 3. Get unique Parent change list + // 4. Set PreviousParent to new Parent + var parentChildrenToAdd = new NativeParallelMultiHashMap(count, Allocator.TempJob); + var parentChildrenToRemove = new NativeParallelMultiHashMap(count, Allocator.TempJob); + var uniqueParents = new NativeParallelHashMap(count, Allocator.TempJob); + var gatherChangedParentsJob = new GatherChangedParents + { + ParentChildrenToAdd = parentChildrenToAdd.AsParallelWriter(), + ParentChildrenToRemove = parentChildrenToRemove.AsParallelWriter(), + UniqueParents = uniqueParents.AsParallelWriter(), + PreviousParentTypeHandle = state.GetComponentTypeHandle(false), + ChildFromEntity = state.GetBufferFromEntity(), + ParentTypeHandle = state.GetComponentTypeHandle(true), + EntityTypeHandle = state.GetEntityTypeHandle(), + LastSystemVersion = state.LastSystemVersion + }; + var gatherChangedParentsJobHandle = gatherChangedParentsJob.ScheduleParallel(m_ExistingParentsQuery); + gatherChangedParentsJobHandle.Complete(); + + // 5. (Structural change) Add any missing Child to Parents + var parentsMissingChild = new NativeList(Allocator.TempJob); + var findMissingChildJob = new FindMissingChild + { + UniqueParents = uniqueParents, + ChildFromEntity = state.GetBufferFromEntity(true), + ParentsMissingChild = parentsMissingChild + }; + //var findMissingChildJobHandle = findMissingChildJob.Schedule(); + //findMissingChildJobHandle.Complete(); + findMissingChildJob.Execute(); + + state.EntityManager.AddComponent(parentsMissingChild.AsArray(), ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + + // 6. Get Child[] for each unique Parent + // 7. Update Child[] for each unique Parent + var fixupChangedChildrenJob = new FixupChangedChildren + { + ParentChildrenToAdd = parentChildrenToAdd, + ParentChildrenToRemove = parentChildrenToRemove, + UniqueParents = uniqueParents, + ChildFromEntity = state.GetBufferFromEntity() + }; + + //var fixupChangedChildrenJobHandle = fixupChangedChildrenJob.Schedule(); + //fixupChangedChildrenJobHandle.Complete(); + fixupChangedChildrenJob.Execute(); + + parentChildrenToAdd.Dispose(); + parentChildrenToRemove.Dispose(); + uniqueParents.Dispose(); + parentsMissingChild.Dispose(); + } + + [BurstCompile] + struct GatherChildEntities : IJob + { + [ReadOnly] public NativeArray Parents; + public NativeList Children; + [ReadOnly] public BufferFromEntity ChildFromEntity; + [ReadOnly] public ComponentDataFromEntity ParentFromEntity; + + public void Execute() + { + for (int i = 0; i < Parents.Length; i++) + { + var parentEntity = Parents[i]; + var childEntitiesSource = ChildFromEntity[parentEntity].AsNativeArray(); + for (int j = 0; j < childEntitiesSource.Length; j++) + { + var childEntity = childEntitiesSource[j].Value; + if (ParentFromEntity.HasComponent(childEntity) && ParentFromEntity[childEntity].Value == parentEntity) + { + Children.Add(childEntitiesSource[j].Value); + } + } + } + } + } + + void UpdateDeletedParents(ref SystemState state) + { + if (m_DeletedParentsQuery.IsEmptyIgnoreFilter) + return; + + state.CompleteDependency(); + + var previousParents = m_DeletedParentsQuery.ToEntityArray(Allocator.TempJob); + var childEntities = new NativeList(Allocator.TempJob); + var gatherChildEntitiesJob = new GatherChildEntities + { + Parents = previousParents, + Children = childEntities, + ChildFromEntity = state.GetBufferFromEntity(true), + ParentFromEntity = state.GetComponentDataFromEntity(true), + }; + //var gatherChildEntitiesJobHandle = gatherChildEntitiesJob.Schedule(); + //gatherChildEntitiesJobHandle.Complete(); + gatherChildEntitiesJob.Execute(); + + state.EntityManager.RemoveComponent( + childEntities, + ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + state.EntityManager.RemoveComponent(childEntities, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + state.EntityManager.RemoveComponent(childEntities, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + state.EntityManager.RemoveComponent(m_DeletedParentsQuery, ComponentType.FromTypeIndex( + TypeManager.GetTypeIndex())); + + childEntities.Dispose(); + previousParents.Dispose(); + } + + //burst disabled pending IJobBurstSchedulable not requiring hardcoded calls to init + //for every distinct job + [BurstCompile] + public void OnUpdate(ref SystemState state) //JobHandle inputDeps) + { + //inputDeps.Complete(); // #todo + //state.Dependency.Complete(); + + k_ProfileDeletedParents.Begin(); + UpdateDeletedParents(ref state); + k_ProfileDeletedParents.End(); + + k_ProfileRemoveParents.Begin(); + UpdateRemoveParents(ref state); + k_ProfileRemoveParents.End(); + + k_ProfileNewParents.Begin(); + UpdateNewParents(ref state); + k_ProfileNewParents.End(); + + k_ProfileChangeParents.Begin(); + UpdateChangeParents(ref state); + k_ProfileChangeParents.End(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedParentSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedParentSystem.cs.meta new file mode 100644 index 0000000..e6cb072 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/Transforms/ImprovedTransforms/ImprovedParentSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 055134a6fa358cb479f84be9a74bf5ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials.meta new file mode 100644 index 0000000..d7296dc --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 63280560a87ba9e49ad5c378e2855ca5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/BlackboardUnmanagedSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/BlackboardUnmanagedSystem.cs new file mode 100644 index 0000000..f3db4d3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/BlackboardUnmanagedSystem.cs @@ -0,0 +1,58 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Systems +{ + [BurstCompile, DisableAutoCreation] + internal partial struct BlackboardUnmanagedSystem : ISystem + { + public EntityWith worldBlackboardEntity; + public EntityWith sceneBlackboardEntity; + + [BurstCompile] + public void OnCreate(ref SystemState state) + { + state.Enabled = false; + } + [BurstCompile] + public void OnDestroy(ref SystemState state) + { + } + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.Enabled = false; + } + } +} + +namespace Latios +{ + public static class SystemStateBlackboardExtensions + { + public static BlackboardEntity GetWorldBlackboardEntity(this ref SystemState state) + { + var entity = state.WorldUnmanaged.GetExistingUnmanagedSystem().Struct.worldBlackboardEntity; + return new BlackboardEntity(entity, state.EntityManager); + } + + public static BlackboardEntity GetSceneBlackboardEntity(this ref SystemState state) + { + var entity = state.WorldUnmanaged.GetExistingUnmanagedSystem().Struct.sceneBlackboardEntity; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (entity == Entity.Null) + { + throw new InvalidOperationException( + "The sceneBlackboardEntity has not been initialized yet. If you are trying to access this entity in OnCreate(), please use OnNewScene() or another callback instead."); + } +#endif + return new BlackboardEntity(entity, state.EntityManager); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/BlackboardUnmanagedSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/BlackboardUnmanagedSystem.cs.meta new file mode 100644 index 0000000..8655897 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/BlackboardUnmanagedSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d5971249711983478458662b6ab7d4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/FixedSimulationSystemGroup.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/FixedSimulationSystemGroup.cs new file mode 100644 index 0000000..fc6173d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/FixedSimulationSystemGroup.cs @@ -0,0 +1,17 @@ +using Unity.Entities; +using Unity.Entities.Exposed; + +namespace Latios.Systems +{ + [DisableAutoCreation, NoGroupInjection] + public class FixedSimulationSystemGroup : ComponentSystemGroup + { + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/FixedSimulationSystemGroup.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/FixedSimulationSystemGroup.cs.meta new file mode 100644 index 0000000..9c9c2a2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/FixedSimulationSystemGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ef0335e7a6e44243b0456a6001ee28f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/LatiosInitializationSystemGroup.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/LatiosInitializationSystemGroup.cs new file mode 100644 index 0000000..91b5b65 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/LatiosInitializationSystemGroup.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using Debug = UnityEngine.Debug; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Entities.Exposed.Dangerous; + +namespace Latios.Systems +{ + [DisableAutoCreation, NoGroupInjection] + public class LatiosInitializationSystemGroup : InitializationSystemGroup + { + private SyncPointPlaybackSystem m_syncPlayback; + private MergeBlackboardsSystem m_mergeGlobals; + private ManagedComponentsReactiveSystemGroup m_cleanupGroup; + private LatiosWorldSyncGroup m_syncGroup; + private PreSyncPointGroup m_preSyncGroup; + + protected override void OnCreate() + { + base.OnCreate(); + m_syncPlayback = World.CreateSystem(); + m_mergeGlobals = World.CreateSystem(); + m_cleanupGroup = World.CreateSystem(); + m_syncGroup = World.GetOrCreateSystem(); + m_preSyncGroup = World.GetOrCreateSystem(); + AddSystemToUpdateList(m_syncPlayback); + AddSystemToUpdateList(m_syncGroup); + AddSystemToUpdateList(m_preSyncGroup); + m_syncGroup.AddSystemToUpdateList(m_mergeGlobals); + m_syncGroup.AddSystemToUpdateList(m_cleanupGroup); + } + + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME + this.ClearSystemIds(); +#endif + + LatiosWorld lw = World as LatiosWorld; + lw.FrameStart(); + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } + + [DisableAutoCreation] + [UpdateInGroup(typeof(InitializationSystemGroup))] + [UpdateAfter(typeof(Unity.Scenes.SceneSystemGroup))] + [UpdateAfter(typeof(ConvertToEntitySystem))] + public class LatiosWorldSyncGroup : ComponentSystemGroup + { + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } + + [DisableAutoCreation] + [UpdateInGroup(typeof(InitializationSystemGroup), OrderFirst = true)] + [UpdateBefore(typeof(BeginInitializationEntityCommandBufferSystem))] + [UpdateBefore(typeof(SyncPointPlaybackSystem))] + public class PreSyncPointGroup : ComponentSystemGroup + { + SystemSortingTracker m_tracker; + + protected override void OnUpdate() + { + SuperSystem.DoSuperSystemUpdate(this, ref m_tracker); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/LatiosInitializationSystemGroup.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/LatiosInitializationSystemGroup.cs.meta new file mode 100644 index 0000000..da6578b --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/LatiosInitializationSystemGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b0f011b62f3f1a40a55514c92ceb310 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/ManagedStructStorageCleanupSystems.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/ManagedStructStorageCleanupSystems.cs new file mode 100644 index 0000000..375064a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/ManagedStructStorageCleanupSystems.cs @@ -0,0 +1,232 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using Debug = UnityEngine.Debug; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios.Systems +{ + internal interface ManagedComponentsReactiveSystem + { + EntityQuery Query { get; } + } + + [DisableAutoCreation] + [UpdateInGroup(typeof(LatiosWorldSyncGroup), OrderFirst = true)] + [UpdateAfter(typeof(MergeBlackboardsSystem))] + public class ManagedComponentsReactiveSystemGroup : RootSuperSystem + { + struct AssociatedTypeSysStateTagTypePair : IEquatable + { + public ComponentType associatedType; + public ComponentType sysStateTagType; + + public bool Equals(AssociatedTypeSysStateTagTypePair other) + { + return associatedType == other.associatedType && sysStateTagType == other.sysStateTagType; + } + } + + private EntityQuery m_allSystemsQuery; + + public override bool ShouldUpdateSystem() + { + for (int i = 0; i < Systems.Count; i++) + { + var mcrs = Systems[i] as ManagedComponentsReactiveSystem; + if (!mcrs.Query.IsEmptyIgnoreFilter) + return true; + } + return false; + + //return m_allSystemsQuery.IsEmptyIgnoreFilter == false; + } + + private struct ManagedDefeatStripTag : IComponentData { } + private struct ManagedDefeatStripComponent : IManagedComponent + { + public Type AssociatedComponentType => typeof(ManagedDefeatStripTag); + } + + private struct CollectionDefeatStripTag : IComponentData { } + private struct CollectionDefeatStripComponent : ICollectionComponent + { + public Type AssociatedComponentType => typeof(ManagedDefeatStripTag); + public JobHandle Dispose(JobHandle inputDeps) => default; + } + + protected override void CreateSystems() + { + // Defeat stripping + World.DestroySystem(World.CreateSystem >()); + World.DestroySystem(World.CreateSystem >()); + World.DestroySystem(World.CreateSystem >()); + World.DestroySystem(World.CreateSystem >()); + + var managedCreateType = typeof(ManagedComponentCreateSystem<>); + var managedDestroyType = typeof(ManagedComponentDestroySystem<>); + var collectionCreateType = typeof(CollectionComponentCreateSystem<>); + var collectionDestroyType = typeof(CollectionComponentDestroySystem<>); + var managedSysStateTagType = typeof(ManagedComponentSystemStateTag<>); + var collectionSysStateTagType = typeof(CollectionComponentSystemStateTag<>); + + var typePairs = new NativeParallelHashSet(128, Allocator.TempJob); + + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + if (!BootstrapTools.IsAssemblyReferencingLatios(assembly)) + continue; + + foreach (var type in assembly.GetTypes()) + { + if (type.GetCustomAttribute(typeof(DisableAutoTypeRegistrationAttribute)) != null) + continue; + + if (type == typeof(IManagedComponent) || type == typeof(ICollectionComponent) || type == typeof(ManagedDefeatStripComponent) || + type == typeof(CollectionDefeatStripComponent)) + continue; + + if (typeof(IManagedComponent).IsAssignableFrom(type)) + { + GetOrCreateAndAddSystem(managedCreateType.MakeGenericType(type)); + GetOrCreateAndAddSystem(managedDestroyType.MakeGenericType(type)); + typePairs.Add(new AssociatedTypeSysStateTagTypePair + { + sysStateTagType = ComponentType.ReadOnly(managedSysStateTagType.MakeGenericType(type)), + associatedType = ComponentType.ReadOnly((Activator.CreateInstance(type) as IManagedComponent).AssociatedComponentType) + }); + } + else if (typeof(ICollectionComponent).IsAssignableFrom(type)) + { + GetOrCreateAndAddSystem(collectionCreateType.MakeGenericType(type)); + GetOrCreateAndAddSystem(collectionDestroyType.MakeGenericType(type)); + typePairs.Add(new AssociatedTypeSysStateTagTypePair + { + sysStateTagType = ComponentType.ReadOnly(collectionSysStateTagType.MakeGenericType(type)), + associatedType = ComponentType.ReadOnly((Activator.CreateInstance(type) as ICollectionComponent).AssociatedComponentType) + }); + } + } + } + + //Todo: Bug in Unity prevents iterating over NativeHashSet (value is defaulted). + var typePairsArr = typePairs.ToNativeArray(Allocator.TempJob); + EntityQueryDesc[] descs = new EntityQueryDesc[typePairsArr.Length * 2]; + int i = 0; + foreach (var pair in typePairsArr) + { + descs[i] = new EntityQueryDesc + { + All = new ComponentType[] { pair.associatedType }, + None = new ComponentType[] { pair.sysStateTagType } + }; + i++; + descs[i] = new EntityQueryDesc + { + All = new ComponentType[] { pair.sysStateTagType }, + None = new ComponentType[] { pair.associatedType } + }; + i++; + } + //Bug in Unity prevents constructing this EntityQuery because the scratch buffer is hardcoded to a size of 1024 which is not enough. + //m_allSystemsQuery = GetEntityQuery(descs); + typePairsArr.Dispose(); + typePairs.Dispose(); + } + + protected override void OnDestroy() + { + base.OnDestroy(); + latiosWorld.CollectionComponentStorage.Dispose(); + latiosWorld.UnmanagedExtraInterfacesDispatcher.Dispose(); + } + } + + internal partial class ManagedComponentCreateSystem : SubSystem, ManagedComponentsReactiveSystem where T : struct, IManagedComponent + { + private EntityQuery m_query; + public EntityQuery Query => m_query; + + protected override void OnCreate() + { + m_query = GetEntityQuery(ComponentType.Exclude >(), ComponentType.ReadOnly(new T().AssociatedComponentType)); + } + + protected override void OnUpdate() + { + var entities = m_query.ToEntityArray(Allocator.TempJob); + foreach (var e in entities) + { + EntityManager.AddManagedComponent(e, new T()); + } + entities.Dispose(); + } + } + + internal partial class ManagedComponentDestroySystem : SubSystem, ManagedComponentsReactiveSystem where T : struct, IManagedComponent + { + private EntityQuery m_query; + public EntityQuery Query => m_query; + + protected override void OnCreate() + { + m_query = GetEntityQuery(ComponentType.ReadOnly >(), ComponentType.Exclude(new T().AssociatedComponentType)); + } + + protected override void OnUpdate() + { + var entities = m_query.ToEntityArray(Allocator.TempJob); + foreach(var e in entities) + { + EntityManager.RemoveManagedComponent(e); + } + entities.Dispose(); + } + } + + internal partial class CollectionComponentCreateSystem : SubSystem, ManagedComponentsReactiveSystem where T : struct, ICollectionComponent + { + private EntityQuery m_query; + public EntityQuery Query => m_query; + + protected override void OnCreate() + { + m_query = GetEntityQuery(ComponentType.Exclude >(), ComponentType.ReadOnly(new T().AssociatedComponentType)); + } + + protected override void OnUpdate() + { + var entities = m_query.ToEntityArray(Allocator.TempJob); + foreach (var e in entities) + { + EntityManager.AddCollectionComponent(e, new T(), false); + } + entities.Dispose(); + } + } + + internal partial class CollectionComponentDestroySystem : SubSystem, ManagedComponentsReactiveSystem where T : struct, ICollectionComponent + { + private EntityQuery m_query; + public EntityQuery Query => m_query; + + protected override void OnCreate() + { + m_query = GetEntityQuery(ComponentType.ReadOnly >(), ComponentType.Exclude(new T().AssociatedComponentType)); + } + + protected override void OnUpdate() + { + var entities = m_query.ToEntityArray(Allocator.TempJob); + foreach (var e in entities) + { + EntityManager.RemoveCollectionComponentAndDispose(e); + } + entities.Dispose(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/ManagedStructStorageCleanupSystems.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/ManagedStructStorageCleanupSystems.cs.meta new file mode 100644 index 0000000..381657f --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/ManagedStructStorageCleanupSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 510e5646251377f4b8907eef67d460c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/MergeBlackboardsSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/MergeBlackboardsSystem.cs new file mode 100644 index 0000000..35bc64d --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/MergeBlackboardsSystem.cs @@ -0,0 +1,107 @@ +using System; +using System.Reflection; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios.Systems +{ + [DisableAutoCreation] + [UpdateInGroup(typeof(LatiosWorldSyncGroup), OrderFirst = true)] + //[UpdateBefore(typeof(ManagedComponentsReactiveSystemGroup))] // Constrained in ManagedComponentsReactiveSystemGroup. + public partial class MergeBlackboardsSystem : SubSystem + { + private EntityDataCopyKit m_copyKit; + private EntityQuery m_query; + + protected override void OnCreate() + { + m_copyKit = new EntityDataCopyKit(EntityManager); + } + + protected override void OnUpdate() + { + Entities.WithStoreEntityQueryInField(ref m_query).WithStructuralChanges().ForEach((Entity entity, ref BlackboardEntityData globalEntityData) => + { + var types = EntityManager.GetComponentTypes(entity, Unity.Collections.Allocator.TempJob); + var targetEntity = globalEntityData.blackboardScope == BlackboardScope.World ? worldBlackboardEntity : sceneBlackboardEntity; + + ComponentType errorType = default; + bool error = false; + foreach (var type in types) + { + if (type.TypeIndex == ComponentType.ReadWrite().TypeIndex) + continue; + if (globalEntityData.mergeMethod == MergeMethod.Overwrite || !targetEntity.HasComponent(type)) + m_copyKit.CopyData(entity, targetEntity, type); + else if (globalEntityData.mergeMethod == MergeMethod.ErrorOnConflict) + { + errorType = type; + error = true; + } + } + types.Dispose(); + if (error) + { + throw new InvalidOperationException( + $"Entity {entity} could not copy component {errorType.GetManagedType()} onto {(globalEntityData.blackboardScope == BlackboardScope.World ? "world" : "scene")} entity because the component already exists and the MergeMethod was set to ErrorOnConflict."); + } + }).Run(); + EntityManager.DestroyEntity(m_query); + } + + /*private EntityQuery group; + + protected override void OnCreate() + { + group = GetEntityQuery(typeof(BlackboardEntityData)); + } + + protected override void OnUpdate() + { + int length = group.CalculateEntityCount(); + length = group.CalculateEntityCount(); + if (length <= 1) + return; + var array = group.ToEntityArray(Unity.Collections.Allocator.TempJob); + + UnityEngine.Debug.Log("Merging singletons, found entities to merge: " + array.Length); + for (int a = 0; a < array.Length; a++) + { + if (array[a] == SingletonEntity) + continue; + Entity singletonEntity = SingletonEntity; + Entity entity = array[a]; + object[] entityArg = new object[] { entity }; + var types = EntityManager.GetComponentTypes(entity); + for (int i = 0; i < types.Length; i++) + { + if (EntityManager.HasComponent(SingletonEntity, types[i])) + continue; + + var type = types[i].GetManagedType(); + if (typeof(IComponentData).IsAssignableFrom(type) || typeof(ISystemStateComponentData).IsAssignableFrom(type)) + { + var getter = typeof(EntityManager).GetMethod("GetComponentData"); + var gGetter = getter.MakeGenericMethod(type); + var setter = typeof(EntityManager).GetMethod("AddComponentData"); + var gSetter = setter.MakeGenericMethod(type); + dynamic icd = gGetter.Invoke(EntityManager, entityArg); + gSetter.Invoke(EntityManager, new object[] { singletonEntity, icd }); + } + else if (typeof(ISharedComponentData).IsAssignableFrom(type) || typeof(ISystemStateSharedComponentData).IsAssignableFrom(type)) + { + var getter = typeof(InitMergeSingletonsSystem).GetMethod("GetSharedComponentData"); + var gGetter = getter.MakeGenericMethod(type); + var setter = typeof(InitMergeSingletonsSystem).GetMethod("AddOrSetSharedComponentData"); + var gSetter = setter.MakeGenericMethod(type); + dynamic iscd = gGetter.Invoke(EntityManager, entityArg); + gSetter.Invoke(EntityManager, new object[] { singletonEntity, iscd }); + } + } + EntityManager.DestroyEntity(entity); + } + array.Dispose(); + }*/ + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/MergeBlackboardsSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/MergeBlackboardsSystem.cs.meta new file mode 100644 index 0000000..ca57822 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/MergeBlackboardsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 855859ea8d1f7f84d9d1753a64b737ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/SyncPointPlaybackSystem.cs b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/SyncPointPlaybackSystem.cs new file mode 100644 index 0000000..6310aaf --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/SyncPointPlaybackSystem.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Entities.Exposed; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine.Profiling; + +namespace Latios.Systems +{ + [DisableAutoCreation] + [UpdateInGroup(typeof(InitializationSystemGroup), OrderFirst = true)] + [UpdateBefore(typeof(BeginInitializationEntityCommandBufferSystem))] + public partial class SyncPointPlaybackSystem : SubSystem + { + enum PlaybackType + { + Entity, + Enable, + Disable, + Destroy, + InstantiateNoData, + InstantiateUntyped + } + + struct PlaybackInstance + { + public PlaybackType type; + public Type requestingSystemType; + } + + List m_playbackInstances = new List(); + List m_entityCommandBuffers = new List(); + List m_enableCommandBuffers = new List(); + List m_disableCommandBuffers = new List(); + List m_destroyCommandBuffers = new List(); + List m_instantiateCommandBuffersWithoutData = new List(); + List m_instantiateCommandBuffersUntyped = new List(); + + NativeList m_jobHandles; + + protected override void OnCreate() + { + m_jobHandles = new NativeList(Allocator.Persistent); + } + + protected override void OnDestroy() + { + m_playbackInstances.Clear(); + JobHandle.CompleteAll(m_jobHandles); + m_jobHandles.Dispose(); + foreach (var ecb in m_entityCommandBuffers) + ecb.Dispose(); + foreach (var ecb in m_enableCommandBuffers) + ecb.Dispose(); + foreach (var dcb in m_disableCommandBuffers) + dcb.Dispose(); + foreach (var dcb in m_destroyCommandBuffers) + dcb.Dispose(); + foreach (var icb in m_instantiateCommandBuffersWithoutData) + icb.Dispose(); + foreach (var icb in m_instantiateCommandBuffersUntyped) + icb.Dispose(); + } + + public override bool ShouldUpdateSystem() + { + return m_playbackInstances.Count > 0; + } + + protected override void OnUpdate() + { + JobHandle.CompleteAll(m_jobHandles); + m_jobHandles.Clear(); + CompleteDependency(); + + int entityIndex = 0; + int enableIndex = 0; + int disableIndex = 0; + int destroyIndex = 0; + int instantiateNoDataIndex = 0; + int instantiateUntypedIndex = 0; + foreach (var instance in m_playbackInstances) + { + //Todo: We don't fail as gracefully as EntityCommandBufferSystem, but I'm not sure what is exactly required to meet that. There's way too much magic there. + var systemName = instance.requestingSystemType != null? TypeManager.GetSystemName(instance.requestingSystemType) : "Unknown"; + Profiler.BeginSample(systemName); + switch (instance.type) + { + case PlaybackType.Entity: + { + var ecb = m_entityCommandBuffers[entityIndex]; + ecb.Playback(EntityManager); + //ecb.Dispose(); + entityIndex++; + break; + } + case PlaybackType.Enable: + { + var ecb = m_enableCommandBuffers[enableIndex]; + ecb.Playback(EntityManager, GetBufferFromEntity(true)); + //ecb.Dispose(); + enableIndex++; + break; + } + case PlaybackType.Disable: + { + var dcb = m_disableCommandBuffers[disableIndex]; + dcb.Playback(EntityManager, GetBufferFromEntity(true)); + //dcb.Dispose(); + disableIndex++; + break; + } + case PlaybackType.Destroy: + { + var dcb = m_destroyCommandBuffers[destroyIndex]; + dcb.Playback(EntityManager); + //dcb.Dispose(); + destroyIndex++; + break; + } + case PlaybackType.InstantiateNoData: + { + var icb = m_instantiateCommandBuffersWithoutData[instantiateNoDataIndex]; + icb.Playback(EntityManager); + //icb.Dispose(); + instantiateNoDataIndex++; + break; + } + case PlaybackType.InstantiateUntyped: + { + var icb = m_instantiateCommandBuffersUntyped[instantiateUntypedIndex]; + try + { + icb.Playback(EntityManager); + } + catch (Exception e) + { + UnityEngine.Debug.LogError(e.Message + e.StackTrace); + throw e; + } + //icb.Dispose(); + instantiateUntypedIndex++; + break; + } + } + Profiler.EndSample(); + } + m_playbackInstances.Clear(); + m_entityCommandBuffers.Clear(); + m_enableCommandBuffers.Clear(); + m_disableCommandBuffers.Clear(); + m_destroyCommandBuffers.Clear(); + m_instantiateCommandBuffersWithoutData.Clear(); + m_instantiateCommandBuffersUntyped.Clear(); + } + + public EntityCommandBuffer CreateEntityCommandBuffer() + { + var ecb = new EntityCommandBuffer(World.UpdateAllocator.ToAllocator, PlaybackPolicy.SinglePlayback); + var instance = new PlaybackInstance + { + type = PlaybackType.Entity, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_entityCommandBuffers.Add(ecb); + return ecb; + } + + public EnableCommandBuffer CreateEnableCommandBuffer() + { + //Todo: We use Persistent allocator here because of the NativeReference. This recreates the DisposeSentinal stuff except with the slower allocator. + var ecb = new EnableCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.Enable, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_enableCommandBuffers.Add(ecb); + return ecb; + } + + public DisableCommandBuffer CreateDisableCommandBuffer() + { + //Todo: We use Persistent allocator here because of the NativeReference. This recreates the DisposeSentinal stuff except with the slower allocator. + var dcb = new DisableCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.Disable, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_disableCommandBuffers.Add(dcb); + return dcb; + } + + public DestroyCommandBuffer CreateDestroyCommandBuffer() + { + //Todo: We use Persistent allocator here because of the NativeReference. This recreates the DisposeSentinal stuff except with the slower allocator. + var dcb = new DestroyCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.Destroy, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_destroyCommandBuffers.Add(dcb); + return dcb; + } + + public InstantiateCommandBuffer CreateInstantiateCommandBuffer() + { + //Todo: We use Persistent allocator here for consistency, though I suspect it might be possible to improve this. + var icb = new InstantiateCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.InstantiateNoData, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_instantiateCommandBuffersWithoutData.Add(icb); + return icb; + } + + public InstantiateCommandBuffer CreateInstantiateCommandBuffer() where T0 : unmanaged, IComponentData + { + //Todo: We use Persistent allocator here for consistency, though I suspect it might be possible to improve this. + var icb = new InstantiateCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.InstantiateUntyped, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_instantiateCommandBuffersUntyped.Add(icb.m_instantiateCommandBufferUntyped); + return icb; + } + + public InstantiateCommandBuffer CreateInstantiateCommandBuffer() where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData + { + //Todo: We use Persistent allocator here for consistency, though I suspect it might be possible to improve this. + var icb = new InstantiateCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.InstantiateUntyped, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_instantiateCommandBuffersUntyped.Add(icb.m_instantiateCommandBufferUntyped); + return icb; + } + + public InstantiateCommandBuffer CreateInstantiateCommandBuffer() where T0 : unmanaged, IComponentData where T1 : unmanaged, + IComponentData where T2 : unmanaged, IComponentData + { + //Todo: We use Persistent allocator here for consistency, though I suspect it might be possible to improve this. + var icb = new InstantiateCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.InstantiateUntyped, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_instantiateCommandBuffersUntyped.Add(icb.m_instantiateCommandBufferUntyped); + return icb; + } + + public InstantiateCommandBuffer CreateInstantiateCommandBuffer() where T0 : unmanaged, IComponentData where T1 : unmanaged, + IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData + { + //Todo: We use Persistent allocator here for consistency, though I suspect it might be possible to improve this. + var icb = new InstantiateCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.InstantiateUntyped, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_instantiateCommandBuffersUntyped.Add(icb.m_instantiateCommandBufferUntyped); + return icb; + } + + public InstantiateCommandBuffer CreateInstantiateCommandBuffer() where T0 : unmanaged, IComponentData where T1 : unmanaged, + IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData + { + //Todo: We use Persistent allocator here for consistency, though I suspect it might be possible to improve this. + var icb = new InstantiateCommandBuffer(World.UpdateAllocator.ToAllocator); + var instance = new PlaybackInstance + { + type = PlaybackType.InstantiateUntyped, + requestingSystemType = World.ExecutingSystemType(), + }; + m_playbackInstances.Add(instance); + m_instantiateCommandBuffersUntyped.Add(icb.m_instantiateCommandBufferUntyped); + return icb; + } + + public void AddJobHandleForProducer(JobHandle handle) + { + //Todo, maybe we could reason about this better and get better job scheduling, but this seems fine for now. + //We will always need this if a request comes from a MonoBehaviour or something. + m_jobHandles.Add(handle); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/SyncPointPlaybackSystem.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/SyncPointPlaybackSystem.cs.meta new file mode 100644 index 0000000..d5563af --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Systems/_Essentials/SyncPointPlaybackSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79299e007b4e4d142bc262ec395d9ee6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties.meta b/Packages/com.latios.latios-framework/Core/Core/Utilties.meta new file mode 100644 index 0000000..e84bfde --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6bb9f03f6c7d4b42a9c973e9ae5d5f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/BlobBuilderExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Utilties/BlobBuilderExtensions.cs new file mode 100644 index 0000000..cea2d3c --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/BlobBuilderExtensions.cs @@ -0,0 +1,41 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; + +namespace Latios +{ + public static class BlobBuilderExtensions + { + public static BlobBuilderArray ConstructFromNativeArray(this BlobBuilder builder, ref BlobArray ptr, NativeArray array) where T : struct + { + var result = builder.Allocate(ref ptr, array.Length); + for (int i = 0; i < array.Length; i++) + result[i] = array[i]; + return result; + } + + unsafe public static void AllocateFixedString(ref this BlobBuilder builder, ref BlobString blobStr, T fixedString) where T : INativeList, IUTF8Bytes + { + var res = builder.Allocate(ref UnsafeUtility.As >(ref blobStr), fixedString.Length); + for (int i = 0; i < fixedString.Length; i++) + { + res[i] = fixedString[i]; + } + } + } +} + +namespace Latios.Unsafe +{ + public static class BlobBuilderUnsafeExtensions + { + public static unsafe BlobBuilderArray ConstructFromNativeArray(this BlobBuilder builder, ref BlobArray ptr, T* array, int length) where T : unmanaged + { + var result = builder.Allocate(ref ptr, length); + for (int i = 0; i < length; i++) + result[i] = array[i]; + return result; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/BlobBuilderExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Utilties/BlobBuilderExtensions.cs.meta new file mode 100644 index 0000000..46bbc7b --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/BlobBuilderExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3cb3b00454490c7498f8413404322e34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/CollectionsExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Utilties/CollectionsExtensions.cs new file mode 100644 index 0000000..d05408a --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/CollectionsExtensions.cs @@ -0,0 +1,15 @@ +using Unity.Collections; +using Unity.Entities; + +namespace Latios +{ + public static class CollectionsExtensions + { + public static void AddRangeFromBlob(this NativeList list, ref BlobArray data) where T : unmanaged + { + for (int i = 0; i < data.Length; i++) + list.Add(data[i]); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/CollectionsExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Utilties/CollectionsExtensions.cs.meta new file mode 100644 index 0000000..2f27159 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/CollectionsExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc2ea5c560b4a6841b96fdbdb8fb8bfe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/EntityManagerExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Utilties/EntityManagerExtensions.cs new file mode 100644 index 0000000..5f33592 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/EntityManagerExtensions.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.LowLevel.Unsafe; + +namespace Latios +{ + public static class EntityManagerExtensions + { + [BurstCompatible] + public static unsafe void CopyComponentData(this EntityManager entityManager, Entity src, Entity dst, ComponentType componentType) + { + CheckComponentTypeIsUnmanagedComponentData(componentType); + + // We do this to prevent MemCpy from being confused (might not actually be an issue) + if (src == dst) + return; + + entityManager.AddComponent(dst, componentType); + + if (componentType.IsZeroSized) + return; + + var typeInfo = TypeManager.GetTypeInfo(componentType.TypeIndex); + var size = typeInfo.SizeInChunk; + var typeRO = componentType; + typeRO.AccessModeType = ComponentType.AccessMode.ReadOnly; + var typeRW = componentType; + typeRW.AccessModeType = ComponentType.AccessMode.ReadWrite; + var handleRW = entityManager.GetDynamicComponentTypeHandle(typeRW); + var handleRO = entityManager.GetDynamicComponentTypeHandle(typeRO); + var srcChunk = entityManager.GetStorageInfo(src); + var dstChunk = entityManager.GetStorageInfo(dst); + var dstPtr = (byte*)dstChunk.Chunk.GetDynamicComponentDataArrayReinterpret(handleRW, size).GetUnsafePtr(); + dstPtr += dstChunk.IndexInChunk * size; + var srcPtr = (byte*)srcChunk.Chunk.GetDynamicComponentDataArrayReinterpret(handleRO, size).GetUnsafeReadOnlyPtr(); + srcPtr += srcChunk.IndexInChunk * size; + UnsafeUtility.MemCpy(dstPtr, srcPtr, size); + } + + [BurstCompatible] + public static unsafe void CopyDynamicBuffer(this EntityManager entityManager, Entity src, Entity dst, ComponentType componentType) + { + CheckComponentTypeIsBuffer(componentType); + + entityManager.AddComponent(dst, componentType); + + // We do this to prevent MemCpy from being confused (might not actually be an issue) + if (src == dst) + return; + + var typeRO = componentType; + typeRO.AccessModeType = ComponentType.AccessMode.ReadOnly; + var typeRW = componentType; + typeRW.AccessModeType = ComponentType.AccessMode.ReadWrite; + var handleRW = entityManager.GetDynamicComponentTypeHandle(typeRW); + var handleRO = entityManager.GetDynamicComponentTypeHandle(typeRO); + var srcChunk = entityManager.GetStorageInfo(src); + var dstChunk = entityManager.GetStorageInfo(dst); + var dstBufferAccess = dstChunk.Chunk.GetUntypedBufferAccessor(ref handleRW); + var srcBufferAccess = srcChunk.Chunk.GetUntypedBufferAccessor(ref handleRO); + var srcPtr = srcBufferAccess.GetUnsafeReadOnlyPtrAndLength(srcChunk.IndexInChunk, out int length); + dstBufferAccess.ResizeUninitialized(dstChunk.IndexInChunk, length); + UnsafeUtility.MemCpy(dstBufferAccess.GetUnsafePtr(dstChunk.IndexInChunk), srcPtr, dstBufferAccess.ElementSize * (long)length); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] + private static void CheckComponentTypeIsUnmanagedComponentData(ComponentType type) + { + if (type.IsBuffer || type.IsChunkComponent || type.IsManagedComponent || type.IsSharedComponent) + throw new ArgumentException($"Attempted to call EntityManager.CopyComponentData on {type} which is not an unmanaged IComponentData"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] + private static void CheckComponentTypeIsBuffer(ComponentType type) + { + if (!type.IsBuffer) + throw new ArgumentException($"Attempted to call EntityManager.CopyDynamicBuffer on {type} which is not an IBufferElementData"); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/EntityManagerExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Utilties/EntityManagerExtensions.cs.meta new file mode 100644 index 0000000..702e4db --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/EntityManagerExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 51d6a24bedd84bb48ad5ee532087e75f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/StringBuilderExtensions.cs b/Packages/com.latios.latios-framework/Core/Core/Utilties/StringBuilderExtensions.cs new file mode 100644 index 0000000..7b7a1e3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/StringBuilderExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Text; +using Unity.Collections; + +namespace Latios +{ + public static class StringBuilderExtensions + { + public static void Append(this StringBuilder builder, in FixedString32Bytes fixedString) + { + foreach (var c in fixedString) + { + builder.Append((char)c.value); + } + } + + public static void Append(this StringBuilder builder, in FixedString64Bytes fixedString) + { + foreach (var c in fixedString) + { + builder.Append((char)c.value); + } + } + + public static void Append(this StringBuilder builder, in FixedString128Bytes fixedString) + { + foreach (var c in fixedString) + { + builder.Append((char)c.value); + } + } + + public static void Append(this StringBuilder builder, in FixedString512Bytes fixedString) + { + foreach (var c in fixedString) + { + builder.Append((char)c.value); + } + } + + public static void Append(this StringBuilder builder, in FixedString4096Bytes fixedString) + { + foreach (var c in fixedString) + { + builder.Append((char)c.value); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Core/Core/Utilties/StringBuilderExtensions.cs.meta b/Packages/com.latios.latios-framework/Core/Core/Utilties/StringBuilderExtensions.cs.meta new file mode 100644 index 0000000..3d0b4d8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Core/Core/Utilties/StringBuilderExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a629c187c672634fa5cb3446d7840c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/EntitiesExposed.meta b/Packages/com.latios.latios-framework/EntitiesExposed.meta new file mode 100644 index 0000000..8800bb6 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 390f8b3a27fdbc94f87dee84d8dc0406 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/BlobExposed.cs b/Packages/com.latios.latios-framework/EntitiesExposed/BlobExposed.cs new file mode 100644 index 0000000..fa31a50 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/BlobExposed.cs @@ -0,0 +1,26 @@ +using System.Collections; +using Unity.Entities.LowLevel.Unsafe; +using UnityEngine; + +namespace Unity.Entities.Exposed +{ + public static class BlobAssetReferenceExtensions + { + public static unsafe int GetLength(this in UnsafeUntypedBlobAssetReference blobReference) + { + if (blobReference.m_data.m_Ptr == null) + return 0; + + return blobReference.m_data.Header->Length; + } + + public static unsafe int GetLength(this in BlobAssetReference blobReference) where T : unmanaged + { + if (blobReference.m_data.m_Ptr == null) + return 0; + + return blobReference.m_data.Header->Length; + } + } +} + diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/BlobExposed.cs.meta b/Packages/com.latios.latios-framework/EntitiesExposed/BlobExposed.cs.meta new file mode 100644 index 0000000..5d446b3 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/BlobExposed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4605c6c06a1deed4b943d9a48f6a2768 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/ComponentSystemGroupExposed.cs b/Packages/com.latios.latios-framework/EntitiesExposed/ComponentSystemGroupExposed.cs new file mode 100644 index 0000000..7ee2436 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/ComponentSystemGroupExposed.cs @@ -0,0 +1,99 @@ +using System.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs.LowLevel.Unsafe; +using UnityEngine; + +namespace Unity.Entities.Exposed +{ + public static class ComponentSystemGroupExposed + { + public static ComponentSystemGroupSystemEnumerator GetSystemEnumerator(this ComponentSystemGroup group) => new ComponentSystemGroupSystemEnumerator { + group = group + }; + } + + public struct ComponentSystemGroupSystemEnumerator + { + public ComponentSystemBase currentManaged { get; private set; } + public SystemHandleUntyped current { get; private set; } + public ComponentSystemGroup group { get; internal set; } + public bool IsCurrentManaged => currentManaged != null; + + int m_masterOrderIndex; + int m_cachedLength; + + public bool MoveNext() + { + if (group == null) + return false; + + // Cache the update list length before updating; any new systems added mid-loop will change the length and + // should not be processed until the subsequent group update, to give SortSystems() a chance to run. + if (m_masterOrderIndex == 0) + m_cachedLength = group.m_MasterUpdateList.Length; + + if (m_masterOrderIndex >= m_cachedLength) + { + current = default; + currentManaged = null; + return false; + } + var index = group.m_MasterUpdateList[m_masterOrderIndex]; + if (index.IsManaged) + { + currentManaged = group.m_systemsToUpdate[index.Index]; + current = currentManaged.SystemHandleUntyped; + } + else + { + current = group.m_UnmanagedSystemsToUpdate[index.Index]; + currentManaged = null; + } + m_masterOrderIndex++; + return true; + } + } + + public struct SystemSortingTracker + { + int lastSystemCount; + bool lastEnableSortingSetting; + + public void CheckAndSortSystems(ComponentSystemGroup group) + { + int systemCount = group.m_MasterUpdateList.Length; + bool enableSorting = group.EnableSystemSorting; + bool hasSystemsToRemove = group.m_systemsToRemove.Count > 0 || !group.m_UnmanagedSystemsToRemove.IsEmpty; + bool sortingTurnedOn = lastEnableSortingSetting == false && enableSorting == true; + + if (systemCount != lastSystemCount || sortingTurnedOn || hasSystemsToRemove) + group.SortSystems(); + + lastSystemCount = systemCount; + lastEnableSortingSetting = enableSorting; + } + } +} + +namespace Unity.Entities.Exposed.Dangerous +{ + public static class ComponentSystemGroupExposedDangerous + { + public static void SetExecutingSystem(this ComponentSystemGroup group, ref WorldUnmanaged world, SystemHandleUntyped system) => world.ExecutingSystem = system; + public static void DestroyPendingSystemsInWorld(this ComponentSystemGroup group, ref WorldUnmanaged world) => world.DestroyPendingSystems(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS && !UNITY_DOTSRUNTIME + public static void ClearSystemIds(this ComponentSystemGroup group) => JobsUtility.ClearSystemIds(); +#endif + } + + public static class SystemStateExposedDangerous + { + public static unsafe SystemRef GetStronglyTypedUnmanagedSystem(this ref SystemState state) where T : unmanaged, ISystem + { + return new SystemRef(state.m_SystemPtr, + new SystemHandle(state.SystemHandleUntyped.m_Handle, state.SystemHandleUntyped.m_Version, state.SystemHandleUntyped.m_WorldSeqNo)); + } + } +} + diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/ComponentSystemGroupExposed.cs.meta b/Packages/com.latios.latios-framework/EntitiesExposed/ComponentSystemGroupExposed.cs.meta new file mode 100644 index 0000000..d7776cb --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/ComponentSystemGroupExposed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 37fe9654ec821c44aaf8f7c146989efc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/EntitiesExposed.asmref b/Packages/com.latios.latios-framework/EntitiesExposed/EntitiesExposed.asmref new file mode 100644 index 0000000..3ca7dea --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/EntitiesExposed.asmref @@ -0,0 +1,3 @@ +{ + "reference": "Unity.Entities" +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/EntitiesExposed.asmref.meta b/Packages/com.latios.latios-framework/EntitiesExposed/EntitiesExposed.asmref.meta new file mode 100644 index 0000000..514b035 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/EntitiesExposed.asmref.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 85252bf738bdb2b44b9d9fe946661657 +AssemblyDefinitionReferenceImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/EntityManagerExposed.cs b/Packages/com.latios.latios-framework/EntitiesExposed/EntityManagerExposed.cs new file mode 100644 index 0000000..38b0cd0 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/EntityManagerExposed.cs @@ -0,0 +1,65 @@ +using System; +using System.Diagnostics; +using Unity.Collections; + +namespace Unity.Entities.Exposed +{ + public unsafe struct EntityLocationInChunk : IEquatable, IComparable + { + public ArchetypeChunk chunk; + public int indexInChunk; + + public ulong ChunkAddressAsUlong => (ulong)chunk.m_Chunk; + + public int CompareTo(EntityLocationInChunk other) + { + ulong lhs = (ulong)chunk.m_Chunk; + ulong rhs = (ulong)other.chunk.m_Chunk; + int chunkCompare = lhs < rhs ? -1 : 1; + int indexCompare = indexInChunk - other.indexInChunk; + return (lhs != rhs) ? chunkCompare : indexCompare; + } + + public bool Equals(EntityLocationInChunk other) + { + return chunk.Equals(other.chunk) && indexInChunk.Equals(other.indexInChunk); + } + } + + public static unsafe class EntityManagerExposed + { + [BurstCompatible] + public static EntityLocationInChunk GetEntityLocationInChunk(this EntityManager entityManager, Entity entity) + { + var ecs = entityManager.GetCheckedEntityDataAccess()->EntityComponentStore; + var entityInChunk = ecs->GetEntityInChunk(entity); + return new EntityLocationInChunk + { + chunk = new ArchetypeChunk(entityInChunk.Chunk, ecs), + indexInChunk = entityInChunk.IndexInChunk + }; + } + + // Todo: Make a Burst-Compatible version. + [NotBurstCompatible] + public static void CopySharedComponent(this EntityManager entityManager, Entity src, Entity dst, ComponentType componentType) + { + CheckComponentTypeIsSharedComponent(componentType); + + entityManager.AddComponent(dst, componentType); + var handle = entityManager.GetDynamicSharedComponentTypeHandle(componentType); + var chunk = entityManager.GetStorageInfo(src); + var index = chunk.Chunk.GetSharedComponentIndex(handle); + var box = index != 0 ? chunk.Chunk.GetSharedComponentDataBoxed(handle, entityManager) : null; + entityManager.SetSharedComponentDataBoxedDefaultMustBeNull(dst, componentType.TypeIndex, box); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] + private static void CheckComponentTypeIsSharedComponent(ComponentType type) + { + if (!type.IsSharedComponent) + throw new ArgumentException($"Attempted to call EntityManager.CopySharedComponent on {type} which is not an ISharedComponentData"); + } + } +} + diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/EntityManagerExposed.cs.meta b/Packages/com.latios.latios-framework/EntitiesExposed/EntityManagerExposed.cs.meta new file mode 100644 index 0000000..e4c77c4 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/EntityManagerExposed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4a36de35c86bd14fa6e2f10a7062b48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/WorldExposed.cs b/Packages/com.latios.latios-framework/EntitiesExposed/WorldExposed.cs new file mode 100644 index 0000000..04ce9a2 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/WorldExposed.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Entities.Exposed +{ + public static class WorldExposedExtensions + { + /// + /// Returns the managed executing system type if one is executing. + /// + /// + /// + [NotBurstCompatible] + public static Type ExecutingSystemType(this World world) + { + return world.Unmanaged.GetTypeOfSystem(world.Unmanaged.ExecutingSystem); + } + + public static SystemHandleUntyped ExecutingSystemHandle(this ref WorldUnmanaged world) => world.ExecutingSystem; + + [NotBurstCompatible] + public static unsafe ComponentSystemBase AsManagedSystem(this World world, SystemHandleUntyped system) + { + return world.Unmanaged.ResolveSystemState(system)->ManagedSystem; + } + + [NotBurstCompatible] + public static unsafe ComponentSystemBase AsManagedSystem(this ref WorldUnmanaged world, SystemHandleUntyped system) + { + return world.ResolveSystemState(system)->ManagedSystem; + } + + public unsafe struct UnmanagedSystemStateArray + { + internal NativeArray m_systemStatePtrs; + + public bool IsCreated => m_systemStatePtrs.IsCreated; + public int Length => m_systemStatePtrs.Length; + + public ref SystemState At(int index) + { + SystemState* ssPtr = (SystemState*)m_systemStatePtrs[index]; + return ref UnsafeUtility.AsRef(ssPtr); + } + + public void Dispose() => m_systemStatePtrs.Dispose(); + } + + public static unsafe UnmanagedSystemStateArray GetAllSystemStates(this ref WorldUnmanaged world, Allocator allocator) + { + return new UnmanagedSystemStateArray + { + m_systemStatePtrs = world.GetAllUnmanagedSystemStates(allocator) + }; + } + + [NotBurstCompatible] + public static unsafe int GetMetaIdForType(Type t) + { + return SystemBaseRegistry.GetSystemTypeMetaIndex(BurstRuntime.GetHashCode64(t)); + } + + [NotBurstCompatible] + public static unsafe NativeArray CreateUnmanagedSystems(this World world, IList unmanagedTypes, Allocator allocator) + { + int count = unmanagedTypes.Count; + var result = new NativeArray(count, allocator); + + var unmanaged = world.Unmanaged; + + for (int i = 0; i < count; ++i) + { + result[i] = unmanaged.CreateUnmanagedSystem(world, unmanagedTypes[i], false); + } + + for (int i = 0; i < count; ++i) + { + var systemState = unmanaged.ResolveSystemState(result[i]); + SystemBaseRegistry.CallOnCreate(systemState); + SystemBaseRegistry.CallOnCreateForCompiler(systemState); + } + + return result; + } + + public static event Action OnWorldCreated + { + add + { + World.WorldCreated += value; + } + remove + { + World.WorldCreated -= value; + } + } + + public static event Action OnSystemCreated + { + add + { + World.SystemCreated += value; + } + remove + { + World.SystemCreated -= value; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/EntitiesExposed/WorldExposed.cs.meta b/Packages/com.latios.latios-framework/EntitiesExposed/WorldExposed.cs.meta new file mode 100644 index 0000000..db1f4e6 --- /dev/null +++ b/Packages/com.latios.latios-framework/EntitiesExposed/WorldExposed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 86806873d249d1a4ca100c9648d8bec9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation.meta b/Packages/com.latios.latios-framework/Kinemation.meta new file mode 100644 index 0000000..d2f5d85 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90a8c14c4dbd66e4dbc70cad0b5558b7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity.meta new file mode 100644 index 0000000..83a7dd0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c12245af34db54c488ca0b90c7f86a27 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/ACL_Unity.asmdef b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/ACL_Unity.asmdef new file mode 100644 index 0000000..b1e569e --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/ACL_Unity.asmdef @@ -0,0 +1,19 @@ +{ + "name": "ACL_Unity", + "rootNamespace": "AclUnity", + "references": [ + "Unity.Collections", + "Unity.Burst", + "Unity.Jobs", + "Unity.Mathematics" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/ACL_Unity.asmdef.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/ACL_Unity.asmdef.meta new file mode 100644 index 0000000..a8b5c32 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/ACL_Unity.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a655924d5e83d054fb97f0b7d01a12ea +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCommon.cs b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCommon.cs new file mode 100644 index 0000000..858cdda --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCommon.cs @@ -0,0 +1,72 @@ +using System.Runtime.InteropServices; +using Unity.Burst; +using Unity.Burst.Intrinsics; +using Unity.Mathematics; + +namespace AclUnity +{ + [StructLayout(LayoutKind.Explicit, Size = 48)] + public struct Qvv + { + [FieldOffset(0)] public quaternion rotation; + [FieldOffset(16)] public float4 translation; + [FieldOffset(32)] public float4 scale; + } + + public struct SemanticVersion + { + public short major; + public short minor; + public short patch; + + public bool IsValid => major > 0 || minor > 0 || patch > 0; + public bool IsUnrecognized => major == -1 && minor == -1 && patch == -1; + } + + [BurstCompile] + public static class AclUnityCommon + { + public static SemanticVersion GetVersion() + { + int version = 0; + if (X86.Avx2.IsAvx2Supported) + version = AVX.getVersion(); + else + { + //UnityEngine.Debug.Log("Fetched without AVX"); + version = NoExtensions.getVersion(); + } + + if (version == -1) + return new SemanticVersion { major = -1, minor = -1, patch = -1 }; + + short patch = (short)(version & 0x3ff); + short minor = (short)((version >> 10) & 0x3ff); + short major = (short)((version >> 20) & 0x3ff); + return new SemanticVersion { major = major, minor = minor, patch = patch }; + } + + public static string GetPluginName() + { + if (X86.Avx2.IsAvx2Supported) + return dllNameAVX; + return dllName; + } + + internal const string dllName = "AclUnity"; + internal const string dllNameAVX = "AclUnity_AVX"; + + static class NoExtensions + { + [DllImport(dllName)] + public static extern int getVersion(); + } + + static class AVX + { + [DllImport(dllNameAVX)] + public static extern int getVersion(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCommon.cs.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCommon.cs.meta new file mode 100644 index 0000000..372d113 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCommon.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f32d098da5167d246926ead7f980865e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCompression.cs b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCompression.cs new file mode 100644 index 0000000..dccaa69 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCompression.cs @@ -0,0 +1,299 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Unity.Burst.Intrinsics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace AclUnity +{ + public static unsafe class Compression + { + public struct AclCompressedClipResult : IDisposable + { + public NativeArray.ReadOnly compressedDataToCopyFrom => compressedData.AsReadOnly(); + internal NativeArray compressedData; + + public void Dispose() + { + DisposeCompressedTrack(this); + } + } + + public struct SkeletonCompressionSettings + { + public short compressionLevel; + public float maxDistanceError; + public float sampledErrorDistanceFromBone; + public float maxNegligibleTranslationDrift; + public float maxNegligibleScaleDrift; + } + + public static readonly SkeletonCompressionSettings kDefaultSettings = new SkeletonCompressionSettings + { + compressionLevel = 2, + maxDistanceError = 0.0001f, + sampledErrorDistanceFromBone = 0.03f, + maxNegligibleScaleDrift = 0.00001f, + maxNegligibleTranslationDrift = 0.00001f + }; + + public static AclCompressedClipResult CompressSkeletonClip(NativeArray parentIndices, + NativeArray aosClipData, + float sampleRate, + SkeletonCompressionSettings settings + ) + { + CheckParentIndicesIsValid(parentIndices); + CheckClipDataIsValid(aosClipData, parentIndices.Length); + CheckSampleRateIsValid(sampleRate); + CheckSkeletonSettingsIsValid(settings); + + var alignedClipData = (float*)aosClipData.GetUnsafeReadOnlyPtr(); + if (!CollectionHelper.IsAligned(alignedClipData, 16)) + { + alignedClipData = (float*)UnsafeUtility.Malloc(UnsafeUtility.SizeOf() * aosClipData.Length, math.max(UnsafeUtility.AlignOf(), 16), Allocator.TempJob); + UnsafeUtility.MemCpy(alignedClipData, aosClipData.GetUnsafeReadOnlyPtr(), UnsafeUtility.SizeOf() * aosClipData.Length); + } + + int outCompressedSizeInBytes = 0; + void* compressedClipPtr; + + if (X86.Avx2.IsAvx2Supported) + { + compressedClipPtr = AVX.compressSkeletonClip((short*)parentIndices.GetUnsafeReadOnlyPtr(), + (short)parentIndices.Length, + settings.compressionLevel, + alignedClipData, + aosClipData.Length / parentIndices.Length, + sampleRate, + settings.maxDistanceError, + settings.sampledErrorDistanceFromBone, + settings.maxNegligibleTranslationDrift, + settings.maxNegligibleScaleDrift, + &outCompressedSizeInBytes + ); + } + else + { + compressedClipPtr = NoExtensions.compressSkeletonClip((short*)parentIndices.GetUnsafeReadOnlyPtr(), + (short)parentIndices.Length, + settings.compressionLevel, + alignedClipData, + aosClipData.Length / parentIndices.Length, + sampleRate, + settings.maxDistanceError, + settings.sampledErrorDistanceFromBone, + settings.maxNegligibleTranslationDrift, + settings.maxNegligibleScaleDrift, + &outCompressedSizeInBytes + ); + } + + if (aosClipData.GetUnsafeReadOnlyPtr() != alignedClipData) + { + UnsafeUtility.Free(alignedClipData, Allocator.TempJob); + } + + var resultArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(compressedClipPtr, outCompressedSizeInBytes, Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var safety = AtomicSafetyHandle.Create(); + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref resultArray, safety); +#endif + + return new AclCompressedClipResult + { + compressedData = resultArray + }; + } + + public static AclCompressedClipResult CompressScalarsClip(NativeArray clipData, + NativeArray maxErrorsByTrack, + float sampleRate, + short compressionLevel + ) + { + CheckErrorsByTrackIsValid(maxErrorsByTrack); + CheckClipDataIsValid(clipData, maxErrorsByTrack.Length); + CheckSampleRateIsValid(sampleRate); + CheckCompressionLevelIsValid(compressionLevel); + + int outCompressedSizeInBytes = 0; + void* compressedClipPtr; + + if (X86.Avx2.IsAvx2Supported) + { + compressedClipPtr = AVX.compressScalarsClip((short)maxErrorsByTrack.Length, + compressionLevel, + (float*)clipData.GetUnsafeReadOnlyPtr(), + clipData.Length / maxErrorsByTrack.Length, + sampleRate, + (float*)maxErrorsByTrack.GetUnsafeReadOnlyPtr(), + &outCompressedSizeInBytes); + } + else + { + compressedClipPtr = NoExtensions.compressScalarsClip((short)maxErrorsByTrack.Length, + compressionLevel, + (float*)clipData.GetUnsafeReadOnlyPtr(), + clipData.Length / maxErrorsByTrack.Length, + sampleRate, + (float*)maxErrorsByTrack.GetUnsafeReadOnlyPtr(), + &outCompressedSizeInBytes); + } + + return new AclCompressedClipResult + { + compressedData = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(compressedClipPtr, outCompressedSizeInBytes, Allocator.None), + }; + } + + // Note: It shouldn't matter which DLL actually does the disposal since + // this is a movable serializable type. So we don't have to worry about + // Burst races in the Editor. + internal static void DisposeCompressedTrack(AclCompressedClipResult clip) + { + if (X86.Avx2.IsAvx2Supported) + AVX.disposeCompressedTracksBuffer(clip.compressedData.GetUnsafePtr()); + else + NoExtensions.disposeCompressedTracksBuffer(clip.compressedData.GetUnsafePtr()); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.Release(NativeArrayUnsafeUtility.GetAtomicSafetyHandle(clip.compressedData)); +#endif + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckParentIndicesIsValid(NativeArray parentIndices) + { + if (!parentIndices.IsCreated || parentIndices.Length == 0) + throw new ArgumentException("parentIndices is invalid"); + + if (parentIndices[0] != 0) + throw new ArgumentException("parentIndices has invalid root index"); + + for (int i = 1; i < parentIndices.Length; i++) + { + if (math.abs(parentIndices[i]) > i) + throw new ArgumentException("parentIndices has invalid index"); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckClipDataIsValid(NativeArray aosClipData, int boneCount) + { + if (!aosClipData.IsCreated || aosClipData.Length == 0) + throw new ArgumentException("aosClipData is invalid"); + if (aosClipData.Length % boneCount != 0 || aosClipData.Length < boneCount) + throw new ArgumentException("aosClipData is not sized correctly relative to the bone count"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckErrorsByTrackIsValid(NativeArray maxErrorsByTrack) + { + if (!maxErrorsByTrack.IsCreated || maxErrorsByTrack.Length == 0) + throw new ArgumentException("maxErrorsByTrack is invalid"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckClipDataIsValid(NativeArray clipData, int trackCount) + { + if (!clipData.IsCreated || clipData.Length == 0) + throw new ArgumentException("clipData is invalid"); + if (clipData.Length % trackCount != 0 || clipData.Length < trackCount) + throw new ArgumentException("clipData is not sized correctly relative to the track count"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckSampleRateIsValid(float sampleRate) + { + if (sampleRate <= math.EPSILON) + throw new ArgumentOutOfRangeException("sampleRate is negative or too small"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckCompressionLevelIsValid(short compressionLevel) + { + var clampedLevel = math.clamp(compressionLevel, 0, 4); + if (compressionLevel != clampedLevel) + throw new ArgumentOutOfRangeException("compressionLevel must be between 0 (lowest/fastest_to_compress) and 4 (highest/slowest_to_compress)"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckSkeletonSettingsIsValid(SkeletonCompressionSettings settings) + { + CheckCompressionLevelIsValid(settings.compressionLevel); + + if (settings.maxDistanceError <= math.EPSILON) + throw new ArgumentOutOfRangeException("maxDistanceError is negative or too small"); + if (settings.maxNegligibleScaleDrift <= math.EPSILON) + throw new ArgumentOutOfRangeException("maxNegligivelScaleDrift is negative or too small"); + if (settings.maxNegligibleTranslationDrift <= math.EPSILON) + throw new ArgumentOutOfRangeException("maxNegligibleTranslationDrift is negative or too small"); + if (settings.sampledErrorDistanceFromBone <= math.EPSILON) + throw new ArgumentOutOfRangeException("sampledErrorDistanceFromBone is negative or too small"); + } + + static class NoExtensions + { + const string dllName = AclUnityCommon.dllName; + + [DllImport(dllName)] + public static extern void* compressSkeletonClip(short* parentIndices, + short numBones, + short compressionLevel, + float* aosClipData, + int numSamples, + float sampleRate, + float maxDistanceError, + float sampledErrorDistanceFromBone, + float maxNegligibleTranslationDrift, + float maxNegligibleScaleDrift, + int* outCompressedSizeInBytes); + + [DllImport(dllName)] + public static extern void* compressScalarsClip(short numTracks, + short compressionLevel, + float* clipData, + int numSamples, + float sampleRate, + float* maxErrors, + int* outCompressedSizeInBytes); + + [DllImport(dllName)] + public static extern void* disposeCompressedTracksBuffer(void* compressedTracksBuffer); + } + + static class AVX + { + const string dllName = AclUnityCommon.dllNameAVX; + + [DllImport(dllName)] + public static extern void* compressSkeletonClip(short* parentIndices, + short numBones, + short compressionLevel, + float* aosClipData, + int numSamples, + float sampleRate, + float maxDistanceError, + float sampledErrorDistanceFromBone, + float maxNegligibleTranslationDrift, + float maxNegligibleScaleDrift, + int* outCompressedSizeInBytes); + + [DllImport(dllName)] + public static extern void* compressScalarsClip(short numTracks, + short compressionLevel, + float* clipData, + int numSamples, + float sampleRate, + float* maxErrors, + int* outCompressedSizeInBytes); + + [DllImport(dllName)] + public static extern void* disposeCompressedTracksBuffer(void* compressedTracksBuffer); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCompression.cs.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCompression.cs.meta new file mode 100644 index 0000000..0bcc9ef --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityCompression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7543cbf0f7461f4685f0d22035f9e88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityDecompression.cs b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityDecompression.cs new file mode 100644 index 0000000..409584c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityDecompression.cs @@ -0,0 +1,236 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Unity.Burst.Intrinsics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace AclUnity +{ + public static unsafe class Decompression + { + public enum KeyframeInterpolationMode : byte + { + Interpolate = 0, + Floor = 1, + Ceil = 2, + Nearest = 3 + } + + // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash + public static void SamplePoseAos(void* compressedTransformsClip, NativeArray outputBuffer, float time, KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + CheckOutputArrayIsCreated(outputBuffer); + + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseAOS(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseAOS(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), time, (byte)keyframeInterpolationMode); + } + } + + // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash + public static void SamplePoseAosBlendedFirst(void* compressedTransformsClip, + NativeArray outputBuffer, + float blendFactor, + float time, + KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + CheckOutputArrayIsCreated(outputBuffer); + + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseAOSBlendedFirst(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), blendFactor, time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseAOSBlendedFirst(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), blendFactor, time, (byte)keyframeInterpolationMode); + } + } + + // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash + public static void SamplePoseAosBlendedAdd(void* compressedTransformsClip, + NativeArray outputBuffer, + float blendFactor, + float time, + KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + CheckOutputArrayIsCreated(outputBuffer); + + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseAOSBlendedAdd(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), blendFactor, time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseAOSBlendedAdd(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), blendFactor, time, (byte)keyframeInterpolationMode); + } + } + + // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash + public static void SamplePoseSoa(void* compressedTransformsClip, NativeArray outputBuffer, float time, KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + CheckOutputArrayIsCreated(outputBuffer); + + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseSOA(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseSOA(compressedTransformsClip, (float*)outputBuffer.GetUnsafePtr(), time, (byte)keyframeInterpolationMode); + } + } + + public static Qvv SampleBone(void* compressedTransformsClip, int boneIndex, float time, KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + CheckIndexIsValid(boneIndex); + + Qvv qvv; + + if (X86.Avx2.IsAvx2Supported) + { + AVX.sampleBone(compressedTransformsClip, (float*)(&qvv), boneIndex, time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.sampleBone(compressedTransformsClip, (float*)(&qvv), boneIndex, time, (byte)keyframeInterpolationMode); + } + + return qvv; + } + + // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash + public static void SampleFloats(void* compressedFloatsClip, NativeArray outputBuffer, float time, KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedFloatsClip); + CheckOutputArrayIsCreated(outputBuffer); + + if (X86.Avx2.IsAvx2Supported) + { + AVX.sampleFloats(compressedFloatsClip, (float*)outputBuffer.GetUnsafePtr(), time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.sampleFloats(compressedFloatsClip, (float*)outputBuffer.GetUnsafePtr(), time, (byte)keyframeInterpolationMode); + } + } + + public static float SampleFloat(void* compressedFloatsClip, int trackIndex, float time, KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedFloatsClip); + CheckIndexIsValid(trackIndex); + + if (X86.Avx2.IsAvx2Supported) + { + return AVX.sampleFloat(compressedFloatsClip, trackIndex, time, (byte)keyframeInterpolationMode); + } + else + { + return NoExtensions.sampleFloat(compressedFloatsClip, trackIndex, time, (byte)keyframeInterpolationMode); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckCompressedClipIsValid(void* compressedClip) + { + if (compressedClip == null) + throw new ArgumentNullException("compressedClip is null"); + if (!CollectionHelper.IsAligned(compressedClip, 16)) + throw new ArgumentException("compressedClip is not aligned to a 16 byte boundary"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckOutputArrayIsCreated(NativeArray outputBuffer) + { + if (!outputBuffer.IsCreated || outputBuffer.Length == 0) + throw new ArgumentException("outputBuffer is invalid"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckOutputArrayIsCreated(NativeArray outputBuffer) + { + if (!outputBuffer.IsCreated || outputBuffer.Length == 0) + throw new ArgumentException("outputBuffer is invalid"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckIndexIsValid(int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException("Bone or track index is negative"); + } + + static class NoExtensions + { + const string dllName = AclUnityCommon.dllName; + + [DllImport(dllName)] + public static extern void samplePoseAOS(void* compressedTransformTracks, float* aosOutputBuffer, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void samplePoseAOSBlendedFirst(void* compressedTransformTracks, + float* aosOutputBuffer, + float blendFactor, + float time, + byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void samplePoseAOSBlendedAdd(void* compressedTransformTracks, float* aosOutputBuffer, float blendFactor, float time, + byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void samplePoseSOA(void* compressedTransformTracks, float* soaOutputBuffer, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void sampleBone(void* compressedTransformTracks, float* boneQVV, int boneIndex, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void sampleFloats(void* compressedFloatTracks, float* floatOutputBuffer, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern float sampleFloat(void* compressedFloatTracks, int trackIndex, float time, byte keyframeInterpolationMode); + } + + static class AVX + { + const string dllName = AclUnityCommon.dllNameAVX; + + [DllImport(dllName)] + public static extern void samplePoseAOS(void* compressedTransformTracks, float* aosOutputBuffer, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void samplePoseAOSBlendedFirst(void* compressedTransformTracks, + float* aosOutputBuffer, + float blendFactor, + float time, + byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void samplePoseAOSBlendedAdd(void* compressedTransformTracks, float* aosOutputBuffer, float blendFactor, float time, + byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void samplePoseSOA(void* compressedTransformTracks, float* soaOutputBuffer, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void sampleBone(void* compressedTransformTracks, float* boneQVV, int boneIndex, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern void sampleFloats(void* compressedFloatTracks, float* floatOutputBuffer, float time, byte keyframeInterpolationMode); + + [DllImport(dllName)] + public static extern float sampleFloat(void* compressedFloatTracks, int trackIndex, float time, byte keyframeInterpolationMode); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityDecompression.cs.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityDecompression.cs.meta new file mode 100644 index 0000000..a328bae --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/AclUnityDecompression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d080df6b8aa6d214182b78b044edb8be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins.meta new file mode 100644 index 0000000..bff7957 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 31cc39d789e8c0245b88ea3030d79d2e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux.meta new file mode 100644 index 0000000..4947fc8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 369f2f8122c694148888bd8cfde9aecb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity.so b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity.so new file mode 100644 index 0000000..7b32573 Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity.so differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity.so.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity.so.meta new file mode 100644 index 0000000..c399cd6 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity.so.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: bd0b7be70f703034285412d2f83c307f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 1 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: Linux + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity_AVX.so b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity_AVX.so new file mode 100644 index 0000000..324afb8 Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity_AVX.so differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity_AVX.so.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity_AVX.so.meta new file mode 100644 index 0000000..3c8743c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Linux/AclUnity_AVX.so.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: 8f01a798f36e3ec4188d4944a0b7ef9f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 1 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: Linux + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX.meta new file mode 100644 index 0000000..e90f048 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9a7f799fe6600945baf5e12efb75d4e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64.meta new file mode 100644 index 0000000..6516984 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b370212ab8b683c45b10da2897555f0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib new file mode 100644 index 0000000..587c342 Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib.meta new file mode 100644 index 0000000..8a9300e --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: 930221a877c9b0b40999ef1996290b57 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: ARM64 + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: ARM64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86.meta new file mode 100644 index 0000000..b01a950 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd7fd7f257f6551408ec261a4837e7f0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity.dylib b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity.dylib new file mode 100644 index 0000000..59a66ed Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity.dylib differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity.dylib.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity.dylib.meta new file mode 100644 index 0000000..8df8315 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity.dylib.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: aea33e74231390d4085d93543419809b +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity_AVX.dylib b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity_AVX.dylib new file mode 100644 index 0000000..46344e0 Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity_AVX.dylib differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity_AVX.dylib.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity_AVX.dylib.meta new file mode 100644 index 0000000..e179cc9 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/OSX/X86/AclUnity_AVX.dylib.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: a5ba23775a0504f439f707534c68eea0 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 0 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows.meta new file mode 100644 index 0000000..33ae924 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8d1f14a4a75dc874f9e029205d0690a2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity.dll b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity.dll new file mode 100644 index 0000000..919a93e Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity.dll differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity.dll.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity.dll.meta new file mode 100644 index 0000000..b8512b4 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity.dll.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: bc772d6ab7c4edc4e9b509ddf79dddea +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: Windows + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity_AVX.dll b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity_AVX.dll new file mode 100644 index 0000000..967c099 Binary files /dev/null and b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity_AVX.dll differ diff --git a/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity_AVX.dll.meta b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity_AVX.dll.meta new file mode 100644 index 0000000..0938bd6 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/ACL_Unity/Plugins/Windows/AclUnity_AVX.dll.meta @@ -0,0 +1,63 @@ +fileFormatVersion: 2 +guid: 7259b21510dd8aa43b7eab3248a653c1 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude Win: 0 + Exclude Win64: 0 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: Windows + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring.meta new file mode 100644 index 0000000..da6575c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b31af472f65e9a439beb28100c3ffef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ClipEvents.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ClipEvents.cs new file mode 100644 index 0000000..0c79977 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ClipEvents.cs @@ -0,0 +1,87 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + /// + /// Defines a single event in an animation clip + /// + public struct ClipEvent : IComparable + { + public FixedString64Bytes name; + public int parameter; + public float time; + + public int CompareTo(ClipEvent other) + { + var result = time.CompareTo(other.time); + if (result == 0) + result = name.CompareTo(other.name); + if (result == 0) + result = parameter.CompareTo(other.parameter); + return result; + } + } + + public static class AnimationClipEventExtensions + { + /// + /// Generates an array of ClipEvent which can be used for generating Kinemation animation clip blobs. + /// For each AnimationEvent in the clip, functionName, intParameter, and time are extracted. + /// + public static ClipEvent[] ExtractKinemationClipEvents(this AnimationClip clip) + { + var srcEvents = clip.events; + var dstEvents = new ClipEvent[srcEvents.Length]; + + for (int i = 0; i < srcEvents.Length; i++) + { + var src = srcEvents[i]; + + dstEvents[i] = new ClipEvent + { + name = src.functionName, + parameter = src.intParameter, + time = src.time + }; + } + + return dstEvents; + } + } + + internal static class ClipEventsBlobHelpers + { + internal static void Convert(ref ClipEvents dstEvents, ref BlobBuilder builder, UnsafeList srcEvents) + { + if (srcEvents.IsCreated) + { + var times = builder.Allocate(ref dstEvents.times, srcEvents.Length); + var nameHashes = builder.Allocate(ref dstEvents.nameHashes, srcEvents.Length); + var parameters = builder.Allocate(ref dstEvents.parameters, srcEvents.Length); + var names = builder.Allocate(ref dstEvents.names, srcEvents.Length); + + srcEvents.Sort(); + + for (int i = 0; i < srcEvents.Length; i++) + { + times[i] = srcEvents[i].time; + nameHashes[i] = srcEvents[i].name.GetHashCode(); + parameters[i] = srcEvents[i].parameter; + names[i] = srcEvents[i].name; + } + } + else + { + builder.Allocate(ref dstEvents.times, 0); + builder.Allocate(ref dstEvents.nameHashes, 0); + builder.Allocate(ref dstEvents.parameters, 0); + builder.Allocate(ref dstEvents.names, 0); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ClipEvents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ClipEvents.cs.meta new file mode 100644 index 0000000..4c4e6b8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ClipEvents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3ef944caba8812459f3746859a34848 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionContextComponents.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionContextComponents.cs new file mode 100644 index 0000000..ac35a53 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionContextComponents.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using Latios.Authoring; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + internal class SkeletonConversionContext : IComponentData + { + public BoneTransformData[] skeleton; + public bool isOptimized; + public Animator animator; + public SkeletonAuthoring authoring; + + public GameObject shadowHierarchy + { + get + { + if (m_shadowHierarchy == null) + { + m_shadowHierarchy = ShadowHierarchyBuilder.BuildShadowHierarchy(animator.gameObject, isOptimized); + } + return m_shadowHierarchy; + } + } + private GameObject m_shadowHierarchy = null; + + public void DestroyShadowHierarchy() + { + if (m_shadowHierarchy != null) + { + m_shadowHierarchy.DestroyDuringConversion(); + } + } + } + + internal class SkinnedMeshConversionContext : IComponentData + { + public string[] bonePathsReversed; + public SkeletonConversionContext skeletonContext; + public SkinnedMeshRenderer renderer; + public SkinnedMeshSettingsAuthoring authoring; + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionContextComponents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionContextComponents.cs.meta new file mode 100644 index 0000000..027d2a9 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionContextComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6f1dcad6afa2ad46a5a92dad0e5aa2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems.meta new file mode 100644 index 0000000..234a83b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b0a79d682b5bad94a9151465cf4717dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/AddMasksConversionSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/AddMasksConversionSystem.cs new file mode 100644 index 0000000..a3385a9 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/AddMasksConversionSystem.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; + +namespace Latios.Kinemation.Authoring.Systems +{ + [UpdateInGroup(typeof(GameObjectConversionGroup), OrderLast = true)] + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + public class AddMasksConversionSystem : GameObjectConversionSystem + { + protected override void OnUpdate() + { + var query = DstEntityManager.Fluent().WithAll(true).Without(true).IncludePrefabs().IncludeDisabled().Build(); + DstEntityManager.AddComponent(query, new ComponentTypes(ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent())); + query.Dispose(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/AddMasksConversionSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/AddMasksConversionSystem.cs.meta new file mode 100644 index 0000000..3aba627 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/AddMasksConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49266758d5471f14cbe10a46ebd814c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverSkeletonsConversionSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverSkeletonsConversionSystem.cs new file mode 100644 index 0000000..8ebce91 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverSkeletonsConversionSystem.cs @@ -0,0 +1,276 @@ +using System.Collections.Generic; +using System.Text; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; + +namespace Latios.Kinemation.Authoring.Systems +{ + [UpdateInGroup(typeof(GameObjectBeforeConversionGroup))] + [ConverterVersion("Latios", 2)] + [DisableAutoCreation] + public class DiscoverSkeletonsConversionSystem : GameObjectConversionSystem + { + protected override void OnUpdate() + { + var skinnedMeshToAuthoringEntityDictionary = new Dictionary(); + Entities.ForEach((Entity entity, SkinnedMeshRenderer renderer) => skinnedMeshToAuthoringEntityDictionary.Add(renderer, entity)); + + Entities.ForEach((Entity entity, Animator animator) => + { + //Debug.Log($"Discovered skeleton on {animator.gameObject.name}"); + + var context = new SkeletonConversionContext(); + context.animator = animator; + if (EntityManager.HasComponent(entity)) + { + context.authoring = EntityManager.GetComponentObject(entity); + + if (context.authoring.bindingMode == BindingMode.Import) + { + switch (context.authoring.m_importStatus) + { + case ImportStatus.AmbiguityError: + Debug.LogError($"{animator.gameObject.name} contains a skeleton ambiguity error and will not be converted to an entity correctly."); + return; + case ImportStatus.Uninitialized: + Debug.LogError($"{animator.gameObject.name} is trying to use skeleton BindingMode.Import, but this feature has not been implemented yet."); + return; + case ImportStatus.UnknownError: + Debug.LogError( + $"{animator.gameObject.name} encountered an unexpected error during skeleton import. Try reimporting and pay attention to any errors logged."); + return; + case ImportStatus.Success: + { + context.skeleton = context.authoring.m_importSkeleton; + break; + } + } + } + else if (context.authoring.bindingMode == BindingMode.Custom) + { + if (context.authoring.customSkeleton == null) + { + Debug.LogError($"{animator.gameObject.name} is trying to use skeleton BindingMode.Custom, but no custom skeleton data was provided."); + return; + } + context.skeleton = context.authoring.customSkeleton.ToArray(); + } + } + + var hierarchy = animator.gameObject; + context.isOptimized = !animator.hasTransformHierarchy; + if (context.isOptimized) + { + hierarchy = context.shadowHierarchy; + } + + if (context.skeleton == null) + context.skeleton = AnalyzeHierarchy(hierarchy.transform); + + if (context.isOptimized) + { + for (int i = 0; i < context.skeleton.Length; i++) + { + var bone = context.skeleton[i]; + + var tracker = bone.gameObjectTransform.GetComponent(); + if (tracker == null) + { + bone.gameObjectTransform = null; + } + else + { + bone.gameObjectTransform = tracker.source.transform; + } + + context.skeleton[i] = bone; + } + } + else + { + foreach (var bone in context.skeleton) + { + if (bone.gameObjectTransform != null) + { + DeclareDependency(animator, bone.gameObjectTransform); + DeclareDependency(animator.gameObject, bone.gameObjectTransform.gameObject); + } + } + } + + PostUpdateCommands.AddComponent(entity, context); + DiscoverSkinnedMeshes(context, skinnedMeshToAuthoringEntityDictionary); + }); + } + + List m_boneCache = new List(); + Queue<(Transform, int)> m_breadthQueue = new Queue<(Transform, int)>(); + StringBuilder m_stringBuilderCache = new StringBuilder(); + + BoneTransformData[] AnalyzeHierarchy(Transform root) + { + m_boneCache.Clear(); + m_breadthQueue.Clear(); + + m_breadthQueue.Enqueue((root.transform, -1)); + + while (m_breadthQueue.Count > 0) + { + var (bone, parentIndex) = m_breadthQueue.Dequeue(); + int currentIndex = m_boneCache.Count; + m_boneCache.Add(new BoneTransformData + { + gameObjectTransform = bone, + hierarchyReversePath = GetReversePath(root.transform, bone), + localPosition = bone.localPosition, + localRotation = bone.localRotation, + localScale = bone.localScale, + parentIndex = parentIndex + }); + + for (int i = 0; i < bone.childCount; i++) + { + var child = bone.GetChild(i); + if (child.GetComponent() == null) + m_breadthQueue.Enqueue((bone.GetChild(i), currentIndex)); + } + } + + return m_boneCache.ToArray(); + } + + string GetReversePath(Transform root, Transform pathTarget, bool excludeRootFromPath = false) + { + m_stringBuilderCache.Clear(); + + var t = pathTarget; + + while (t != root) + { + m_stringBuilderCache.Append(t.gameObject.name); + m_stringBuilderCache.Append('/'); + t = t.parent; + } + + if (!excludeRootFromPath) + { + var unshadowedRoot = t.GetComponent(); + if (unshadowedRoot != null) + t = unshadowedRoot.source.transform; + m_stringBuilderCache.Append(t.gameObject.name); + m_stringBuilderCache.Append('/'); + } + + return m_stringBuilderCache.ToString(); + } + + List m_smrCache = new List(); + void DiscoverSkinnedMeshes(SkeletonConversionContext skeletonContext, Dictionary smrToAuthoringEntityDictionary) + { + m_smrCache.Clear(); + Transform root; + if (skeletonContext.isOptimized) + { + skeletonContext.shadowHierarchy.GetComponentsInChildren(true, m_smrCache); + root = skeletonContext.shadowHierarchy.transform; + } + else + { + skeletonContext.animator.GetComponentsInChildren(true, m_smrCache); + root = skeletonContext.animator.transform; + } + + foreach (var smr in m_smrCache) + { + var authoring = smr.GetComponent(); + var realSmr = smr; + + if (skeletonContext.isOptimized) + { + var tracker = smr.GetComponent(); + if (tracker != null) + { + realSmr = tracker.source.GetComponent(); + authoring = tracker.source.GetComponent(); + } + } + + if (authoring != null) + { + if (authoring.bindingMode == BindingMode.Import) + { + if (authoring.m_importBonePathsReversed == null) + { + Debug.LogError($"Skinned Mesh {smr.gameObject.name} is trying to use skeleton BindingMode.Import, but this feature hasn't been implemented yet."); + continue; + } + var meshContext = new SkinnedMeshConversionContext + { + authoring = authoring, + bonePathsReversed = authoring.m_importBonePathsReversed, + renderer = realSmr, + skeletonContext = skeletonContext + }; + PostUpdateCommands.AddComponent(GetPrimaryEntity(authoring), meshContext); + continue; + } + else if (authoring.bindingMode == BindingMode.Custom) + { + if (authoring.customBonePathsReversed == null) + { + Debug.LogError($"{smr.gameObject.name} is trying to use skinned mesh BindingMode.Custom, but no custom skeleton data was provided."); + continue; + } + var meshContext = new SkinnedMeshConversionContext + { + authoring = authoring, + bonePathsReversed = authoring.customBonePathsReversed.ToArray(), + renderer = realSmr, + skeletonContext = skeletonContext + }; + PostUpdateCommands.AddComponent(GetPrimaryEntity(authoring), meshContext); + continue; + } + } + + if (smr.bones == null) + { + Debug.LogError($"Skinned mesh {smr.gameObject.name} does not have valid bones."); + continue; + } + + var bones = smr.bones; + var paths = new string[bones.Length]; + + for (int i = 0; i < bones.Length; i++) + { + paths[i] = GetReversePath(root, bones[i], true); + if (smr == realSmr) + { + DeclareDependency(realSmr, bones[i]); + DeclareDependency(realSmr.gameObject, bones[i].gameObject); + } + } + DeclareDependency(realSmr.gameObject, skeletonContext.animator.gameObject); + + { + var meshContext = new SkinnedMeshConversionContext + { + authoring = authoring, + bonePathsReversed = paths, + renderer = realSmr, + skeletonContext = skeletonContext + }; + var smrEntity = smrToAuthoringEntityDictionary[realSmr]; + PostUpdateCommands.AddComponent(smrEntity, meshContext); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverSkeletonsConversionSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverSkeletonsConversionSystem.cs.meta new file mode 100644 index 0000000..5174d26 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverSkeletonsConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b167e1a25a1e3f47adff2b617eb9a54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverUnboundSkinnedMeshesConversionSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverUnboundSkinnedMeshesConversionSystem.cs new file mode 100644 index 0000000..3518b13 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverUnboundSkinnedMeshesConversionSystem.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Text; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; + +namespace Latios.Kinemation.Authoring.Systems +{ + [UpdateInGroup(typeof(GameObjectBeforeConversionGroup))] + [UpdateAfter(typeof(DiscoverSkeletonsConversionSystem))] + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + public class DiscoverUnboundSkinnedMeshesConversionSystem : GameObjectConversionSystem + { + protected override void OnUpdate() + { + Entities.WithNone().ForEach((SkinnedMeshSettingsAuthoring authoring, SkinnedMeshRenderer renderer) => + { + if (authoring.bindingMode == BindingMode.ConversionTime) + { + Debug.LogError($"Skinned Mesh {renderer.gameObject.name} does not have a skeleton. Skipping."); + } + else if (authoring.bindingMode == BindingMode.Import) + { + if (authoring.m_importBonePathsReversed == null) + { + Debug.LogError($"Skinned Mesh {renderer.gameObject.name} is trying to use skeleton BindingMode.Import, but this feature hasn't been implemented yet."); + return; + } + var meshContext = new SkinnedMeshConversionContext + { + authoring = authoring, + bonePathsReversed = authoring.m_importBonePathsReversed, + renderer = renderer, + skeletonContext = null + }; + PostUpdateCommands.AddComponent(GetPrimaryEntity(authoring), meshContext); + } + else if (authoring.bindingMode == BindingMode.Custom) + { + if (authoring.customBonePathsReversed == null) + { + Debug.LogError($"{renderer.gameObject.name} is trying to use skinned mesh BindingMode.Custom, but no custom skeleton data was provided."); + return; + } + var meshContext = new SkinnedMeshConversionContext + { + authoring = authoring, + bonePathsReversed = authoring.customBonePathsReversed.ToArray(), + renderer = renderer, + skeletonContext = null + }; + PostUpdateCommands.AddComponent(GetPrimaryEntity(authoring), meshContext); + } + }); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverUnboundSkinnedMeshesConversionSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverUnboundSkinnedMeshesConversionSystem.cs.meta new file mode 100644 index 0000000..6ef231a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/DiscoverUnboundSkinnedMeshesConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7da3a484b31c80548809939994eb3c19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/KinemationCleanupConversionSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/KinemationCleanupConversionSystem.cs new file mode 100644 index 0000000..e6d52f5 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/KinemationCleanupConversionSystem.cs @@ -0,0 +1,193 @@ +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; + +// Note: We avoid adding components that would otherwise be added at runtime by the reactive systems. +// That way we keep the serialized data size down. +namespace Latios.Kinemation.Authoring.Systems +{ + [UpdateInGroup(typeof(GameObjectConversionGroup), OrderFirst = true)] + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + public class KinemationCleanupConversionSystem : GameObjectConversionSystem + { + EntityQuery m_skeletonQuery; + EntityQuery m_meshQuery; + + protected override void OnCreate() + { + base.OnCreate(); + m_skeletonQuery = GetEntityQuery(typeof(SkeletonConversionContext)); + m_meshQuery = GetEntityQuery(typeof(SkinnedMeshConversionContext)); + } + + protected override void OnUpdate() + { + var exposedBoneCullingTypes = CullingUtilities.GetBoneCullingComponentTypes(); + var transformComponentsToRemove = new ComponentTypes(typeof(Translation), typeof(Rotation), typeof(NonUniformScale)); + + var ecb = new EntityCommandBuffer(Allocator.TempJob); + + Entities.ForEach((SkeletonConversionContext context) => + { + var entity = GetPrimaryEntity(context.animator); + + DstEntityManager.AddComponent(entity); + + if (context.isOptimized) + { + var ltrBuffer = DstEntityManager.AddBuffer(entity).Reinterpret(); + ltrBuffer.ResizeUninitialized(context.skeleton.Length); + short i = 0; + foreach (var b in context.skeleton) + { + var ltp = float4x4.TRS(b.localPosition, b.localRotation, b.localScale); + if (b.parentIndex < 0) + ltrBuffer[i] = ltp; + else + ltrBuffer[i] = math.mul(ltrBuffer[b.parentIndex], ltp); + + if (b.gameObjectTransform != null) + { + var boneEntity = GetPrimaryEntity(b.gameObjectTransform); + ecb.AddComponent(boneEntity, new BoneOwningSkeletonReference { skeletonRoot = entity }); + if (b.gameObjectTransform.parent == context.animator.transform) + { + ecb.AddComponent(boneEntity, new CopyLocalToParentFromBone { boneIndex = i }); + ecb.RemoveComponent(boneEntity, transformComponentsToRemove); + } + } + + i++; + } + } + else + { + var boneReferenceBuffer = DstEntityManager.AddBuffer(entity).Reinterpret(); + boneReferenceBuffer.ResizeUninitialized(context.skeleton.Length); + short i = 0; + + foreach (var b in context.skeleton) + { + var boneEntity = GetPrimaryEntity(b.gameObjectTransform); + ecb.AddComponent(boneEntity, exposedBoneCullingTypes); + ecb.SetComponent(boneEntity, new BoneOwningSkeletonReference { skeletonRoot = entity }); + ecb.SetComponent(boneEntity, new BoneIndex { index = i }); + boneReferenceBuffer[i] = boneEntity; + + // Animation can have scale even if the default pose doesn't. + ecb.AddComponent(boneEntity, new NonUniformScale { Value = 1f }); + if (b.ignoreParentScale) + ecb.AddComponent(boneEntity); + i++; + } + } + + context.DestroyShadowHierarchy(); + }); + + ecb.Playback(DstEntityManager); + ecb.Dispose(); + + var computePropertyID = UnityEngine.Shader.PropertyToID("_ComputeMeshIndex"); + var linearBlendPropertyID = UnityEngine.Shader.PropertyToID("_SkinMatrixIndex"); + var materialsCache = new List(); + + var renderMeshConversionContext = new RenderMeshConversionContext(DstEntityManager, this) + { + AttachToPrimaryEntityForSingleMaterial = true + }; + + Entities.ForEach((SkinnedMeshConversionContext context) => + { + var entity = GetPrimaryEntity(context.renderer); + bool needsComputeDeformFromCopy = false; + bool needsLinearBlendFromCopy = false; + + context.renderer.GetSharedMaterials(materialsCache); + if (materialsCache.Count > 1) + { + // We want the primary bound entity to stay the primary entity as that is intuitive for users. + // So to do that, we convert only the first material of the skinned mesh as the primary. + // And then convert all the materials as children. This duplicates the first material which we + // then destroy right after. + var firstMaterial = materialsCache[0]; + materialsCache.Clear(); + materialsCache.Add(firstMaterial); + renderMeshConversionContext.Convert(context.renderer, context.renderer.sharedMesh, materialsCache, context.renderer.transform); + + // A null cache will cause the context to fetch the materials using its own cache. + renderMeshConversionContext.Convert(context.renderer, context.renderer.sharedMesh, null, context.renderer.transform); + + var entityToDestroy = Entity.Null; + foreach (var candidateEntity in GetEntities(context.renderer)) + { + if (candidateEntity == entity) + continue; + if (DstEntityManager.HasComponent(candidateEntity)) + { + var rm = DstEntityManager.GetSharedComponentData(candidateEntity); + if (rm.subMesh == 0) + { + entityToDestroy = candidateEntity; + } + else + { + DstEntityManager.AddComponentData(candidateEntity, new ShareSkinFromEntity { sourceSkinnedEntity = entity }); + DstEntityManager.AddChunkComponentData(candidateEntity); + if (rm.material.HasProperty(computePropertyID)) + { + DstEntityManager.AddComponent(candidateEntity); + needsComputeDeformFromCopy = true; + } + if (rm.material.HasProperty(linearBlendPropertyID)) + { + DstEntityManager.AddComponent(candidateEntity); + needsLinearBlendFromCopy = true; + } + } + } + } + + // Todo: Is this safe? + DstEntityManager.DestroyEntity(entityToDestroy); + } + else + renderMeshConversionContext.Convert(context.renderer, context.renderer.sharedMesh, materialsCache, context.renderer.transform); + + // Even if this material doesn't use a particular skinning property, its child might, + // and that child will want to copy a valid property from the parent. + if (materialsCache[0].HasProperty(computePropertyID) || needsComputeDeformFromCopy) + { + DstEntityManager.AddComponent(entity); + DstEntityManager.AddChunkComponentData(entity); + } + if (materialsCache[0].HasProperty(linearBlendPropertyID) || needsLinearBlendFromCopy) + { + DstEntityManager.AddComponent(entity); + DstEntityManager.AddChunkComponentData(entity); + } + + if (context.skeletonContext != null) + { + var root = GetPrimaryEntity(context.skeletonContext.animator); + DstEntityManager.AddComponentData(entity, new BindSkeletonRoot { root = root }); + DstEntityManager.AddComponentData(entity, new Parent { Value = root }); + DstEntityManager.AddComponentData(entity, new LocalToParent { Value = float4x4.identity }); + DstEntityManager.RemoveComponent(entity, transformComponentsToRemove); + } + }); + + renderMeshConversionContext.EndConversion(); + + EntityManager.RemoveComponent( m_skeletonQuery); + EntityManager.RemoveComponent(m_meshQuery); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/KinemationCleanupConversionSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/KinemationCleanupConversionSystem.cs.meta new file mode 100644 index 0000000..7563b93 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/KinemationCleanupConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8cca6bba8a48924ab0b73772a9e1f48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshPathsSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshPathsSmartBlobberSystem.cs new file mode 100644 index 0000000..5efaba2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshPathsSmartBlobberSystem.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Authoring.Systems +{ + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + public class MeshPathsSmartBlobberSystem : SmartBlobberConversionSystem + { + struct AuthoringHandlePair + { + public SkinnedMeshConversionContext authoring; + public SmartBlobberHandle handle; + } + + List m_contextList = new List(); + + protected override void GatherInputs() + { + m_contextList.Clear(); + Entities.ForEach((SkinnedMeshConversionContext context) => + { + var handle = AddToConvert(context.renderer.gameObject, new MeshPathsBakeData { bones = context.bonePathsReversed }); + m_contextList.Add(new AuthoringHandlePair { authoring = context, handle = handle }); + }); + } + + protected override void FinalizeOutputs() + { + foreach (var pair in m_contextList) + { + var context = pair.authoring; + var go = context.renderer.gameObject; + var entity = GetPrimaryEntity(go); + DstEntityManager.AddComponentData(entity, new MeshBindingPathsBlobReference { blob = pair.handle.Resolve() }); + } + } + + protected override unsafe bool Filter(in MeshPathsBakeData input, UnityEngine.GameObject gameObject, out MeshPathsConverter converter) + { + var allocator = World.UpdateAllocator.ToAllocator; + converter.paths = new UnsafeList >(input.bones.Length, allocator); + FixedString4096Bytes cache; + for (int i = 0; i < input.bones.Length; i++) + { + cache = input.bones[i]; + var path = new UnsafeList(cache.Length, allocator); + path.AddRange(cache.GetUnsafePtr(), cache.Length); + converter.paths.Add(path); + } + return true; + } + } + + public struct MeshPathsBakeData + { + public string[] bones; + } + + public struct MeshPathsConverter : ISmartBlobberSimpleBuilder + { + public UnsafeList > paths; + + public unsafe BlobAssetReference BuildBlob() + { + var builder = new BlobBuilder(Allocator.Temp); + + ref var root = ref builder.ConstructRoot(); + var pathsOuter = builder.Allocate(ref root.pathsInReversedNotation, paths.Length); + for (int i = 0; i < paths.Length; i++) + { + builder.ConstructFromNativeArray(ref pathsOuter[i], paths[i].Ptr, paths[i].Length); + } + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshPathsSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshPathsSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..bcb15f5 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshPathsSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f819509e0fd7f54c98088bc9b5a8e6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshSkinningSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshSkinningSmartBlobberSystem.cs new file mode 100644 index 0000000..2176a73 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshSkinningSmartBlobberSystem.cs @@ -0,0 +1,303 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Kinemation.Authoring.Systems +{ + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + public class MeshSkinningSmartBlobberSystem : SmartBlobberConversionSystem + { + struct AuthoringHandlePair + { + public SkinnedMeshConversionContext authoring; + public SmartBlobberHandle handle; + } + + List m_contextList = new List(); + + protected override void GatherInputs() + { + m_contextList.Clear(); + + bool isEditorAndSubscene = false; +#if UNITY_EDITOR + isEditorAndSubscene = this.GetSettings().sceneGUID != default; +#endif + + Entities.ForEach((SkinnedMeshConversionContext context) => + { + var sharedMesh = context.renderer.sharedMesh; + + // Todo: Should we throw an error here? + if (sharedMesh == null) + return; + + if (!isEditorAndSubscene && !sharedMesh.isReadable) + { + Debug.LogError( + $"Attempted to runtime-convert skinned mesh {context.renderer.gameObject.name} with mesh {sharedMesh.name} but the mesh is not been imported with read/write access."); + return; + } + var handle = AddToConvert(context.renderer.gameObject, new MeshSkinningBakeData { sharedMesh = sharedMesh }); + m_contextList.Add(new AuthoringHandlePair { authoring = context, handle = handle }); + }); + } + + protected override void FinalizeOutputs() + { + foreach (var pair in m_contextList) + { + var context = pair.authoring; + var go = context.renderer.gameObject; + var entity = GetPrimaryEntity(go); + DstEntityManager.AddComponentData(entity, new MeshSkinningBlobReference { blob = pair.handle.Resolve() }); + } + } + + protected override void Filter(FilterBlobberData blobberData, ref MeshSkinningContext context, NativeArray inputToFilteredMapping) + { + var hashes = new NativeArray(blobberData.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < blobberData.Count; i++) + { + var input = blobberData.input[i]; + + DeclareAssetDependency(blobberData.associatedObject[i], input.sharedMesh); + hashes[i] = input.sharedMesh.GetInstanceID(); + } + + new DeduplicateJob { hashes = hashes, inputToFilteredMapping = inputToFilteredMapping }.Run(); + hashes.Dispose(); + } + + protected override unsafe void PostFilter(PostFilterBlobberData blobberData, ref MeshSkinningContext context) + { + var meshList = new List(); + var bindPosesCache = new List(); + + var converters = blobberData.converters; + + var allocator = World.UpdateAllocator.ToAllocator; + + for (int i = 0; i < blobberData.Count; i++) + { + var mesh = blobberData.input[i].sharedMesh; + meshList.Add(mesh); + + bindPosesCache.Clear(); + mesh.GetBindposes(bindPosesCache); + var bindposes = new UnsafeList(bindPosesCache.Count, allocator); + foreach (var bp in bindPosesCache) + bindposes.Add(bp); + + var weightsArray = mesh.GetAllBoneWeights(); + var boneWeights = new UnsafeList(weightsArray.Length, allocator); + boneWeights.AddRange(weightsArray.GetUnsafeReadOnlyPtr(), weightsArray.Length); + + var weightCountsArray = mesh.GetBonesPerVertex(); + var weightCounts = new UnsafeList(weightCountsArray.Length, allocator); + weightCounts.AddRange(weightCountsArray.GetUnsafeReadOnlyPtr(), weightCountsArray.Length); + + converters[i] = new MeshSkinningConverter + { + name = mesh.name, + bindPoses = bindposes, + boneWeights = boneWeights, + boneWeightCountsPerVertex = weightCounts + }; + } + + context.meshes = Mesh.AcquireReadOnlyMeshData(meshList); + } + + [BurstCompile] + struct DeduplicateJob : IJob + { + [ReadOnly] public NativeArray hashes; + public NativeArray inputToFilteredMapping; + + public void Execute() + { + var map = new NativeParallelHashMap(hashes.Length, Allocator.Temp); + for (int i = 0; i < hashes.Length; i++) + { + if (inputToFilteredMapping[i] < 0) + continue; + + if (map.TryGetValue(hashes[i], out int index)) + inputToFilteredMapping[i] = index; + else + map.Add(hashes[i], i); + } + } + } + } + + public struct MeshSkinningConverter : ISmartBlobberContextBuilder + { + public FixedString128Bytes name; + public UnsafeList boneWeightCountsPerVertex; + public UnsafeList boneWeights; + public UnsafeList bindPoses; + + public unsafe BlobAssetReference BuildBlob(int prefilterIndex, int postfilterIndex, ref MeshSkinningContext context) + { + if (!context.vector3Cache.IsCreated) + { + context.vector3Cache = new NativeList(Allocator.Temp); + context.vector4Cache = new NativeList(Allocator.Temp); + } + + var builder = new BlobBuilder(Allocator.Temp); + + ref var blobRoot = ref builder.ConstructRoot(); + var mesh = context.meshes[postfilterIndex]; + + //builder.AllocateFixedString(ref blobRoot.name, meshNames[data.meshIndex]); + blobRoot.name = name; + + // Vertices + var verticesToSkin = (VertexToSkin*)builder.Allocate(ref blobRoot.verticesToSkin, mesh.vertexCount).GetUnsafePtr(); + context.vector3Cache.ResizeUninitialized(mesh.vertexCount); + mesh.GetVertices(context.vector3Cache); + var t = context.vector3Cache.AsArray().Reinterpret(); + for (int i = 0; i < mesh.vertexCount; i++) + { + verticesToSkin[i].position = t[i]; + } + mesh.GetNormals(context.vector3Cache); + for (int i = 0; i < mesh.vertexCount; i++) + { + verticesToSkin[i].normal = t[i]; + } + context.vector4Cache.ResizeUninitialized(mesh.vertexCount); + mesh.GetTangents(context.vector4Cache); + var tt = context.vector4Cache.AsArray().Reinterpret(); + for (int i = 0; i < mesh.vertexCount; i++) + { + verticesToSkin[i].tangent = tt[i].xyz; + } + + // BindPoses + builder.ConstructFromNativeArray(ref blobRoot.bindPoses, (float4x4*)bindPoses.Ptr, bindPoses.Length); + + // Weights + var maxRadialOffsets = builder.Allocate(ref blobRoot.maxRadialOffsetsInBoneSpaceByBone, bindPoses.Length); + for (int i = 0; i < maxRadialOffsets.Length; i++) + maxRadialOffsets[i] = 0; + + // The compute shader processes batches of 1024 vertices at a time. Before each batch, there's a special + // "bone weight" which serves as a header and holds an offset to the next header. + int boneWeightBatches = mesh.vertexCount / 1024; + if (mesh.vertexCount % 1024 != 0) + boneWeightBatches++; + var boneWeightStarts = builder.Allocate(ref blobRoot.boneWeightBatchStarts, boneWeightBatches); + + var boneWeightsDst = builder.Allocate(ref blobRoot.boneWeights, boneWeights.Length + boneWeightBatches); + + var weightStartsPerCache = stackalloc int[1024]; + int weightStartsBatchOffset = 0; + + int dst = 0; + for (int batchIndex = 0; batchIndex < boneWeightBatches; batchIndex++) + { + // Keep this, because we need to write to it after we know how many weights to jump over. + int batchHeaderIndex = dst; + dst++; + int verticesInBatch = math.min(1024, mesh.vertexCount - batchIndex * 1024); + int batchOffset = batchIndex * 1024; + int threadsAlive = verticesInBatch; + + int weightStartCounter = 0; + for (int i = 0; i < verticesInBatch; i++) + { + // Find the first bone weight for each vertex in the batch in the source bone weights. + weightStartsPerCache[i] = weightStartCounter; + weightStartCounter += boneWeightCountsPerVertex[batchOffset + i]; + } + + // We have as many rounds as weights in a batch of vertices. + // The number of rounds translates directly to inner loop iterations per batch on the GPU. + for (int weightRound = 1; threadsAlive > 0; weightRound++) + { + for (int i = 0; i < verticesInBatch; i++) + { + // If the number of weights for this vertex is less than the weightRound, + // this vertex has already finished. + int weightsForThisVertex = boneWeightCountsPerVertex[batchOffset + i]; + if (weightsForThisVertex < weightRound) + continue; + // If this is the last weight for this vertex, we'll set the weight negative + // to signal to the GPU it is the last weight. Packing signals into sign bits + // is free on most GPUs. + bool retireThisRound = weightsForThisVertex == weightRound; + // First, find the offset in the source weights related to this batch. + // Then, find the offset for this vertex. + // Then, add the weight round and convert from base-1 to base-0. + var srcWeight = boneWeights[weightStartsBatchOffset + weightStartsPerCache[i] + weightRound - 1]; + var dstWeight = new BoneWeightLinkedList + { + weight = math.select(srcWeight.weight, -srcWeight.weight, retireThisRound), + // There are up to 1024 vertices in a batch, but we only need the next offset when + // at least one vertex is active. So we map the range of [1, 1024] to [0, 1023]. + next10Lds7Bone15 = (((uint)threadsAlive - 1) << 22) | (uint)(srcWeight.boneIndex & 0x7fff) + }; + + boneWeightsDst[dst] = dstWeight; + dst++; + + // Compute how much the vertex deviates from the bone it is targeting. + // That deviation is applied to the maxRadialOffsets for that bone for culling. + float3 boneSpacePosition = math.transform(bindPoses[srcWeight.boneIndex], verticesToSkin[i].position); + maxRadialOffsets[srcWeight.boneIndex] = math.max(maxRadialOffsets[srcWeight.boneIndex], math.length(boneSpacePosition)); + + if (retireThisRound) + threadsAlive--; + } + } + + // When we were first finding bone weight offsets per vertex, we used this counter. + // It now holds the number of weights in the batch, so we add it here for the next batch. + weightStartsBatchOffset += weightStartCounter; + + // And now we write the header. We write mostly debug metadata into the weight. + // The counter is how many weights we need to jump over to reach the next header. + // We add one to account for the fact that the first vertex weight is after this + // header, effectively making it a base-1 array and the count indexing the last + // weight rather than the next header. + boneWeightsDst[batchHeaderIndex] = new BoneWeightLinkedList + { + weight = math.asfloat(0xbb000000 | (uint)batchIndex), + next10Lds7Bone15 = (uint)weightStartCounter + 1 + }; + boneWeightStarts[batchIndex] = (uint)batchHeaderIndex; + } + + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } + + public struct MeshSkinningContext : System.IDisposable + { + [ReadOnly] public Mesh.MeshDataArray meshes; + + [NativeDisableContainerSafetyRestriction] public NativeList vector3Cache; + [NativeDisableContainerSafetyRestriction] public NativeList vector4Cache; + + public void Dispose() => meshes.Dispose(); + } + + public struct MeshSkinningBakeData + { + public Mesh sharedMesh; + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshSkinningSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshSkinningSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..4b59217 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/MeshSkinningSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec4265ed4ed5cba4b9e8c596487f7157 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/ParameterClipSetSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/ParameterClipSetSmartBlobberSystem.cs new file mode 100644 index 0000000..0e0406c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/ParameterClipSetSmartBlobberSystem.cs @@ -0,0 +1,279 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + /// + /// Defines a general purpose parameter animation clip as well as its compression settings. + /// All values must be allocated with the conversion world's UpdateAllocator. + /// Kinemation Smart Blobbers only ever read this config, meaning it is safe to share an UnsafeList between multiple configs. + /// + public struct ParameterClipConfig + { + /// + /// An array of arrays of samples per parameter. + /// The first and last sample of each parameter must match for looping clips or else there will be a "jump". + /// + public UnsafeList > clipData; + /// + /// The error threshold allowed for each parameter relative to the input values. + /// If left defaulted, globalMaxError will be used instead. + /// + public UnsafeList maxErrorsByParameter; + /// + /// An array of events to be attached to the clip blob. The order will be sorted during blob creation. + /// + public UnsafeList events; + /// + /// If maxErrorsByParamemeter.IsCreated is false, this value will be used for all parameters. + /// + public float globalMaxError; + /// + /// The name of the clip. + /// + public FixedString128Bytes name; + /// + /// The number of samples per second. + /// + public float sampleRate; + /// + /// Higher levels lead to longer compression time but more compressed clips. + /// Values range from 0 to 4. Typical default is 2. + /// + public short compressionLevel; + + public static readonly short defaultCompressionLevel = 2; + public static readonly float defaultMaxError = 0.00001f; + + /// + /// Samples an AnimationCurve repeatedly and stores the samples as the samples for a specified parameter + /// + /// A destination buffer which must be preallocated with enough memory for all samples for all parameters + /// The index of the parameter to sample the curve for + /// The number of samples to use when sampling the curve. + /// This value should match for all parameters or else the result will be undefined. + /// The curve to be sampled + public static UnsafeList SampleCurve(int sampleCountPerParameter, AnimationCurve curve, ref RewindableAllocator worldUpdateAllocator) + { + var samples = new UnsafeList(sampleCountPerParameter, worldUpdateAllocator.ToAllocator); + samples.Resize(sampleCountPerParameter); + + // We subtract 1 because we need to capture the samples at t = 0 and t = 1 + float timeStep = math.rcp(sampleCountPerParameter - 1); + for (int i = 0; i < sampleCountPerParameter; i++) + { + samples[i] = curve.Evaluate(timeStep * i); + } + return samples; + } + } + + /// + /// Input for the ParameterClipSetBlob Smart Blobber. + /// All values must be allocated with the conversion world's UpdateAllocator. + /// Kinemation Smart Blobbers only ever read this bake data, meaning it is safe to share an UnsafeList between multiple bake data instances. + /// + public struct ParameterClipSetBakeData + { + /// + /// The list of clips and their compression settings which should be baked into the clip set. + /// + public UnsafeList clips; + /// + /// The list of parameter names which should be baked into the clip set. The length of this array + /// should either match the number of parameters in each and every clip or be left defaulted. + /// + public UnsafeList parameterNames; + } + + public static class ParameterClipBlobberAPIExtensions + { + /// + /// Requests creation of a ParameterClipSetBlob by a Smart Blobber. + /// This method must be called before the Smart Blobber is executed, such as during IRequestBlobAssets. + /// + /// + /// The Game Object to be converted that this blob should primarily be associated with. + /// It is usually okay if this isn't quite accurate, such as if the blob will be added to multiple entities. + /// + /// The inputs used to generate the blob asset + /// Returns a handle that can be resolved into a blob asset after the Smart Blobber has executed, such as during IConvertGameObjectToEntity + public static SmartBlobberHandle CreateBlob(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + ParameterClipSetBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvert(gameObject, bakeData); + } + + /// + /// Requests creation of a ParameterClipSetBlob by a Smart Blobber. + /// This method must be called before the Smart Blobber is executed, such as during IRequestBlobAssets. + /// + /// + /// The Game Object to be converted that this blob should primarily be associated with. + /// It is usually okay if this isn't quite accurate, such as if the blob will be added to multiple entities. + /// + /// The inputs used to generate the blob asset + /// Returns a handle that can be resolved into an untyped blob asset after the Smart Blobber has executed, such as during IConvertGameObjectToEntity + public static SmartBlobberHandleUntyped CreateBlobUntyped(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + ParameterClipSetBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvertUntyped(gameObject, bakeData); + } + } +} + +namespace Latios.Kinemation.Authoring.Systems +{ + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + [BurstCompile] + public class ParameterClipSetSmartBlobberSystem : SmartBlobberConversionSystem + { + protected override bool Filter(in ParameterClipSetBakeData input, GameObject gameObject, out ParameterClipSetConverter converter) + { + converter.bakeData = input; + + return FilterBursted(input, gameObject.name); + } + + [BurstCompile] + private static bool FilterBursted(in ParameterClipSetBakeData input, in FixedString128Bytes gameObjectName) + { + if (!input.clips.IsCreated) + return false; + + int parameterCount = -1; + foreach (var clip in input.clips) + { + if (!clip.clipData.IsCreated) + { + Debug.LogError($"Kinemation failed to convert parameter clip \"{clip.name}\" on GameObject {gameObjectName} because the clipData was unitialized."); + return false; + } + if (parameterCount < 0) + parameterCount = clip.clipData.Length; + else if (parameterCount != clip.clipData.Length) + { + Debug.LogError( + $"Kinemation failed to convert parameter clip \"{clip.name}\" on GameObject {gameObjectName} because the length of clipData ({clip.clipData.Length}) did not match previous clip parameter count of {parameterCount}."); + return false; + } + + var sampleCount = -1; + foreach (var samples in clip.clipData) + { + if (!samples.IsCreated) + { + Debug.LogError($"Kinemation failed to convert parameter clip \"{clip.name}\" on GameObject {gameObjectName} because some of its samples were unitialized."); + return false; + } + + if (sampleCount < 0) + sampleCount = samples.Length; + else if (sampleCount != samples.Length) + { + Debug.LogError( + $"Kinemation failed to convert parameter clip \"{clip.name}\" on GameObject {gameObjectName} because the number of samples is not the same for all parameters."); + return false; + } + } + + if (clip.maxErrorsByParameter.IsCreated) + { + if (clip.maxErrorsByParameter.Length != parameterCount) + { + Debug.LogError( + $"Kinemation failed to convert parameter clip \"{clip.name}\" on GameObject {gameObjectName} because the length of maxErrorsByParameter ({clip.maxErrorsByParameter.Length}) did not match the clip parameter count of {parameterCount}."); + return false; + } + } + } + + if (input.parameterNames.IsCreated) + { + if (input.parameterNames.Length != parameterCount) + { + Debug.LogError( + $"Kinemation failed to convert parameter clip set on GameObject {gameObjectName} because the length of parameterNames ({input.parameterNames.Length}) did not match the parameter count of {parameterCount}."); + return false; + } + } + + return true; + } + } + + public struct ParameterClipSetConverter : ISmartBlobberSimpleBuilder + { + public ParameterClipSetBakeData bakeData; + + public unsafe BlobAssetReference BuildBlob() + { + var builder = new BlobBuilder(Allocator.Temp); + ref var root = ref builder.ConstructRoot(); + + // Build names + if (bakeData.parameterNames.IsCreated) + { + var names = builder.Allocate(ref root.parameterNames, bakeData.parameterNames.Length); + var hashes = builder.Allocate(ref root.parameterNameHashes, bakeData.parameterNames.Length); + + for (int i = 0; i < bakeData.parameterNames.Length; i++) + { + names[i] = bakeData.parameterNames[i]; + hashes[i] = bakeData.parameterNames[i].GetHashCode(); + } + } + else + { + builder.Allocate(ref root.parameterNames, 0); + builder.Allocate(ref root.parameterNameHashes, 0); + } + + // Build clips + var dstClips = builder.Allocate(ref root.clips, bakeData.clips.Length); + for (int i = 0; i < bakeData.clips.Length; i++) + { + dstClips[i] = default; + dstClips[i].name = bakeData.clips[i].name; + dstClips[i].parameterCount = (short)bakeData.clips[i].clipData.Length; + dstClips[i].sampleRate = bakeData.clips[i].sampleRate; + dstClips[i].duration = math.rcp(bakeData.clips[i].sampleRate) * (bakeData.clips[i].clipData[0].Length - 1); + ClipEventsBlobHelpers.Convert(ref dstClips[i].events, ref builder, bakeData.clips[i].events); + + var srcClipDataPacked = new NativeArray(bakeData.clips[i].clipData.Length * bakeData.clips[i].clipData[0].Length, Allocator.Temp); + var srcClipErrors = new NativeArray(bakeData.clips[i].clipData.Length, Allocator.Temp); + + float* packedPtr = (float*)srcClipDataPacked.GetUnsafePtr(); + for (int j = 0; j < bakeData.clips[i].clipData.Length; i++) + { + UnsafeUtility.MemCpy(packedPtr, bakeData.clips[i].clipData[j].Ptr, bakeData.clips[i].clipData[j].Length); + packedPtr += bakeData.clips[i].clipData[j].Length; + + srcClipErrors[j] = bakeData.clips[i].maxErrorsByParameter.IsCreated ? bakeData.clips[i].maxErrorsByParameter[j] : bakeData.clips[i].globalMaxError; + } + + var compressedClip = AclUnity.Compression.CompressScalarsClip(srcClipDataPacked, srcClipErrors, bakeData.clips[i].sampleRate, bakeData.clips[i].compressionLevel); + var compressedData = builder.Allocate(ref dstClips[i].compressedClipDataAligned16, compressedClip.compressedDataToCopyFrom.Length, 16); + UnsafeUtility.MemCpy(compressedData.GetUnsafePtr(), compressedClip.compressedDataToCopyFrom.GetUnsafeReadOnlyPtr(), compressedClip.compressedDataToCopyFrom.Length); + compressedClip.Dispose(); + } + + root.parameterCount = dstClips[0].parameterCount; + + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/ParameterClipSetSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/ParameterClipSetSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..e08fae9 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/ParameterClipSetSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e28f476272c58c846b4455689f6fc1d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonClipSetSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonClipSetSmartBlobberSystem.cs new file mode 100644 index 0000000..a9c7c9b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonClipSetSmartBlobberSystem.cs @@ -0,0 +1,482 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; +using UnityEngine.Jobs; + +namespace Latios.Kinemation.Authoring +{ + /// + /// Defines a skeleton animation clip as well as its compression settings + /// + public struct SkeletonClipConfig + { + /// + /// The clip which should be played on the animator and baked into the blob + /// + public AnimationClip clip; + /// + /// The compression settings used for compressing the clip. + /// + public SkeletonClipCompressionSettings settings; + /// + /// An array of events to be attached to the clip blob. The order will be sorted during blob creation. + /// An array can be auto-generated by calling AnimationClip.ExtractKinemationClipEvents(). + /// + public ClipEvent[] events; + } + + /// + /// Input for the SkeletonClipSetBlob Smart Blobber + /// + public struct SkeletonClipSetBakeData + { + /// + /// The UnityEngine.Animator that should sample this clip. + /// The animator's GameObject must be a target for conversion. + /// The converted clip will only work correctly with that GameObject's converted skeleton entity + /// or another skeleton entity with an identical hierarchy. + /// + public Animator animator; + /// + /// The list of clips and their compression settings which should be baked into the clip set. + /// + public SkeletonClipConfig[] clips; + } + + /// + /// Compression settings for a skeleton animation clip. + /// + public struct SkeletonClipCompressionSettings + { + /// + /// Higher levels lead to longer compression time but more compressed clips. + /// Values range from 0 to 4. Typical default is 2. + /// + public short compressionLevel; + /// + /// The maximum distance a point sampled some distance away from the bone can + /// deviate from the original authored animation due to lossy compression. + /// Typical default is one ten-thousandth of a Unity unit. + /// + public float maxDistanceError; + /// + /// How far away from the bone points are sampled when evaluating distance error. + /// Defaults to 3% of a Unity unit. + /// + public float sampledErrorDistanceFromBone; + /// + /// The noise threshold for scale animation to be considered constant for a brief time range. + /// Defaults to 1 / 100_000. + /// + public float maxNegligibleTranslationDrift; + /// + /// The noise threshold for translation animation to be considered constant for a brief time range. + /// Defaults to 1 / 100_000. + /// + public float maxNegligibleScaleDrift; + + /// + /// Looping clips must have matching start and end poses. + /// If the source clip does not have this, setting this value to true can correct clip. + /// + public bool copyFirstKeyAtEnd; + + /// + /// Default animation clip compression settings. These provide relative fast compression, + /// decently small clip sizes, and typically acceptable accuracy. + /// (The accuracy is way higher than Unity's default animation compression) + /// + public static readonly SkeletonClipCompressionSettings kDefaultSettings = new SkeletonClipCompressionSettings + { + compressionLevel = 2, + maxDistanceError = 0.0001f, + sampledErrorDistanceFromBone = 0.03f, + maxNegligibleScaleDrift = 0.00001f, + maxNegligibleTranslationDrift = 0.00001f, + copyFirstKeyAtEnd = false + }; + } + + public static class SkeletonClipBlobberAPIExtensions + { + /// + /// Requests creation of a SkeletonClipSetBlob by a Smart Blobber. + /// This method must be called before the Smart Blobber is executed, such as during IRequestBlobAssets. + /// + /// + /// The Game Object to be converted that this blob should primarily be associated with. + /// It is usually okay if this isn't quite accurate, such as if the blob will be added to multiple entities. + /// + /// The inputs used to generate the blob asset + /// Returns a handle that can be resolved into a blob asset after the Smart Blobber has executed, such as during IConvertGameObjectToEntity + public static SmartBlobberHandle CreateBlob(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + SkeletonClipSetBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvert(gameObject, bakeData); + } + + /// + /// Requests creation of a SkeletonClipSetBlob by a Smart Blobber. + /// This method must be called before the Smart Blobber is executed, such as during IRequestBlobAssets. + /// + /// + /// The Game Object to be converted that this blob should primarily be associated with. + /// It is usually okay if this isn't quite accurate, such as if the blob will be added to multiple entities. + /// + /// The inputs used to generate the blob asset + /// Returns a handle that can be resolved into an untyped blob asset after the Smart Blobber has executed, such as during IConvertGameObjectToEntity + public static SmartBlobberHandleUntyped CreateBlobUntyped(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + SkeletonClipSetBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvertUntyped(gameObject, bakeData); + } + } +} + +namespace Latios.Kinemation.Authoring.Systems +{ + [ConverterVersion("Latios", 3)] + [DisableAutoCreation] + public class SkeletonClipSetSmartBlobberSystem : SmartBlobberConversionSystem + { + Dictionary animatorToContextLookup = new Dictionary(); + + protected override void GatherInputs() + { + animatorToContextLookup.Clear(); + Entities.ForEach((Animator animator, SkeletonConversionContext context) => animatorToContextLookup.Add(animator, context)); + } + + protected override bool Filter(in SkeletonClipSetBakeData input, GameObject gameObject, out SkeletonClipSetConverter converter) + { + converter = default; + + if (input.clips == null || input.animator == null) + return false; + foreach (var clip in input.clips) + { + if (clip.clip == null) + { + Debug.LogError($"Kinemation failed to convert clip set on animator {input.animator.gameObject.name} due to one fo the clips being null."); + return false; + } + DeclareAssetDependency(gameObject, clip.clip); + } + + if (!animatorToContextLookup.TryGetValue(input.animator, out SkeletonConversionContext context)) + { + Debug.LogError($"Kinemation failed to convert clip set on animator {input.animator.gameObject.name} because the passed-in animator is not marked for conversion."); + return false; + } + + // Todo: Need to fix this for squash and stretch on optimized hierarchies. + if (context.authoring != null && context.authoring.bindingMode != BindingMode.ConversionTime) + { + Debug.LogError( + $"Conversion of animation clips is not currently supported for a BindingMode other than ConversionTime. If you need this feature, let me know! Failed to convert clip set on animator {input.animator.gameObject.name}"); + return false; + } + + var shadowHierarchy = BuildHierarchyFromShadow(context); + if (!shadowHierarchy.isCreated) + { + // Assume error has already been logged. + return false; + } + + var allocator = World.UpdateAllocator.ToAllocator; + converter.parents = new UnsafeList(context.skeleton.Length, allocator); + converter.hasParentScaleInverses = new UnsafeList(context.skeleton.Length, allocator); + converter.parents.Resize(context.skeleton.Length); + converter.hasParentScaleInverses.Resize(context.skeleton.Length); + for (int i = 0; i < context.skeleton.Length; i++) + { + converter.parents[i] = context.skeleton[i].parentIndex; + converter.hasParentScaleInverses[i] = context.skeleton[i].ignoreParentScale; + } + + converter.clipsToConvert = new UnsafeList(input.clips.Length, allocator); + converter.clipsToConvert.Resize(input.clips.Length); + int targetClip = 0; + foreach (var clip in input.clips) + { + var clipEvents = new UnsafeList(clip.events?.Length ?? 0, allocator); + if (clip.events != null) + { + clipEvents.Resize(clip.events.Length); + for (int i = 0; i < clip.events.Length; i++) + { + clipEvents[i] = clip.events[i]; + } + } + + converter.clipsToConvert[targetClip] = new SkeletonClipSetConverter.SkeletonClipConversionData + { + clipName = clip.clip.name, + events = clipEvents, + sampleRate = clip.clip.frameRate, + settings = clip.settings, + sampledLocalTransforms = SampleClip(shadowHierarchy, clip.clip, allocator, clip.settings.copyFirstKeyAtEnd) + }; + targetClip++; + } + shadowHierarchy.Dispose(); + + return true; + } + + Queue m_breadthQueeue = new Queue(); + List<(Transform, HideThis.ShadowCloneTracker)> m_hierarchyCache = new List<(Transform, HideThis.ShadowCloneTracker)>(); + + // Todo: If a single bone doesn't match, this currently fails entirely. Is that the correct solution? + // The only failure case is if an optimized bone's path gets altered. + // In that case, it might be best to log a warning and assign the skeleton definition's transform + // to all samples for that bone. + unsafe TransformAccessArray BuildHierarchyFromShadow(SkeletonConversionContext context) + { + var boneCount = context.skeleton.Length; + var result = new TransformAccessArray(boneCount); + m_breadthQueeue.Clear(); + m_hierarchyCache.Clear(); + + var root = context.shadowHierarchy.transform; + m_breadthQueeue.Enqueue(root); + while (m_breadthQueeue.Count > 0) + { + var bone = m_breadthQueeue.Dequeue(); + var tracker = bone.GetComponent(); + m_hierarchyCache.Add((bone, tracker)); + + for (int i = 0; i < bone.childCount; i++) + { + var child = bone.GetChild(i); + if (child.GetComponent() == null) + m_breadthQueeue.Enqueue(bone.GetChild(i)); + } + } + + int boneIndex = 0; + foreach (var registeredBone in context.skeleton) + { + bool found = false; + + if (registeredBone.gameObjectTransform != null) + { + // The shadow hierarchy is an exact replica of the current hierarchy. + // And while the skeleton structure may have been modified, this reference + // persists through the current hierarchy at conversion time. + foreach ((var shadowBone, var tracker) in m_hierarchyCache) + { + if (tracker != null && tracker.source.transform == registeredBone.gameObjectTransform) + { + result.Add(shadowBone); + found = true; + break; + } + } + } + else + { + // The bone is optimized, meaning that its name in the shadow hierarchy + // should match the name in the skeleton definition and the ancestry up + // to the first exposed or exported bone. + FixedString4096Bytes registeredPath = registeredBone.hierarchyReversePath; + int ancestorsToTraverse = 0; + int currentParentIndex = registeredBone.parentIndex; + for (var t = registeredBone.gameObjectTransform; t == null; ancestorsToTraverse++) + { + t = context.skeleton[currentParentIndex].gameObjectTransform; + currentParentIndex = context.skeleton[currentParentIndex].parentIndex; + } + + foreach ((var shadowBone, var _) in m_hierarchyCache) + { + FixedString4096Bytes shadowPath = default; + var shadowTransform = shadowBone; + bool skip = false; + for (int i = 0; i < ancestorsToTraverse; i++) + { + shadowPath.Append(shadowTransform.gameObject.name); + shadowPath.Append('/'); + shadowTransform = shadowTransform.parent; + if (shadowTransform == null) + { + skip = true; + break; + } + } + if (!skip && UnsafeUtility.MemCmp(shadowPath.GetUnsafePtr(), registeredPath.GetUnsafePtr(), shadowPath.Length) == 0) + { + found = true; + result.Add(shadowBone); + break; + } + } + } + + if (!found) + { + // We didn't find a match. Log and return an uncreated array so that the filter can further error handle. + Debug.LogError( + $"Conversion of an animation clip failed for animator {context.animator.gameObject.name} because no matching bone data was found for bone index {boneIndex} with path: {registeredBone.hierarchyReversePath}\nPlease ensure the gameObjectTransform is a valid descendent, or in the case of an optimized out bone, that its hierarchyReversedPath is correct."); + result.Dispose(); + return default; + } + + boneIndex++; + } + + return result; + } + + unsafe UnsafeList SampleClip(TransformAccessArray shadowHierarchy, AnimationClip clip, Allocator allocator, bool copyFirstPose) + { + int requiredSamples = Mathf.CeilToInt(clip.frameRate * clip.length) + (copyFirstPose ? 1 : 0); + int requiredTransforms = requiredSamples * shadowHierarchy.length; + var result = new UnsafeList(requiredTransforms, allocator); + result.Resize(requiredTransforms); + + var oldWrapMode = clip.wrapMode; + clip.wrapMode = WrapMode.Clamp; + var root = shadowHierarchy[0].gameObject; + float timestep = math.rcp(clip.frameRate); + var job = new CaptureSampledBonesJob + { + boneTransforms = result, + samplesPerBone = requiredSamples, + currentSample = 0 + }; + + if (copyFirstPose) + requiredSamples--; + + for (int i = 0; i < requiredSamples; i++) + { + clip.SampleAnimation(root, timestep * i); + job.currentSample = i; + job.RunReadOnly(shadowHierarchy); + } + + if (copyFirstPose) + { + clip.SampleAnimation(root, 0f); + job.currentSample = requiredSamples; + job.RunReadOnly(shadowHierarchy); + } + + clip.wrapMode = oldWrapMode; + + return result; + } + + [BurstCompile] + struct CaptureSampledBonesJob : IJobParallelForTransform + { + // Todo: This throws not created error on job schedule. + public UnsafeList boneTransforms; + public int samplesPerBone; + public int currentSample; + + public void Execute(int index, TransformAccess transform) + { + int target = index * samplesPerBone + currentSample; + boneTransforms[target] = new BoneTransform(transform.localRotation, transform.localPosition, transform.localScale); + } + } + } + + public struct SkeletonClipSetConverter : ISmartBlobberSimpleBuilder + { + internal struct SkeletonClipConversionData + { + public UnsafeList sampledLocalTransforms; + public UnsafeList events; + public FixedString128Bytes clipName; + public SkeletonClipCompressionSettings settings; + public float sampleRate; + } + + internal UnsafeList clipsToConvert; + internal UnsafeList parents; + public UnsafeList hasParentScaleInverses; + + public unsafe BlobAssetReference BuildBlob() + { + var builder = new BlobBuilder(Allocator.Temp); + ref var root = ref builder.ConstructRoot(); + root.boneCount = (short)parents.Length; + var blobClips = builder.Allocate(ref root.clips, clipsToConvert.Length); + + // Step 1: Patch parent hierarchy for ACL + var parentIndices = new NativeArray(parents.Length, Allocator.Temp); + for (short i = 0; i < parents.Length; i++) + { + short index = (short)parents[i]; + if (index < 0) + index = i; + if (hasParentScaleInverses[i]) + index *= -1; + parentIndices[i] = index; + } + + int targetClip = 0; + foreach (var clip in clipsToConvert) + { + // Step 2: Convert settings + var aclSettings = new AclUnity.Compression.SkeletonCompressionSettings + { + compressionLevel = clip.settings.compressionLevel, + maxDistanceError = clip.settings.maxDistanceError, + maxNegligibleScaleDrift = clip.settings.maxNegligibleScaleDrift, + maxNegligibleTranslationDrift = clip.settings.maxNegligibleTranslationDrift, + sampledErrorDistanceFromBone = clip.settings.sampledErrorDistanceFromBone + }; + + // Step 3: Encode bone samples into QVV array + var qvvArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(clip.sampledLocalTransforms.Ptr, + clip.sampledLocalTransforms.Length, + Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var safety = AtomicSafetyHandle.Create(); + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref qvvArray, safety); +#endif + + // Step 4: Compress + var compressedClip = AclUnity.Compression.CompressSkeletonClip(parentIndices, qvvArray, clip.sampleRate, aclSettings); + + // Step 5: Build blob clip + blobClips[targetClip] = default; + blobClips[targetClip].name = clip.clipName; + blobClips[targetClip].duration = math.rcp(clip.sampleRate) * ((qvvArray.Length / parents.Length) - 1); + blobClips[targetClip].boneCount = root.boneCount; + blobClips[targetClip].sampleRate = clip.sampleRate; + ClipEventsBlobHelpers.Convert(ref blobClips[targetClip].events, ref builder, clip.events); + + var compressedData = builder.Allocate(ref blobClips[targetClip].compressedClipDataAligned16, compressedClip.compressedDataToCopyFrom.Length, 16); + UnsafeUtility.MemCpy(compressedData.GetUnsafePtr(), compressedClip.compressedDataToCopyFrom.GetUnsafeReadOnlyPtr(), compressedClip.compressedDataToCopyFrom.Length); + + // Step 6: Dispose ACL memory and safety + compressedClip.Dispose(); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.Release(safety); +#endif + + targetClip++; + } + + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonClipSetSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonClipSetSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..a536a44 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonClipSetSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15a8d79269555f44a8bfff3a361e6b7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonHierarchySmartBlobberSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonHierarchySmartBlobberSystem.cs new file mode 100644 index 0000000..af67b81 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonHierarchySmartBlobberSystem.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Authoring.Systems +{ + [ConverterVersion("Latios", 2)] + [DisableAutoCreation] + public class SkeletonHierarchySmartBlobberSystem : SmartBlobberConversionSystem + { + struct AuthoringHandlePair + { + public SkeletonConversionContext authoring; + public SmartBlobberHandle handle; + } + + List m_contextList = new List(); + + protected override void GatherInputs() + { + m_contextList.Clear(); + Entities.ForEach((SkeletonConversionContext context) => + { + if (context.isOptimized) + { + var handle = AddToConvert(context.animator.gameObject, new SkeletonHierarchyBakeData { bones = context.skeleton }); + m_contextList.Add(new AuthoringHandlePair { authoring = context, handle = handle }); + } + }); + } + + protected override void FinalizeOutputs() + { + foreach (var pair in m_contextList) + { + var context = pair.authoring; + var go = context.animator.gameObject; + var entity = GetPrimaryEntity(go); + DstEntityManager.AddComponentData(entity, new OptimizedSkeletonHierarchyBlobReference { blob = pair.handle.Resolve() }); + } + } + + protected override bool Filter(in SkeletonHierarchyBakeData input, UnityEngine.GameObject gameObject, out SkeletonHierarchyConverter converter) + { + converter.parents = new UnsafeList(input.bones.Length, World.UpdateAllocator.ToAllocator); + converter.hasParentScaleInverses = new UnsafeList(input.bones.Length, World.UpdateAllocator.ToAllocator); + converter.parents.Resize(input.bones.Length); + converter.hasParentScaleInverses.Resize(input.bones.Length); + for (int i = 0; i < input.bones.Length; i++) + { + converter.parents[i] = input.bones[i].parentIndex; + converter.hasParentScaleInverses[i] = input.bones[i].ignoreParentScale; + } + return true; + } + } + + public struct SkeletonHierarchyBakeData + { + public BoneTransformData[] bones; + } + + public struct SkeletonHierarchyConverter : ISmartBlobberSimpleBuilder + { + public UnsafeList parents; + public UnsafeList hasParentScaleInverses; + + public BlobAssetReference BuildBlob() + { + var builder = new BlobBuilder(Allocator.Temp); + + ref var root = ref builder.ConstructRoot(); + var indices = builder.Allocate(ref root.parentIndices, parents.Length); + var hasPSI = builder.Allocate(ref root.hasParentScaleInverseBitmask, (int)math.ceil(parents.Length / 64f)); // length is max 16 bits so this division is safe in float. + var hasChildPSI = builder.Allocate(ref root.hasChildWithParentScaleInverseBitmask, hasPSI.Length); + + root.hasAnyParentScaleInverseBone = false; + for (int i = 0; i < hasPSI.Length; i++) + { + hasPSI[i] = new BitField64(0UL); + hasChildPSI[i] = new BitField64(0UL); + } + for (int i = 0; i < parents.Length; i++) + { + indices[i] = (short)parents[i]; + + if (hasParentScaleInverses[i]) + { + int index = i / 64; + var field = hasPSI[index]; + field.SetBits(i % 64, true); + hasPSI[index] = field; + + var parentPSI = hasChildPSI[parents[i] / 64]; + parentPSI.SetBits(parents[i] % 64, true); + hasChildPSI[parents[i]] = parentPSI; + + root.hasAnyParentScaleInverseBone = true; + } + } + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonHierarchySmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonHierarchySmartBlobberSystem.cs.meta new file mode 100644 index 0000000..b34e1fc --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonHierarchySmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 864496946d244874db822f0961c3ca35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonPathsSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonPathsSmartBlobberSystem.cs new file mode 100644 index 0000000..198723b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonPathsSmartBlobberSystem.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Authoring.Systems +{ + [ConverterVersion("Latios", 1)] + [DisableAutoCreation] + public class SkeletonPathsSmartBlobberSystem : SmartBlobberConversionSystem + { + struct AuthoringHandlePair + { + public SkeletonConversionContext authoring; + public SmartBlobberHandle handle; + } + + List m_contextList = new List(); + + protected override void GatherInputs() + { + m_contextList.Clear(); + Entities.ForEach((SkeletonConversionContext context) => + { + var handle = AddToConvert(context.animator.gameObject, new SkeletonPathsBakeData { bones = context.skeleton }); + m_contextList.Add(new AuthoringHandlePair { authoring = context, handle = handle }); + }); + } + + protected override void FinalizeOutputs() + { + foreach (var pair in m_contextList) + { + var context = pair.authoring; + var go = context.animator.gameObject; + var entity = GetPrimaryEntity(go); + DstEntityManager.AddComponentData(entity, new SkeletonBindingPathsBlobReference { blob = pair.handle.Resolve() }); + } + } + + protected override unsafe bool Filter(in SkeletonPathsBakeData input, UnityEngine.GameObject gameObject, out SkeletonPathsConverter converter) + { + var allocator = World.UpdateAllocator.ToAllocator; + converter.paths = new UnsafeList >(input.bones.Length, allocator); + FixedString4096Bytes cache; + for (int i = 0; i < input.bones.Length; i++) + { + cache = input.bones[i].hierarchyReversePath; + var path = new UnsafeList(cache.Length, allocator); + path.AddRange(cache.GetUnsafePtr(), cache.Length); + converter.paths.Add(path); + } + return true; + } + } + + public struct SkeletonPathsBakeData + { + public BoneTransformData[] bones; + } + + public struct SkeletonPathsConverter : ISmartBlobberSimpleBuilder + { + public UnsafeList > paths; + + public unsafe BlobAssetReference BuildBlob() + { + var builder = new BlobBuilder(Allocator.Temp); + + ref var root = ref builder.ConstructRoot(); + var pathsOuter = builder.Allocate(ref root.pathsInReversedNotation, paths.Length); + for (int i = 0; i < paths.Length; i++) + { + builder.ConstructFromNativeArray(ref pathsOuter[i], paths[i].Ptr, paths[i].Length); + } + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonPathsSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonPathsSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..e3cc707 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ConversionSystems/SkeletonPathsSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a299448baa0c0644aa960ff274ddd52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/KinemationConversionBootstrap.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/KinemationConversionBootstrap.cs new file mode 100644 index 0000000..f283063 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/KinemationConversionBootstrap.cs @@ -0,0 +1,49 @@ +using Latios.Authoring; +using Latios.Kinemation.Authoring.Systems; +using Unity.Entities; +using Unity.Rendering; + +namespace Latios.Kinemation.Authoring +{ + /// + /// Static class containing installers for optional authoring time features in the Kinemation module + /// + public static class KinemationConversionBootstrap + { + /// + /// Adds Kinemation conversion systems into conversion world and disables the Hybrid Renderer's SkinnedMeshRenderer conversion + /// + /// The conversion world in which to install the Kinemation conversion systems + public static void InstallKinemationConversion(World world) + { + if (!world.Flags.HasFlag(WorldFlags.Conversion)) + throw new System.InvalidOperationException("Kinemation Conversion must be installed in a conversion world."); + + { + var builtinConversionSystem = world.GetExistingSystem(); + if (builtinConversionSystem != null) + builtinConversionSystem.Enabled = false; + } + + BootstrapTools.InjectSystem(typeof(DiscoverSkeletonsConversionSystem), world); + BootstrapTools.InjectSystem(typeof(DiscoverUnboundSkinnedMeshesConversionSystem), world); + BootstrapTools.InjectSystem(typeof(SkeletonPathsSmartBlobberSystem), world); + BootstrapTools.InjectSystem(typeof(SkeletonHierarchySmartBlobberSystem), world); + BootstrapTools.InjectSystem(typeof(MeshSkinningSmartBlobberSystem), world); + BootstrapTools.InjectSystem(typeof(MeshPathsSmartBlobberSystem), world); + BootstrapTools.InjectSystem(typeof(SkeletonClipSetSmartBlobberSystem), world); + BootstrapTools.InjectSystem(typeof(ParameterClipSetSmartBlobberSystem), world); + BootstrapTools.InjectSystem(typeof(KinemationCleanupConversionSystem), world); + var system = BootstrapTools.InjectSystem(typeof(AddMasksConversionSystem), world); + + var cs = system.systemManaged as GameObjectConversionSystem; + cs.GetSettings().OnPostCreateConversionWorldWrapper.OnPostCreateConversionWorld += (w, s) => + { + var builtinConversionSystem = world.GetExistingSystem(); + if (builtinConversionSystem != null) + builtinConversionSystem.Enabled = false; + }; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/KinemationConversionBootstrap.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/KinemationConversionBootstrap.cs.meta new file mode 100644 index 0000000..13398e4 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/KinemationConversionBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dbc9dc6e2cb352f499416d493e6b0326 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowCloneTracker.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowCloneTracker.cs new file mode 100644 index 0000000..0b5147d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowCloneTracker.cs @@ -0,0 +1,15 @@ +using System.Collections; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + [DisallowMultipleComponent] + class HideThis + { + public class ShadowCloneTracker : MonoBehaviour + { + public GameObject source; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowCloneTracker.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowCloneTracker.cs.meta new file mode 100644 index 0000000..08c4453 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowCloneTracker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9de29939cb570fc4988c12ed4fb1ce7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowHierarchyBuilder.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowHierarchyBuilder.cs new file mode 100644 index 0000000..780d217 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowHierarchyBuilder.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + internal static class ShadowHierarchyBuilder + { + public static GameObject BuildShadowHierarchy(GameObject source, bool sourceIsOptimized) + { + var shadow = GameObject.Instantiate(source); + shadow.transform.localPosition = Vector3.zero; + shadow.transform.localRotation = Quaternion.identity; + shadow.transform.localScale = Vector3.one; + + Recurse(source.transform, shadow.transform); + + if (sourceIsOptimized) + { + AnimatorUtility.DeoptimizeTransformHierarchy(shadow); + } + + return shadow; + } + + static void Recurse(Transform source, Transform shadow) + { + var tracker = shadow.gameObject.AddComponent(); + tracker.source = source.gameObject; + + var sourceChildCount = source.childCount; + if (sourceChildCount != shadow.childCount) + Debug.LogError("Instantiate did not preserve hierarchy. This is an internal bug between Kinemation and Unity. Please report!"); + + for (int i = 0; i < sourceChildCount; i++) + { + Recurse(source.GetChild(i), shadow.GetChild(i)); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowHierarchyBuilder.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowHierarchyBuilder.cs.meta new file mode 100644 index 0000000..4cb1b1b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/ShadowHierarchyBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e38e17175bc22a4a8dd6f66d18f80b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/SkeletonAuthoring.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkeletonAuthoring.cs new file mode 100644 index 0000000..622e2d2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkeletonAuthoring.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Kinemation/Skeleton (Animated Hierarchy)")] + public class SkeletonAuthoring : MonoBehaviour + { + public BindingMode bindingMode = BindingMode.ConversionTime; // Make this Import once supported + public List customSkeleton; + + // Public for debugging + public BoneTransformData[] m_importSkeleton; + public ImportStatus m_importStatus; + } + + public enum BindingMode + { + Import, + ConversionTime, + Custom + } + + public enum ImportStatus + { + Uninitialized, + Success, + UnknownError, + AmbiguityError + } + + [Serializable] + public struct BoneTransformData + { + public float3 localPosition; + public quaternion localRotation; + public float3 localScale; + public Transform gameObjectTransform; // Null if not exposed + public int parentIndex; // -1 if root, otherwise must be less than current index + public bool ignoreParentScale; + public string hierarchyReversePath; // Example: "foot.l/lower leg.l/upper leg.l/hips/armature/red soldier/" + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/SkeletonAuthoring.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkeletonAuthoring.cs.meta new file mode 100644 index 0000000..b6f7305 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkeletonAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a5d8ccc6583e954fb67f36a024bb3e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/SkinnedMeshSettingsAuthoring.cs b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkinnedMeshSettingsAuthoring.cs new file mode 100644 index 0000000..b0306ab --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkinnedMeshSettingsAuthoring.cs @@ -0,0 +1,19 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Latios.Kinemation.Authoring +{ + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Kinemation/Skinned Mesh Settings")] + public class SkinnedMeshSettingsAuthoring : MonoBehaviour + { + public BindingMode bindingMode = BindingMode.ConversionTime; // Make this Import once supported + + public List customBonePathsReversed; + + // Public for debugging + public string[] m_importBonePathsReversed; + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Authoring/SkinnedMeshSettingsAuthoring.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkinnedMeshSettingsAuthoring.cs.meta new file mode 100644 index 0000000..4b2e19a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Authoring/SkinnedMeshSettingsAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9dc2655909f083c44aabfb6a735cde0f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Components.meta b/Packages/com.latios.latios-framework/Kinemation/Components.meta new file mode 100644 index 0000000..37b3f34 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f3fc78b934658724294eebea8047c40d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/AnimationComponents.cs b/Packages/com.latios.latios-framework/Kinemation/Components/AnimationComponents.cs new file mode 100644 index 0000000..f70d440 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/AnimationComponents.cs @@ -0,0 +1,472 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Kinemation +{ + /// + /// A struct containing local-space translation, rotation, and scale. + /// + [StructLayout(LayoutKind.Explicit, Size = 48)] + public struct BoneTransform + { + [FieldOffset(0)] private quaternion m_rotation; + [FieldOffset(16)] private float4 m_translation; + [FieldOffset(32)] private float4 m_scale; + + public quaternion rotation { get => m_rotation; set => m_rotation = value; } + public float3 translation { get => m_translation.xyz; set => m_translation.xyz = value; } + public float3 scale { get => m_scale.xyz; set => m_scale.xyz = value; } + + public BoneTransform(quaternion rotation, float3 translation, float3 scale) + { + m_rotation = rotation; + m_translation = new float4(translation, 0f); + m_scale = new float4(scale, 1f); + } + + internal unsafe BoneTransform(AclUnity.Qvv qvv) + { + m_rotation = qvv.rotation; + m_translation = qvv.translation; + m_scale = qvv.scale; + } + } + + /// + /// The mechanism used to sample an animation when the time value lies between two keyframes. + /// Keyframes are evenly distributed based on the authored clip's sample rate. + /// + public enum KeyframeInterpolationMode : byte + { + Interpolate = 0, + Floor = 1, + Ceil = 2, + Nearest = 3 + } + + /// + /// Blob data containing a collection of skeleton animation clips + /// + public struct SkeletonClipSetBlob + { + public short boneCount; + public BlobArray clips; + } + + /// + /// Partial blob data containing a single skeleton animation clip + /// + public struct SkeletonClip + { + internal BlobArray compressedClipDataAligned16; + + /// + /// The duration of the clip in seconds + /// + public float duration; + + /// + /// The internal sample rate of the clip + /// + public float sampleRate; + + /// + /// The number of bones in the clip + /// + public short boneCount; + + /// + /// Events associated with the clip + /// + public ClipEvents events; + + /// + /// The name of the original authoring clip + /// + public FixedString128Bytes name; + + /// + /// Computes a wrapped time value from an unbounded time as if the clip looped infinitely + /// + /// The time value which may exceed the duration of the clip + /// A clip time between 0 and the clip's duration + /// If the clip is 5 seconds long, then a value of 7 would return 2, and a value of -2 would return 3. + public float LoopToClipTime(float time) + { + float wrappedTime = math.fmod(time, duration); + wrappedTime += math.select(0f, duration, wrappedTime < 0f); + return wrappedTime; + } + + /// + /// Samples the animation clip for the given bone index at the given time + /// + /// The bone index to sample. This value is automatically clamped to a valid value. + /// + /// The time value to sample the the clip in seconds. + /// This value is automatically clamped to a value between 0f and the clip's duration. + /// The mechanism used to sample a time value between two keyframes + /// A bone transform sampled from the clip in local space of the bone + public unsafe BoneTransform SampleBone(int boneIndex, float time, KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + + // Note: ACL clamps time, so we don't need to worry about it. + // ACLUnity also clamps the boneIndex, so we don't need to worry about that either. + var qvv = AclUnity.Decompression.SampleBone(compressedClipDataAligned16.GetUnsafePtr(), boneIndex, time, mode); + return new BoneTransform(qvv); + } + + public unsafe void SamplePose(ref BufferPoseBlender blender, + float blendWeight, + float time, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + CheckBlenderIsBigEnoughForClip(in blender, boneCount); + + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + + if (blender.sampledFirst) + AclUnity.Decompression.SamplePoseAosBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvv, blendWeight, time, mode); + else if (blendWeight == 1f) + { + AclUnity.Decompression.SamplePoseAos(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvv, time, mode); + blender.sampledFirst = true; + } + else + { + AclUnity.Decompression.SamplePoseAosBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvv, blendWeight, time, mode); + blender.sampledFirst = true; + } + } + + /// + /// Samples the animation clip for the entire skeleton at the given time and computes the entire OptimizedBoneToRoot buffer. + /// This method uses a special fast-path. + /// + /// The buffer to write the results to + /// The hierarchy info for the skeleton + /// The time value to sample the the clip in seconds. + /// This value is automatically clamped to a value between 0f and the clip's duration. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SamplePose(DynamicBuffer destination, + BlobAssetReference hierarchy, + float time, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + CheckBufferIsBigEnoughForClip(destination, boneCount); + CheckHierarchyIsBigEnoughForClip(hierarchy, boneCount); + + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + + var destinationAsArray = destination.Reinterpret().AsNativeArray(); + // Mask out the section of the array that we intend to mutate + if (destinationAsArray.Length > boneCount) + destinationAsArray = destinationAsArray.GetSubArray(0, boneCount); + var destinationAsFloat4 = destinationAsArray.Reinterpret(64); + // Qvv is 12 floats whereas a buffer element is 16. + // Therefore we stuff the data at the back three quarters of the buffer. + var destinationQvvSubArray = destinationAsFloat4.GetSubArray(destinationAsFloat4.Length - 3 * boneCount, 3 * boneCount); + var destinationAsQvv = destinationQvvSubArray.Reinterpret(16); + AclUnity.Decompression.SamplePoseAos(compressedClipDataAligned16.GetUnsafePtr(), destinationAsQvv, time, mode); + + if (!hierarchy.Value.hasAnyParentScaleInverseBone) + { + // Fast path. + destinationAsArray[0] = float4x4.identity; + + for (int i = 1; i < boneCount; i++) + { + var qvv = destinationAsQvv[i]; + var mat = float4x4.TRS(qvv.translation.xyz, qvv.rotation, qvv.scale.xyz); + Hint.Assume(hierarchy.Value.parentIndices[i] < i); + mat = math.mul(destinationAsArray[hierarchy.Value.parentIndices[i]], mat); + destinationAsArray[i] = mat; + } + } + else + { + // Slower path because we pack inverse scale into the fourth row of each matrix. + + // We need to explicitly check for parentScaleInverse for index 0. + if (hierarchy.Value.hasChildWithParentScaleInverseBitmask[0].IsSet(0)) + { + var inverseScale = math.rcp(destinationAsQvv[0].scale); + var mat = float4x4.identity; + mat.c0.w = inverseScale.x; + mat.c1.w = inverseScale.y; + mat.c2.w = inverseScale.z; + destinationAsArray[0] = mat; + } + + for (int i = 1; i < boneCount; i++) + { + var qvv = destinationAsQvv[i]; + Hint.Assume(hierarchy.Value.parentIndices[i] < i); + + var parentMat = destinationAsArray[hierarchy.Value.parentIndices[i]]; + bool hasParentScaleInverse = hierarchy.Value.hasParentScaleInverseBitmask[i / 64].IsSet(i % 64); + var psi = float4x4.Scale(math.select(1f, new float3(parentMat.c0.w, parentMat.c1.w, parentMat.c2.w), hasParentScaleInverse)); + parentMat.c0.w = 0f; + parentMat.c1.w = 0f; + parentMat.c2.w = 0f; + var mat = math.mul(float4x4.Translate(qvv.translation.xyz), psi); + mat = math.mul(mat, new float4x4(qvv.rotation, 0f)); + mat = math.mul(mat, float4x4.Scale(qvv.scale.xyz)); + mat = math.mul(parentMat, mat); + + bool needsInverseScale = hierarchy.Value.hasChildWithParentScaleInverseBitmask[i / 64].IsSet(i % 64); + var inverseScale = math.select(0f, math.rcp(qvv.scale), needsInverseScale); + mat.c0.w = inverseScale.x; + mat.c1.w = inverseScale.y; + mat.c2.w = inverseScale.z; + destinationAsArray[i] = mat; + } + + // Now we need to clean up the inverse scales. We wrote zeros where we didn't need them. + // So we can do a tzcnt walk. + for (int maskId = 0; maskId * 64 < boneCount; maskId++) + { + var mask = hierarchy.Value.hasChildWithParentScaleInverseBitmask[maskId]; + for (int i = mask.CountTrailingZeros(); i < 64 && maskId * 64 + i < boneCount; mask.SetBits(i, false), i = mask.CountTrailingZeros()) + { + var mat = destinationAsArray[maskId * 64 + i]; + mat.c0.w = 0f; + mat.c1.w = 0f; + mat.c2.w = 0f; + destinationAsArray[maskId * 64 + i] = mat; + } + } + } + } + + // Todo: SamplePose functions. + // The reason these aren't originally supported is that they need a buffer to write to. + // Since users will likely want to blend and stuff, that requires lots of temporary buffers + // which will overflow Allocator.Temp. + // The solution is a custom allocator that is rewindable per threadIndex. + // But this was out of scope for the initial release of 0.5. + + /// + /// The size the clip would be if uncompressed, ignoring padding bytes + /// + public int sizeUncompressed => ((int)math.round(sampleRate * duration) + 1) * 40; + /// + /// The size of the clip in its compressed form. + /// + public int sizeCompressed => compressedClipDataAligned16.Length; + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckBlenderIsBigEnoughForClip(in BufferPoseBlender blender, short boneCount) + { + if (blender.bufferAs4x4.Length < boneCount) + throw new ArgumentException("The blender does not contain enough elements to store the sampled pose."); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckBufferIsBigEnoughForClip(DynamicBuffer buffer, short boneCount) + { + if (buffer.Length < boneCount) + throw new ArgumentException("The dynamic buffer does not contain enough elements to store the sampled pose."); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckHierarchyIsBigEnoughForClip(BlobAssetReference hierarchy, short boneCount) + { + if (!hierarchy.IsCreated) + throw new ArgumentNullException("The hierarchy blob asset is null."); + + if (hierarchy.Value.parentIndices.Length < boneCount) + throw new ArgumentException("The hierarchy blob asset represents a skeleton with less bones than the clip."); + } + } + + /// + /// Blob data containing a collection of general purpose parameter animation clips + /// + public struct ParameterClipSetBlob + { + public short parameterCount; + public BlobArray clips; + /// + /// Equivalent to the FixedString64Bytes.GetHashCode() for each parameter name + /// + public BlobArray parameterNameHashes; + public BlobArray parameterNames; + } + + /// + /// Partial blob data containing a single clip for a collection of parameters + /// + public struct ParameterClip + { + internal BlobArray compressedClipDataAligned16; + + /// + /// The duration of the clip in seconds + /// + public float duration; + + /// + /// The internal sample rate of the clip + /// + public float sampleRate; + + /// + /// The number of parameters in the clip + /// + public short parameterCount; + + /// + /// Events associated with the clip + /// + public ClipEvents events; + + /// + /// The name of the original authoring clip + /// + public FixedString128Bytes name; + + /// + /// Computes a wrapped time value from an unbounded time as if the clip looped infinitely + /// + /// The time value which may exceed the duration of the clip + /// A clip time between 0 and the clip's duration + /// If the clip is 5 seconds long, then a value of 7 would return 2, and a value of -2 would return 3. + public float LoopToClipTime(float time) + { + float wrappedTime = math.fmod(time, duration); + wrappedTime += math.select(0f, duration, wrappedTime < 0f); + return wrappedTime; + } + + /// + /// Samples the animation clip for the given parameter index at the given time + /// + /// The parameter index to sample. This value is automatically clamped to a valid value. + /// + /// The time value to sample the the clip in seconds. + /// This value is automatically clamped to a value between 0f and the clip's duration. + /// The mechanism used to sample a time value between two keyframes + /// A parameter sampled from the clip in local space of the bone + public unsafe float SampleParameter(int parameterIndex, float time, KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + + // Note: ACL clamps time, so we don't need to worry about it. + // ACLUnity also clamps the parameterIndex, so we don't need to worry about that either. + return AclUnity.Decompression.SampleFloat(compressedClipDataAligned16.GetUnsafePtr(), parameterIndex, time, mode); + } + + public unsafe void SampleAllParameters(NativeArray destination, + float time, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + CheckBufferIsBigEnoughForClip(destination, parameterCount); + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + AclUnity.Decompression.SampleFloats(compressedClipDataAligned16.GetUnsafePtr(), destination, time, mode); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckBufferIsBigEnoughForClip(NativeArray buffer, short parameterCount) + { + if (buffer.Length < parameterCount) + throw new ArgumentException("The Native Array does not contain enough elements to store all the parameters."); + } + } + + /// + /// A time-sorted SOA of events where each event has a name and an extra int parameter + /// + public struct ClipEvents + { + public BlobArray times; + public BlobArray nameHashes; + public BlobArray parameters; + public BlobArray names; + + /// + /// Finds all events within the time range (previousTime, currentTime] and returns the first index and the count. + /// If currentTime is less than previousTime, if iterating i = firstEventIndex while i < firstEventIndex + count, + /// events should be indexed as [i % times.length] to account for looping behavior. + /// + /// The previous time to start searching for events. This time value is exclusive. + /// The current time to end searching for events. This time value is inclusive. + /// The index of the first event found. -1 if no events are found. + /// The number of events found + /// True if events were found, false otherwise + public bool TryGetEventsRange(float previousTime, float currentTime, out int firstEventIndex, out int eventCount) + { + if (previousTime == currentTime || times.Length == 0) + { + firstEventIndex = -1; + eventCount = 0; + return false; + } + + // Todo: Vectorize and optimize + firstEventIndex = -1; + for (int i = 0; i < times.Length; i++) + { + if (times[i] > previousTime) + { + firstEventIndex = i; + break; + } + } + int onePastLastEventIndex = -1; + for (int i = 0; i < times.Length; i++) + { + if (times[i] > currentTime) + { + onePastLastEventIndex = i; + break; + } + } + + if (previousTime < currentTime) + { + if (firstEventIndex == -1) + { + eventCount = 0; + return false; + } + + if (onePastLastEventIndex == -1) + { + // The time is beyond the last event. + eventCount = times.Length - firstEventIndex; + return true; + } + + eventCount = onePastLastEventIndex - firstEventIndex; + return true; + } + + // We wrapped around + if (onePastLastEventIndex <= 0 && firstEventIndex == -1) + { + // We start past the last event and the current time is before the first event + eventCount = 0; + return false; + } + + if (firstEventIndex == -1) + { + firstEventIndex = 0; + eventCount = onePastLastEventIndex; + return true; + } + + eventCount = times.Length - firstEventIndex + onePastLastEventIndex; + return true; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/AnimationComponents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Components/AnimationComponents.cs.meta new file mode 100644 index 0000000..47a3d7d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/AnimationComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8be84ed8ecde3e5408fa3425c77448d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/CullingComponents.cs b/Packages/com.latios.latios-framework/Kinemation/Components/CullingComponents.cs new file mode 100644 index 0000000..edbcd96 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/CullingComponents.cs @@ -0,0 +1,90 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine.Rendering; + +namespace Latios.Kinemation +{ + /// + /// Contains the visibility mask for the current camera culling pass + /// Usage: Read or Write + /// This is a chunk component and also a WriteGroup target. + /// To iterate these, you must include ChunkHeader in your query. + /// Every mesh entity has one of these as a chunk component, + /// with a max of 128 mesh instances per chunk (all of the same RenderMesh). + /// A true value for a bit will cause the mesh at that index to be rendered + /// by the current camera. This must happen inside the KinemationCullingSuperSystem. + /// + public struct ChunkPerCameraCullingMask : IComponentData + { + public BitField64 lower; + public BitField64 upper; + } + + /// + /// Contains the bitwise ORed visibility mask for all previous camera culling passes this frame + /// Usage: Read Only (No exceptions!) + /// You can read from this to figure out if a previous culling pass rendered an entity. + /// + [WriteGroup(typeof(ChunkPerCameraCullingMask))] + public struct ChunkPerFrameCullingMask : IComponentData + { + public BitField64 lower; + public BitField64 upper; + } + + /// + /// The culling planes of the camera for the current culling pass + /// Usage: Read Only (No exceptions!) + /// This lives on the worldBlackboardEntity and is set on the main thread for each camera. + /// For SIMD culling, use Kinemation.CullingUtilities and Unity.Rendering.FrustumPlanes. + /// + [InternalBufferCapacity(0)] + public struct CullingPlane : IBufferElementData + { + public UnityEngine.Plane plane; + } + + /// + /// Useful culling paramaters of the camera for the current culling pass + /// Usage: Read Only (No exceptions!) + /// This lives on the worldBlackboardEntity and is set on the main thread for each camera. + /// + public struct CullingContext : IComponentData + { + public LODParameters lodParameters; + public float4x4 cullingMatrix; + public float nearPlane; + public int cullIndexThisFrame; + } + + /// + /// Mask of components in a chunk which are dirty and need to be uploaded + /// if an entity in the chunk is rendered + /// Usage: Write only if necessary + /// Change filtering on material properties does not work during culling. + /// Instead, you can write a true to the material property index instead. + /// Be careful, because changing a property of an entity rendered by a + /// previous camera is a race condition. + /// + public struct ChunkMaterialPropertyDirtyMask : IComponentData + { + public BitField64 lower; + public BitField64 upper; + } + + /// + /// The types of components that the ChunkMaterialPropertyDirtyMask corresponds to. + /// Usage: Read Only (No exceptions!) + /// This lives on the worldBlackboardEntity and is set whenever the global list of instanced + /// material properties changes. Search through this buffer to find the correct bit to set in + /// the ChunkMaterialPropertyDirtyMask. + /// + [InternalBufferCapacity(0)] + public struct MaterialPropertyComponentType : IBufferElementData + { + public ComponentType type; + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/CullingComponents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Components/CullingComponents.cs.meta new file mode 100644 index 0000000..4efdd86 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/CullingComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c442e58cd07f7b48a54249c9c674274 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/InternalComponents.cs b/Packages/com.latios.latios-framework/Kinemation/Components/InternalComponents.cs new file mode 100644 index 0000000..74df707 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/InternalComponents.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Generic; +using Latios.Psyshock; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; +using UnityEngine.Rendering; + +namespace Latios.Kinemation +{ + // Meshes + internal struct MatrixPreviousCache : IComponentData + { + public float2x4 cachedFirstTwoRows; + } + + [WriteGroup(typeof(LocalToParent))] + internal struct SkeletonDependent : ISystemStateComponentData + { + public EntityWith root; + public BlobAssetReference skinningBlob; + public BlobAssetReference meshBindingBlob; + public BlobAssetReference skeletonBindingBlob; + public int meshEntryIndex; + public int boneOffsetEntryIndex; + public float shaderEffectRadialBounds; + } + + [MaterialProperty("_ComputeMeshIndex", MaterialPropertyFormat.Float)] + internal struct ComputeDeformShaderIndex : IComponentData + { + public uint firstVertexIndex; + } + + [WriteGroup(typeof(ChunkPerCameraCullingMask))] + internal struct ChunkComputeDeformMemoryMetadata : IComponentData + { + public int vertexStartPrefixSum; + public int verticesPerMesh; + public int entitiesInChunk; + } + + [MaterialProperty("_SkinMatrixIndex", MaterialPropertyFormat.Float)] + internal struct LinearBlendSkinningShaderIndex : IComponentData + { + public int firstMatrixIndex; + } + + [WriteGroup(typeof(ChunkPerCameraCullingMask))] + internal struct ChunkLinearBlendSkinningMemoryMetadata : IComponentData + { + public int bonesStartPrefixSum; + public int bonesPerMesh; + public int entitiesInChunk; + } + + [WriteGroup(typeof(ChunkPerCameraCullingMask))] + internal struct ChunkCopySkinShaderData : IComponentData + { + // Todo: Can chunk components be tags? + internal byte dummy; + } + + // All skeletons + // This is system state to prevent copies on instantiate + [InternalBufferCapacity(1)] + internal struct DependentSkinnedMesh : ISystemStateBufferElementData + { + public EntityWith skinnedMesh; + public int meshVerticesStart; + public int meshVerticesCount; + public int meshWeightsStart; + public int meshBindPosesStart; + public int meshBindPosesCount; + public int boneOffsetsStart; + } + + [MaximumChunkCapacity(128)] + internal struct PerFrameSkeletonBufferMetadata : IComponentData + { + public int bufferId; + public int startIndexInBuffer; + } + + // Exposed skeletons + internal struct ExposedSkeletonCullingIndex : ISystemStateComponentData + { + public int cullingIndex; + } + + internal struct BoneCullingIndex : IComponentData + { + public int cullingIndex; + } + + internal struct BoneBounds : IComponentData + { + public float radialOffsetInBoneSpace; + public float radialOffsetInWorldSpace; + } + + internal struct BoneWorldBounds : IComponentData + { + public Aabb bounds; + } + + internal struct ChunkBoneWorldBounds : IComponentData + { + public AABB chunkBounds; + } + + // Optimized skeletons + + // There's currently no other system state for optimized skeletons, so we need something + // to track conversions between skeleton types. + internal struct OptimizedSkeletonTag : ISystemStateComponentData { } + + internal struct SkeletonShaderBoundsOffset : IComponentData + { + public float radialBoundsInWorldSpace; + } + + internal struct SkeletonWorldBounds : IComponentData + { + public AABB bounds; + } + + // The length of this should be 0 when no meshes are bound. + [InternalBufferCapacity(0)] + internal struct OptimizedBoneBounds : IBufferElementData + { + public float radialOffsetInBoneSpace; + } + + internal struct ChunkSkeletonWorldBounds : IComponentData + { + public AABB chunkBounds; + } + + #region Blackboard + internal struct ExposedCullingIndexManagerTag : IComponentData { } + + internal struct ExposedCullingIndexManager : ICollectionComponent + { + public NativeParallelHashMap skeletonToCullingIndexMap; + public NativeReference maxIndex; + public NativeList indexFreeList; + public NativeParallelHashMap > cullingIndexToSkeletonMap; + + public Type AssociatedComponentType => typeof(ExposedCullingIndexManagerTag); + + public JobHandle Dispose(JobHandle inputDeps) + { + inputDeps = skeletonToCullingIndexMap.Dispose(inputDeps); + inputDeps = maxIndex.Dispose(inputDeps); + inputDeps = indexFreeList.Dispose(inputDeps); + inputDeps = cullingIndexToSkeletonMap.Dispose(inputDeps); + return inputDeps; + } + } + + internal struct MeshGpuManagerTag : IComponentData { } + + internal struct MeshGpuUploadCommand + { + public BlobAssetReference blob; + public int verticesIndex; + public int weightsIndex; + public int bindPosesIndex; + } + + internal struct MeshGpuEntry + { + public BlobAssetReference blob; + public int referenceCount; + public int verticesStart; + public int weightsStart; + public int bindPosesStart; + } + + internal struct MeshGpuRequiredSizes + { + public int requiredVertexBufferSize; + public int requiredWeightBufferSize; + public int requiredBindPoseBufferSize; + public int requiredVertexUploadSize; + public int requiredWeightUploadSize; + public int requiredBindPoseUploadSize; + } + + internal struct MeshGpuManager : ICollectionComponent + { + public NativeParallelHashMap, int> blobIndexMap; + + public NativeList entries; + public NativeList indexFreeList; + public NativeList verticesGaps; + public NativeList weightsGaps; + public NativeList bindPosesGaps; + + public NativeList uploadCommands; + public NativeReference requiredBufferSizes; + + public Type AssociatedComponentType => typeof(MeshGpuManagerTag); + + public JobHandle Dispose(JobHandle inputDeps) + { + inputDeps = blobIndexMap.Dispose(inputDeps); + inputDeps = entries.Dispose(inputDeps); + inputDeps = indexFreeList.Dispose(inputDeps); + inputDeps = verticesGaps.Dispose(inputDeps); + inputDeps = weightsGaps.Dispose(inputDeps); + inputDeps = bindPosesGaps.Dispose(inputDeps); + inputDeps = uploadCommands.Dispose(inputDeps); + inputDeps = requiredBufferSizes.Dispose(inputDeps); + return inputDeps; + } + } + + internal struct BoneOffsetsGpuManagerTag : IComponentData { } + + internal struct BoneOffsetsEntry + { + public uint2 hash; + public int pathsReferences; + public int overridesReferences; + public int start; + public short count; + public short gpuCount; + public bool isValid; + } + + internal struct PathMappingPair : IEquatable + { + public BlobAssetReference skeletonPaths; + public BlobAssetReference meshPaths; + + public bool Equals(PathMappingPair other) + { + return skeletonPaths.Equals(other.skeletonPaths) && meshPaths.Equals(other.meshPaths); + } + + public override int GetHashCode() + { + return new int2(skeletonPaths.GetHashCode(), meshPaths.GetHashCode()).GetHashCode(); + } + } + + internal struct BoneOffsetsGpuManager : ICollectionComponent + { + public NativeList entries; + public NativeList offsets; + public NativeList indexFreeList; + public NativeList gaps; + public NativeReference isDirty; + + public NativeParallelHashMap hashToEntryMap; + public NativeParallelHashMap pathPairToEntryMap; + + public Type AssociatedComponentType => typeof(BoneOffsetsGpuManagerTag); + + public JobHandle Dispose(JobHandle inputDeps) + { + inputDeps = entries.Dispose(inputDeps); + inputDeps = offsets.Dispose(inputDeps); + inputDeps = indexFreeList.Dispose(inputDeps); + inputDeps = gaps.Dispose(inputDeps); + inputDeps = isDirty.Dispose(inputDeps); + inputDeps = hashToEntryMap.Dispose(inputDeps); + inputDeps = pathPairToEntryMap.Dispose(inputDeps); + return inputDeps; + } + } + + internal struct GpuUploadBuffersTag : IComponentData { } + + internal struct GpuUploadBuffers : ICollectionComponent + { + // Not owned by this + public UnityEngine.ComputeBuffer verticesBuffer; + public UnityEngine.ComputeBuffer weightsBuffer; + public UnityEngine.ComputeBuffer bindPosesBuffer; + public UnityEngine.ComputeBuffer boneOffsetsBuffer; + public UnityEngine.ComputeBuffer verticesUploadBuffer; + public UnityEngine.ComputeBuffer weightsUploadBuffer; + public UnityEngine.ComputeBuffer bindPosesUploadBuffer; + public UnityEngine.ComputeBuffer boneOffsetsUploadBuffer; + public UnityEngine.ComputeBuffer verticesUploadMetaBuffer; + public UnityEngine.ComputeBuffer weightsUploadMetaBuffer; + public UnityEngine.ComputeBuffer bindPosesUploadMetaBuffer; + public UnityEngine.ComputeBuffer boneOffsetsUploadMetaBuffer; + public int verticesUploadBufferWriteCount; + public int weightsUploadBufferWriteCount; + public int bindPosesUploadBufferWriteCount; + public int boneOffsetsUploadBufferWriteCount; + public int verticesUploadMetaBufferWriteCount; + public int weightsUploadMetaBufferWriteCount; + public int bindPosesUploadMetaBufferWriteCount; + public int boneOffsetsUploadMetaBufferWriteCount; + public bool needsMeshCommitment; + public bool needsBoneOffsetCommitment; + + public Type AssociatedComponentType => typeof(GpuUploadBuffersTag); + + public JobHandle Dispose(JobHandle inputDeps) => inputDeps; + } + + internal struct ComputeBufferManagerTag : IComponentData { } + + internal struct ComputeBufferManager : ICollectionComponent + { + public ComputeBufferTrackingPool pool; + + public Type AssociatedComponentType => typeof(ComputeBufferManagerTag); + + public JobHandle Dispose(JobHandle inputDeps) + { + pool.Dispose(); + return inputDeps; + } + } + + internal struct BrgCullingContextTag : IComponentData { } + + internal unsafe struct BrgCullingContext : ICollectionComponent + { + public BatchCullingContext cullingContext; + public NativeArray internalToExternalMappingIds; + + public Type AssociatedComponentType => typeof(BrgCullingContextTag); + public JobHandle Dispose(JobHandle inputDeps) + { + // We don't own this data + return inputDeps; + } + } + + internal struct BoneMatricesPerFrameBuffersManagerTag : IComponentData { } + + internal struct BoneMatricesPerFrameBuffersManager : ICollectionComponent + { + public List boneMatricesBuffers; + + public Type AssociatedComponentType => typeof(BoneMatricesPerFrameBuffersManagerTag); + public JobHandle Dispose(JobHandle inputDeps) + { + // We don't own the buffers. + return inputDeps; + } + } + + internal struct MaxRequiredDeformVertices : IComponentData + { + public int verticesCount; + } + + internal struct MaxRequiredLinearBlendMatrices : IComponentData + { + public int matricesCount; + } + + internal struct MaterialPropertiesUploadContextTag : IComponentData { } + + internal struct MaterialPropertiesUploadContext : ICollectionComponent + { + public NativeList defaultValueBlits; + public int requiredPersistentBufferSize; + + public int hybridRenderedChunkCount; + public NativeArray chunkProperties; + public ComponentTypeCache componentTypeCache; + + public Type AssociatedComponentType => typeof(MaterialPropertiesUploadContextTag); + public JobHandle Dispose(JobHandle inputDeps) + { + // We don't own this data + return inputDeps; + } + } + + internal struct ExposedSkeletonBoundsArraysTag : IComponentData { } + + internal struct ExposedSkeletonBoundsArrays : ICollectionComponent + { + public NativeList allAabbs; + public NativeList batchedAabbs; + public const int kCountPerBatch = 32; // Todo: Is there a better size? + + public Type AssociatedComponentType => typeof(ExposedSkeletonBoundsArraysTag); + public JobHandle Dispose(JobHandle inputDeps) + { + inputDeps = allAabbs.Dispose(inputDeps); + return batchedAabbs.Dispose(inputDeps); + } + } + #endregion +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/InternalComponents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Components/InternalComponents.cs.meta new file mode 100644 index 0000000..0a65273 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/InternalComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 974ee0522b6882e48a0a534d97aa9b6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/MeshComponents.cs b/Packages/com.latios.latios-framework/Kinemation/Components/MeshComponents.cs new file mode 100644 index 0000000..64c26fb --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/MeshComponents.cs @@ -0,0 +1,138 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Kinemation +{ + /// + /// An optional flag which specifies when a skinned mesh needs to be rebound + /// Usage: Add/Write this component to true whenever binding needs to occur. + /// This will become an Enabled component tag in 1.0. + /// Binding must occur whenever the presence or value of any of the following changes: + /// - BindSkeletonRoot + /// - MeshSkinningBlobReference + /// - MeshBindingPathsBlobReference + /// - OverrideSkinningBoneIndex + /// - ShaderEffectRadialBounds + /// - LockToSkeletonRootTag + /// An initial attempt at binding will be made when the SkeletonMeshBindingReactiveSystem + /// first processes a mesh entity, even without this flag component. + /// However, if the flag component is present and set to false at this time, no binding + /// attempt will be made. + /// + public struct NeedsBindingFlag : IComponentData + { + public bool needsBinding; + } + + /// + /// The skeleton entity this skinned mesh should be bound to + /// Usage: Add/Write to attach a skinned mesh to a skeleton. + /// + [MaximumChunkCapacity(128)] + public struct BindSkeletonRoot : IComponentData + { + public EntityWith root; + } + + /// + /// The skinned mesh skinning data to be used for skinning on the GPU + /// Usage: Typically Read Only (Add/Write for procedural meshes) + /// After binding, this component can be safely removed. + /// + public struct MeshSkinningBlobReference : IComponentData + { + public BlobAssetReference blob; + } + + /// + /// Authored binding paths used to help a skinned mesh find the correct bones + /// in the skeleton to bind to + /// Usage: Typically Read Only (Add/Write for procedural meshes) + /// After binding, this component can be safely removed. + /// + public struct MeshBindingPathsBlobReference : IComponentData + { + public BlobAssetReference blob; + } + + /// + /// This buffer allows for manual binding of bone indices with the skeleton + /// Usage: Add/Write to create a custom mapping from mesh bone indices to + /// skeleton bone indices. The length of this buffer must match the length + /// of MeshSkinningBlob.bindPoses. + /// After binding, this buffer can be safely removed + /// + public struct OverrideSkinningBoneIndex : IBufferElementData + { + public short boneOffset; + } + + /// + /// An additional offset to the bounds to account for vertex shader effects + /// Usage: Add/Write to apply additional bounds inflation for a mesh + /// that modifies vertex positions in its shader (aside from skinning) + /// After binding, this buffer can be safely removed + /// + public struct ShaderEffectRadialBounds : IComponentData + { + public float radialBounds; + } + + /// + /// Instructs this entity to reuse skinning data from another entity with the + /// same mesh rather than perform its own skinning + /// Usage: Add to share a skin from another entity. + /// This is used when an authored Skinned Mesh is decomposed into multiple + /// RenderMesh instances with different submesh indices + /// + public struct ShareSkinFromEntity : IComponentData + { + public EntityWith sourceSkinnedEntity; + } + + /// + /// An entity that becomes the parent target for all skinned mesh entities + /// which failed to bind to their intended skeleton target + /// Usage: Query to get the entity where all children are failed bindings. + /// Currently the transform system (including framework variants) will + /// crash if a Parent has a Null entity value. + /// For performance reasons, all bound entities have Parent and LocalToParent + /// components added even if the bindings failed. So entities with failed + /// bindings are parented to a singleton entity with this tag instead. + /// + public struct FailedBindingsRootTag : IComponentData { } + + #region BlobData + public struct BoneWeightLinkedList + { + public float weight; + public uint next10Lds7Bone15; + } + + public struct VertexToSkin + { + public float3 position; + public float3 normal; + public float3 tangent; + } + + public struct MeshSkinningBlob + { + public BlobArray bindPoses; + public BlobArray maxRadialOffsetsInBoneSpaceByBone; + public BlobArray verticesToSkin; + public BlobArray boneWeights; + public BlobArray boneWeightBatchStarts; + public FixedString128Bytes name; + } + + public struct MeshBindingPathsBlob + { + // Todo: Make this a BlobArray once supported in Burst + public BlobArray > pathsInReversedNotation; + } + #endregion +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/MeshComponents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Components/MeshComponents.cs.meta new file mode 100644 index 0000000..97d627b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/MeshComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e16d56622235ecd4984c16e078843eb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/SkeletonComponents.cs b/Packages/com.latios.latios-framework/Kinemation/Components/SkeletonComponents.cs new file mode 100644 index 0000000..f762f9b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/SkeletonComponents.cs @@ -0,0 +1,212 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation +{ + #region All Skeletons + + /// + /// A tag attached to the skeleton entity + /// Usage: Required for a skeleton to be valid. + /// Add/Write for procedural skeletons. + /// + public struct SkeletonRootTag : IComponentData { } + + /// + /// A reference to the skeleton entity used by an exposed or exported bone + /// Usage: Typically Read Only (Add/Write for procedural skeletons) + /// This component is used internally to point mesh bindings to the skeleton + /// root and for when exported bones look up copy their transforms. + /// After conversion, it is not maintained internally. + /// If you make procedural changes to the skeleton, + /// you are responsible for maintaining this component. + /// + public struct BoneOwningSkeletonReference : IComponentData + { + public EntityWith skeletonRoot; + } + + /// + /// A mask which specifies if this skeleton is visible for the current + /// camera culling pass + /// Usage: Typically Read Only + /// + public struct ChunkPerCameraSkeletonCullingMask : IComponentData + { + public BitField64 lower; + public BitField64 upper; + } + + public struct SkeletonBindingPathsBlob + { + // Todo: Make this a BlobArray once supported in Burst + public BlobArray > pathsInReversedNotation; + + /// + /// Returns true if the path in reversed notation begins with the passed in string, case sensitive + /// + /// The index into pathsInReversedNotation, which is also a boneIndex + /// The string to search + /// Returns true if the string matches the start + public unsafe bool StartsWith(int pathIndex, in FixedString64Bytes searchString) + { + if (searchString.Length > pathsInReversedNotation[pathIndex].Length) + return false; + + return UnsafeUtility.MemCmp(searchString.GetUnsafePtr(), pathsInReversedNotation[pathIndex].GetUnsafePtr(), searchString.Length) == 0; + } + + /// + /// Searches through all paths to find one starting with the search string, case sensitive + /// + /// The string to search + /// The first path index that began with the search string. This index corresponds to a bone index. + /// Returns true if a match was found + public bool TryGetFirstPathIndexThatStartsWith(in FixedString64Bytes searchString, out int foundPathIndex) + { + for (foundPathIndex = 0; foundPathIndex < pathsInReversedNotation.Length; foundPathIndex++) + { + if (StartsWith(foundPathIndex, in searchString)) + return true; + } + foundPathIndex = -1; + return false; + } + } + + /// + /// Authored binding paths used to help a skinned mesh find the correct bones + /// in the skeleton to bind to + /// Usage: Typically Read Only (Add/Write for procedural meshes) + /// After all bindings are complete, this component can be safely removed. + /// + public struct SkeletonBindingPathsBlobReference : IComponentData + { + public BlobAssetReference blob; + } + + #endregion + #region Exposed skeleton + + /// + /// The bone index in the skeleton this bone corresponds to + /// Usage: Typically Read Only (Add/Write for procedural skeletons) + /// This component is added during conversion for user convenience + /// and is written to by SkeletonMeshBindingReactiveSystem but never + /// read internally. It can be used for sampling skeleton clips. + /// + public struct BoneIndex : IComponentData + { + public short index; + } + + /// + /// A buffer containing the bone entities in the exposed skeleton + /// Usage: Typically Read Only (Add/Write for procedural skeletons) + /// Lives on the Skeleton Root. All LocalToWorld values will be used as + /// bone matrices for skinning purposes. The first bone is the reference + /// space for deformations and should be the skeleton root entity. + /// If creating bones from scratch, you also should call + /// CullingUtilities.GetBoneCullingComponentTypes() and add to each bone + /// in this buffer. After the components have been added, you must set the + /// BoneReferenceIsDirtyFlag to true (you may need to add that component). + /// The bones will be synchronized with the skeleton during + /// SkeletonMeshBindingReactiveSystem. You do not need to set the flag + /// if the system has not processed the skeleton at least once yet. + /// + /// WARNING: If a bone with a BoneIndex or the culling components is added + /// to multiple BoneReference buffers, there will be a data race! + /// + [InternalBufferCapacity(0)] + public struct BoneReference : IBufferElementData + { + public EntityWith bone; + } + + /// + /// An optional flag component which specifies if the bone entities + /// need to be resynced with the BoneReference buffer + /// Usage: If a skeleton has this component and a value of true, + /// it will synchronize its skeleton with all the bones in the buffer, + /// populating the BoneIndex, removing old bones from culling, and + /// allowing new bones to report culling. + /// This happens during SkeletonMeshBindingReactiveSystem and is only + /// required if you modify the BoneReference buffer after that system + /// has ran on the skeleton entity once. + /// + public struct BoneReferenceIsDirtyFlag : IComponentData + { + public bool isDirty; + } + + #endregion + #region Optimized skeleton + /// + /// Blob asset containing authored hierarchical information about a skeleton + /// + public struct OptimizedSkeletonHierarchyBlob + { + /// + /// The index to each bone's parent, or -1 if the bone does not have a parent. + /// A parent is guaranteed to have a smaller index than its child bone. + /// A maximum of 32767 bones is supported. + /// + public BlobArray parentIndices; + /// + /// A bit array specifying if a bone expects ParentScaleInverse behavior to be applied. + /// This allows animators to achieve extreme and often cartoony expressions. + /// + public BlobArray hasParentScaleInverseBitmask; + /// + /// A bit array specifying if a bone needs to calculate an inverse scale because a + /// child requires it for ParentScaleInverse behavior. + /// + public BlobArray hasChildWithParentScaleInverseBitmask; + /// + /// If true, at least one bone expects ParentScaleInverse behavior to be applied. + /// Some fast-paths may be enabled when this value is false. + /// + public bool hasAnyParentScaleInverseBone; + } + + /// + /// The blob asset reference for an optimized skeleton describing its hierarchical structure + /// Usage: Typically Read Only (Add/Write for procedural skeletons) + /// + public struct OptimizedSkeletonHierarchyBlobReference : IComponentData + { + public BlobAssetReference blob; + } + + /// + /// The bone matrices of an optimized hierarchy which get copied to exported bones + /// and uploaded to the GPU for skinning + /// Usage: Read or Write for Animations + /// When animating an optimized hierarchy, you must write to this buffer. + /// The matrices are the bone transform relative to the root. Use the + /// OptimizedSkeletonHierarchyBlobReference to compute hierarchical transforms. + /// + [InternalBufferCapacity(0)] + public struct OptimizedBoneToRoot : IBufferElementData + { + public float4x4 boneToRoot; + } + + /// + /// Describes an exported bone entity which should inherit the transform of an + /// optimized bone. + /// Usage: Add to an entity to make it track a bone in an optimized skeleton. + /// The exported bone should be parented to the skeleton entity. + /// + [WriteGroup(typeof(LocalToParent))] + public struct CopyLocalToParentFromBone : IComponentData + { + public short boneIndex; + } + #endregion +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Components/SkeletonComponents.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Components/SkeletonComponents.cs.meta new file mode 100644 index 0000000..7e9afaa --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Components/SkeletonComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a38d757636f60f4d9b8b4d5406ed134 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Internal.meta b/Packages/com.latios.latios-framework/Kinemation/Internal.meta new file mode 100644 index 0000000..30fd49d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ab6aa8888f379bd4fb34215a79bc0dd8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Internal/ComputeBufferTrackingPool.cs b/Packages/com.latios.latios-framework/Kinemation/Internal/ComputeBufferTrackingPool.cs new file mode 100644 index 0000000..0bf1366 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Internal/ComputeBufferTrackingPool.cs @@ -0,0 +1,404 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Rendering; + +// Todo: A lot of this code is written naively. Is it's impact worth optimizing? +namespace Latios.Kinemation +{ + internal class ComputeBufferTrackingPool + { + PersistentPool m_lbsMatsBufferPool; + PersistentPool m_deformBufferPool; + PersistentPool m_meshVerticesPool; + PersistentPool m_meshWeightsPool; + PersistentPool m_meshBindPosesPool; + PersistentPool m_boneOffsetsPool; + + PerFramePool m_meshVerticesUploadPool; + PerFramePool m_meshWeightsUploadPool; + PerFramePool m_meshBindPosesUploadPool; + PerFramePool m_boneOffsetsUploadPool; + PerFramePool m_bonesPool; + PerFramePool m_skinningMetaPool; + PerFramePool m_uploadMetaPool; + + FencePool m_fencePool; + + ComputeShader m_copyVerticesShader; + ComputeShader m_copyMatricesShader; + //ComputeShader m_copyShortIndicesShader; + ComputeShader m_copyByteAddressShader; + + List m_destructionQueue; + + const int kMinMeshVerticesUploadSize = 16 * 1024; + const int kMinMeshWeightsUploadSize = 4 * 16 * 1024; + const int kMinMeshBindPosesUploadSize = 256; + const int kMinBoneOffsetsUploadSize = 128; + const int kMinBonesSize = 128 * 128; + const int kMinSkinningMetaSize = 128; + const int kMinUploadMetaSize = 128; + + public ComputeBufferTrackingPool() + { + m_destructionQueue = new List(); + m_copyVerticesShader = Resources.Load("CopyVertices"); + m_copyMatricesShader = Resources.Load("CopyMatrices"); + m_copyByteAddressShader = Resources.Load("CopyBytes"); + + m_lbsMatsBufferPool = new PersistentPool(1024, 3 * 4 * 4, ComputeBufferType.Structured, m_copyMatricesShader, m_destructionQueue); + m_deformBufferPool = new PersistentPool(256 * 1024, 3 * 3 * 4, ComputeBufferType.Structured, m_copyVerticesShader, m_destructionQueue); + m_meshVerticesPool = new PersistentPool(64 * 1024, 3 * 3 * 4, ComputeBufferType.Structured, m_copyVerticesShader, m_destructionQueue); + m_meshWeightsPool = new PersistentPool(2 * 4 * 64 * 1024, 4, ComputeBufferType.Raw, m_copyByteAddressShader, m_destructionQueue); + m_meshBindPosesPool = new PersistentPool(1024, 3 * 4 * 4, ComputeBufferType.Structured, m_copyMatricesShader, m_destructionQueue); + m_boneOffsetsPool = new PersistentPool(512, 4, ComputeBufferType.Raw, m_copyByteAddressShader, m_destructionQueue); + + m_meshVerticesUploadPool = new PerFramePool(3 * 3 * 4, ComputeBufferType.Structured); + m_meshWeightsUploadPool = new PerFramePool(4, ComputeBufferType.Raw); + m_meshBindPosesUploadPool = new PerFramePool(3 * 4 * 4, ComputeBufferType.Structured); + m_boneOffsetsUploadPool = new PerFramePool(4, ComputeBufferType.Raw); + m_bonesPool = new PerFramePool(3 * 4 * 4, ComputeBufferType.Structured); + m_skinningMetaPool = new PerFramePool(4, ComputeBufferType.Raw); + m_uploadMetaPool = new PerFramePool(4, ComputeBufferType.Raw); + + m_fencePool = new FencePool(true); + } + + public void Update() + { + m_fencePool.Update(); + m_meshVerticesUploadPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + m_meshWeightsUploadPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + m_meshBindPosesUploadPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + m_boneOffsetsUploadPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + m_bonesPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + m_skinningMetaPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + m_uploadMetaPool.CollectFinishedBuffers(m_fencePool.RecoveredFrameId); + + for (int i = 0; i < m_destructionQueue.Count; i++) + { + if (IsEqualOrNewer(m_fencePool.RecoveredFrameId, m_destructionQueue[i].frameId)) + { + m_destructionQueue[i].buffer.Dispose(); + m_destructionQueue.RemoveAtSwapBack(i); + i--; + } + } + } + + public void Dispose() + { + foreach (var b in m_destructionQueue) + b.buffer.Dispose(); + m_lbsMatsBufferPool.Dispose(); + m_deformBufferPool.Dispose(); + m_meshVerticesPool.Dispose(); + m_meshWeightsPool.Dispose(); + m_meshBindPosesPool.Dispose(); + m_meshVerticesUploadPool.Dispose(); + m_meshWeightsUploadPool.Dispose(); + m_meshBindPosesUploadPool.Dispose(); + m_boneOffsetsPool.Dispose(); + m_boneOffsetsUploadPool.Dispose(); + m_bonesPool.Dispose(); + m_skinningMetaPool.Dispose(); + m_uploadMetaPool.Dispose(); + m_fencePool.Dispose(); + } + + public ComputeBuffer GetLbsMatsBuffer(int requiredSize) + { + return m_lbsMatsBufferPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetDeformBuffer(int requiredSize) + { + return m_deformBufferPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetMeshVerticesBuffer(int requiredSize) + { + return m_meshVerticesPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetMeshWeightsBuffer(int requiredSize) + { + return m_meshWeightsPool.GetBuffer(requiredSize * 2, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetMeshBindPosesBuffer(int requiredSize) + { + return m_meshBindPosesPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetBoneOffsetsBuffer(int requiredSize) + { + return m_boneOffsetsPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetMeshVerticesUploadBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinMeshVerticesUploadSize); + return m_meshVerticesUploadPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetMeshWeightsUploadBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinMeshWeightsUploadSize); + return m_meshWeightsUploadPool.GetBuffer(requiredSize * 2, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetMeshBindPosesUploadBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinMeshBindPosesUploadSize); + return m_meshBindPosesUploadPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetBoneOffsetsUploadBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinBoneOffsetsUploadSize); + return m_boneOffsetsUploadPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetBonesBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinBonesSize); + return m_bonesPool.GetBuffer(requiredSize, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetSkinningMetaBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinSkinningMetaSize); + return m_skinningMetaPool.GetBuffer(requiredSize * 4, m_fencePool.CurrentFrameId); + } + + public ComputeBuffer GetUploadMetaBuffer(int requiredSize) + { + requiredSize = math.max(requiredSize, kMinUploadMetaSize); + return m_uploadMetaPool.GetBuffer(requiredSize * 3, m_fencePool.CurrentFrameId); + } + + struct BufferQueuedForDestruction + { + public ComputeBuffer buffer; + public uint frameId; + } + + struct PersistentPool : IDisposable + { + ComputeBuffer m_currentBuffer; + ComputeShader m_copyShader; + List m_destructionQueue; + int m_currentSize; + int m_stride; + ComputeBufferType m_type; + + public PersistentPool(int initialSize, int stride, ComputeBufferType bufferType, ComputeShader copyShader, List destructionQueue) + { + int size = math.ceilpow2(initialSize); + m_currentBuffer = new ComputeBuffer(size, stride, bufferType, ComputeBufferMode.Immutable); + m_copyShader = copyShader; + m_destructionQueue = destructionQueue; + m_currentSize = size; + m_stride = stride; + m_type = bufferType; + } + + public void Dispose() + { + m_currentBuffer.Dispose(); + } + + public ComputeBuffer GetBuffer(int requiredSize, uint frameId) + { + //UnityEngine.Debug.Log($"Requested Persistent Buffer of size: {requiredSize} while currentSize is: {m_currentSize}"); + if (requiredSize <= m_currentSize) + return m_currentBuffer; + + int size = math.ceilpow2(requiredSize); + if (requiredSize * m_stride > 1024 * 1024 * 1024) + Debug.LogWarning("Attempted to allocate a mesh deformation buffer over 1 GB. Rendering artifacts may occur."); + if (requiredSize * m_stride < 1024 * 1024 * 1024 && size * m_stride > 1024 * 1024 * 1024) + size = 1024 * 1024 * 1024 / m_stride; + var prevBuffer = m_currentBuffer; + m_currentBuffer = new ComputeBuffer(size, m_stride, m_type, ComputeBufferMode.Immutable); + if (m_copyShader != null) + { + m_copyShader.GetKernelThreadGroupSizes(0, out var threadGroupSize, out _, out _); + m_copyShader.SetBuffer(0, "_dst", m_currentBuffer); + m_copyShader.SetBuffer(0, "_src", prevBuffer); + int copySize = m_type == ComputeBufferType.Raw ? m_currentSize / 4 : m_currentSize; + for (int dispatchesRemaining = copySize / (int)threadGroupSize, start = 0; dispatchesRemaining > 0; ) + { + int dispatchCount = math.min(dispatchesRemaining, 65535); + m_copyShader.SetInt("_start", start * (int)threadGroupSize); + m_copyShader.Dispatch(0, dispatchCount, 1, 1); + dispatchesRemaining -= dispatchCount; + start += dispatchCount; + //UnityEngine.Debug.Log($"Dispatched buffer type: {m_type} with dispatchCount: {dispatchCount}"); + } + } + m_currentSize = size; + m_destructionQueue.Add(new BufferQueuedForDestruction { buffer = prevBuffer, frameId = frameId }); + return m_currentBuffer; + } + } + + struct PerFramePool : IDisposable + { + struct TrackedBuffer + { + public ComputeBuffer buffer; + public int size; + public uint frameId; + } + + int m_stride; + ComputeBufferType m_type; + List m_buffersInPool; + List m_buffersInFlight; + + public PerFramePool(int stride, ComputeBufferType bufferType) + { + m_stride = stride; + m_type = bufferType; + m_buffersInPool = new List(); + m_buffersInFlight = new List(); + } + + public ComputeBuffer GetBuffer(int requiredSize, uint frameId) + { + for (int i = 0; i < m_buffersInPool.Count; i++) + { + if (m_buffersInPool[i].size >= requiredSize) + { + var tracked = m_buffersInPool[i]; + tracked.frameId = frameId; + m_buffersInFlight.Add(tracked); + m_buffersInPool.RemoveAtSwapBack(i); + return tracked.buffer; + } + } + + if (m_buffersInPool.Count > 0) + { + m_buffersInPool[0].buffer.Dispose(); + m_buffersInPool.RemoveAtSwapBack(0); + } + + int size = math.ceilpow2(requiredSize); + var newTracked = new TrackedBuffer + { + buffer = new ComputeBuffer(size, m_stride, m_type, ComputeBufferMode.SubUpdates), + size = size, + frameId = frameId + }; + m_buffersInFlight.Add(newTracked); + return newTracked.buffer; + } + + public void CollectFinishedBuffers(uint finishedFrameId) + { + for (int i = 0; i < m_buffersInFlight.Count; i++) + { + var tracked = m_buffersInFlight[i]; + if (IsEqualOrNewer(finishedFrameId, tracked.frameId)) + { + m_buffersInPool.Add(tracked); + m_buffersInFlight.RemoveAtSwapBack(i); + i--; + } + } + } + + public void Dispose() + { + foreach (var buffer in m_buffersInPool) + buffer.buffer.Dispose(); + foreach (var buffer in m_buffersInFlight) + buffer.buffer.Dispose(); + } + } + + struct FencePool : IDisposable + { + struct TrackedFence + { + public ComputeBuffer buffer; + public uint frameId; + public AsyncGPUReadbackRequest request; + } + + uint m_currentFrameId; + uint m_recoveredFrameId; + + List m_fencesInFlight; + List m_fencesInPool; + + public uint CurrentFrameId => m_currentFrameId; + public uint RecoveredFrameId => m_recoveredFrameId; + + public FencePool(bool dummy) + { + m_fencesInFlight = new List(); + m_fencesInPool = new List(); + m_currentFrameId = 1; + m_recoveredFrameId = 0; + } + + public void Update() + { + for (int i = 0; i < m_fencesInFlight.Count; i++) + { + var fence = m_fencesInFlight[i]; + if (fence.request.done) + { + if (IsEqualOrNewer(fence.frameId, m_recoveredFrameId)) + m_recoveredFrameId = fence.frameId; + m_fencesInPool.Add(fence); + m_fencesInFlight.RemoveAtSwapBack(i); + i--; + } + } + + TrackedFence newFence; + if (m_fencesInPool.Count > 0) + { + newFence = m_fencesInPool[0]; + m_fencesInPool.RemoveAtSwapBack(0); + } + else + { + newFence = new TrackedFence + { + buffer = new ComputeBuffer(1, 4, ComputeBufferType.Default, ComputeBufferMode.Immutable), + }; + } + + newFence.frameId = m_currentFrameId; + newFence.request = AsyncGPUReadback.Request(newFence.buffer); + m_fencesInFlight.Add(newFence); + + m_currentFrameId++; + } + + public void Dispose() + { + foreach (var buffer in m_fencesInPool) + buffer.buffer.Dispose(); + foreach (var buffer in m_fencesInFlight) + buffer.buffer.Dispose(); + } + } + + static bool IsEqualOrNewer(uint potentiallyNewer, uint requiredVersion) + { + return ((int)(potentiallyNewer - requiredVersion)) >= 0; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Internal/ComputeBufferTrackingPool.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Internal/ComputeBufferTrackingPool.cs.meta new file mode 100644 index 0000000..43b6001 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Internal/ComputeBufferTrackingPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0b974d0fcc6f5a40a4eb54e6f41350e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility.meta b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility.meta new file mode 100644 index 0000000..45029d5 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 107dbdd0cbb07c646934bf6818a1bbb7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/InternalsAttribute.cs b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/InternalsAttribute.cs new file mode 100644 index 0000000..369a68c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/InternalsAttribute.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Latios.Kinemation")] + diff --git a/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/InternalsAttribute.cs.meta b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/InternalsAttribute.cs.meta new file mode 100644 index 0000000..768ddb3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/InternalsAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ef09c7ba2a7f4041949cf0d439106c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/Rendering.Hybrid.Internals.asmref b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/Rendering.Hybrid.Internals.asmref new file mode 100644 index 0000000..11eb1fc --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/Rendering.Hybrid.Internals.asmref @@ -0,0 +1,3 @@ +{ + "reference": "Unity.Rendering.Hybrid" +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/Rendering.Hybrid.Internals.asmref.meta b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/Rendering.Hybrid.Internals.asmref.meta new file mode 100644 index 0000000..d3cba34 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/InternalsVisibility/Rendering.Hybrid.Internals.asmref.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2889a4aa91797ad488f7ed645bb4887c +AssemblyDefinitionReferenceImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Latios.Kinemation.asmdef b/Packages/com.latios.latios-framework/Kinemation/Latios.Kinemation.asmdef new file mode 100644 index 0000000..19ca070 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Latios.Kinemation.asmdef @@ -0,0 +1,85 @@ +{ + "name": "Latios.Kinemation", + "rootNamespace": "Latios.Kinemation", + "references": [ + "Latios.Core", + "Latios.Psyshock", + "Unity.Burst", + "Unity.Collections", + "Unity.Deformations", + "Unity.Entities", + "Unity.Entities.Hybrid", + "Unity.Jobs", + "Unity.Mathematics", + "Unity.Rendering.Hybrid", + "Unity.Transforms", + "Unity.Mathematics.Extensions", + "Unity.Mathematics.Extensions.Hybrid", + "ACL_Unity" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.render-pipelines.high-definition", + "expression": "6.9.9", + "define": "HDRP_7_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.universal", + "expression": "6.9.9", + "define": "URP_7_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.core", + "expression": "6.9.9", + "define": "SRP_7_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.high-definition", + "expression": "8.9.9", + "define": "HDRP_9_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.universal", + "expression": "8.9.9", + "define": "URP_9_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.core", + "expression": "8.9.9", + "define": "SRP_9_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.high-definition", + "expression": "9.9.9", + "define": "HDRP_10_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.universal", + "expression": "9.9.9", + "define": "URP_10_0_0_OR_NEWER" + }, + { + "name": "com.unity.render-pipelines.core", + "expression": "9.9.9", + "define": "SRP_10_0_0_OR_NEWER" + }, + { + "name": "com.unity.tiny", + "expression": "0.21.9", + "define": "TINY_0_22_0_OR_NEWER" + }, + { + "name": "com.unity.moc.native", + "expression": "0.0.0", + "define": "UNITY_MOC_NATIVE_AVAILABLE" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Kinemation/Latios.Kinemation.asmdef.meta b/Packages/com.latios.latios-framework/Kinemation/Latios.Kinemation.asmdef.meta new file mode 100644 index 0000000..733a76d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Latios.Kinemation.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 11912cc675165314485c904ed61b2e92 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources.meta b/Packages/com.latios.latios-framework/Kinemation/Resources.meta new file mode 100644 index 0000000..f54dcf1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4d1dcda9885c7d34b97c4fbbe350df8e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning.compute new file mode 100644 index 0000000..c883980 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning.compute @@ -0,0 +1,341 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel BatchSkinningFullLds1024 + +// We disable this warning because it assumes that we only get 16 VGPRs per thread with a threadgroup size of 1024. +// Desktop and console support 32 per thread with good occupancy. This shader uses 26-28 on GCN. +#pragma warning(disable: 4714) + +struct BoneWeight +{ + float weight; + uint packed; +}; + +struct Vertex +{ + float3 position; + float3 normal; + float3 tangent; +}; + +//uniform StructuredBuffer _boneWeights; +uniform ByteAddressBuffer _boneWeights; +uniform StructuredBuffer _srcVertices; +uniform StructuredBuffer _bindPoses; +uniform StructuredBuffer _skeletonMats; +uniform ByteAddressBuffer _boneOffsets; + +// Stride of uint4 +uniform ByteAddressBuffer _metaBuffer; + +uniform RWStructuredBuffer _dstVertices : register(u1); +uniform RWStructuredBuffer _dstMats : register(u2); + +#define THREAD_GROUP_SIZE 1024 +#define THREAD_GROUP_DIVISOR 1024/THREAD_GROUP_SIZE + +groupshared float3x4 gs_skeletonMats[682]; + +uint _startOffset; + +float3x4 mul3x4(float3x4 a, float3x4 b) +{ + float4x4 x = 0.; + x._m00 = a._m00; + x._m10 = a._m10; + x._m20 = a._m20; + x._m30 = 0.; + x._m01 = a._m01; + x._m11 = a._m11; + x._m21 = a._m21; + x._m31 = 0.; + x._m02 = a._m02; + x._m12 = a._m12; + x._m22 = a._m22; + x._m32 = 0.; + x._m03 = a._m03; + x._m13 = a._m13; + x._m23 = a._m23; + x._m33 = 1.; + + float4x4 y = 0.; + y._m00 = b._m00; + y._m10 = b._m10; + y._m20 = b._m20; + y._m30 = 0.; + y._m01 = b._m01; + y._m11 = b._m11; + y._m21 = b._m21; + y._m31 = 0.; + y._m02 = b._m02; + y._m12 = b._m12; + y._m22 = b._m22; + y._m32 = 0.; + y._m03 = b._m03; + y._m13 = b._m13; + y._m23 = b._m23; + y._m33 = 1.; + + float4x4 r = mul(x, y); + + float3x4 result = 0.; + result._m00 = r._m00; + result._m10 = r._m10; + result._m20 = r._m20; + + result._m01 = r._m01; + result._m11 = r._m11; + result._m21 = r._m21; + + result._m02 = r._m02; + result._m12 = r._m12; + result._m22 = r._m22; + + result._m03 = r._m03; + result._m13 = r._m13; + result._m23 = r._m23; + + return result; +} + +[numthreads(THREAD_GROUP_SIZE, 1, 1)] +void BatchSkinningFullLds1024(uint threadId : SV_GroupIndex, uint3 groupIds : SV_GroupID) +{ + uint groupId = groupIds.x; + uint4 skeletonMeta = _metaBuffer.Load4((groupId + _startOffset) * 16 ); + + uint skeletonIndex = skeletonMeta.x; + uint skeletonCount = skeletonMeta.y; + + for (uint i = threadId; i < skeletonCount; i += THREAD_GROUP_SIZE) + { + gs_skeletonMats[i] = _skeletonMats[i + skeletonIndex]; + } + + GroupMemoryBarrierWithGroupSync(); + + const uint meshStart = skeletonMeta.z; + const uint meshCount = skeletonMeta.w; + + //float3x4 debug = 0; + //debug._m00 = 1; + //debug._m11 = 1; + //debug._m22 = 1; + + for (uint meshIndex = 0; meshIndex < meshCount; meshIndex++) + { + const uint4 meshMeta = _metaBuffer.Load4((meshStart + meshIndex * 2) * 16); + const uint meshOperation = meshMeta.x & 0xffff; + const uint meshBonesCount = meshMeta.x >> 16; + const uint meshBindposesStart = meshMeta.y; + const uint meshBoneOffsetsStart = meshMeta.z; + const uint matsDst = meshMeta.w; + + const uint4 meshMeta2 = _metaBuffer.Load4((meshStart + meshIndex * 2) * 16 + 16); + const uint meshVertexStart = meshMeta2.x; + const uint meshVertexCount = meshMeta2.y; + const uint meshWeightsStart = meshMeta2.z; + const uint meshDst = meshMeta2.w; + + GroupMemoryBarrierWithGroupSync(); + for (uint i = threadId; i < meshBonesCount; i += THREAD_GROUP_SIZE) + { + uint boneOffset = _boneOffsets.Load(((meshBoneOffsetsStart + i) * 2) & 0xfffffffc); + if ((i & 0x1) != 0) + boneOffset = boneOffset >> 16; + boneOffset = boneOffset & 0x7fff; + + gs_skeletonMats[i + skeletonCount] = mul3x4(gs_skeletonMats[boneOffset], _bindPoses[meshBindposesStart + i]); + } + GroupMemoryBarrierWithGroupSync(); + + // Operation 0 - Copy to LBS matrices + if (meshOperation & 0x1) + { + for (uint i = threadId; i < meshBonesCount; i += THREAD_GROUP_SIZE) + { + _dstMats[matsDst + i] = gs_skeletonMats[i + skeletonCount]; + } + } + + // Operation 1 - Compute skinning from source mesh + if (meshOperation & 0x2) + { + uint currentWeightBatchStart = meshWeightsStart; + uint nextWeightBatchStart = meshWeightsStart; + + uint vertexIndexBase = 0; + for (vertexIndexBase = 0; vertexIndexBase + 1023 < meshVertexCount; vertexIndexBase += 1024) + { + nextWeightBatchStart += _boneWeights.Load(currentWeightBatchStart * 8 + 4); + currentWeightBatchStart++; + + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _srcVertices[meshVertexStart + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + //_dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = vertex; + } + + currentWeightBatchStart = nextWeightBatchStart; + } + + currentWeightBatchStart++; + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + if (vertexIndexBase + threadId < meshVertexCount) + { + + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _srcVertices[meshVertexStart + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + //_dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = vertex; + } + } + } + + // Operation 2 - Compute skinning from dst mesh + if (meshOperation & 0x4) + { + uint currentWeightBatchStart = meshWeightsStart; + uint nextWeightBatchStart = meshWeightsStart; + + uint vertexIndexBase = 0; + for (vertexIndexBase = 0; vertexIndexBase + 1023 < meshVertexCount; vertexIndexBase += 1024) + { + nextWeightBatchStart += _boneWeights.Load(currentWeightBatchStart * 8 + 4); + currentWeightBatchStart++; + + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + } + + currentWeightBatchStart = nextWeightBatchStart; + } + + currentWeightBatchStart++; + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + if (vertexIndexBase + threadId < meshVertexCount) + { + + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + } + } + } + } +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning.compute.meta new file mode 100644 index 0000000..87089f0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: cefbcaee98964124d8bc2519bd2e8864 +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning512.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning512.compute new file mode 100644 index 0000000..adaeb7d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning512.compute @@ -0,0 +1,341 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel BatchSkinningFullLds512 + +// We disable this warning because it assumes that we only get 16 VGPRs per thread with a threadgroup size of 1024. +// Desktop and console support 32 per thread with good occupancy. This shader uses 26-28 on GCN. +#pragma warning(disable: 4714) + +struct BoneWeight +{ + float weight; + uint packed; +}; + +struct Vertex +{ + float3 position; + float3 normal; + float3 tangent; +}; + +//uniform StructuredBuffer _boneWeights; +uniform ByteAddressBuffer _boneWeights; +uniform StructuredBuffer _srcVertices; +uniform StructuredBuffer _bindPoses; +uniform StructuredBuffer _skeletonMats; +uniform ByteAddressBuffer _boneOffsets; + +// Stride of uint4 +uniform ByteAddressBuffer _metaBuffer; + +uniform RWStructuredBuffer _dstVertices : register(u1); +uniform RWStructuredBuffer _dstMats : register(u2); + +#define THREAD_GROUP_SIZE 512 +#define THREAD_GROUP_DIVISOR 1024/THREAD_GROUP_SIZE + +groupshared float3x4 gs_skeletonMats[682]; + +uint _startOffset; + +float3x4 mul3x4(float3x4 a, float3x4 b) +{ + float4x4 x = 0.; + x._m00 = a._m00; + x._m10 = a._m10; + x._m20 = a._m20; + x._m30 = 0.; + x._m01 = a._m01; + x._m11 = a._m11; + x._m21 = a._m21; + x._m31 = 0.; + x._m02 = a._m02; + x._m12 = a._m12; + x._m22 = a._m22; + x._m32 = 0.; + x._m03 = a._m03; + x._m13 = a._m13; + x._m23 = a._m23; + x._m33 = 1.; + + float4x4 y = 0.; + y._m00 = b._m00; + y._m10 = b._m10; + y._m20 = b._m20; + y._m30 = 0.; + y._m01 = b._m01; + y._m11 = b._m11; + y._m21 = b._m21; + y._m31 = 0.; + y._m02 = b._m02; + y._m12 = b._m12; + y._m22 = b._m22; + y._m32 = 0.; + y._m03 = b._m03; + y._m13 = b._m13; + y._m23 = b._m23; + y._m33 = 1.; + + float4x4 r = mul(x, y); + + float3x4 result = 0.; + result._m00 = r._m00; + result._m10 = r._m10; + result._m20 = r._m20; + + result._m01 = r._m01; + result._m11 = r._m11; + result._m21 = r._m21; + + result._m02 = r._m02; + result._m12 = r._m12; + result._m22 = r._m22; + + result._m03 = r._m03; + result._m13 = r._m13; + result._m23 = r._m23; + + return result; +} + +[numthreads(THREAD_GROUP_SIZE, 1, 1)] +void BatchSkinningFullLds512(uint threadId : SV_GroupIndex, uint3 groupIds : SV_GroupID) +{ + uint groupId = groupIds.x; + uint4 skeletonMeta = _metaBuffer.Load4((groupId + _startOffset) * 16 ); + + uint skeletonIndex = skeletonMeta.x; + uint skeletonCount = skeletonMeta.y; + + for (uint i = threadId; i < skeletonCount; i += THREAD_GROUP_SIZE) + { + gs_skeletonMats[i] = _skeletonMats[i + skeletonIndex]; + } + + GroupMemoryBarrierWithGroupSync(); + + const uint meshStart = skeletonMeta.z; + const uint meshCount = skeletonMeta.w; + + //float3x4 debug = 0; + //debug._m00 = 1; + //debug._m11 = 1; + //debug._m22 = 1; + + for (uint meshIndex = 0; meshIndex < meshCount; meshIndex++) + { + const uint4 meshMeta = _metaBuffer.Load4((meshStart + meshIndex * 2) * 16); + const uint meshOperation = meshMeta.x & 0xffff; + const uint meshBonesCount = meshMeta.x >> 16; + const uint meshBindposesStart = meshMeta.y; + const uint meshBoneOffsetsStart = meshMeta.z; + const uint matsDst = meshMeta.w; + + const uint4 meshMeta2 = _metaBuffer.Load4((meshStart + meshIndex * 2) * 16 + 16); + const uint meshVertexStart = meshMeta2.x; + const uint meshVertexCount = meshMeta2.y; + const uint meshWeightsStart = meshMeta2.z; + const uint meshDst = meshMeta2.w; + + GroupMemoryBarrierWithGroupSync(); + for (uint i = threadId; i < meshBonesCount; i += THREAD_GROUP_SIZE) + { + uint boneOffset = _boneOffsets.Load(((meshBoneOffsetsStart + i) * 2) & 0xfffffffc); + if ((i & 0x1) != 0) + boneOffset = boneOffset >> 16; + boneOffset = boneOffset & 0x7fff; + + gs_skeletonMats[i + skeletonCount] = mul3x4(gs_skeletonMats[boneOffset], _bindPoses[meshBindposesStart + i]); + } + GroupMemoryBarrierWithGroupSync(); + + // Operation 0 - Copy to LBS matrices + if (meshOperation & 0x1) + { + for (uint i = threadId; i < meshBonesCount; i += THREAD_GROUP_SIZE) + { + _dstMats[matsDst + i] = gs_skeletonMats[i + skeletonCount]; + } + } + + // Operation 1 - Compute skinning from source mesh + if (meshOperation & 0x2) + { + uint currentWeightBatchStart = meshWeightsStart; + uint nextWeightBatchStart = meshWeightsStart; + + uint vertexIndexBase = 0; + for (vertexIndexBase = 0; vertexIndexBase + 1023 < meshVertexCount; vertexIndexBase += 1024) + { + nextWeightBatchStart += _boneWeights.Load(currentWeightBatchStart * 8 + 4); + currentWeightBatchStart++; + + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _srcVertices[meshVertexStart + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + //_dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = vertex; + } + + currentWeightBatchStart = nextWeightBatchStart; + } + + currentWeightBatchStart++; + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + if (vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE < meshVertexCount) + { + + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _srcVertices[meshVertexStart + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + //_dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = vertex; + } + } + } + + // Operation 2 - Compute skinning from dst mesh + if (meshOperation & 0x4) + { + uint currentWeightBatchStart = meshWeightsStart; + uint nextWeightBatchStart = meshWeightsStart; + + uint vertexIndexBase = 0; + for (vertexIndexBase = 0; vertexIndexBase + 1023 < meshVertexCount; vertexIndexBase += 1024) + { + nextWeightBatchStart += _boneWeights.Load(currentWeightBatchStart * 8 + 4); + currentWeightBatchStart++; + + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + } + + currentWeightBatchStart = nextWeightBatchStart; + } + + currentWeightBatchStart++; + [unroll] + for (uint inner = 0; inner < THREAD_GROUP_DIVISOR; inner++) + { + if (vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE < meshVertexCount) + { + + uint nextWeightIndex = currentWeightBatchStart + threadId + inner * THREAD_GROUP_SIZE; + bool isEnd = false; + float3x4 deform = 0; + + do + { + uint2 temp = _boneWeights.Load2(nextWeightIndex * 8); + BoneWeight boneWeight = (BoneWeight)0; + boneWeight.weight = asfloat(temp.x); + boneWeight.packed = temp.y; + float weight = boneWeight.weight; + isEnd = weight < 0.; + weight = abs(weight); + uint boneIndex = boneWeight.packed & 0x7fff; + nextWeightIndex += boneWeight.packed >> 22; + nextWeightIndex++; + + float3x4 skinMatrix = gs_skeletonMats[boneIndex + skeletonCount]; + //float3x4 skinMatrix = _skeletonMats[boneIndex + skeletonIndex]; + deform += skinMatrix * weight; + } while (!isEnd); + + Vertex dstVertex = (Vertex)0; + Vertex vertex = _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE]; + dstVertex.position = mul(deform, float4(vertex.position, 1)); + dstVertex.normal = mul(deform, float4(vertex.normal, 0)); + dstVertex.tangent = mul(deform, float4(vertex.tangent, 0)); + + _dstVertices[meshDst + vertexIndexBase + threadId + inner * THREAD_GROUP_SIZE] = dstVertex; + } + } + } + } +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning512.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning512.compute.meta new file mode 100644 index 0000000..aa10b20 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/BatchSkinning512.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ff8c4f04ca513f7468e7aaee54e286e3 +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/CopyBytes.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyBytes.compute new file mode 100644 index 0000000..9c99fb3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyBytes.compute @@ -0,0 +1,16 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel CopyBytes + +uniform ByteAddressBuffer _src; +uniform RWByteAddressBuffer _dst; + +int _start; + +// Todo: What is the nominal value per platform? +[numthreads(64, 1, 1)] +void CopyBytes(uint3 id : SV_DispatchThreadID) +{ + int index = _start + id.x; + uint4 data = _src.Load4(index); + _dst.Store4(index, data); +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/CopyBytes.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyBytes.compute.meta new file mode 100644 index 0000000..bea06c4 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyBytes.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 24ccb6e04a768df489d280708c85c3c4 +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/CopyMatrices.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyMatrices.compute new file mode 100644 index 0000000..e88e5bc --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyMatrices.compute @@ -0,0 +1,15 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel CopyMatrices + +uniform StructuredBuffer _src; +uniform RWStructuredBuffer _dst; + +int _start; + +// Todo: What is the nominal value per platform? +[numthreads(64, 1, 1)] +void CopyMatrices(uint3 id : SV_DispatchThreadID) +{ + int index = _start + id.x; + _dst[index] = _src[index]; +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/CopyMatrices.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyMatrices.compute.meta new file mode 100644 index 0000000..06a71fb --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyMatrices.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f9ebecc9f7ab061458359162920706b9 +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/CopyVertices.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyVertices.compute new file mode 100644 index 0000000..79ab83b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyVertices.compute @@ -0,0 +1,21 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel CopyVertices + +struct Vertex +{ + float3 position; + float3 normal; + float3 tangent; +}; +uniform StructuredBuffer _src; +uniform RWStructuredBuffer _dst; + +int _start; + +// Todo: What is the nominal value per platform? +[numthreads(64,1,1)] +void CopyVertices (uint3 id : SV_DispatchThreadID) +{ + int index = _start + id.x; + _dst[index] = _src[index]; +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/CopyVertices.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyVertices.compute.meta new file mode 100644 index 0000000..fb03a9e --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/CopyVertices.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 133fdf3e6967a3943b6da9245ceddfac +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/UploadBytes.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadBytes.compute new file mode 100644 index 0000000..0df24d3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadBytes.compute @@ -0,0 +1,33 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel UploadBytes + +uniform ByteAddressBuffer _src; +uniform RWByteAddressBuffer _dst; + +uniform ByteAddressBuffer _meta; + +uint _elementSizeInBytes; +uint _startOffset; + +[numthreads(64, 1, 1)] +void UploadBytes(uint threadId : SV_GroupIndex, uint3 groupIds : SV_GroupID) +{ + uint3 m = _meta.Load3((groupIds.x + _startOffset) * 12); + + uint srcBase = m.x * _elementSizeInBytes; + uint dstBase = m.y * _elementSizeInBytes; + uint elementCount = m.z * (_elementSizeInBytes / 4); + + uint i = 0; + for (i = 0; i + 64 < elementCount; i += 64) + { + uint val = _src.Load(srcBase + (i + threadId) * 4); + _dst.Store(dstBase + (i + threadId) * 4, val); + } + + if (i + threadId < elementCount) + { + uint val = _src.Load(srcBase + (i + threadId) * 4); + _dst.Store(dstBase + (i + threadId) * 4, val); + } +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/UploadBytes.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadBytes.compute.meta new file mode 100644 index 0000000..66e9188 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadBytes.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5c37a4687610e3d43b962442496f6bef +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/UploadMatrices.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadMatrices.compute new file mode 100644 index 0000000..47a3f81 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadMatrices.compute @@ -0,0 +1,28 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel UploadMatrices + +uniform StructuredBuffer _src; +uniform RWStructuredBuffer _dst; + +uniform ByteAddressBuffer _meta; + +uint _startOffset; + +[numthreads(64, 1, 1)] +void UploadMatrices(uint threadId : SV_GroupIndex, uint3 groupIds : SV_GroupID) +{ + uint3 m = _meta.Load3((groupIds.x + _startOffset) * 12); + + uint srcBase = m.x; + uint dstBase = m.y; + uint elementCount = m.z; + + uint i = 0; + for (i = 0; i + 64 < elementCount; i += 64) + { + _dst[dstBase + i + threadId] = _src[srcBase + i + threadId]; + } + + if (i + threadId < elementCount) + _dst[dstBase + i + threadId] = _src[srcBase + i + threadId]; +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/UploadMatrices.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadMatrices.compute.meta new file mode 100644 index 0000000..9b15371 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadMatrices.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b573ead72afd92a448a02018169f50be +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/UploadVertices.compute b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadVertices.compute new file mode 100644 index 0000000..3431555 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadVertices.compute @@ -0,0 +1,34 @@ +// Each #kernel tells which function to compile; you can have many kernels +#pragma kernel UploadVertices + +struct Vertex +{ + float3 position; + float3 normal; + float3 tangent; +}; +uniform StructuredBuffer _src; +uniform RWStructuredBuffer _dst; + +uniform ByteAddressBuffer _meta; + +uint _startOffset; + +[numthreads(64,1,1)] +void UploadVertices (uint threadId : SV_GroupIndex, uint3 groupIds : SV_GroupID) +{ + uint3 m = _meta.Load3((groupIds.x + _startOffset) * 12); + + uint srcBase = m.x; + uint dstBase = m.y; + uint elementCount = m.z; + + uint i = 0; + for (i = 0; i + 64 < elementCount; i += 64) + { + _dst[dstBase + i + threadId] = _src[srcBase + i + threadId]; + } + + if (i + threadId < elementCount) + _dst[dstBase + i + threadId] = _src[srcBase + i + threadId]; +} diff --git a/Packages/com.latios.latios-framework/Kinemation/Resources/UploadVertices.compute.meta b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadVertices.compute.meta new file mode 100644 index 0000000..b073689 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Resources/UploadVertices.compute.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8b14ae0300adffa4aab772d7df293e9e +ComputeShaderImporter: + externalObjects: {} + currentAPIMask: 4 + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems.meta b/Packages/com.latios.latios-framework/Kinemation/Systems.meta new file mode 100644 index 0000000..9a1588f --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a890138c73a5d3741933942912ee03b6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/CopyTransformFromBoneSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/CopyTransformFromBoneSystem.cs new file mode 100644 index 0000000..b454d06 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/CopyTransformFromBoneSystem.cs @@ -0,0 +1,83 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [UpdateInGroup(typeof(TransformSystemGroup))] + [UpdateAfter(typeof(TRSToLocalToParentSystem))] + [UpdateBefore(typeof(TRSToLocalToWorldSystem))] + [DisableAutoCreation] + [BurstCompile] + public partial struct CopyTransformFromBoneSystem : ISystem + { + EntityQuery m_query; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(false).WithAll(true).WithAll(true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.Dependency = new CopyFromBoneJob + { + fromBoneHandle = state.GetComponentTypeHandle(true), + skeletonHandle = state.GetComponentTypeHandle(true), + btrBfe = state.GetBufferFromEntity(true), + ltpHandle = state.GetComponentTypeHandle(false), + lastSystemVersion = state.LastSystemVersion + }.ScheduleParallel(m_query, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct CopyFromBoneJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle fromBoneHandle; + [ReadOnly] public ComponentTypeHandle skeletonHandle; + [ReadOnly] public BufferFromEntity btrBfe; + public ComponentTypeHandle ltpHandle; + + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var skeletons = batchInChunk.GetNativeArray(skeletonHandle); + + if (!batchInChunk.DidChange(fromBoneHandle, lastSystemVersion) && !batchInChunk.DidChange(skeletonHandle, lastSystemVersion)) + { + bool needsCopy = false; + for (int i = 0; i < batchInChunk.Count; i++) + { + if (btrBfe.DidChange(skeletons[i].skeletonRoot, lastSystemVersion)) + { + needsCopy = true; + break;; + } + } + + if (!needsCopy) + return; + } + + var bones = batchInChunk.GetNativeArray(fromBoneHandle); + var ltps = batchInChunk.GetNativeArray(ltpHandle).Reinterpret(); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var buffer = btrBfe[skeletons[i].skeletonRoot]; + ltps[i] = buffer[bones[i].boneIndex].boneToRoot; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/CopyTransformFromBoneSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/CopyTransformFromBoneSystem.cs.meta new file mode 100644 index 0000000..c47cd6f --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/CopyTransformFromBoneSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38f620c10cee24f458879df358ffeb4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling.meta new file mode 100644 index 0000000..eb7d15a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0e9d7ca9c11d9524e91dffcb074c00a2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateDeformedMeshesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateDeformedMeshesSystem.cs new file mode 100644 index 0000000..fabb701 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateDeformedMeshesSystem.cs @@ -0,0 +1,156 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +// This system doesn't actually allocate the compute buffer. +// Doing so now would introduce a sync point. +// This system just calculates the required size and distributes instance shader properties. +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct AllocateDeformedMeshesSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(true).WithAll().Build(); + + state.GetWorldBlackboardEntity().AddComponent(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + int deformIndex = state.GetWorldBlackboardEntity().GetBuffer(true).Reinterpret().AsNativeArray() + .IndexOf(ComponentType.ReadOnly()); + ulong deformMaterialMaskLower = (ulong)deformIndex >= 64UL ? 0UL : (1UL << deformIndex); + ulong deformMaterialMaskUpper = (ulong)deformIndex >= 64UL ? (1UL << (deformIndex - 64)) : 0UL; + + var metaHandle = state.GetComponentTypeHandle(); + var perCameraMaskHandle = state.GetComponentTypeHandle(true); + var perFrameMaskHandle = state.GetComponentTypeHandle(true); + + var chunkList = state.WorldUnmanaged.UpdateAllocator.AllocateNativeList(m_metaQuery.CalculateEntityCountWithoutFiltering()); + + state.Dependency = new ChunkPrefixSumJob + { + perCameraMaskHandle = perCameraMaskHandle, + perFrameMaskHandle = perFrameMaskHandle, + chunkHeaderHandle = state.GetComponentTypeHandle(true), + metaHandle = metaHandle, + materialMaskHandle = state.GetComponentTypeHandle(), + maxRequiredDeformVerticesCdfe = state.GetComponentDataFromEntity(), + worldBlackboardEntity = state.GetWorldBlackboardEntity(), + changedChunkList = chunkList, + deformMaterialMaskLower = deformMaterialMaskLower, + deformMaterialMaskUpper = deformMaterialMaskUpper + }.Schedule(m_metaQuery, state.Dependency); + + state.Dependency = new AssignComputeDeformMeshOffsetsJob + { + perCameraMaskHandle = perCameraMaskHandle, + perFrameMaskHandle = perFrameMaskHandle, + metaHandle = metaHandle, + changedChunks = chunkList.AsDeferredJobArray(), + indicesHandle = state.GetComponentTypeHandle() + }.Schedule(chunkList, 1, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + // Schedule single + [BurstCompile] + struct ChunkPrefixSumJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + public ComponentTypeHandle metaHandle; + public ComponentTypeHandle materialMaskHandle; + public ComponentDataFromEntity maxRequiredDeformVerticesCdfe; + public Entity worldBlackboardEntity; + public NativeList changedChunkList; + public ulong deformMaterialMaskLower; + public ulong deformMaterialMaskUpper; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var prefixSum = maxRequiredDeformVerticesCdfe[worldBlackboardEntity].verticesCount; + + var cameraMaskArray = batchInChunk.GetNativeArray(perCameraMaskHandle); + var frameMaskArray = batchInChunk.GetNativeArray(perFrameMaskHandle); + var headerArray = batchInChunk.GetNativeArray(chunkHeaderHandle); + var metaArray = batchInChunk.GetNativeArray(metaHandle); + var materialMaskArray = batchInChunk.GetNativeArray(materialMaskHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var cameraMask = cameraMaskArray[i]; + var frameMask = frameMaskArray[i]; + var lower = cameraMask.lower.Value & (~frameMask.lower.Value); + var upper = cameraMask.upper.Value & (~frameMask.upper.Value); + if ((upper | lower) == 0) + continue; + + changedChunkList.Add(headerArray[i].ArchetypeChunk); + var materialMask = materialMaskArray[i]; + materialMask.lower.Value |= deformMaterialMaskLower; + materialMask.upper.Value |= deformMaterialMaskUpper; + materialMaskArray[i] = materialMask; + + var meta = metaArray[i]; + + meta.vertexStartPrefixSum = prefixSum; + var newEntities = math.countbits(lower) + math.countbits(upper); + prefixSum += newEntities * meta.verticesPerMesh; + metaArray[i] = meta; + } + maxRequiredDeformVerticesCdfe[worldBlackboardEntity] = new MaxRequiredDeformVertices { verticesCount = prefixSum }; + } + } + + [BurstCompile] + struct AssignComputeDeformMeshOffsetsJob : IJobParallelForDefer + { + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle metaHandle; + [ReadOnly] public NativeArray changedChunks; + + public ComponentTypeHandle indicesHandle; + + public void Execute(int index) + { + var batchInChunk = changedChunks[index]; + + var metadata = batchInChunk.GetChunkComponentData(metaHandle); + var cameraMask = batchInChunk.GetChunkComponentData(perCameraMaskHandle); + var frameMask = batchInChunk.GetChunkComponentData(perFrameMaskHandle); + var lower = new BitField64(cameraMask.lower.Value & (~frameMask.lower.Value)); + var upper = new BitField64(cameraMask.upper.Value & (~frameMask.upper.Value)); + + var indices = batchInChunk.GetNativeArray(indicesHandle).Reinterpret(); + int prefixSum = metadata.vertexStartPrefixSum; + + for (int i = lower.CountTrailingZeros(); i < 64; lower.SetBits(i, false), i = lower.CountTrailingZeros()) + { + indices[i] = (uint)prefixSum; + prefixSum += metadata.verticesPerMesh; + } + + for (int i = upper.CountTrailingZeros(); i < 64; upper.SetBits(i, false), i = upper.CountTrailingZeros()) + { + indices[i + 64] = (uint)prefixSum; + prefixSum += metadata.verticesPerMesh; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateDeformedMeshesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateDeformedMeshesSystem.cs.meta new file mode 100644 index 0000000..cf2c837 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateDeformedMeshesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d4eee1453933a34aba10485fdf80e62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateLinearBlendMatricesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateLinearBlendMatricesSystem.cs new file mode 100644 index 0000000..3fec725 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateLinearBlendMatricesSystem.cs @@ -0,0 +1,156 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +// This system doesn't actually allocate the compute buffer. +// Doing so now would introduce a sync point. +// This system just calculates the required size and distributes instance shader properties. +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct AllocateLinearBlendMatricesSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(true).WithAll().Build(); + + state.GetWorldBlackboardEntity().AddComponent(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + int linearBlendIndex = state.GetWorldBlackboardEntity().GetBuffer(true).Reinterpret().AsNativeArray() + .IndexOf(ComponentType.ReadOnly()); + ulong linearBlendMaterialMaskLower = (ulong)linearBlendIndex >= 64UL ? 0UL : (1UL << linearBlendIndex); + ulong linearBlendMaterialMaskUpper = (ulong)linearBlendIndex >= 64UL ? (1UL << (linearBlendIndex - 64)) : 0UL; + + var metaHandle = state.GetComponentTypeHandle(); + var perCameraMaskHandle = state.GetComponentTypeHandle(true); + var perFrameMaskHandle = state.GetComponentTypeHandle(true); + + var chunkList = state.WorldUnmanaged.UpdateAllocator.AllocateNativeList(m_metaQuery.CalculateEntityCountWithoutFiltering()); + + state.Dependency = new ChunkPrefixSumJob + { + perCameraMaskHandle = perCameraMaskHandle, + perFrameMaskHandle = perFrameMaskHandle, + chunkHeaderHandle = state.GetComponentTypeHandle(true), + metaHandle = metaHandle, + materialMaskHandle = state.GetComponentTypeHandle(), + maxRequiredLinearBlendMatricesCdfe = state.GetComponentDataFromEntity(), + worldBlackboardEntity = state.GetWorldBlackboardEntity(), + changedChunkList = chunkList, + linearBlendMaterialMaskLower = linearBlendMaterialMaskLower, + linearBlendMaterialMaskUpper = linearBlendMaterialMaskUpper + }.Schedule(m_metaQuery, state.Dependency); + + state.Dependency = new AssignComputeDeformMeshOffsetsJob + { + perCameraMaskHandle = perCameraMaskHandle, + perFrameMaskHandle = perFrameMaskHandle, + metaHandle = metaHandle, + changedChunks = chunkList.AsDeferredJobArray(), + indicesHandle = state.GetComponentTypeHandle() + }.Schedule(chunkList, 1, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + // Schedule single + [BurstCompile] + struct ChunkPrefixSumJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + public ComponentTypeHandle metaHandle; + public ComponentTypeHandle materialMaskHandle; + public ComponentDataFromEntity maxRequiredLinearBlendMatricesCdfe; + public Entity worldBlackboardEntity; + public NativeList changedChunkList; + public ulong linearBlendMaterialMaskLower; + public ulong linearBlendMaterialMaskUpper; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var prefixSum = maxRequiredLinearBlendMatricesCdfe[worldBlackboardEntity].matricesCount; + + var cameraMaskArray = batchInChunk.GetNativeArray(perCameraMaskHandle); + var frameMaskArray = batchInChunk.GetNativeArray(perFrameMaskHandle); + var headerArray = batchInChunk.GetNativeArray(chunkHeaderHandle); + var metaArray = batchInChunk.GetNativeArray(metaHandle); + var materialMaskArray = batchInChunk.GetNativeArray(materialMaskHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var cameraMask = cameraMaskArray[i]; + var frameMask = frameMaskArray[i]; + var lower = cameraMask.lower.Value & (~frameMask.lower.Value); + var upper = cameraMask.upper.Value & (~frameMask.upper.Value); + if ((upper | lower) == 0) + continue; + + changedChunkList.Add(headerArray[i].ArchetypeChunk); + var materialMask = materialMaskArray[i]; + materialMask.lower.Value |= linearBlendMaterialMaskLower; + materialMask.upper.Value |= linearBlendMaterialMaskUpper; + materialMaskArray[i] = materialMask; + + var meta = metaArray[i]; + + meta.bonesStartPrefixSum = prefixSum; + var newEntities = math.countbits(lower) + math.countbits(upper); + prefixSum += newEntities * meta.bonesPerMesh; + metaArray[i] = meta; + } + maxRequiredLinearBlendMatricesCdfe[worldBlackboardEntity] = new MaxRequiredLinearBlendMatrices { matricesCount = prefixSum }; + } + } + + [BurstCompile] + struct AssignComputeDeformMeshOffsetsJob : IJobParallelForDefer + { + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle metaHandle; + [ReadOnly] public NativeArray changedChunks; + + public ComponentTypeHandle indicesHandle; + + public void Execute(int index) + { + var batchInChunk = changedChunks[index]; + + var metadata = batchInChunk.GetChunkComponentData(metaHandle); + var cameraMask = batchInChunk.GetChunkComponentData(perCameraMaskHandle); + var frameMask = batchInChunk.GetChunkComponentData(perFrameMaskHandle); + var lower = new BitField64(cameraMask.lower.Value & (~frameMask.lower.Value)); + var upper = new BitField64(cameraMask.upper.Value & (~frameMask.upper.Value)); + + var indices = batchInChunk.GetNativeArray(indicesHandle).Reinterpret(); + int prefixSum = metadata.bonesStartPrefixSum; + + for (int i = lower.CountTrailingZeros(); i < 64; lower.SetBits(i, false), i = lower.CountTrailingZeros()) + { + indices[i] = (uint)prefixSum; + prefixSum += metadata.bonesPerMesh; + } + + for (int i = upper.CountTrailingZeros(); i < 64; upper.SetBits(i, false), i = upper.CountTrailingZeros()) + { + indices[i + 64] = (uint)prefixSum; + prefixSum += metadata.bonesPerMesh; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateLinearBlendMatricesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateLinearBlendMatricesSystem.cs.meta new file mode 100644 index 0000000..96dff2b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/AllocateLinearBlendMatricesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbf77bcdda79dbc478bc48efba125582 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/CopySkinWithCullingSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/CopySkinWithCullingSystem.cs new file mode 100644 index 0000000..b6ec94a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/CopySkinWithCullingSystem.cs @@ -0,0 +1,239 @@ +using Unity.Burst; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct CopySkinWithCullingSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(true).WithAll(true).WithAll(true).WithAll(true) + .WithAll(true).WithAll(false).UseWriteGroups().Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + int linearBlendIndex = state.GetWorldBlackboardEntity().GetBuffer(true).Reinterpret().AsNativeArray() + .IndexOf(ComponentType.ReadOnly()); + ulong linearBlendMaterialMaskLower = (ulong)linearBlendIndex >= 64UL ? 0UL : (1UL << linearBlendIndex); + ulong linearBlendMaterialMaskUpper = (ulong)linearBlendIndex >= 64UL ? (1UL << (linearBlendIndex - 64)) : 0UL; + + int deformIndex = state.GetWorldBlackboardEntity().GetBuffer(true).Reinterpret().AsNativeArray() + .IndexOf(ComponentType.ReadOnly()); + ulong deformMaterialMaskLower = (ulong)deformIndex >= 64UL ? 0UL : (1UL << deformIndex); + ulong deformMaterialMaskUpper = (ulong)deformIndex >= 64UL ? (1UL << (deformIndex - 64)) : 0UL; + + state.Dependency = new CopySkinJob + { + hybridChunkInfoHandle = state.GetComponentTypeHandle(true), + chunkHeaderHandle = state.GetComponentTypeHandle(true), + chunkPerFrameMaskHandle = state.GetComponentTypeHandle(true), + referenceHandle = state.GetComponentTypeHandle(true), + sife = state.GetStorageInfoFromEntity(), + chunkPerCameraMaskHandle = state.GetComponentTypeHandle(false), + chunkMaterialPropertyDirtyMask = state.GetComponentTypeHandle(false), + computeCdfe = state.GetComponentDataFromEntity(false), + linearBlendCdfe = state.GetComponentDataFromEntity(false), + computeDeformHandle = state.GetComponentTypeHandle(false), + linearBlendHandle = state.GetComponentTypeHandle(false), + linearBlendMaterialMaskLower = linearBlendMaterialMaskLower, + linearBlendMaterialMaskUpper = linearBlendMaterialMaskUpper, + deformMaterialMaskLower = deformMaterialMaskLower, + deformMaterialMaskUpper = deformMaterialMaskUpper + }.ScheduleParallel(m_metaQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + unsafe struct CopySkinJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle hybridChunkInfoHandle; + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + [ReadOnly] public ComponentTypeHandle chunkPerFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle referenceHandle; + + [ReadOnly] public StorageInfoFromEntity sife; + + public ComponentTypeHandle chunkPerCameraMaskHandle; + public ComponentTypeHandle chunkMaterialPropertyDirtyMask; + + [NativeDisableParallelForRestriction] public ComponentDataFromEntity computeCdfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity linearBlendCdfe; + + [NativeDisableContainerSafetyRestriction] public ComponentTypeHandle computeDeformHandle; + [NativeDisableContainerSafetyRestriction] public ComponentTypeHandle linearBlendHandle; + + public ulong linearBlendMaterialMaskLower; + public ulong linearBlendMaterialMaskUpper; + public ulong deformMaterialMaskLower; + public ulong deformMaterialMaskUpper; + + public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex) + { + var hybridChunkInfos = archetypeChunk.GetNativeArray(hybridChunkInfoHandle); + var chunkHeaders = archetypeChunk.GetNativeArray(chunkHeaderHandle); + var chunkCameraMasks = archetypeChunk.GetNativeArray(chunkPerCameraMaskHandle); + var chunkFrameMasks = archetypeChunk.GetNativeArray(chunkPerFrameMaskHandle); + var chunkMaterialDirtyMasks = archetypeChunk.GetNativeArray(chunkMaterialPropertyDirtyMask); + + var context = new MaterialContext(); + context.Init(linearBlendCdfe, computeCdfe, linearBlendHandle, computeDeformHandle); + + for (var metaIndex = 0; metaIndex < archetypeChunk.Count; metaIndex++) + { + var hybridChunkInfo = hybridChunkInfos[metaIndex]; + if (!hybridChunkInfo.Valid) + continue; + + var chunkHeader = chunkHeaders[metaIndex]; + + ref var chunkCullingData = ref hybridChunkInfo.CullingData; + + var chunkInstanceCount = chunkHeader.ArchetypeChunk.Count; + var chunkEntityLodEnabled = chunkCullingData.InstanceLodEnableds; + var anyLodEnabled = (chunkEntityLodEnabled.Enabled[0] | chunkEntityLodEnabled.Enabled[1]) != 0; + + if (anyLodEnabled) + { + // Todo: Throw error if not per-instance? + //var perInstanceCull = 0 != (chunkCullingData.Flags & HybridChunkCullingData.kFlagInstanceCulling); + + var chunk = chunkHeader.ArchetypeChunk; + context.ResetChunk(chunk); + + var references = chunk.GetNativeArray(referenceHandle); + var invertedFrameMasks = chunkFrameMasks[metaIndex]; + invertedFrameMasks.lower.Value = ~invertedFrameMasks.lower.Value; + invertedFrameMasks.upper.Value = ~invertedFrameMasks.upper.Value; + + var lodWord = chunkEntityLodEnabled.Enabled[0]; + BitField64 maskWordLower; + maskWordLower.Value = 0; + for (int i = math.tzcnt(lodWord); i < 64; lodWord ^= 1ul << i, i = math.tzcnt(lodWord)) + { + bool isIn = IsReferenceVisible(references[i].sourceSkinnedEntity, + invertedFrameMasks.lower.IsSet(i), + i, + ref context); + maskWordLower.Value |= math.select(0ul, 1ul, isIn) << i; + } + lodWord = chunkEntityLodEnabled.Enabled[1]; + BitField64 maskWordUpper; + maskWordUpper.Value = 0; + for (int i = math.tzcnt(lodWord); i < 64; lodWord ^= 1ul << i, i = math.tzcnt(lodWord)) + { + bool isIn = IsReferenceVisible(references[i + 64].sourceSkinnedEntity, + invertedFrameMasks.upper.IsSet(i), + i + 64, + ref context); + maskWordUpper.Value |= math.select(0ul, 1ul, isIn) << i; + } + + chunkCameraMasks[metaIndex] = new ChunkPerCameraCullingMask { lower = maskWordLower, upper = maskWordUpper }; + + var dirtyMask = chunkMaterialDirtyMasks[metaIndex]; + if (context.linearBlendDirty) + { + dirtyMask.lower.Value |= linearBlendMaterialMaskLower; + dirtyMask.upper.Value |= linearBlendMaterialMaskUpper; + } + if (context.computeDeformDirty) + { + dirtyMask.lower.Value |= deformMaterialMaskLower; + dirtyMask.upper.Value |= deformMaterialMaskUpper; + } + chunkMaterialDirtyMasks[metaIndex] = dirtyMask; + } + } + } + + bool IsReferenceVisible(Entity reference, bool needsCopy, int entityIndex, ref MaterialContext context) + { + if (reference == Entity.Null || !sife.Exists(reference)) + return false; + + var info = sife[reference]; + var referenceMask = info.Chunk.GetChunkComponentData(chunkPerCameraMaskHandle); + bool result; + if (info.IndexInChunk >= 64) + result = referenceMask.upper.IsSet(info.IndexInChunk - 64); + else + result = referenceMask.lower.IsSet(info.IndexInChunk); + if (result && needsCopy) + { + context.CopySkin(entityIndex, + reference); + } + return result; + } + + struct MaterialContext + { + bool newChunk; + ArchetypeChunk currentChunk; + NativeArray linearBlendChunkArray; + NativeArray computeDeformChunkArray; + bool hasLinearBlend; + bool hasComputeDeform; + ComponentTypeHandle copySkinLinearBlendHandle; + ComponentDataFromEntity referenceLinearBlendCdfe; + ComponentTypeHandle copySkinComputeDeformHandle; + ComponentDataFromEntity referenceComputeDeformCdfe; + + public void Init(ComponentDataFromEntity lbsCdfe, ComponentDataFromEntity cdsCdfe, + ComponentTypeHandle lbsHandle, ComponentTypeHandle cdsHandle) + { + copySkinLinearBlendHandle = lbsHandle; + referenceLinearBlendCdfe = lbsCdfe; + copySkinComputeDeformHandle = cdsHandle; + referenceComputeDeformCdfe = cdsCdfe; + } + + public void ResetChunk(ArchetypeChunk chunk) + { + newChunk = true; + hasComputeDeform = false; + hasLinearBlend = false; + currentChunk = chunk; + } + + public bool linearBlendDirty => hasLinearBlend; + public bool computeDeformDirty => hasComputeDeform; + + public void CopySkin(int entityIndex, Entity reference) + { + if (Hint.Unlikely(newChunk)) + { + newChunk = false; + hasLinearBlend = currentChunk.Has(copySkinLinearBlendHandle); + hasComputeDeform = currentChunk.Has(copySkinComputeDeformHandle); + if (hasLinearBlend) + linearBlendChunkArray = currentChunk.GetNativeArray(copySkinLinearBlendHandle); + if (hasComputeDeform) + computeDeformChunkArray = currentChunk.GetNativeArray(copySkinComputeDeformHandle); + } + + if (hasLinearBlend) + linearBlendChunkArray[entityIndex] = referenceLinearBlendCdfe[reference]; + if (hasComputeDeform) + computeDeformChunkArray[entityIndex] = referenceComputeDeformCdfe[reference]; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/CopySkinWithCullingSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/CopySkinWithCullingSystem.cs.meta new file mode 100644 index 0000000..0e1c4bf --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/CopySkinWithCullingSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f421daadb730bcc48b17e2e68fe03aa0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullExposedSkeletonsSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullExposedSkeletonsSystem.cs new file mode 100644 index 0000000..bbd7d9b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullExposedSkeletonsSystem.cs @@ -0,0 +1,191 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public class FrustumCullExposedSkeletonsSystem : SubSystem + { + EntityQuery m_query; + + protected override void OnCreate() + { + m_query = Fluent.WithAll(true).WithAll(true) + .WithAll(false, true).Build(); + } + + protected override void OnUpdate() + { + // Todo: We only need the max index, so we may want to store that in an ICD instead. + var exposedCullingIndexManager = worldBlackboardEntity.GetCollectionComponent(true, out var cullingIndexJH); + var boundsArrays = worldBlackboardEntity.GetCollectionComponent(true); + cullingIndexJH.Complete(); + + var planesBuffer = worldBlackboardEntity.GetBuffer(true); + var unmanaged = World.Unmanaged; + var planes = CullingUtilities.BuildSOAPlanePackets(planesBuffer.Reinterpret().AsNativeArray(), ref unmanaged); + + var perThreadBitArrays = unmanaged.UpdateAllocator.AllocateNativeArray(JobsUtility.MaxJobThreadCount); + for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++) + perThreadBitArrays[i] = default; + + Dependency = new CullExposedBoundsJob + { + aabbs = boundsArrays.allAabbs.AsDeferredJobArray(), + batchAabbs = boundsArrays.batchedAabbs.AsDeferredJobArray(), + planePackets = planes, + maxBitIndex = exposedCullingIndexManager.maxIndex, + perThreadBitArrays = perThreadBitArrays, + allocator = unmanaged.UpdateAllocator.ToAllocator + }.ScheduleBatch(exposedCullingIndexManager.maxIndex.Value + 1, 32, Dependency); + + Dependency = new CollapseBitsJob + { + perThreadBitArrays = perThreadBitArrays, + }.Schedule(Dependency); + + Dependency = new SkeletonCullingJob + { + chunkMaskHandle = GetComponentTypeHandle(false), + cullingIndexHandle = GetComponentTypeHandle(true), + perThreadBitArrays = perThreadBitArrays + }.ScheduleParallel(m_query, Dependency); + } + + // Todo: Is it worth iterating over meta chunks? + [BurstCompile] + struct CullExposedBoundsJob : IJobParallelForBatch + { + [ReadOnly] public NativeArray aabbs; + [ReadOnly] public NativeArray batchAabbs; + [ReadOnly] public NativeArray planePackets; + [ReadOnly] public NativeReference maxBitIndex; + [NativeDisableParallelForRestriction] public NativeArray perThreadBitArrays; + public Allocator allocator; + + [NativeSetThreadIndex] int m_NativeThreadIndex; + + public void Execute(int startIndex, int count) + { + var cullType = FrustumPlanes.Intersect2(planePackets, batchAabbs[startIndex / 32]); + if (cullType == FrustumPlanes.IntersectResult.Out) + { + return; + } + + var perThreadBitArray = perThreadBitArrays[m_NativeThreadIndex]; + if (!perThreadBitArray.IsCreated) + { + perThreadBitArray = new UnsafeBitArray(CollectionHelper.Align(maxBitIndex.Value + 1, 64), + allocator, + NativeArrayOptions.ClearMemory); + perThreadBitArrays[m_NativeThreadIndex] = perThreadBitArray; + } + + if (cullType == FrustumPlanes.IntersectResult.In) + { + for (int i = 0; i < count; i++) + { + perThreadBitArray.Set(startIndex + i, true); + } + } + else + { + for (int i = 0; i < count; i++) + { + bool bit = perThreadBitArray.IsSet(startIndex + i); + bit |= FrustumPlanes.Intersect2NoPartial(planePackets, aabbs[startIndex + i]) == FrustumPlanes.IntersectResult.In; + perThreadBitArray.Set(startIndex + i, bit); + } + } + } + } + + [BurstCompile] + unsafe struct CollapseBitsJob : IJob + { + public NativeArray perThreadBitArrays; + + public void Execute() + { + int startFrom = -1; + for (int i = 0; i < perThreadBitArrays.Length; i++) + { + if (perThreadBitArrays[i].IsCreated) + { + startFrom = i + 1; + perThreadBitArrays[0] = perThreadBitArrays[i]; + perThreadBitArrays[i] = default; + break; + } + } + + if (startFrom == -1) + { + // This happens if chunk culling removes all bones. Unlikely but possible. + // In this case, we will need to check for this in future jobs. + return; + } + + for (int arrayIndex = startFrom; arrayIndex < perThreadBitArrays.Length; arrayIndex++) + { + if (!perThreadBitArrays[arrayIndex].IsCreated) + continue; + var dstArray = perThreadBitArrays[0]; + var dstArrayPtr = dstArray.Ptr; + var srcArrayPtr = perThreadBitArrays[arrayIndex].Ptr; + + for (int i = 0, bitCount = 0; bitCount < dstArray.Length; i++, bitCount += 64) + { + dstArrayPtr[i] |= srcArrayPtr[i]; + } + } + } + } + + [BurstCompile] + unsafe struct SkeletonCullingJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle cullingIndexHandle; + public ComponentTypeHandle chunkMaskHandle; + + [ReadOnly] public NativeArray perThreadBitArrays; + + public void Execute(ArchetypeChunk chunk, int chunkIndex) + { + if (!perThreadBitArrays[0].IsCreated) + { + chunk.SetChunkComponentData(chunkMaskHandle, default); + return; + } + + var cullingIndices = chunk.GetNativeArray(cullingIndexHandle); + + BitField64 maskWordLower; + maskWordLower.Value = 0; + for (int i = 0; i < math.min(64, chunk.Count); i++) + { + bool isIn = perThreadBitArrays[0].IsSet(cullingIndices[i].cullingIndex); + maskWordLower.Value |= math.select(0ul, 1ul, isIn) << i; + } + BitField64 maskWordUpper; + maskWordUpper.Value = 0; + for (int i = 0; i < math.max(0, chunk.Count - 64); i++) + { + bool isIn = perThreadBitArrays[0].IsSet(cullingIndices[i + 64].cullingIndex); + maskWordUpper.Value |= math.select(0ul, 1ul, isIn) << i; + } + + chunk.SetChunkComponentData(chunkMaskHandle, new ChunkPerCameraSkeletonCullingMask { lower = maskWordLower, upper = maskWordUpper }); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullExposedSkeletonsSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullExposedSkeletonsSystem.cs.meta new file mode 100644 index 0000000..a07d453 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullExposedSkeletonsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e74366ef7969eb14793b2e309e28c7ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullOptimizedSkeletonsSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullOptimizedSkeletonsSystem.cs new file mode 100644 index 0000000..7545a51 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullOptimizedSkeletonsSystem.cs @@ -0,0 +1,113 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct FrustumCullOptimizedSkeletonsSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(true).WithAll(true).WithAll(false).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + var planesBuffer = state.GetWorldBlackboardEntity().GetBuffer(true); + var unmanaged = state.WorldUnmanaged; + var planes = CullingUtilities.BuildSOAPlanePackets(planesBuffer, ref unmanaged); + + state.Dependency = new SkeletonCullingJob + { + cullingPlanes = planes, + boundsHandle = state.GetComponentTypeHandle(true), + chunkHeaderHandle = state.GetComponentTypeHandle(true), + chunkBoundsHandle = state.GetComponentTypeHandle(true), + chunkMaskHandle = state.GetComponentTypeHandle(false) + }.ScheduleParallel(m_metaQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + unsafe struct SkeletonCullingJob : IJobEntityBatch + { + [ReadOnly] public NativeArray cullingPlanes; + + [ReadOnly] public ComponentTypeHandle boundsHandle; + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + [ReadOnly] public ComponentTypeHandle chunkBoundsHandle; + public ComponentTypeHandle chunkMaskHandle; + + public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex) + { + var chunkHeaderArray = archetypeChunk.GetNativeArray(chunkHeaderHandle); + var chunkBoundsArray = archetypeChunk.GetNativeArray(chunkBoundsHandle); + var chunkMaskArray = archetypeChunk.GetNativeArray(chunkMaskHandle); + + for (var metaIndex = 0; metaIndex < archetypeChunk.Count; metaIndex++) + { + var chunkHeader = chunkHeaderArray[metaIndex]; + var chunkBounds = chunkBoundsArray[metaIndex]; + + var chunkIn = FrustumPlanes.Intersect2(cullingPlanes, chunkBounds.chunkBounds); + + if (chunkIn == FrustumPlanes.IntersectResult.Partial) + { + var chunk = chunkHeader.ArchetypeChunk; + + var chunkInstanceBounds = chunk.GetNativeArray(boundsHandle); + + BitField64 maskWordLower; + maskWordLower.Value = 0; + for (int i = 0; i < math.min(64, chunk.Count); i++) + { + bool isIn = FrustumPlanes.Intersect2NoPartial(cullingPlanes, chunkInstanceBounds[i].bounds) != FrustumPlanes.IntersectResult.Out; + maskWordLower.Value |= math.select(0ul, 1ul, isIn) << i; + } + BitField64 maskWordUpper; + maskWordUpper.Value = 0; + for (int i = 0; i < math.max(0, chunk.Count - 64); i++) + { + bool isIn = FrustumPlanes.Intersect2NoPartial(cullingPlanes, chunkInstanceBounds[i + 64].bounds) != FrustumPlanes.IntersectResult.Out; + maskWordUpper.Value |= math.select(0ul, 1ul, isIn) << i; + } + + chunkMaskArray[metaIndex] = new ChunkPerCameraSkeletonCullingMask { lower = maskWordLower, upper = maskWordUpper }; + } + else if (chunkIn == FrustumPlanes.IntersectResult.In) + { + var chunk = chunkHeader.ArchetypeChunk; + + BitField64 lodWordLower = default; + BitField64 lodWordUpper = default; + if (chunk.Count > 64) + { + lodWordUpper.SetBits(0, true, math.max(chunk.Count - 64, 0)); + lodWordLower.Value = ~0UL; + } + else + { + lodWordLower.SetBits(0, true, chunk.Count); + lodWordUpper.Clear(); + } + chunkMaskArray[metaIndex] = new ChunkPerCameraSkeletonCullingMask { lower = lodWordLower, upper = lodWordUpper }; + } + else + chunkMaskArray[metaIndex] = default; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullOptimizedSkeletonsSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullOptimizedSkeletonsSystem.cs.meta new file mode 100644 index 0000000..e8eabb3 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullOptimizedSkeletonsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cde6487aed134d4596043330ec6be93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullSkinnedEntitiesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullSkinnedEntitiesSystem.cs new file mode 100644 index 0000000..d6af4c1 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullSkinnedEntitiesSystem.cs @@ -0,0 +1,125 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +// Todo: If this gets slow, try sweeping through the culled skeleton buffers +// and a chunk index chunkComponent from the skinned meshes to write to a per-thread +// mask buffer. +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct FrustumCullSkinnedEntitiesSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(true).WithAll(true).WithAll(true).WithAll(true) + .WithAny(true).WithAny(true).WithAll(false) + .UseWriteGroups().Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.Dependency = new SkinnedCullingJob + { + hybridChunkInfoHandle = state.GetComponentTypeHandle(true), + chunkHeaderHandle = state.GetComponentTypeHandle(true), + dependentHandle = state.GetComponentTypeHandle(true), + chunkSkeletonMaskHandle = state.GetComponentTypeHandle(true), + sife = state.GetStorageInfoFromEntity(), + chunkMaskHandle = state.GetComponentTypeHandle(false) + }.ScheduleParallel(m_metaQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + unsafe struct SkinnedCullingJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle hybridChunkInfoHandle; + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + [ReadOnly] public ComponentTypeHandle dependentHandle; + [ReadOnly] public ComponentTypeHandle chunkSkeletonMaskHandle; + + [ReadOnly] public StorageInfoFromEntity sife; + + public ComponentTypeHandle chunkMaskHandle; + + public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex) + { + var hybridChunkInfos = archetypeChunk.GetNativeArray(hybridChunkInfoHandle); + var chunkHeaders = archetypeChunk.GetNativeArray(chunkHeaderHandle); + var chunkMasks = archetypeChunk.GetNativeArray(chunkMaskHandle); + + for (var metaIndex = 0; metaIndex < archetypeChunk.Count; metaIndex++) + { + var hybridChunkInfo = hybridChunkInfos[metaIndex]; + if (!hybridChunkInfo.Valid) + continue; + + var chunkHeader = chunkHeaders[metaIndex]; + + ref var chunkCullingData = ref hybridChunkInfo.CullingData; + + var chunkInstanceCount = chunkHeader.ArchetypeChunk.Count; + var chunkEntityLodEnabled = chunkCullingData.InstanceLodEnableds; + var anyLodEnabled = (chunkEntityLodEnabled.Enabled[0] | chunkEntityLodEnabled.Enabled[1]) != 0; + + if (anyLodEnabled) + { + // Todo: Throw error if not per-instance? + //var perInstanceCull = 0 != (chunkCullingData.Flags & HybridChunkCullingData.kFlagInstanceCulling); + + var chunk = chunkHeader.ArchetypeChunk; + + if (!chunk.Has(dependentHandle)) + continue; + + var rootRefs = chunk.GetNativeArray(dependentHandle); + + var lodWord = chunkEntityLodEnabled.Enabled[0]; + BitField64 maskWordLower; + maskWordLower.Value = 0; + for (int i = math.tzcnt(lodWord); i < 64; lodWord ^= 1ul << i, i = math.tzcnt(lodWord)) + { + bool isIn = IsSkeletonVisible(rootRefs[i].root); + maskWordLower.Value |= math.select(0ul, 1ul, isIn) << i; + } + lodWord = chunkEntityLodEnabled.Enabled[1]; + BitField64 maskWordUpper; + maskWordUpper.Value = 0; + for (int i = math.tzcnt(lodWord); i < 64; lodWord ^= 1ul << i, i = math.tzcnt(lodWord)) + { + bool isIn = IsSkeletonVisible(rootRefs[i + 64].root); + maskWordUpper.Value |= math.select(0ul, 1ul, isIn) << i; + } + + chunkMasks[metaIndex] = new ChunkPerCameraCullingMask { lower = maskWordLower, upper = maskWordUpper }; + } + } + } + + bool IsSkeletonVisible(Entity root) + { + if (root == Entity.Null || !sife.Exists(root)) + return false; + + var info = sife[root]; + var skeletonMask = info.Chunk.GetChunkComponentData(chunkSkeletonMaskHandle); + if (info.IndexInChunk >= 64) + return skeletonMask.upper.IsSet(info.IndexInChunk - 64); + else + return skeletonMask.lower.IsSet(info.IndexInChunk); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullSkinnedEntitiesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullSkinnedEntitiesSystem.cs.meta new file mode 100644 index 0000000..270d668 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullSkinnedEntitiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2787db405d529164aa2ea6bf70cbe035 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullUnskinnedEntitiesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullUnskinnedEntitiesSystem.cs new file mode 100644 index 0000000..8dee8e2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullUnskinnedEntitiesSystem.cs @@ -0,0 +1,124 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct FrustumCullUnskinnedEntitiesSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(true).WithAll(true).WithAll(true).WithAll(true) + .WithAll(false).UseWriteGroups().Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + var planesBuffer = state.GetWorldBlackboardEntity().GetBuffer(true); + var unmanaged = state.WorldUnmanaged; + var planes = CullingUtilities.BuildSOAPlanePackets(planesBuffer, ref unmanaged); + + state.Dependency = new SimpleCullingJob + { + cullingPlanes = planes, + boundsHandle = state.GetComponentTypeHandle(true), + hybridChunkInfoHandle = state.GetComponentTypeHandle(true), + chunkHeaderHandle = state.GetComponentTypeHandle(true), + chunkBoundsHandle = state.GetComponentTypeHandle(true), + chunkMaskHandle = state.GetComponentTypeHandle(false) + }.ScheduleParallel(m_metaQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + unsafe struct SimpleCullingJob : IJobEntityBatch + { + [ReadOnly] public NativeArray cullingPlanes; + + [ReadOnly] public ComponentTypeHandle boundsHandle; + [ReadOnly] public ComponentTypeHandle hybridChunkInfoHandle; + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + [ReadOnly] public ComponentTypeHandle chunkBoundsHandle; + public ComponentTypeHandle chunkMaskHandle; + + public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex) + { + var hybridChunkInfoArray = archetypeChunk.GetNativeArray(hybridChunkInfoHandle); + var chunkHeaderArray = archetypeChunk.GetNativeArray(chunkHeaderHandle); + var chunkBoundsArray = archetypeChunk.GetNativeArray(chunkBoundsHandle); + var chunkMaskArray = archetypeChunk.GetNativeArray(chunkMaskHandle); + + for (var metaIndex = 0; metaIndex < archetypeChunk.Count; metaIndex++) + { + var hybridChunkInfo = hybridChunkInfoArray[metaIndex]; + if (!hybridChunkInfo.Valid) + continue; + + var chunkHeader = chunkHeaderArray[metaIndex]; + var chunkBounds = chunkBoundsArray[metaIndex]; + + ref var chunkCullingData = ref hybridChunkInfo.CullingData; + + var chunkInstanceCount = chunkHeader.ArchetypeChunk.Count; + var chunkEntityLodEnabled = chunkCullingData.InstanceLodEnableds; + var anyLodEnabled = (chunkEntityLodEnabled.Enabled[0] | chunkEntityLodEnabled.Enabled[1]) != 0; + + if (anyLodEnabled) + { + var perInstanceCull = 0 != (chunkCullingData.Flags & HybridChunkCullingData.kFlagInstanceCulling); + + var chunkIn = perInstanceCull ? + FrustumPlanes.Intersect2(cullingPlanes, chunkBounds.Value) : + FrustumPlanes.Intersect2NoPartial(cullingPlanes, chunkBounds.Value); + + if (chunkIn == FrustumPlanes.IntersectResult.Partial) + { + var chunk = chunkHeader.ArchetypeChunk; + + var chunkInstanceBounds = chunk.GetNativeArray(boundsHandle); + + var lodWord = chunkEntityLodEnabled.Enabled[0]; + BitField64 maskWordLower; + maskWordLower.Value = 0; + for (int i = math.tzcnt(lodWord); i < 64; lodWord ^= 1ul << i, i = math.tzcnt(lodWord)) + { + bool isIn = FrustumPlanes.Intersect2NoPartial(cullingPlanes, chunkInstanceBounds[i].Value) != FrustumPlanes.IntersectResult.Out; + maskWordLower.Value |= math.select(0ul, 1ul, isIn) << i; + } + lodWord = chunkEntityLodEnabled.Enabled[1]; + BitField64 maskWordUpper; + maskWordUpper.Value = 0; + for (int i = math.tzcnt(lodWord); i < 64; lodWord ^= 1ul << i, i = math.tzcnt(lodWord)) + { + bool isIn = FrustumPlanes.Intersect2NoPartial(cullingPlanes, chunkInstanceBounds[i + 64].Value) != FrustumPlanes.IntersectResult.Out; + maskWordUpper.Value |= math.select(0ul, 1ul, isIn) << i; + } + + chunkMaskArray[metaIndex] = new ChunkPerCameraCullingMask { lower = maskWordLower, upper = maskWordUpper }; + } + else if (chunkIn == FrustumPlanes.IntersectResult.In) + { + var chunk = chunkHeader.ArchetypeChunk; + + BitField64 lodWordLower = new BitField64(chunkEntityLodEnabled.Enabled[0]); + BitField64 lodWordUpper = new BitField64(chunkEntityLodEnabled.Enabled[1]); + chunkMaskArray[metaIndex] = new ChunkPerCameraCullingMask { lower = lodWordLower, upper = lodWordUpper }; + } + } + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullUnskinnedEntitiesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullUnskinnedEntitiesSystem.cs.meta new file mode 100644 index 0000000..4ad3d87 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/FrustumCullUnskinnedEntitiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d842e99b885764f42974c2073e032359 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/SkinningDispatchSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/SkinningDispatchSystem.cs new file mode 100644 index 0000000..8ad253b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/SkinningDispatchSystem.cs @@ -0,0 +1,669 @@ +using Latios.Psyshock; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; +using UnityEngine.Profiling; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class SkinningDispatchSystem : SubSystem + { + EntityQuery m_skeletonQuery; + + UnityEngine.ComputeShader m_batchSkinningShader; + + int _dstMats; + int _dstVertices; + int _srcVertices; + int _boneWeights; + int _bindPoses; + int _boneOffsets; + int _metaBuffer; + int _skeletonMats; + int _startOffset; + int _DeformedMeshData; + int _SkinMatrices; + + protected override void OnCreate() + { + m_skeletonQuery = Fluent.WithAll(true).WithAll(false).Build(); + + if (UnityEngine.SystemInfo.maxComputeWorkGroupSizeX < 1024) + m_batchSkinningShader = UnityEngine.Resources.Load("BatchSkinning512"); + else + m_batchSkinningShader = UnityEngine.Resources.Load("BatchSkinning"); + + _dstMats = UnityEngine.Shader.PropertyToID("_dstMats"); + _dstVertices = UnityEngine.Shader.PropertyToID("_dstVertices"); + _srcVertices = UnityEngine.Shader.PropertyToID("_srcVertices"); + _boneWeights = UnityEngine.Shader.PropertyToID("_boneWeights"); + _bindPoses = UnityEngine.Shader.PropertyToID("_bindPoses"); + _boneOffsets = UnityEngine.Shader.PropertyToID("_boneOffsets"); + _metaBuffer = UnityEngine.Shader.PropertyToID("_metaBuffer"); + _skeletonMats = UnityEngine.Shader.PropertyToID("_skeletonMats"); + _startOffset = UnityEngine.Shader.PropertyToID("_startOffset"); + _DeformedMeshData = UnityEngine.Shader.PropertyToID("_DeformedMeshData"); + _SkinMatrices = UnityEngine.Shader.PropertyToID("_SkinMatrices"); + } + + protected override void OnUpdate() + { + Profiler.BeginSample("Setup gather jobs"); + var skeletonChunkCount = m_skeletonQuery.CalculateChunkCountWithoutFiltering(); + + var skinnedMeshesBufferHandle = GetBufferTypeHandle(true); + var boneReferenceBufferHandle = GetBufferTypeHandle(true); + var optimizedBoneBufferHandle = GetBufferTypeHandle(true); + + var meshDataStream = new NativeStream(skeletonChunkCount, Allocator.TempJob); + var countsArray = new NativeArray(skeletonChunkCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + var boneMatsBufferList = worldBlackboardEntity.GetCollectionComponent(false, out var boneMatsBufferJH); + boneMatsBufferJH.Complete(); + + var skeletonCountsByBufferByBatch = new NativeArray(skeletonChunkCount * (boneMatsBufferList.boneMatricesBuffers.Count + 1), + Allocator.TempJob, + NativeArrayOptions.ClearMemory); + + Dependency = new CollectMeshMetadataJob + { + entityHandle = GetEntityTypeHandle(), + skinnedMeshesBufferHandle = skinnedMeshesBufferHandle, + perFrameMetadataHandle = GetComponentTypeHandle(true), + skeletonCullingMaskHandle = GetComponentTypeHandle(true), + boneReferenceBufferHandle = boneReferenceBufferHandle, + optimizedBoneBufferHandle = optimizedBoneBufferHandle, + meshPerCameraCullingMaskHandle = GetComponentTypeHandle(true), + meshPerFrameCullingMaskHandle = GetComponentTypeHandle(true), + computeDeformShaderIndexCdfe = GetComponentDataFromEntity(true), + linearBlendSkinningShaderIndexCdfe = GetComponentDataFromEntity(true), + sife = GetStorageInfoFromEntity(), + meshDataStream = meshDataStream.AsWriter(), + countsArray = countsArray, + skeletonCountsByBufferByBatch = skeletonCountsByBufferByBatch, + bufferId = boneMatsBufferList.boneMatricesBuffers.Count + }.ScheduleParallel(m_skeletonQuery, Dependency); + + var totalCounts = new NativeReference(Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var countsArrayPrefixSumJH = new PrefixSumCountsJob + { + array = countsArray, + finalValues = totalCounts + }.Schedule(Dependency); + + var totalSkeletonCountsByBuffer = + new NativeArray(boneMatsBufferList.boneMatricesBuffers.Count + 1, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var skeletonOffsetsByBuffer = new NativeArray(boneMatsBufferList.boneMatricesBuffers.Count + 1, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + var skeletonCountsByBufferByBatchPrefixSumJH = new PrefixSumPerBufferIdSkeletonCountsJob + { + counts = skeletonCountsByBufferByBatch, + finalValues = totalSkeletonCountsByBuffer, + offsetsByBuffer = skeletonOffsetsByBuffer, + numberOfBatches = skeletonChunkCount + }.Schedule(Dependency); + + JobHandle.ScheduleBatchedJobs(); + + var pool = worldBlackboardEntity.GetCollectionComponent(false, out var poolJH).pool; + poolJH.Complete(); + Profiler.EndSample(); + + countsArrayPrefixSumJH.Complete(); + if (totalCounts.Value.skeletonCount == 0) + { + // Cleanup and early exit. + var dependencyList = new NativeList(6, Allocator.Temp); + dependencyList.Add(meshDataStream.Dispose(default)); + dependencyList.Add(countsArray.Dispose(default)); + dependencyList.Add(skeletonCountsByBufferByBatch.Dispose(skeletonCountsByBufferByBatchPrefixSumJH)); + dependencyList.Add(totalCounts.Dispose(default)); + dependencyList.Add(totalSkeletonCountsByBuffer.Dispose(skeletonCountsByBufferByBatchPrefixSumJH)); + dependencyList.Add(skeletonOffsetsByBuffer.Dispose(skeletonCountsByBufferByBatchPrefixSumJH)); + Dependency = JobHandle.CombineDependencies(dependencyList); + return; + } + + Profiler.BeginSample("Setup write jobs"); + var skinningMetaBuffer = pool.GetSkinningMetaBuffer(totalCounts.Value.meshCount * 2 + totalCounts.Value.skeletonCount); + var skinningMetaArray = skinningMetaBuffer.BeginWrite(0, totalCounts.Value.meshCount * 2 + totalCounts.Value.skeletonCount); + NativeArray boneMatsArray; + UnityEngine.ComputeBuffer boneMatsBuffer = null; + + if (totalCounts.Value.boneCount > 0) + { + boneMatsBuffer = pool.GetBonesBuffer(totalCounts.Value.boneCount); + boneMatsArray = boneMatsBuffer.BeginWrite(0, totalCounts.Value.boneCount); + } + else + { + boneMatsArray = new NativeArray(0, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + } + + Dependency = new WriteBuffersJob + { + skinnedMeshesBufferHandle = skinnedMeshesBufferHandle, + boneReferenceBufferHandle = boneReferenceBufferHandle, + optimizedBoneBufferHandle = optimizedBoneBufferHandle, + ltwCdfe = GetComponentDataFromEntity(true), + ltwHandle = GetComponentTypeHandle(true), + meshDataStream = meshDataStream.AsReader(), + countsArray = countsArray, + skeletonOffsetsByBuffer = skeletonOffsetsByBuffer, + perFrameMetadataHandle = GetComponentTypeHandle(false), + skeletonCountsByBufferByBatch = skeletonCountsByBufferByBatch, + boneMatsBuffer = boneMatsArray, + metaBuffer = skinningMetaArray, + skeletonCount = totalCounts.Value.skeletonCount, + bufferId = boneMatsBufferList.boneMatricesBuffers.Count, + }.ScheduleParallel(m_skeletonQuery, skeletonCountsByBufferByBatchPrefixSumJH); + + JobHandle.ScheduleBatchedJobs(); + + // While that heavy job is running, try and do whatever else we need to do in this system so that after we complete the job, we can exit out as fast as possible. + int verticesRequired = worldBlackboardEntity.GetComponentData().verticesCount; + var deformBuffer = pool.GetDeformBuffer(verticesRequired); + int matricesRequired = worldBlackboardEntity.GetComponentData().matricesCount; + var linearBlendSkinningBuffer = pool.GetLbsMatsBuffer(matricesRequired); + var gpuUploadbuffers = worldBlackboardEntity.GetCollectionComponent(false, out var gpuUploadBuffersJH); + + var disposeDependencies = new NativeList(Allocator.Temp); + disposeDependencies.Add(meshDataStream.Dispose(Dependency)); + disposeDependencies.Add(countsArray.Dispose(Dependency)); + disposeDependencies.Add(skeletonCountsByBufferByBatch.Dispose(Dependency)); + if (boneMatsBuffer == null) + { + disposeDependencies.Add(boneMatsArray.Dispose(Dependency)); + } + else + { + boneMatsBufferList.boneMatricesBuffers.Add(boneMatsBuffer); + } + + gpuUploadBuffersJH.Complete(); + m_batchSkinningShader.SetBuffer(0, _dstMats, linearBlendSkinningBuffer); + m_batchSkinningShader.SetBuffer(0, _dstVertices, deformBuffer); + m_batchSkinningShader.SetBuffer(0, _srcVertices, gpuUploadbuffers.verticesBuffer); + m_batchSkinningShader.SetBuffer(0, _boneWeights, gpuUploadbuffers.weightsBuffer); + m_batchSkinningShader.SetBuffer(0, _bindPoses, gpuUploadbuffers.bindPosesBuffer); + m_batchSkinningShader.SetBuffer(0, _boneOffsets, gpuUploadbuffers.boneOffsetsBuffer); + m_batchSkinningShader.SetBuffer(0, _metaBuffer, skinningMetaBuffer); + + int boneMatsWriteCount = totalCounts.Value.boneCount; + int skinningMetaWriteCount = totalCounts.Value.meshCount * 2 + totalCounts.Value.skeletonCount; + totalCounts.Dispose(); + Profiler.EndSample(); + + // Alright. It is go time! + gpuUploadBuffersJH.Complete(); + CompleteDependency(); + + //foreach (var metaVal in skinningMetaArray) + // UnityEngine.Debug.LogError(metaVal); + + Profiler.BeginSample("Dispatch Compute Shaders"); + if (boneMatsBuffer != null) + boneMatsBuffer.EndWrite(boneMatsWriteCount); + skinningMetaBuffer.EndWrite(skinningMetaWriteCount); + for (int bufferId = 0; bufferId < skeletonOffsetsByBuffer.Length; bufferId++) + { + int skeletonCount = totalSkeletonCountsByBuffer[bufferId]; + if (skeletonCount <= 0) + continue; + + m_batchSkinningShader.SetBuffer(0, _skeletonMats, boneMatsBufferList.boneMatricesBuffers[bufferId]); + for (int dispatchesRemaining = skeletonCount, offset = skeletonOffsetsByBuffer[bufferId]; dispatchesRemaining > 0;) + { + int dispatchCount = math.min(dispatchesRemaining, 65535); + m_batchSkinningShader.SetInt(_startOffset, offset); + m_batchSkinningShader.Dispatch(0, dispatchCount, 1, 1); + offset += dispatchCount; + dispatchesRemaining -= dispatchCount; + //UnityEngine.Debug.Log($"Dispatching skinning dispatchCount: {dispatchCount}"); + } + } + UnityEngine.Shader.SetGlobalBuffer(_DeformedMeshData, deformBuffer); + UnityEngine.Shader.SetGlobalBuffer(_SkinMatrices, linearBlendSkinningBuffer); + Profiler.EndSample(); + + Profiler.BeginSample("Cleanup"); + disposeDependencies.Add(totalSkeletonCountsByBuffer.Dispose(default)); + disposeDependencies.Add(skeletonOffsetsByBuffer.Dispose(default)); + Dependency = JobHandle.CombineDependencies(disposeDependencies); + Profiler.EndSample(); + } + + struct MeshDataStreamHeader + { + public int indexInSkeletonChunk; + public int meshCount; + } + + struct MeshDataStreamElement + { + public int indexInDependentBuffer; + public uint computeDeformShaderIndex; + public int linearBlendShaderIndex; + public uint operationsCode; + + public const uint linearBlendOpCode = 1; + public const uint computeSkinningFromSrcOpCode = 2; + public const uint computeSkinningFromDstOpCode = 4; + } + + struct CountsElement + { + public int boneCount; // For new bufferId + public int skeletonCount; // For all bufferIds + public int meshCount; // For all bufferIds + } + + [BurstCompile] + struct CollectMeshMetadataJob : IJobEntityBatch + { + [ReadOnly] public EntityTypeHandle entityHandle; + [ReadOnly] public BufferTypeHandle skinnedMeshesBufferHandle; + [ReadOnly] public ComponentTypeHandle perFrameMetadataHandle; + [ReadOnly] public ComponentTypeHandle skeletonCullingMaskHandle; + + [ReadOnly] public BufferTypeHandle boneReferenceBufferHandle; + [ReadOnly] public BufferTypeHandle optimizedBoneBufferHandle; + + [ReadOnly] public ComponentTypeHandle meshPerCameraCullingMaskHandle; + [ReadOnly] public ComponentTypeHandle meshPerFrameCullingMaskHandle; + [ReadOnly] public ComponentDataFromEntity computeDeformShaderIndexCdfe; + [ReadOnly] public ComponentDataFromEntity linearBlendSkinningShaderIndexCdfe; + [ReadOnly] public StorageInfoFromEntity sife; + + [NativeDisableParallelForRestriction] public NativeStream.Writer meshDataStream; + [NativeDisableParallelForRestriction] public NativeArray countsArray; + [NativeDisableParallelForRestriction] public NativeArray skeletonCountsByBufferByBatch; + + public int bufferId; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + meshDataStream.BeginForEachIndex(batchIndex); + int boneCount = 0; + int skeletonCount = 0; + int meshCount = 0; + + int stride = bufferId + 1; + var skeletonCountsByBuffer = skeletonCountsByBufferByBatch.GetSubArray(stride * batchIndex, stride); + if (batchInChunk.Has(boneReferenceBufferHandle)) + { + ProcessExposed(batchInChunk, ref boneCount, ref skeletonCount, ref meshCount, skeletonCountsByBuffer); + } + else if (batchInChunk.Has(optimizedBoneBufferHandle)) + { + ProcessOptimized(batchInChunk, ref boneCount, ref skeletonCount, ref meshCount, skeletonCountsByBuffer); + } + + countsArray[batchIndex] = new CountsElement + { + boneCount = boneCount, + skeletonCount = skeletonCount, + meshCount = meshCount + }; + meshDataStream.EndForEachIndex(); + } + + void ProcessExposed(ArchetypeChunk batchInChunk, ref int batchBoneCount, ref int skeletonCount, ref int meshCount, NativeArray skeletonCountsByBuffer) + { + var entityArray = batchInChunk.GetNativeArray(entityHandle); + var skinnedMeshesAccessor = batchInChunk.GetBufferAccessor(skinnedMeshesBufferHandle); + var boneBufferAccessor = batchInChunk.GetBufferAccessor(boneReferenceBufferHandle); + var perFrameMetadataArray = batchInChunk.GetNativeArray(perFrameMetadataHandle); + var skeletonCullingMask = batchInChunk.GetChunkComponentData(skeletonCullingMaskHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var mask = i >= 64 ? skeletonCullingMask.upper : skeletonCullingMask.lower; + if (mask.IsSet(i % 64)) + { + if (CollectMeshData(entityArray[i], skinnedMeshesAccessor[i].AsNativeArray(), ref meshCount, i, boneBufferAccessor[i].Length)) + { + skeletonCount++; + if ( perFrameMetadataArray[i].bufferId < 0) + { + batchBoneCount += boneBufferAccessor[i].Length; + skeletonCountsByBuffer[bufferId]++; + } + else + { + skeletonCountsByBuffer[perFrameMetadataArray[i].bufferId]++; + } + } + } + } + } + + void ProcessOptimized(ArchetypeChunk batchInChunk, ref int batchBoneCount, ref int skeletonCount, ref int meshCount, NativeArray skeletonCountsByBuffer) + { + var entityArray = batchInChunk.GetNativeArray(entityHandle); + var skinnedMeshesAccessor = batchInChunk.GetBufferAccessor(skinnedMeshesBufferHandle); + var boneBufferAccessor = batchInChunk.GetBufferAccessor(optimizedBoneBufferHandle); + var perFrameMetadataArray = batchInChunk.GetNativeArray(perFrameMetadataHandle); + var skeletonCullingMask = batchInChunk.GetChunkComponentData(skeletonCullingMaskHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var mask = i >= 64 ? skeletonCullingMask.upper : skeletonCullingMask.lower; + if (mask.IsSet(i % 64)) + { + if (CollectMeshData(entityArray[i], skinnedMeshesAccessor[i].AsNativeArray(), ref meshCount, i, boneBufferAccessor.Length)) + { + skeletonCount++; + if (perFrameMetadataArray[i].bufferId < 0) + { + batchBoneCount += boneBufferAccessor[i].Length; + skeletonCountsByBuffer[bufferId]++; + } + else + { + skeletonCountsByBuffer[perFrameMetadataArray[i].bufferId]++; + } + } + } + } + } + + // Returns true if new meshes need skinning. + // Already skinned meshes will update the component but not add to the totals nor require any further processing. + unsafe bool CollectMeshData(Entity skeletonEntity, NativeArray meshes, ref int meshCount, int indexInBatch, int skeletonBonesCount) + { + MeshDataStreamHeader* header = null; + + for (int i = 0; i < meshes.Length; i++) + { + var meshEntity = meshes[i].skinnedMesh; + + if (skeletonBonesCount + meshes[i].meshBindPosesCount > 682) + { + UnityEngine.Debug.LogError( + $"Skeleton entity {skeletonEntity} has {skeletonBonesCount} bones. Skinned mesh entity {meshEntity} has {meshes[i].meshBindPosesCount} bone references. The sum of these exceed the max shader capacity of 682."); + continue; + } + + var storageInfo = sife[meshEntity]; + var cameraMask = storageInfo.Chunk.GetChunkComponentData(meshPerCameraCullingMaskHandle); + var frameMask = storageInfo.Chunk.GetChunkComponentData(meshPerFrameCullingMaskHandle); + bool isNewMesh = false; + if (storageInfo.IndexInChunk >= 64) + { + cameraMask.upper.Value &= ~frameMask.upper.Value; + isNewMesh = cameraMask.upper.IsSet(storageInfo.IndexInChunk - 64); + } + else + { + cameraMask.lower.Value &= ~frameMask.lower.Value; + isNewMesh = cameraMask.lower.IsSet(storageInfo.IndexInChunk); + } + + if (isNewMesh) + { + if (header == null) + { + header = (MeshDataStreamHeader*)UnsafeUtility.AddressOf(ref meshDataStream.Allocate()); + header->indexInSkeletonChunk = indexInBatch; + header->meshCount = 0; + } + + bool hasComputeDeform = computeDeformShaderIndexCdfe.HasComponent(meshEntity); + bool hasLinearBlend = linearBlendSkinningShaderIndexCdfe.HasComponent(meshEntity); + + meshDataStream.Write(new MeshDataStreamElement + { + computeDeformShaderIndex = hasComputeDeform ? computeDeformShaderIndexCdfe[meshEntity].firstVertexIndex : 0, + linearBlendShaderIndex = hasLinearBlend ? linearBlendSkinningShaderIndexCdfe[meshEntity].firstMatrixIndex : 0, + indexInDependentBuffer = i, + operationsCode = math.select(0, MeshDataStreamElement.linearBlendOpCode, hasLinearBlend) + + math.select(0, MeshDataStreamElement.computeSkinningFromSrcOpCode, hasComputeDeform) + }); + header->meshCount++; + meshCount++; + } + } + + return header != null; + } + } + + [BurstCompile] + struct PrefixSumCountsJob : IJob + { + public NativeArray array; + public NativeReference finalValues; + + public void Execute() + { + CountsElement running = default; + for (int i = 0; i < array.Length; i++) + { + var temp = array[i]; + array[i] = running; + running.boneCount += temp.boneCount; + running.skeletonCount += temp.skeletonCount; + running.meshCount += temp.meshCount; + } + + finalValues.Value = running; + } + } + + [BurstCompile] + struct PrefixSumPerBufferIdSkeletonCountsJob : IJob + { + public NativeArray counts; + public NativeArray finalValues; + public NativeArray offsetsByBuffer; + public int numberOfBatches; + + public void Execute() + { + int stride = finalValues.Length; + var temp = new NativeArray(stride, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < numberOfBatches; i++) + { + NativeArray.Copy(counts, i * stride, temp, 0, stride); + NativeArray.Copy(finalValues, 0, counts, i * stride, stride); + for (int j = 0; j < stride; j++) + { + finalValues[j] += temp[j]; + } + } + + int offset = 0; + for (int i = 0; i < stride; i++) + { + offsetsByBuffer[i] = offset; + offset += finalValues[i]; + } + } + } + + [BurstCompile] + struct WriteBuffersJob : IJobEntityBatch + { + [ReadOnly] public BufferTypeHandle skinnedMeshesBufferHandle; + + [ReadOnly] public BufferTypeHandle boneReferenceBufferHandle; + [ReadOnly] public BufferTypeHandle optimizedBoneBufferHandle; + + [ReadOnly] public ComponentDataFromEntity ltwCdfe; + [ReadOnly] public ComponentTypeHandle ltwHandle; + + [ReadOnly] public NativeStream.Reader meshDataStream; + [ReadOnly] public NativeArray countsArray; + + [ReadOnly] public NativeArray skeletonOffsetsByBuffer; + + public ComponentTypeHandle perFrameMetadataHandle; + + [NativeDisableParallelForRestriction] public NativeArray skeletonCountsByBufferByBatch; + [NativeDisableParallelForRestriction] public NativeArray boneMatsBuffer; + [NativeDisableParallelForRestriction] public NativeArray metaBuffer; + + public int skeletonCount; + public int bufferId; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + int count = meshDataStream.BeginForEachIndex(batchIndex); + if (count == 0) + { + meshDataStream.EndForEachIndex(); + return; + } + + var countsElement = countsArray[batchIndex]; + int stride = bufferId + 1; + var skeletonCountsByBuffer = skeletonCountsByBufferByBatch.GetSubArray(stride * batchIndex, stride); + + if (batchInChunk.Has(boneReferenceBufferHandle)) + { + ProcessExposed(batchInChunk, countsElement, count, skeletonCountsByBuffer); + } + else if (batchInChunk.Has(optimizedBoneBufferHandle)) + { + ProcessOptimized(batchInChunk, countsElement, count, skeletonCountsByBuffer); + } + + meshDataStream.EndForEachIndex(); + } + + void ProcessExposed(ArchetypeChunk batchInChunk, CountsElement countsElement, int streamWriteCount, NativeArray skeletonCountsByBuffer) + { + int boneOffset = countsElement.boneCount; + int meshOffset = countsElement.meshCount * 2 + skeletonCount; + + var perFrameMetaArray = batchInChunk.GetNativeArray(perFrameMetadataHandle); + var meshesAccessor = batchInChunk.GetBufferAccessor(skinnedMeshesBufferHandle); + + var bonesAccessor = batchInChunk.GetBufferAccessor(boneReferenceBufferHandle); + var skeletonLtws = batchInChunk.GetNativeArray(ltwHandle); + + for (int streamWrites = 0; streamWrites < streamWriteCount;) + { + var header = meshDataStream.Read(); + streamWrites++; + + var bones = bonesAccessor[header.indexInSkeletonChunk].AsNativeArray(); + + bool alreadyUploaded = perFrameMetaArray[header.indexInSkeletonChunk].bufferId >= 0; + int targetBuffer = math.select(bufferId, perFrameMetaArray[header.indexInSkeletonChunk].bufferId, alreadyUploaded); + int skeletonIndex = skeletonCountsByBuffer[targetBuffer] + skeletonOffsetsByBuffer[targetBuffer]; + skeletonCountsByBuffer[targetBuffer]++; + metaBuffer[skeletonIndex] = new uint4 + { + x = (uint)boneOffset, + y = (uint)bones.Length, + z = (uint)meshOffset, + w = (uint)header.meshCount + }; + + if (!alreadyUploaded) + { + float4x4 worldToRoot = math.inverse(skeletonLtws[header.indexInSkeletonChunk].Value); + for (int i = 0; i < bones.Length; i++) + { + var entity = bones[i].bone; + var boneToWorld = ltwCdfe[entity].Value; + var boneToRoot = math.mul(worldToRoot, boneToWorld); + boneMatsBuffer[boneOffset + i] = Shrink(boneToRoot); + } + + boneOffset += bones.Length; + } + + var meshes = meshesAccessor[header.indexInSkeletonChunk].AsNativeArray(); + ProcessMeshes(meshes, header.meshCount, ref meshOffset, ref streamWrites); + } + } + + void ProcessOptimized(ArchetypeChunk batchInChunk, CountsElement countsElement, int streamWriteCount, NativeArray skeletonCountsByBuffer) + { + int boneOffset = countsElement.boneCount; + int meshOffset = countsElement.meshCount * 2 + skeletonCount; + + var perFrameMetaArray = batchInChunk.GetNativeArray(perFrameMetadataHandle); + var meshesAccessor = batchInChunk.GetBufferAccessor(skinnedMeshesBufferHandle); + + var bonesAccessor = batchInChunk.GetBufferAccessor(optimizedBoneBufferHandle); + + for (int streamWrites = 0; streamWrites < streamWriteCount;) + { + var header = meshDataStream.Read(); + streamWrites++; + + var bones = bonesAccessor[header.indexInSkeletonChunk].AsNativeArray(); + + bool alreadyUploaded = perFrameMetaArray[header.indexInSkeletonChunk].bufferId >= 0; + int targetBuffer = math.select(bufferId, perFrameMetaArray[header.indexInSkeletonChunk].bufferId, alreadyUploaded); + int skeletonIndex = skeletonCountsByBuffer[targetBuffer] + skeletonOffsetsByBuffer[targetBuffer]; + skeletonCountsByBuffer[targetBuffer]++; + metaBuffer[skeletonIndex] = new uint4 + { + x = (uint)boneOffset, + y = (uint)bones.Length, + z = (uint)meshOffset, + w = (uint)header.meshCount + }; + + if (!alreadyUploaded) + { + for (int i = 0; i < bones.Length; i++) + { + boneMatsBuffer[boneOffset + i] = Shrink(bones[i].boneToRoot); + } + boneOffset += bones.Length; + } + + var meshes = meshesAccessor[header.indexInSkeletonChunk].AsNativeArray(); + ProcessMeshes(meshes, header.meshCount, ref meshOffset, ref streamWrites); + } + } + + void ProcessMeshes(NativeArray meshes, int meshCount, ref int meshOffset, ref int streamWrites) + { + for (int i = 0; i < meshCount; i++) + { + var element = meshDataStream.Read(); + streamWrites++; + + var mesh = meshes[element.indexInDependentBuffer]; + metaBuffer[meshOffset] = new uint4 + { + x = element.operationsCode | ((uint)mesh.meshBindPosesCount << 16), + y = (uint)mesh.meshBindPosesStart, + z = (uint)mesh.boneOffsetsStart, + w = (uint)element.linearBlendShaderIndex + }; + meshOffset++; + metaBuffer[meshOffset] = new uint4 + { + x = (uint)mesh.meshVerticesStart, + y = (uint)mesh.meshVerticesCount, + z = (uint)mesh.meshWeightsStart, + w = element.computeDeformShaderIndex + }; + meshOffset++; + } + } + + float3x4 Shrink(float4x4 a) + { + return new float3x4(a.c0.xyz, a.c1.xyz, a.c2.xyz, a.c3.xyz); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/SkinningDispatchSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/SkinningDispatchSystem.cs.meta new file mode 100644 index 0000000..dc0e4d8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/SkinningDispatchSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 014e458add24f7a46a60107ecedba005 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateLODsSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateLODsSystem.cs new file mode 100644 index 0000000..117104d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateLODsSystem.cs @@ -0,0 +1,227 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +// Unity's LOD system is currently a bit of a mess. The behavior of this version has been modified for more +// predictable behavior at the cost of performance. Some of the things that make no sense in Unity's original +// implementation which have been modified in this version include: +// 1) ForceLowLOD is created with cleared memory and then assigned to the SelectLodEnabled job as [ReadOnly]. +// It is not touched anywhere else in HR V2. +// 2) The algorithm tries to cache LOD levels by camera. This doesn't make much sense since there is typically +// more than one camera rendering (shadows). However, since the checks are cheap and there's a chance that +// two cameras have similar LOD requirements, I'm leaving that logic in. +// 3) There's a ResetLod function which is public API, but is only called at the beginning of each render by +// HybridV2RenderSystem. I suspect this is to cover structural changes. But since we have a real system, we +// can do that a smarter way using order versions. +// 4) CullingStats gets cleared immediately after scheduling a job using it every time there's a LOD update. +// I think this is actually supposed to be cleared every frame. + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public unsafe partial struct UpdateLODsSystem : ISystem + { + LODGroupExtensions.LODParams m_PrevLODParams; + float3 m_PrevCameraPos; + float m_PrevLodDistanceScale; + + EntityQuery m_query; + + int m_lastLodRangeOrderVersion; + int m_lastChunkInfoOrderVersion; + bool m_firstRun; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(true).WithAll(false).Build(); + m_firstRun = true; + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + var lodParams = LODGroupExtensions.CalculateLODParams(state.GetWorldBlackboardEntity().GetComponentData().lodParameters); + + bool lodParamsMatchPrev = lodParams.Equals(m_PrevLODParams); + var resetLod = !lodParamsMatchPrev; + resetLod |= m_firstRun; + resetLod |= (state.EntityManager.GetComponentOrderVersion() - m_lastLodRangeOrderVersion) > 0; + resetLod |= (state.EntityManager.GetComponentOrderVersion() - m_lastChunkInfoOrderVersion) > 0; + + if (resetLod) + { + float cameraMoveDistance = math.length(m_PrevCameraPos - lodParams.cameraPos); + var lodDistanceScaleChanged = lodParams.distanceScale != m_PrevLodDistanceScale; + + var selectLodEnabledJob = new SelectLodEnabled + { + lodParamsMatchPrev = lodParamsMatchPrev, + lastSystemVersion = state.LastSystemVersion, + + LODParams = lodParams, + RootLODRanges = state.GetComponentTypeHandle(true), + RootLODReferencePoints = state.GetComponentTypeHandle(true), + LODRanges = state.GetComponentTypeHandle(true), + LODReferencePoints = state.GetComponentTypeHandle(true), + HybridChunkInfo = state.GetComponentTypeHandle(), + ChunkHeader = state.GetComponentTypeHandle(), + CameraMoveDistanceFixed16 = + Fixed16CamDistance.FromFloatCeil(cameraMoveDistance * lodParams.distanceScale), + DistanceScale = lodParams.distanceScale, + DistanceScaleChanged = lodDistanceScaleChanged, + }; + + state.Dependency = selectLodEnabledJob.ScheduleParallel(m_query, state.Dependency); + + m_PrevLODParams = lodParams; + m_PrevLodDistanceScale = lodParams.distanceScale; + m_PrevCameraPos = lodParams.cameraPos; + m_firstRun = false; + } + m_lastLodRangeOrderVersion = state.EntityManager.GetComponentOrderVersion(); + m_lastChunkInfoOrderVersion = state.EntityManager.GetComponentOrderVersion(); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + unsafe struct SelectLodEnabled : IJobEntityBatch + { + public bool lodParamsMatchPrev; + public uint lastSystemVersion; + + [ReadOnly] public LODGroupExtensions.LODParams LODParams; + [ReadOnly] public ComponentTypeHandle RootLODRanges; + [ReadOnly] public ComponentTypeHandle RootLODReferencePoints; + [ReadOnly] public ComponentTypeHandle LODRanges; + [ReadOnly] public ComponentTypeHandle LODReferencePoints; + public ushort CameraMoveDistanceFixed16; + public float DistanceScale; + public bool DistanceScaleChanged; + + public ComponentTypeHandle HybridChunkInfo; + [ReadOnly] public ComponentTypeHandle ChunkHeader; + + public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex) + { + var hybridChunkInfoArray = archetypeChunk.GetNativeArray(HybridChunkInfo); + var chunkHeaderArray = archetypeChunk.GetNativeArray(ChunkHeader); + + for (var entityIndex = 0; entityIndex < archetypeChunk.Count; entityIndex++) + { + var hybridChunkInfo = hybridChunkInfoArray[entityIndex]; + if (!hybridChunkInfo.Valid) + continue; + + var chunkHeader = chunkHeaderArray[entityIndex]; + var chunk = chunkHeader.ArchetypeChunk; + + var chunkOrderChanged = chunk.DidOrderChange(lastSystemVersion); + + var internalBatchIndex = hybridChunkInfo.InternalIndex; + var chunkInstanceCount = chunk.Count; + var isOrtho = LODParams.isOrtho; + + ref var chunkCullingData = ref hybridChunkInfo.CullingData; + ChunkInstanceLodEnabled chunkEntityLodEnabled = chunkCullingData.InstanceLodEnableds; + + if (0 == (chunkCullingData.Flags & HybridChunkCullingData.kFlagHasLodData)) + { + chunkEntityLodEnabled.Enabled[0] = 0; + chunkEntityLodEnabled.Enabled[1] = 0; + for (int i = 0; i < chunkInstanceCount; ++i) + { + int wordIndex = i >> 6; + int bitIndex = i & 63; + chunkEntityLodEnabled.Enabled[wordIndex] |= 1ul << bitIndex; + } + } + else + { + int diff = (int)chunkCullingData.MovementGraceFixed16 - CameraMoveDistanceFixed16; + chunkCullingData.MovementGraceFixed16 = (ushort)math.max(0, diff); + + var graceExpired = chunkCullingData.MovementGraceFixed16 == 0; + if (graceExpired || DistanceScaleChanged || chunkOrderChanged) + { + chunkEntityLodEnabled.Enabled[0] = 0; + chunkEntityLodEnabled.Enabled[1] = 0; + + var rootLODRanges = chunk.GetNativeArray(RootLODRanges); + var rootLODReferencePoints = chunk.GetNativeArray(RootLODReferencePoints); + var lodRanges = chunk.GetNativeArray(LODRanges); + var lodReferencePoints = chunk.GetNativeArray(LODReferencePoints); + + float graceDistance = float.MaxValue; + + for (int i = 0; i < chunkInstanceCount; i++) + { + var rootLODRange = rootLODRanges[i]; + var rootLODReferencePoint = rootLODReferencePoints[i]; + + var rootLodDistance = + math.select( + DistanceScale * + math.length(LODParams.cameraPos - rootLODReferencePoint.Value), + DistanceScale, isOrtho); + + float rootMinDist = rootLODRange.LOD.MinDist; + float rootMaxDist = rootLODRange.LOD.MaxDist; + + graceDistance = math.min(math.abs(rootLodDistance - rootMinDist), graceDistance); + graceDistance = math.min(math.abs(rootLodDistance - rootMaxDist), graceDistance); + + var rootLodIntersect = (rootLodDistance < rootMaxDist) && (rootLodDistance >= rootMinDist); + + if (rootLodIntersect) + { + var lodRange = lodRanges[i]; + var lodReferencePoint = lodReferencePoints[i]; + + var instanceDistance = + math.select( + DistanceScale * + math.length(LODParams.cameraPos - + lodReferencePoint.Value), DistanceScale, + isOrtho); + + var instanceLodIntersect = + (instanceDistance < lodRange.MaxDist) && + (instanceDistance >= lodRange.MinDist); + + graceDistance = math.min(math.abs(instanceDistance - lodRange.MinDist), + graceDistance); + graceDistance = math.min(math.abs(instanceDistance - lodRange.MaxDist), + graceDistance); + + if (instanceLodIntersect) + { + var index = i; + var wordIndex = index >> 6; + var bitIndex = index & 0x3f; + var lodWord = chunkEntityLodEnabled.Enabled[wordIndex]; + + lodWord |= 1UL << bitIndex; + chunkEntityLodEnabled.Enabled[wordIndex] = lodWord; + } + } + } + + chunkCullingData.MovementGraceFixed16 = Fixed16CamDistance.FromFloatFloor(graceDistance); + } + } + + chunkCullingData.InstanceLodEnableds = chunkEntityLodEnabled; + hybridChunkInfoArray[entityIndex] = hybridChunkInfo; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateLODsSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateLODsSystem.cs.meta new file mode 100644 index 0000000..a54f78d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateLODsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b916a1bb54b4f7a438426576d968e7de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateVisibilitiesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateVisibilitiesSystem.cs new file mode 100644 index 0000000..31812b0 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateVisibilitiesSystem.cs @@ -0,0 +1,145 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; +using UnityEngine.Rendering; + +// Unlike the LOD system, Unity's culling implementation is much more sensible. +// There's a couple of oddities that I have corrected, such as using Intersect2NoPartial in opportune locations +// and using Temp memory for the ThreadLocalIndexLists. +// But otherwise, most modifications are for shoehorning the skinning flags. +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class UpdateVisibilitiesSystem : SubSystem + { + EntityQuery m_query; + + protected override void OnCreate() + { + m_query = Fluent.WithAll(true).WithAll(false).WithAll(true).Build(); + } + + protected unsafe override void OnUpdate() + { + var brgCullingContext = worldBlackboardEntity.GetCollectionComponent(); + + Dependency = new ZeroVisibleCountsJob + { + Batches = brgCullingContext.cullingContext.batchVisibility + }.ScheduleParallel(brgCullingContext.cullingContext.batchVisibility.Length, 16, Dependency); + + Dependency = new WriteVisibilitiesJob + { + internalToExternalRemappingTable = brgCullingContext.internalToExternalMappingIds, + chunkHeaderHandle = GetComponentTypeHandle(true), + hybridChunkInfoHandle = GetComponentTypeHandle(false), + perCameraMaskHandle = GetComponentTypeHandle(false), + perFrameMaskHandle = GetComponentTypeHandle(false), + batches = brgCullingContext.cullingContext.batchVisibility, + indexList = brgCullingContext.cullingContext.visibleIndices + }.ScheduleParallel(m_query, Dependency); + } + + [BurstCompile] + unsafe struct ZeroVisibleCountsJob : IJobFor + { + public NativeArray Batches; + + public void Execute(int index) + { + // Can't set individual fields of structs inside NativeArray, so do it via raw pointer + ((BatchVisibility*)Batches.GetUnsafePtr())[index].visibleCount = 0; + } + } + + [BurstCompile] + unsafe struct WriteVisibilitiesJob : IJobEntityBatch + { + [ReadOnly] public NativeArray internalToExternalRemappingTable; + + [ReadOnly] public ComponentTypeHandle chunkHeaderHandle; + public ComponentTypeHandle hybridChunkInfoHandle; + public ComponentTypeHandle perCameraMaskHandle; + public ComponentTypeHandle perFrameMaskHandle; + + [NativeDisableParallelForRestriction] public NativeArray indexList; + [NativeDisableParallelForRestriction] public NativeArray batches; + + public void Execute(ArchetypeChunk archetypeChunk, int chunkIndex) + { + var hybridChunkInfoArray = archetypeChunk.GetNativeArray(hybridChunkInfoHandle); + var chunkHeaderArray = archetypeChunk.GetNativeArray(chunkHeaderHandle); + var perCameraMaskArray = archetypeChunk.GetNativeArray(perCameraMaskHandle); + var perFrameMaskArray = archetypeChunk.GetNativeArray(perFrameMaskHandle); + + for (var metaIndex = 0; metaIndex < archetypeChunk.Count; metaIndex++) + { + var perCameraMask = perCameraMaskArray[metaIndex]; + var perFrameMask = perFrameMaskArray[metaIndex]; + perFrameMask.lower.Value |= perCameraMask.lower.Value; + perFrameMask.upper.Value |= perCameraMask.upper.Value; + perFrameMaskArray[metaIndex] = perFrameMask; + perCameraMaskArray[metaIndex] = default; + + var hybridChunkInfo = hybridChunkInfoArray[metaIndex]; + if (!hybridChunkInfo.Valid) + continue; + + var chunkHeader = chunkHeaderArray[metaIndex]; + + int internalBatchIndex = hybridChunkInfo.InternalIndex; + int externalBatchIndex = internalToExternalRemappingTable[internalBatchIndex]; + + int chunkOutputOffset = 0; + int instanceOutputOffset = 0; + + ref var chunkCullingData = ref hybridChunkInfo.CullingData; + + var pBatch = ((BatchVisibility*)batches.GetUnsafePtr()) + externalBatchIndex; + + int batchOutputOffset = pBatch->offset; + int processedInstanceCount = chunkCullingData.BatchOffset; + + var chunkInstanceCount = chunkHeader.ArchetypeChunk.Count; + var anyVisible = (perCameraMask.upper.Value | perCameraMask.lower.Value) != 0; + + if (anyVisible) + { + // Since the chunk is fully in, we can easily count how many instances we will output + int chunkOutputCount = perCameraMask.lower.CountBits() + perCameraMask.upper.CountBits(); + + chunkOutputOffset = System.Threading.Interlocked.Add( + ref pBatch->visibleCount, chunkOutputCount) - chunkOutputCount; + + chunkOutputOffset += batchOutputOffset; + + for (int j = 0; j < 2; j++) + { + var lodWord = math.select(perCameraMask.lower.Value, perCameraMask.upper.Value, j == 1); + + while (lodWord != 0) + { + var bitIndex = math.tzcnt(lodWord); + var finalIndex = (j << 6) + bitIndex; + indexList[chunkOutputOffset + instanceOutputOffset] = + processedInstanceCount + finalIndex; + + instanceOutputOffset += 1; + lodWord ^= 1ul << bitIndex; + } + } + } + chunkCullingData.StartIndex = (short)chunkOutputOffset; + chunkCullingData.Visible = (short)instanceOutputOffset; + hybridChunkInfoArray[metaIndex] = hybridChunkInfo; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateVisibilitiesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateVisibilitiesSystem.cs.meta new file mode 100644 index 0000000..35c061e --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UpdateVisibilitiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90aad5e181af2c646bb1d9c518498391 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UploadMaterialPropertiesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UploadMaterialPropertiesSystem.cs new file mode 100644 index 0000000..99d9534 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UploadMaterialPropertiesSystem.cs @@ -0,0 +1,521 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; +using UnityEngine.Profiling; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class UploadMaterialPropertiesSystem : SubSystem + { + EntityQuery m_metaQuery; + + UnityEngine.ComputeBuffer m_GPUPersistentInstanceData; + int m_persistentInstanceDataSize = 32 * 1024 * 1024; + + const int kGPUUploaderChunkSize = 4 * 1024 * 1024; + SparseUploader m_GPUUploader; + ThreadedSparseUploader m_ThreadedGPUUploader; + +#if DEBUG_LOG_MEMORY_USAGE + private static ulong PrevUsedSpace = 0; +#endif + + protected override void OnCreate() + { + m_metaQuery = Fluent.WithAll(false).WithAll(true).Build(); + + m_GPUPersistentInstanceData = new UnityEngine.ComputeBuffer( + m_persistentInstanceDataSize / 4, + 4, + UnityEngine.ComputeBufferType.Raw); + m_GPUUploader = new SparseUploader(m_GPUPersistentInstanceData, kGPUUploaderChunkSize); + } + + protected override unsafe void OnUpdate() + { + Profiler.BeginSample("GetBlackboardData"); + var context = worldBlackboardEntity.GetCollectionComponent(false); + var materialPropertyTypes = worldBlackboardEntity.GetBuffer(true); + Profiler.EndSample(); + + // Conservative estimate is that every known type is in every chunk. There will be + // at most one operation per type per chunk, which will be either an actual + // chunk data upload, or a default value blit (a single type should not have both). + int conservativeMaximumGpuUploads = context.hybridRenderedChunkCount * materialPropertyTypes.Length; + var gpuUploadOperations = new NativeArray( + conservativeMaximumGpuUploads, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + var numGpuUploadOperations = new NativeReference( + Allocator.TempJob, + NativeArrayOptions.ClearMemory); + + Profiler.BeginSample("GetTypeHandles"); + // Getting the chunk header here ensures that CheckedState() runs before we do anything crazy. + var chunkHeader = GetComponentTypeHandle(true); + + //context.componentTypeCache.FetchTypeHandles(this); + var worldUnmanaged = World.Unmanaged; + ComponentTypeCacheBurstHelpers.FetchTypeHandlesBursted(ref context.componentTypeCache, SystemHandleUntyped, ref worldUnmanaged, out var typeIndicesTemp); + //var componentTypes = context.componentTypeCache.ToBurstCompatible(Allocator.TempJob); + ComponentTypeCacheBurstHelpers.MakeBurstCompatibleTypeArray(ref context.componentTypeCache, out var componentTypes, Allocator.TempJob, typeIndicesTemp); + Profiler.EndSample(); + + Dependency = new ComputeOperationsJob + { + ChunkHeader = chunkHeader, + ChunkProperties = context.chunkProperties, + chunkPropertyDirtyMaskHandle = GetComponentTypeHandle(false), + chunkPerCameraCullingMaskHandle = GetComponentTypeHandle(true), + ComponentTypes = componentTypes, + GpuUploadOperations = gpuUploadOperations, + HybridChunkInfo = GetComponentTypeHandle(true), + LocalToWorldType = TypeManager.GetTypeIndex(), + NumGpuUploadOperations = numGpuUploadOperations, + PrevLocalToWorldType = TypeManager.GetTypeIndex(), + PrevWorldToLocalType = TypeManager.GetTypeIndex(), + WorldToLocalType = TypeManager.GetTypeIndex(), + }.ScheduleParallel(m_metaQuery, Dependency); + CompleteDependency(); + componentTypes.TypeIndexToArrayIndex.Dispose(); + Dependency = default; + + UnityEngine.Debug.Assert(numGpuUploadOperations.Value <= gpuUploadOperations.Length, "Maximum GPU upload operation count exceeded"); + + ComputeUploadSizeRequirements( + numGpuUploadOperations.Value, gpuUploadOperations, context.defaultValueBlits, + out int numOperations, out int totalUploadBytes, out int biggestUploadBytes); + +#if DEBUG_LOG_UPLOADS + if (numOperations > 0) + { + Debug.Log($"GPU upload operations: {numOperations}, GPU upload bytes: {totalUploadBytes}"); + } +#endif + + // BeginUpdate() + Profiler.BeginSample("StartUpdate"); + if (context.requiredPersistentBufferSize != m_persistentInstanceDataSize) + { + m_persistentInstanceDataSize = context.requiredPersistentBufferSize; + + var newBuffer = new UnityEngine.ComputeBuffer( + m_persistentInstanceDataSize / 4, + 4, + UnityEngine.ComputeBufferType.Raw); + m_GPUUploader.ReplaceBuffer(newBuffer, true); + + if (m_GPUPersistentInstanceData != null) + m_GPUPersistentInstanceData.Dispose(); + m_GPUPersistentInstanceData = newBuffer; + } + + m_ThreadedGPUUploader = m_GPUUploader.Begin(totalUploadBytes, biggestUploadBytes, numOperations); + Profiler.EndSample(); + + new ExecuteGpuUploads + { + GpuUploadOperations = gpuUploadOperations, + ThreadedSparseUploader = m_ThreadedGPUUploader, + }.Schedule(numGpuUploadOperations.Value, 1).Complete(); + numGpuUploadOperations.Dispose(); + gpuUploadOperations.Dispose(); + + // UploadAllBlits() + Profiler.BeginSample("UploadAllBlits"); + UploadBlitJob uploadJob = new UploadBlitJob() + { + BlitList = context.defaultValueBlits, + ThreadedSparseUploader = m_ThreadedGPUUploader + }; + Profiler.EndSample(); + + uploadJob.Schedule(context.defaultValueBlits.Length, 1).Complete(); + context.defaultValueBlits.Clear(); + + Profiler.BeginSample("EndUpdate"); + try + { + // EndUpdate() + m_GPUUploader.EndAndCommit(m_ThreadedGPUUploader); + // Bind compute buffer here globally + // TODO: Bind it once to the shader of the batch! + UnityEngine.Shader.SetGlobalBuffer("unity_DOTSInstanceData", m_GPUPersistentInstanceData); + +#if DEBUG_LOG_MEMORY_USAGE + if (m_GPUPersistentAllocator.UsedSpace != PrevUsedSpace) + { + Debug.Log($"GPU memory: {m_GPUPersistentAllocator.UsedSpace / 1024.0 / 1024.0:F4} / {m_GPUPersistentAllocator.Size / 1024.0 / 1024.0:F4}"); + PrevUsedSpace = m_GPUPersistentAllocator.UsedSpace; + } +#endif + } + finally + { + m_GPUUploader.FrameCleanup(); + } + Profiler.EndSample(); + } + + protected override void OnDestroy() + { + m_GPUUploader.Dispose(); + m_GPUPersistentInstanceData.Dispose(); + } + + private void ComputeUploadSizeRequirements( + int numGpuUploadOperations, + NativeArray gpuUploadOperations, + NativeArray defaultValueBlits, + out int _numOperations, + out int _totalUploadBytes, + out int _biggestUploadBytes) + { + var numOperations = numGpuUploadOperations + defaultValueBlits.Length; + var totalUploadBytes = 0; + var biggestUploadBytes = 0; + Job.WithCode(() => + { + for (int i = 0; i < numGpuUploadOperations; ++i) + { + var numBytes = gpuUploadOperations[i].BytesRequiredInUploadBuffer; + totalUploadBytes += numBytes; + biggestUploadBytes = math.max(biggestUploadBytes, numBytes); + } + + for (int i = 0; i < defaultValueBlits.Length; ++i) + { + var numBytes = defaultValueBlits[i].BytesRequiredInUploadBuffer; + totalUploadBytes += numBytes; + biggestUploadBytes = math.max(biggestUploadBytes, numBytes); + } + }).Run(); + + _numOperations = numOperations; + _totalUploadBytes = totalUploadBytes; + _biggestUploadBytes = biggestUploadBytes; + } + + [BurstCompile] + struct ComputeOperationsJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle HybridChunkInfo; + public ComponentTypeHandle chunkPropertyDirtyMaskHandle; + [ReadOnly] public ComponentTypeHandle chunkPerCameraCullingMaskHandle; + [ReadOnly] public ComponentTypeHandle ChunkHeader; + + [ReadOnly] public NativeArray ChunkProperties; + public int LocalToWorldType; + public int WorldToLocalType; + public int PrevLocalToWorldType; + public int PrevWorldToLocalType; + + [NativeDisableParallelForRestriction] public NativeArray GpuUploadOperations; + [NativeDisableParallelForRestriction] public NativeReference NumGpuUploadOperations; + + public ComponentTypeCache.BurstCompatibleTypeArray ComponentTypes; + + public void Execute(ArchetypeChunk metaChunk, int chunkIndex) + { + // metaChunk is the chunk which contains the meta entities (= entities holding the chunk components) for the actual chunks + + var hybridChunkInfos = metaChunk.GetNativeArray(HybridChunkInfo); + var chunkHeaders = metaChunk.GetNativeArray(ChunkHeader); + var chunkDirtyMasks = metaChunk.GetNativeArray(chunkPropertyDirtyMaskHandle); + var chunkPerCameraMask = metaChunk.GetNativeArray(chunkPerCameraCullingMaskHandle); + + for (int i = 0; i < metaChunk.Count; ++i) + { + var visibleMask = chunkPerCameraMask[i]; + if ((visibleMask.lower.Value | visibleMask.upper.Value) == 0) + continue; + + var chunkInfo = hybridChunkInfos[i]; + var dirtyMask = chunkDirtyMasks[i]; + var chunkHeader = chunkHeaders[i]; + var chunk = chunkHeader.ArchetypeChunk; + + ProcessChunk(in chunkInfo, ref dirtyMask, chunk); + chunkDirtyMasks[i] = dirtyMask; + } + } + + unsafe void ProcessChunk(in HybridChunkInfo chunkInfo, ref ChunkMaterialPropertyDirtyMask dirtyMask, ArchetypeChunk chunk) + { + if (!chunkInfo.Valid) + return; + + var dstOffsetWorldToLocal = -1; + var dstOffsetPrevWorldToLocal = -1; + + fixed (DynamicComponentTypeHandle* fixedT0 = &ComponentTypes.t0) + { + for (int i = chunkInfo.ChunkTypesBegin; i < chunkInfo.ChunkTypesEnd; ++i) + { + var chunkProperty = ChunkProperties[i]; + var type = chunkProperty.ComponentTypeIndex; + if (type == WorldToLocalType) + dstOffsetWorldToLocal = chunkProperty.GPUDataBegin; + else if (type == PrevWorldToLocalType) + dstOffsetPrevWorldToLocal = chunkProperty.GPUDataBegin; + } + + for (int i = chunkInfo.ChunkTypesBegin; i < chunkInfo.ChunkTypesEnd; ++i) + { + var chunkProperty = ChunkProperties[i]; + var type = ComponentTypes.Type(fixedT0, chunkProperty.ComponentTypeIndex); + var typeIndex = ComponentTypes.TypeIndexToArrayIndex[ComponentTypeCache.GetArrayIndex(chunkProperty.ComponentTypeIndex)]; + + var chunkType = chunkProperty.ComponentTypeIndex; + var isLocalToWorld = chunkType == LocalToWorldType; + var isPrevLocalToWorld = chunkType == PrevLocalToWorldType; + + bool copyComponentData = typeIndex >= 64 ? dirtyMask.upper.IsSet(typeIndex - 64) : dirtyMask.lower.IsSet(typeIndex); + + if (copyComponentData) + { +#if DEBUG_LOG_PROPERTIES + Debug.Log($"UpdateChunkProperty(internalBatchIndex: {chunkInfo.InternalIndex}, property: {i}, elementSize: {chunkProperty.ValueSizeBytesCPU})"); +#endif + + var src = chunk.GetDynamicComponentDataArrayReinterpret(type, + chunkProperty.ValueSizeBytesCPU); + +#if PROFILE_BURST_JOB_INTERNALS + ProfileAddUpload.Begin(); +#endif + + int sizeBytes = (int)((uint)chunk.Count * (uint)chunkProperty.ValueSizeBytesCPU); + var srcPtr = src.GetUnsafeReadOnlyPtr(); + var dstOffset = chunkProperty.GPUDataBegin; + if (isLocalToWorld || isPrevLocalToWorld) + { + var numMatrices = sizeBytes / sizeof(float4x4); + AddMatrixUpload( + srcPtr, + numMatrices, + dstOffset, + isLocalToWorld ? dstOffsetWorldToLocal : dstOffsetPrevWorldToLocal, + (chunkProperty.ValueSizeBytesCPU == 4 * 4 * 3) ? + ThreadedSparseUploader.MatrixType.MatrixType3x4 : + ThreadedSparseUploader.MatrixType.MatrixType4x4, + (chunkProperty.ValueSizeBytesGPU == 4 * 4 * 3) ? + ThreadedSparseUploader.MatrixType.MatrixType3x4 : + ThreadedSparseUploader.MatrixType.MatrixType4x4); + +#if USE_PICKING_MATRICES + // If picking support is enabled, also copy the LocalToWorld matrices + // to the traditional instancing matrix array. This should be thread safe + // because the related Burst jobs run during DOTS system execution, and + // are guaranteed to have finished before rendering starts. + if (isLocalToWorld) + { +#if PROFILE_BURST_JOB_INTERNALS + ProfilePickingMatrices.Begin(); +#endif + float4x4* batchPickingMatrices = (float4x4*)BatchPickingMatrices[internalIndex]; + int chunkOffsetInBatch = chunkInfo.CullingData.BatchOffset; + UnsafeUtility.MemCpy( + batchPickingMatrices + chunkOffsetInBatch, + srcPtr, + sizeBytes); +#if PROFILE_BURST_JOB_INTERNALS + ProfilePickingMatrices.End(); +#endif + } +#endif + } + else + { + AddUpload( + srcPtr, + sizeBytes, + dstOffset); + } +#if PROFILE_BURST_JOB_INTERNALS + ProfileAddUpload.End(); +#endif + } + } + } + + dirtyMask = default; + } + + private unsafe void AddUpload(void* srcPtr, int sizeBytes, int dstOffset) + { + int* numGpuUploadOperations = (int*)NumGpuUploadOperations.GetUnsafePtr(); + int index = System.Threading.Interlocked.Add(ref numGpuUploadOperations[0], 1) - 1; + + if (index < GpuUploadOperations.Length) + { + GpuUploadOperations[index] = new GpuUploadOperation + { + Kind = GpuUploadOperation.UploadOperationKind.Memcpy, + Src = srcPtr, + DstOffset = dstOffset, + DstOffsetInverse = -1, + Size = sizeBytes, + Stride = 0, + }; + } + else + { + // Debug.Assert(false, "Maximum amount of GPU upload operations exceeded"); + } + } + + private unsafe void AddMatrixUpload( + void* srcPtr, + int numMatrices, + int dstOffset, + int dstOffsetInverse, + ThreadedSparseUploader.MatrixType matrixTypeCpu, + ThreadedSparseUploader.MatrixType matrixTypeGpu) + { + int* numGpuUploadOperations = (int*)NumGpuUploadOperations.GetUnsafePtr(); + int index = System.Threading.Interlocked.Add(ref numGpuUploadOperations[0], 1) - 1; + + if (index < GpuUploadOperations.Length) + { + GpuUploadOperations[index] = new GpuUploadOperation + { + Kind = (matrixTypeGpu == ThreadedSparseUploader.MatrixType.MatrixType3x4) ? + GpuUploadOperation.UploadOperationKind.SOAMatrixUpload3x4 : + GpuUploadOperation.UploadOperationKind.SOAMatrixUpload4x4, + Src = srcPtr, + DstOffset = dstOffset, + DstOffsetInverse = dstOffsetInverse, + Size = numMatrices, + Stride = 0, + }; + } + else + { + // Debug.Assert(false, "Maximum amount of GPU upload operations exceeded"); + } + } + } + } + + [BurstCompile] + internal static class ComponentTypeCacheBurstHelpers + { + public static unsafe void FetchTypeHandlesBursted(ref ComponentTypeCache cache, in SystemHandleUntyped handle, ref WorldUnmanaged world, out NativeArray typeIndices) + { + var statePtr = world.ResolveSystemState(handle); + + //var types = cache.UsedTypes.GetKeyValueArrays(Allocator.Temp); + var types = UsedTypesGetKeyValueArrays(cache.UsedTypes); + + if (cache.TypeDynamics == null || cache.TypeDynamics.Length < cache.MaxIndex + 1) + // Allocate according to Capacity so we grow with the same geometric formula as NativeList + cache.TypeDynamics = new DynamicComponentTypeHandle[cache.MaxIndex + 1]; + + ref var keys = ref types.Keys; + ref var values = ref types.Values; + int numTypes = keys.Length; + for (int i = 0; i < numTypes; ++i) + { + int arrayIndex = keys[i]; + int typeIndex = values[i]; + //cache.TypeDynamics[arrayIndex] = statePtr->GetDynamicComponentTypeHandle( + // ComponentType.ReadOnly(typeIndex)); + DynamicComponentTypeHandle typeHandle = default; + GetDynamicComponentTypeHandle(statePtr, typeIndex, &typeHandle); + cache.TypeDynamics[arrayIndex] = typeHandle; + } + + typeIndices = types.Values; + } + + public static unsafe void MakeBurstCompatibleTypeArray(ref ComponentTypeCache cache, + out ComponentTypeCache.BurstCompatibleTypeArray result, + Allocator allocator, + NativeArray typeIndices) + { + ComponentTypeCache.BurstCompatibleTypeArray typeArray = default; + + UnityEngine.Debug.Assert(cache.UsedTypeCount > 0, "No types have been registered"); + UnityEngine.Debug.Assert(cache.UsedTypeCount <= ComponentTypeCache.BurstCompatibleTypeArray.kMaxTypes, "Maximum supported amount of types exceeded"); + + typeArray.TypeIndexToArrayIndex = new NativeArray( + cache.MaxIndex + 1, + allocator, + NativeArrayOptions.UninitializedMemory); + ref var toArrayIndex = ref typeArray.TypeIndexToArrayIndex; + + // Use an index guaranteed to cause a crash on invalid indices + uint GuaranteedCrashOffset = 0x80000000; + //for (int i = 0; i < toArrayIndex.Length; ++i) + // toArrayIndex[i] = (int)GuaranteedCrashOffset; + UnsafeUtility.MemCpyReplicate(toArrayIndex.GetUnsafePtr(), &GuaranteedCrashOffset, 4, toArrayIndex.Length); + + //var typeIndices = UsedTypes.GetValueArray(Allocator.Temp); + int numTypes = math.min(typeIndices.Length, ComponentTypeCache.BurstCompatibleTypeArray.kMaxTypes); + var fixedT0 = &typeArray.t0; + + for (int i = 0; i < numTypes; ++i) + { + int typeIndex = typeIndices[i]; + fixedT0[i] = cache.Type(typeIndex); + toArrayIndex[ComponentTypeCache.GetArrayIndex(typeIndex)] = i; + } + + // TODO: Is there a way to avoid this? + // We need valid type objects in each field. + { + var someType = cache.Type(typeIndices[0]); + for (int i = numTypes; i < ComponentTypeCache.BurstCompatibleTypeArray.kMaxTypes; ++i) + fixedT0[i] = someType; + } + + typeIndices.Dispose(); + + result = typeArray; + } + + private static NativeKeyValueArrays UsedTypesGetKeyValueArrays(NativeParallelHashMap usedTypes) + { + var keys = new NativeList(Allocator.TempJob); + var values = new NativeList(Allocator.TempJob); + new ExtractKeyValueArrays { keys = keys, values = values, map = usedTypes }.Run(); + var result = new NativeKeyValueArrays(keys.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + result.Keys.CopyFrom(keys); + result.Values.CopyFrom(values); + keys.Dispose(); + values.Dispose(); + return result; + } + + [BurstCompile] + struct ExtractKeyValueArrays : IJob + { + public NativeList keys; + public NativeList values; + + [ReadOnly] public NativeParallelHashMap map; + + public void Execute() + { + var keyValues = map.GetKeyValueArrays(Allocator.Temp); + keys.AddRange(keyValues.Keys); + values.AddRange(keyValues.Values); + } + } + + [BurstCompile] + public static unsafe void GetDynamicComponentTypeHandle(SystemState* statePtr, int typeIndex, DynamicComponentTypeHandle* handle) + { + *handle = statePtr->GetDynamicComponentTypeHandle(ComponentType.ReadOnly(typeIndex)); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UploadMaterialPropertiesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UploadMaterialPropertiesSystem.cs.meta new file mode 100644 index 0000000..118bf11 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/Culling/UploadMaterialPropertiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ff7f65ada07ff8439650085c3faaa5e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/KinemationSuperSystems.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/KinemationSuperSystems.cs new file mode 100644 index 0000000..12798b7 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/KinemationSuperSystems.cs @@ -0,0 +1,85 @@ +using Unity.Entities; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + /// + /// Subclass this class and add it to the world prior to installing Kinemation + /// to customize the culling loop. + /// + [DisableAutoCreation] + public class KinemationCullingSuperSystem : SuperSystem + { + protected override void CreateSystems() + { + EnableSystemSorting = false; + + GetOrCreateAndAddSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + GetOrCreateAndAddSystem(); + } + } + + [UpdateInGroup(typeof(UpdatePresentationSystemGroup))] + [UpdateAfter(typeof(RenderBoundsUpdateSystem))] + [DisableAutoCreation] + public class KinemationRenderUpdateSuperSystem : RootSuperSystem + { + protected override void CreateSystems() + { + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + } + } + + [UpdateInGroup(typeof(PresentationSystemGroup))] + [UpdateAfter(typeof(LatiosHybridRendererSystem))] + [DisableAutoCreation] + public class KinemationPostRenderSuperSystem : SuperSystem + { + protected override void CreateSystems() + { + GetOrCreateAndAddSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + } + } + + [UpdateInGroup(typeof(StructuralChangePresentationSystemGroup))] + [DisableAutoCreation] + public class KinemationRenderSyncPointSuperSystem : SuperSystem + { + protected override void CreateSystems() + { + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + } + } + + [UpdateInGroup(typeof(Latios.Systems.LatiosWorldSyncGroup))] + [DisableAutoCreation] + public class KinemationFrameSyncPointSuperSystem : SuperSystem + { + protected override void CreateSystems() + { + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddUnmanagedSystem(); + GetOrCreateAndAddSystem(); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/KinemationSuperSystems.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/KinemationSuperSystems.cs.meta new file mode 100644 index 0000000..a7dfcd2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/KinemationSuperSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e7c07ae364a3294daaa53a45aa25272 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/LatiosHybridRendererSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/LatiosHybridRendererSystem.cs new file mode 100644 index 0000000..307bb0a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/LatiosHybridRendererSystem.cs @@ -0,0 +1,3086 @@ +#region Header +// #define DISABLE_HYBRID_V2_SRP_LOGS +// #define DEBUG_LOG_HYBRID_V2 +// #define DEBUG_LOG_CHUNK_CHANGES +// #define DEBUG_LOG_TOP_LEVEL +// #define DEBUG_LOG_BATCHES +// #define DEBUG_LOG_BATCH_UPDATES +// #define DEBUG_LOG_CHUNKS +// #define DEBUG_LOG_INVALID_CHUNKS +// #define DEBUG_LOG_UPLOADS +// #define DEBUG_LOG_PROPERTIES +// #define DEBUG_LOG_OVERRIDES +// #define DEBUG_LOG_VISIBLE_INSTANCES +// #define DEBUG_LOG_MATERIAL_PROPERTIES +// #define DEBUG_LOG_MEMORY_USAGE +// #define DEBUG_LOG_AMBIENT_PROBE +// #define PROFILE_BURST_JOB_INTERNALS + +#if UNITY_EDITOR || DEBUG_LOG_OVERRIDES +#define USE_PROPERTY_ASSERTS +#endif + +#if UNITY_EDITOR +#define USE_PICKING_MATRICES +#endif + +// Define this to remove the performance overhead of error material usage +// #define DISABLE_HYBRID_ERROR_MATERIAL + +// Assert that V2 requirements are met if it's enabled + +#if !UNITY_2020_1_OR_NEWER +#error Hybrid Renderer V2 requires Unity 2020.1 or newer. +#endif +// Hybrid Renderer is disabled if SRP 9 is not found, unless an override define is present +// It is also disabled if -nographics is given from the command line. +#if !(HDRP_9_0_0_OR_NEWER || URP_9_0_0_OR_NEWER || HYBRID_RENDERER_ENABLE_WITHOUT_SRP) +#define HYBRID_RENDERER_DISABLED +#endif + +#if ENABLE_UNITY_OCCLUSION && UNITY_2020_2_OR_NEWER && (!HYBRID_RENDERER_DISABLED) +#define USE_UNITY_OCCLUSION +#endif + +// TODO: +// - Minimize struct sizes to improve memory footprint and cache usage +// - What to do with FrozenRenderSceneTag / ForceLowLOD? +// - Precompute and optimize material property + chunk component matching as much as possible +// - Integrate new occlusion culling +// - PickableObject? + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Mathematics; +using Unity.Profiling; +using Unity.Transforms; +using UnityEditor; +using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.Rendering; +#if UNITY_2020_1_OR_NEWER +// This API only exists since 2020.1 +using Unity.Rendering.HybridV2; +#endif + +#if USE_UNITY_OCCLUSION +using Unity.Rendering.Occlusion; +#endif + +using Unity.Rendering; + +#endregion + +namespace Latios.Kinemation.Systems +{ + /// + /// Renders all Entities containing both RenderMesh and LocalToWorld components. + /// + + [ExecuteAlways] + //@TODO: Necessary due to empty component group. When Component group and archetype chunks are unified this should be removed + [AlwaysUpdateSystem] + [UpdateInGroup(typeof(PresentationSystemGroup))] + [UpdateAfter(typeof(UpdatePresentationSystemGroup))] + [UpdateAfter(typeof(HybridRendererSystem))] + [DisableAutoCreation] + public unsafe partial class LatiosHybridRendererSystem : SubSystem + { + #region UtilityTypes + // Contains the immutable properties that are set + // upon batch creation. Only chunks with identical BatchCreateInfo + // can be combined in a single batch. + private struct BatchCreateInfo : IEquatable + { + public static readonly Bounds BigBounds = + new Bounds(new Vector3(0, 0, 0), new Vector3(1048576.0f, 1048576.0f, 1048576.0f)); + + public RenderMesh RenderMesh; + public EditorRenderData EditorRenderData; + public Bounds Bounds; + public bool FlippedWinding; + public ulong PartitionValue; + + public bool Valid => RenderMesh.mesh != null && + RenderMesh.material != null && + RenderMesh.material.shader != null; + + public bool Equals(BatchCreateInfo other) + { + return RenderMesh.Equals(other.RenderMesh) && + EditorRenderData.Equals(other.EditorRenderData) && + Bounds.Equals(other.Bounds) && + FlippedWinding == other.FlippedWinding && + PartitionValue == other.PartitionValue; + } + + public override bool Equals(object obj) + { + return obj is BatchCreateInfo other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = RenderMesh.GetHashCode(); + hashCode = (hashCode * 397) ^ EditorRenderData.GetHashCode(); + hashCode = (hashCode * 397) ^ Bounds.GetHashCode(); + hashCode = (hashCode * 397) ^ FlippedWinding.GetHashCode(); + hashCode = (hashCode * 397) ^ PartitionValue.GetHashCode(); + return hashCode; + } + } + } + + private class BatchCreateInfoFactory + { + public EntityManager EntityManager; + public SharedComponentTypeHandle RenderMeshTypeHandle; + public SharedComponentTypeHandle EditorRenderDataTypeHandle; + public SharedComponentTypeHandle HybridBatchPartitionHandle; + public ComponentTypeHandle RenderMeshFlippedWindingTagTypeHandle; + public EditorRenderData DefaultEditorRenderData; + + public BatchCreateInfo CreateInfoForChunk(ArchetypeChunk chunk) + { + return new BatchCreateInfo + { + RenderMesh = chunk.GetSharedComponentData(RenderMeshTypeHandle, EntityManager), + EditorRenderData = chunk.Has(EditorRenderDataTypeHandle) ? + chunk.GetSharedComponentData(EditorRenderDataTypeHandle, EntityManager) : + DefaultEditorRenderData, + Bounds = BatchCreateInfo.BigBounds, + FlippedWinding = chunk.Has(RenderMeshFlippedWindingTagTypeHandle), + PartitionValue = chunk.Has(HybridBatchPartitionHandle) ? + chunk.GetSharedComponentData(HybridBatchPartitionHandle, EntityManager).PartitionValue : + 0, + }; + } + } + + private struct BatchCompatibility : IComparer, IDisposable + { + public struct BatchSortEntry + { + // We can't store BatchCreateInfo itself here easily, because it's a managed + // type and those cannot be stored in native collections, so we store just the + // hash and recreate the actual CreateInfo just-in-time when needed. + public int CreateInfoHash; + public bool CreateInfoValid; + public ArchetypeChunk Chunk; + public SharedComponentOverridesInfo SharedOverrides; + public ulong SortKey; + + public void CalculateSortKey() + { + SortKey = ((ulong)(CreateInfoHash * 397)) ^ SharedOverrides.SharedOverrideHash; + } + + public bool IsCompatibleWith(EntityManager entityManager, + LatiosHybridRendererSystem system, + BatchCreateInfoFactory createInfoFactory, + BatchSortEntry e) + { + // NOTE: This archetype property does not seem to hold, do more expensive testing for now + // bool sameArchetype = Chunk.Archetype == e.Chunk.Archetype; + // // If the archetypes are the same, all values of all shared components must be the same. + // if (sameArchetype) + // return true; + + // If the batch settings are not the same, the chunks are never compatible + // We can test the hash first to avoid instantiating the CreateInfo. + bool sameBatchHash = CreateInfoHash == e.CreateInfoHash; + if (!sameBatchHash) + return false; + + var ci0 = createInfoFactory.CreateInfoForChunk(Chunk); + var ci1 = createInfoFactory.CreateInfoForChunk(e.Chunk); + bool compatibleBatchSettings = ci0.Equals(ci1); + if (!compatibleBatchSettings) + return false; + + // If the batch settings are compatible but the archetypes are different, the + // chunks are compatible if and only if they have identical shared component overrides. + + // If the override hash is different, the overrides cannot be the same. + bool sameOverrideHash = SharedOverrides.SharedOverrideHash == e.SharedOverrides.SharedOverrideHash; + if (!sameOverrideHash) + return false; + + // If the override hash is the same, then either the overrides are the same or + // there is a hash collision. Must do full comparison of shared component values to + // know for sure. + + // NOTE: This test is slightly overconservative. It would be enough that the overrides + // used by the shader are the same, but the mapping between shader properties and components + // is only done later. + + var os0 = SharedOverrides.SharedOverrideTypeIndices; + var os1 = e.SharedOverrides.SharedOverrideTypeIndices; + + // If either has no overrides, we are done + if (!os0.IsCreated || !os1.IsCreated) + return os0.IsCreated == os1.IsCreated; + + if (os0.Length != os1.Length) + return false; + + for (int i = 0; i < os0.Length; ++i) + { + int typeIndex = ((int*)os0.Ptr)[i]; + int ti1 = ((int*)os1.Ptr)[i]; + if (typeIndex != ti1) + return false; + + var handle = system.GetDynamicSharedComponentTypeHandle(ComponentType.ReadOnly(typeIndex)); + var c0 = Chunk.GetSharedComponentDataBoxed(handle, entityManager) as IHybridSharedComponentFloat4Override; + var c1 = e.Chunk.GetSharedComponentDataBoxed(handle, entityManager) as IHybridSharedComponentFloat4Override; + var v0 = c0.GetFloat4OverrideData(); + var v1 = c1.GetFloat4OverrideData(); + if (!math.all(v0 == v1)) + return false; + } + + return true; + } + } + + private BatchCreateInfoFactory m_CreateInfoFactory; + private NativeArray m_Chunks; + private NativeArray m_SortIndices; + private NativeArray m_SortEntries; + + public BatchCompatibility( + LatiosHybridRendererSystem system, + BatchCreateInfoFactory createInfoFactory, + NativeArray chunks) + { + m_CreateInfoFactory = createInfoFactory; + m_Chunks = chunks; + m_SortIndices = new NativeArray( + m_Chunks.Length, + Allocator.Temp, + NativeArrayOptions.UninitializedMemory); + m_SortEntries = new NativeArray( + m_Chunks.Length, + Allocator.Temp, + NativeArrayOptions.UninitializedMemory); + + for (int i = 0; i < m_Chunks.Length; ++i) + { + m_SortIndices[i] = i; + m_SortEntries[i] = CreateSortEntry(system, m_Chunks[i]); + } + } + + private BatchSortEntry CreateSortEntry( + LatiosHybridRendererSystem system, + ArchetypeChunk chunk) + { + var ci = m_CreateInfoFactory.CreateInfoForChunk(chunk); + var entry = new BatchSortEntry + { + CreateInfoHash = ci.GetHashCode(), + CreateInfoValid = ci.Valid, + Chunk = chunk, + SharedOverrides = system.SharedComponentOverridesForChunk(chunk), + }; + entry.CalculateSortKey(); + return entry; + } + + public NativeArray SortChunks() + { + // Key-value sort all chunks according to compatibility + m_SortIndices.Sort(this); + var sortedChunks = new NativeArray( + m_Chunks.Length, + Allocator.Temp, + NativeArrayOptions.UninitializedMemory); + + for (int i = 0; i < m_Chunks.Length; ++i) + sortedChunks[i] = m_Chunks[SortedIndex(i)]; + + return sortedChunks; + } + + public int SortedIndex(int i) => m_SortIndices[i]; + + public BatchCreateInfo CreateInfoFor(int sortedIndex) => + m_CreateInfoFactory.CreateInfoForChunk(m_Chunks[SortedIndex(sortedIndex)]); + + public bool IsCompatible(EntityManager entityManager, LatiosHybridRendererSystem system, int sortedI, int sortedJ) + { + return m_SortEntries[SortedIndex(sortedI)].IsCompatibleWith(entityManager, system, m_CreateInfoFactory, m_SortEntries[SortedIndex(sortedJ)]); + } + + public int Compare(int x, int y) + { + // Sort according to CreateInfo validity and hash based sort key. + // Hash collisions can result in bad batching (consecutive incompatible chunks), + // but not incorrect rendering (compatibility is always checked explicitly). + + var sx = m_SortEntries[x]; + var sy = m_SortEntries[y]; + + bool vx = sx.CreateInfoValid; + bool vy = sy.CreateInfoValid; + + // Always sort invalid chunks last, so they can be skipped by shortening the array. + if (!vx || !vy) + { + if (vx) + return -1; + else if (vy) + return 1; + else + return 0; + } + + var hx = sx.SortKey; + var hy = sy.SortKey; + + if (hx < hy) + return -1; + else if (hx > hy) + return 1; + else + return 0; + } + + public void Dispose() + { + m_SortIndices.Dispose(); + m_SortEntries.Dispose(); + } + } + + private enum BatchFlags + { + NeedMotionVectorPassFlag = 0x1 + }; + + enum DefaultValueKind + { + ZeroDefault, + NonzeroDefault, + SharedBuiltin, + } + + struct MaterialPropertyType + { + public int TypeIndex; + public short SizeBytesCPU; + public short SizeBytesGPU; + public DefaultValueKind DefaultValueKind; + }; + + struct PropertyMapping + { + public string Name; + public short SizeCPU; + public short SizeGPU; + public MaterialPropertyDefaultValue DefaultValue; + public bool IsShared; + } + + private struct BatchInfo + { + // There is one BatchProperty per shader property, which can be different from + // the amount of overriding components. + // TODO: Most of this data is no longer needed after the batch has been created, and could be + // allocated from temp memory and freed after the batch has been created. + +#pragma warning disable CS0649 + // CS0649: Field is never assigned to, and will always have its default value 0 + internal struct BatchProperty + { + public int MetadataOffset; + public short SizeBytesCPU; + public short SizeBytesGPU; + public int CbufferIndex; + public int OverrideComponentsIndex; + public int OverrideSharedComponentsIndex; + public int SharedBuiltinOffset; +#if USE_PROPERTY_ASSERTS + public int NameID; +#endif + public BatchPropertyOverrideStatus OverrideStatus; + public HeapBlock GPUAllocation; + public float4x4 DefaultValue; + } + + // There is one BatchOverrideComponent for each component type that can possibly + // override any of the BatchProperty entries. Some entries might have zero, + // some entries might have multiples. Each chunk is only allowed a single overriding component. + // This list is allocated from temporary memory and is freed after the batch has been fully created. + internal struct BatchOverrideComponent + { + public int BatchPropertyIndex; + public int TypeIndex; + } +#pragma warning restore CS0649 + + public UnsafeList Properties; + public UnsafeList OverrideComponents; + public UnsafeList OverrideSharedComponents; + public UnsafeList ChunkMetadataAllocations; + + public void Dispose() + { + if (Properties.IsCreated) + Properties.Dispose(); + if (OverrideComponents.IsCreated) + OverrideComponents.Dispose(); + if (OverrideSharedComponents.IsCreated) + OverrideSharedComponents.Dispose(); + if (ChunkMetadataAllocations.IsCreated) + ChunkMetadataAllocations.Dispose(); + } + } + + public struct SharedComponentOverridesInfo + { + public UnsafeList SharedOverrideTypeIndices; + public ulong SharedOverrideHash; + } + + // Status enums are ordered so that numerically larger enums always take priority + // if at least one chunk in the batch requires that. + public enum BatchPropertyOverrideStatus + { + Uninitialized, // Override status has not been initialized + SharedZeroDefault, // Property uses a zero bit pattern value for all entities + SharedNonzeroDefault, // Property uses a shared value for all entities, but it's not zero + SharedComponentOverride, // Property uses a shared value that is read from a shared component + SharedBuiltin, // Property uses a shared value that is set by special fixed function code + PerEntityOverride, // Property uses per-entity unique values + } + + struct BatchUpdateStatistics + { + // Ifdef struct contents to avoid warnings when ifdef is disabled. +#if DEBUG_LOG_BATCH_UPDATES + public int BatchesWithChangedBounds; + public int BatchesNeedingMotionVectors; + public int BatchesWithoutMotionVectors; + + public bool NonZero => + BatchesWithChangedBounds > 0 || + BatchesNeedingMotionVectors > 0 || + BatchesWithoutMotionVectors > 0; +#endif + } + + private struct CullingComponentTypes + { + public ComponentTypeHandle RootLODRanges; + public ComponentTypeHandle RootLODWorldReferencePoints; + public ComponentTypeHandle LODRanges; + public ComponentTypeHandle LODWorldReferencePoints; + public ComponentTypeHandle PerInstanceCullingTag; + } + + [BurstCompile] + internal struct HybridChunkUpdater + { + public const uint kFloatsPerAABB = 6; + public const int kMinX = 0; + public const int kMinY = 1; + public const int kMinZ = 2; + public const int kMaxX = 3; + public const int kMaxY = 4; + public const int kMaxZ = 5; + + public ComponentTypeCache.BurstCompatibleTypeArray ComponentTypes; + + [NativeDisableParallelForRestriction] + public NativeArray UnreferencedInternalIndices; + [NativeDisableParallelForRestriction] + public NativeArray BatchRequiresUpdates; + [NativeDisableParallelForRestriction] + public NativeArray BatchHadMovingEntities; + + [NativeDisableParallelForRestriction] + [ReadOnly] + public NativeArray ChunkProperties; + + [NativeDisableParallelForRestriction] + [ReadOnly] + public NativeList BatchMotionInfos; + + [NativeDisableParallelForRestriction] + public NativeList BatchAABBs; + public MinMaxAABB ThreadLocalAABB; + +#if USE_PICKING_MATRICES + [NativeDisableParallelForRestriction] + [ReadOnly] + public NativeList BatchPickingMatrices; +#endif + + public uint LastSystemVersion; + public uint lastSystemVersionForProperties; + public int PreviousBatchIndex; + + public int LocalToWorldType; + public int WorldToLocalType; + public int PrevWorldToLocalType; + +#if PROFILE_BURST_JOB_INTERNALS + public ProfilerMarker ProfileAddUpload; + public ProfilerMarker ProfilePickingMatrices; +#endif + + public unsafe void MarkBatchForUpdates(int internalIndex, bool entitiesMoved) + { + AtomicHelpers.IndexToQwIndexAndMask(internalIndex, out int qw, out long mask); + Debug.Assert(qw < BatchRequiresUpdates.Length && qw < BatchHadMovingEntities.Length, + "Batch index out of bounds"); + + var motionInfo = BatchMotionInfos[internalIndex]; + bool mustDisableMotionVectors = motionInfo.MotionVectorFlagSet && !entitiesMoved; + + // If entities moved, we always update the batch since bounds must be updated. + // If no entities moved, we only update the batch if it requires motion vector disable. + if (entitiesMoved || mustDisableMotionVectors) + AtomicHelpers.AtomicOr((long*)BatchRequiresUpdates.GetUnsafePtr(), qw, mask); + + if (entitiesMoved) + AtomicHelpers.AtomicOr((long*)BatchHadMovingEntities.GetUnsafePtr(), qw, mask); + } + + unsafe void MarkBatchAsReferenced(int internalIndex) + { + // If the batch is referenced, remove it from the unreferenced bitfield + + AtomicHelpers.IndexToQwIndexAndMask(internalIndex, out int qw, out long mask); + + Debug.Assert(qw < UnreferencedInternalIndices.Length, "Batch index out of bounds"); + + AtomicHelpers.AtomicAnd( + (long*)UnreferencedInternalIndices.GetUnsafePtr(), + qw, + ~mask); + } + + public void ProcessChunk(ref HybridChunkInfo chunkInfo, ref ChunkMaterialPropertyDirtyMask mask, ArchetypeChunk chunk, ChunkWorldRenderBounds chunkBounds) + { +#if DEBUG_LOG_CHUNKS + Debug.Log( + $"HybridChunkUpdater.ProcessChunk(internalBatchIndex: {chunkInfo.InternalIndex}, valid: {chunkInfo.Valid}, count: {chunk.Count}, chunk: {chunk.GetHashCode()})"); +#endif + + if (chunkInfo.Valid) + ProcessValidChunk(ref chunkInfo, ref mask, chunk, chunkBounds.Value, false); + } + + public unsafe void ProcessValidChunk(ref HybridChunkInfo chunkInfo, ref ChunkMaterialPropertyDirtyMask mask, ArchetypeChunk chunk, + MinMaxAABB chunkAABB, bool isNewChunk) + { + if (!isNewChunk) + MarkBatchAsReferenced(chunkInfo.InternalIndex); + + int internalIndex = chunkInfo.InternalIndex; + UpdateBatchAABB(internalIndex, chunkAABB); + + bool structuralChanges = chunk.DidOrderChange(LastSystemVersion); + + fixed (DynamicComponentTypeHandle* fixedT0 = &ComponentTypes.t0) + { + for (int i = chunkInfo.ChunkTypesBegin; i < chunkInfo.ChunkTypesEnd; ++i) + { + var chunkProperty = ChunkProperties[i]; + var type = chunkProperty.ComponentTypeIndex; + } + + for (int i = chunkInfo.ChunkTypesBegin; i < chunkInfo.ChunkTypesEnd; ++i) + { + var chunkProperty = ChunkProperties[i]; + var type = ComponentTypes.Type(fixedT0, chunkProperty.ComponentTypeIndex); + var typeIndex = ComponentTypes.TypeIndexToArrayIndex[ComponentTypeCache.GetArrayIndex(chunkProperty.ComponentTypeIndex)]; + + var chunkType = chunkProperty.ComponentTypeIndex; + var isLocalToWorld = chunkType == LocalToWorldType; + var isWorldToLocal = chunkType == WorldToLocalType; + var isPrevWorldToLocal = chunkType == PrevWorldToLocalType; + + var skipComponent = isWorldToLocal || isPrevWorldToLocal; + + bool componentChanged = chunk.DidChange(type, lastSystemVersionForProperties); + bool copyComponentData = (isNewChunk || structuralChanges || componentChanged) && !skipComponent; + + if (copyComponentData) + { + if (typeIndex >= 64) + mask.upper.SetBits(typeIndex - 64, true); + else + mask.lower.SetBits(typeIndex, true); +#if DEBUG_LOG_PROPERTIES + Debug.Log($"UpdateChunkProperty(internalBatchIndex: {chunkInfo.InternalIndex}, property: {i}, elementSize: {chunkProperty.ValueSizeBytesCPU})"); +#endif + + var src = chunk.GetDynamicComponentDataArrayReinterpret(type, + chunkProperty.ValueSizeBytesCPU); + +#if PROFILE_BURST_JOB_INTERNALS + ProfileAddUpload.Begin(); +#endif + + int sizeBytes = (int)((uint)chunk.Count * (uint)chunkProperty.ValueSizeBytesCPU); + var srcPtr = src.GetUnsafeReadOnlyPtr(); + var dstOffset = chunkProperty.GPUDataBegin; + if (isLocalToWorld) + { + var numMatrices = sizeBytes / sizeof(float4x4); + +#if USE_PICKING_MATRICES + // If picking support is enabled, also copy the LocalToWorld matrices + // to the traditional instancing matrix array. This should be thread safe + // because the related Burst jobs run during DOTS system execution, and + // are guaranteed to have finished before rendering starts. +#if PROFILE_BURST_JOB_INTERNALS + ProfilePickingMatrices.Begin(); +#endif + float4x4* batchPickingMatrices = (float4x4*)BatchPickingMatrices[internalIndex]; + int chunkOffsetInBatch = chunkInfo.CullingData.BatchOffset; + UnsafeUtility.MemCpy( + batchPickingMatrices + chunkOffsetInBatch, + srcPtr, + sizeBytes); +#if PROFILE_BURST_JOB_INTERNALS + ProfilePickingMatrices.End(); +#endif +#endif + } + +#if PROFILE_BURST_JOB_INTERNALS + ProfileAddUpload.End(); +#endif + } + } + } + } + + private void UpdateBatchAABB(int internalIndex, MinMaxAABB chunkAABB) + { + // As long as we keep processing chunks that belong to the same batch, + // we can keep accumulating a thread local AABB cheaply. + // Once we encounter a different batch, we need to "flush" the thread + // local version to the global one with atomics. + bool sameBatchAsPrevious = internalIndex == PreviousBatchIndex; + + if (sameBatchAsPrevious) + { + ThreadLocalAABB.Encapsulate(chunkAABB); + } + else + { + CommitBatchAABB(); + ThreadLocalAABB = chunkAABB; + PreviousBatchIndex = internalIndex; + } + } + + private unsafe void CommitBatchAABB() + { + bool validThreadLocalAABB = PreviousBatchIndex >= 0; + if (!validThreadLocalAABB) + return; + + int internalIndex = PreviousBatchIndex; + var aabb = ThreadLocalAABB; + + int aabbIndex = (int)(((uint)internalIndex) * kFloatsPerAABB); + float* aabbFloats = (float*)BatchAABBs.GetUnsafePtr(); + AtomicHelpers.AtomicMin(aabbFloats, aabbIndex + kMinX, aabb.Min.x); + AtomicHelpers.AtomicMin(aabbFloats, aabbIndex + kMinY, aabb.Min.y); + AtomicHelpers.AtomicMin(aabbFloats, aabbIndex + kMinZ, aabb.Min.z); + AtomicHelpers.AtomicMax(aabbFloats, aabbIndex + kMaxX, aabb.Max.x); + AtomicHelpers.AtomicMax(aabbFloats, aabbIndex + kMaxY, aabb.Max.y); + AtomicHelpers.AtomicMax(aabbFloats, aabbIndex + kMaxZ, aabb.Max.z); + + PreviousBatchIndex = -1; + } + + public void FinishExecute() + { + CommitBatchAABB(); + } + } + + #endregion + + #region Variables + static private bool s_HybridRendererEnabled = true; + public static bool HybridRendererEnabled => s_HybridRendererEnabled; + + private ulong m_PersistentInstanceDataSize; + + private EntityQuery m_HybridRenderedQuery; + +#if UNITY_EDITOR + private EditorRenderData m_DefaultEditorRenderData = new EditorRenderData + { SceneCullingMask = UnityEditor.SceneManagement.EditorSceneManager.DefaultSceneCullingMask }; +#else + private EditorRenderData m_DefaultEditorRenderData = new EditorRenderData { SceneCullingMask = ~0UL }; +#endif + + const int kInitialMaxBatchCount = 1 * 1024; + const float kMaxBatchGrowFactor = 2f; + const int kMaxEntitiesPerBatch = 1023; // C++ code is restricted to a certain maximum size + const int kNumNewChunksPerThread = 1; // TODO: Tune this + const int kNumScatteredIndicesPerThread = 8; // TODO: Tune this + const int kNumGatheredIndicesPerThread = 128 * 8; // Two cache lines per thread + const int kBuiltinCbufferIndex = 0; + + const int kMaxChunkMetadata = 1 * 1024 * 1024; + const ulong kMaxGPUAllocatorMemory = 1024 * 1024 * 1024; // 1GiB of potential memory space + const ulong kGPUBufferSizeInitial = 32 * 1024 * 1024; + const ulong kGPUBufferSizeMax = 1023 * 1024 * 1024; + + private BatchRendererGroup m_BatchRendererGroup; + + private HeapAllocator m_GPUPersistentAllocator; + private HeapBlock m_SharedZeroAllocation; + private HeapBlock m_SharedAmbientProbeAllocation; + private HeapBlock m_SharedSpecCubeDecodeAllocation; + + private HeapAllocator m_ChunkMetadataAllocator; + + private NativeList m_BatchInfos; + private NativeList m_BatchMotionInfos; +#if USE_PICKING_MATRICES + private NativeList m_BatchPickingMatrices; +#endif + private NativeArray m_ChunkProperties; + private NativeParallelHashMap m_ExistingBatchInternalIndices; + private ComponentTypeCache m_ComponentTypeCache; + + private NativeList m_BatchAABBs; + + private NativeList m_InternalToExternalIds; + private NativeList m_ExternalToInternalIds; + private NativeList m_InternalIdFreelist; + private int m_ExternalBatchCount; + private SortedSet m_SortedInternalIds; + + private EntityQuery m_MetaEntitiesForHybridRenderableChunks; + + private NativeList m_DefaultValueBlits; + + private JobHandle m_AABBsCleared; + private bool m_AABBClearKicked; + + NativeParallelMultiHashMap m_MaterialPropertyTypes; + NativeParallelMultiHashMap m_MaterialPropertyTypesShared; + NativeParallelHashSet m_SharedComponentOverrideTypeIndices; + + // When extra debugging is enabled, store mappings from NameIDs to property names, + // and from type indices to type names. + Dictionary m_MaterialPropertyNames; + Dictionary m_MaterialPropertyTypeNames; + Dictionary m_MaterialPropertyDefaultValues; + Dictionary m_MaterialPropertySharedBuiltins; + static Dictionary s_TypeToPropertyMappings = new Dictionary(); + +#if USE_UNITY_OCCLUSION + private OcclusionCulling m_OcclusionCulling; +#endif + + private bool m_FirstFrameAfterInit; + private Shader m_BuiltinErrorShader; + private Material m_ErrorMaterial; +#if UNITY_EDITOR + private TrackShaderReflectionChangesSystem m_ShaderReflectionChangesSystem; + private Dictionary m_ShaderHasCompileErrors; +#endif + + NativeParallelHashMap m_ArchetypeSharedOverrideInfos; + + private SHProperties m_GlobalAmbientProbe; + private bool m_GlobalAmbientProbeDirty; + + private float4 m_GlobalSpecCubeDecode; + private bool m_GlobalSpecCubeDecodeDirty; + + int m_cullIndexThisFrame = 0; + uint m_lastSystemVersionForProperties = 0; + KinemationCullingSuperSystem m_cullingSuperSystem; + #endregion + + #region UtilityFunctions + + private SharedComponentOverridesInfo SharedComponentOverridesForChunk(ArchetypeChunk chunk) + { + // Since archetypes are immutable, we can memoize the override infos which are a bit + // costly to create. + if (m_ArchetypeSharedOverrideInfos.TryGetValue(chunk.Archetype, out var existingInfo)) + return existingInfo; + + var componentTypes = chunk.Archetype.GetComponentTypes(); + int numOverrides = 0; + + // First, count the amount of overrides so we know how much to allocate + for (int i = 0; i < componentTypes.Length; ++i) + { + int typeIndex = componentTypes[i].TypeIndex; + + // If the type is not in this hash map, it's not a shared component override + if (!m_SharedComponentOverrideTypeIndices.Contains(typeIndex)) + continue; + + ++numOverrides; + } + + // If there are no overrides, no need to allocate or hash anything + if (numOverrides == 0) + { + var nullInfo = new SharedComponentOverridesInfo + { + SharedOverrideTypeIndices = default, + SharedOverrideHash = 0, + }; + m_ArchetypeSharedOverrideInfos[chunk.Archetype] = nullInfo; + return nullInfo; + } + + var overridesArray = new UnsafeList( + numOverrides, + Allocator.Persistent, + NativeArrayOptions.UninitializedMemory); + overridesArray.Resize(numOverrides, NativeArrayOptions.UninitializedMemory); + var overrides = overridesArray.Ptr; + + int j = 0; + for (int i = 0; i < componentTypes.Length; ++i) + { + int typeIndex = componentTypes[i].TypeIndex; + + // If the type is not in this hash map, it's not a shared component override + if (!m_SharedComponentOverrideTypeIndices.Contains(typeIndex)) + continue; + + overrides[j] = typeIndex; + ++j; + } + componentTypes.Dispose(); + + // Sort the type indices so every archetype has them in the same order + overridesArray.Sort(); + + // Finally, hash the actual contents + xxHash3.StreamingState hash = new xxHash3.StreamingState(true); + for (int i = 0; i < overridesArray.Length; ++i) + { + int typeIndex = overrides[i]; + var handle = GetDynamicSharedComponentTypeHandle(ComponentType.ReadOnly(typeIndex)); + var c = chunk.GetSharedComponentDataBoxed(handle, EntityManager) as IHybridSharedComponentFloat4Override; + var v = c.GetFloat4OverrideData(); + hash.Update(&v, UnsafeUtility.SizeOf()); + } + uint2 h64 = hash.DigestHash64(); + + var info = new SharedComponentOverridesInfo + { + SharedOverrideTypeIndices = overridesArray, + SharedOverrideHash = (ulong)h64.x | (((ulong)h64.y) << 32), + }; + m_ArchetypeSharedOverrideInfos[chunk.Archetype] = info; + return info; + } + + private static bool PropertyIsZero(BatchPropertyOverrideStatus status) => + status == BatchPropertyOverrideStatus.SharedZeroDefault; + + private static bool PropertyRequiresAllocation(BatchPropertyOverrideStatus status) => + !PropertyIsZero(status) && status != BatchPropertyOverrideStatus.SharedBuiltin; + + private static bool PropertyRequiresBlit(BatchPropertyOverrideStatus status) => + status == BatchPropertyOverrideStatus.SharedNonzeroDefault || + status == BatchPropertyOverrideStatus.SharedComponentOverride; + + private static bool PropertyHasPerEntityValues(BatchPropertyOverrideStatus status) => + status == BatchPropertyOverrideStatus.PerEntityOverride; + + private static int PropertyValuesPerAllocation(BatchPropertyOverrideStatus status, int numInstances) => + PropertyHasPerEntityValues(status) ? + numInstances : + 1; + + private void RegisterSharedBuiltin(string propertyName, int sharedBuiltinOffset) + { + int nameID = Shader.PropertyToID(propertyName); + m_MaterialPropertySharedBuiltins[nameID] = sharedBuiltinOffset; + } + + private void BlitBytes(HeapBlock heapBlock, void* bytes, int sizeBytes) + { + int size4x4 = UnsafeUtility.SizeOf(); + int offset = 0; + + // Since each default blit can only copy a float4x4, split to multiple blits + while (sizeBytes > 0) + { + int blitSize = math.min(sizeBytes, size4x4); + var desc = new DefaultValueBlitDescriptor + { + DestinationOffset = (uint)heapBlock.begin + (uint)offset, + ValueSizeBytes = (uint)blitSize, + Count = 1, + }; + UnsafeUtility.MemCpy(&desc.DefaultValue, (byte*)bytes + offset, blitSize); + m_DefaultValueBlits.Add(desc); + + sizeBytes -= blitSize; + offset += blitSize; + } + } + + public static void RegisterMaterialPropertyType(Type type, string propertyName, short overrideTypeSizeGPU = -1, MaterialPropertyDefaultValue defaultValue = default) + { + Debug.Assert(type != null, "type must be non-null"); + Debug.Assert(!string.IsNullOrEmpty(propertyName), "Property name must be valid"); + + short typeSizeCPU = (short)UnsafeUtility.SizeOf(type); + if (overrideTypeSizeGPU == -1) + overrideTypeSizeGPU = typeSizeCPU; + + // For now, we only support overriding one material property with one type. + // Several types can override one property, but not the other way around. + // If necessary, this restriction can be lifted in the future. + if (s_TypeToPropertyMappings.ContainsKey(type)) + { + string prevPropertyName = s_TypeToPropertyMappings[type].Name; + Debug.Assert(propertyName.Equals( + prevPropertyName), + $"Attempted to register type {type.Name} with multiple different property names. Registered with \"{propertyName}\", previously registered with \"{prevPropertyName}\"."); + } + else + { + var pm = new PropertyMapping(); + pm.Name = propertyName; + pm.SizeCPU = typeSizeCPU; + pm.SizeGPU = overrideTypeSizeGPU; + pm.DefaultValue = defaultValue; + pm.IsShared = typeof(ISharedComponentData).IsAssignableFrom(type); + s_TypeToPropertyMappings[type] = pm; + } + } + + public static void RegisterMaterialPropertyType(string propertyName, short overrideTypeSizeGPU = -1, MaterialPropertyDefaultValue defaultValue = default) + where T : IComponentData + { + RegisterMaterialPropertyType(typeof(T), propertyName, overrideTypeSizeGPU, defaultValue); + } + + private void InitializeMaterialProperties() + { + m_MaterialPropertyTypes.Clear(); + m_MaterialPropertyTypesShared.Clear(); + m_MaterialPropertyDefaultValues.Clear(); + + foreach (var kv in s_TypeToPropertyMappings) + { + Type type = kv.Key; + string propertyName = kv.Value.Name; + + short sizeBytesCPU = kv.Value.SizeCPU; + short sizeBytesGPU = kv.Value.SizeGPU; + int typeIndex = TypeManager.GetTypeIndex(type); + int nameID = Shader.PropertyToID(propertyName); + var defaultValue = kv.Value.DefaultValue; + bool isShared = kv.Value.IsShared; + + DefaultValueKind defaultKind; + if (m_MaterialPropertySharedBuiltins.ContainsKey(nameID)) + defaultKind = DefaultValueKind.SharedBuiltin; + else + defaultKind = defaultValue.Nonzero ? + DefaultValueKind.NonzeroDefault : + DefaultValueKind.ZeroDefault; + + if (isShared) + { + m_MaterialPropertyTypesShared.Add(nameID, + new MaterialPropertyType + { + TypeIndex = typeIndex, + SizeBytesCPU = sizeBytesCPU, + SizeBytesGPU = sizeBytesGPU, + DefaultValueKind = defaultKind, + }); + m_SharedComponentOverrideTypeIndices.Add(typeIndex); + } + else + { + m_MaterialPropertyTypes.Add(nameID, + new MaterialPropertyType + { + TypeIndex = typeIndex, + SizeBytesCPU = sizeBytesCPU, + SizeBytesGPU = sizeBytesGPU, + DefaultValueKind = defaultKind, + }); + } + + if (defaultValue.Nonzero) + m_MaterialPropertyDefaultValues[typeIndex] = defaultValue.Value; + +#if USE_PROPERTY_ASSERTS + m_MaterialPropertyNames[nameID] = propertyName; + m_MaterialPropertyTypeNames[typeIndex] = type.Name; +#endif + +#if DEBUG_LOG_MATERIAL_PROPERTIES + Debug.Log( + $"Type \"{type.Name}\" ({sizeBytesCPU} bytes) overrides material property \"{propertyName}\" (nameID: {nameID}, typeIndex: {typeIndex}, defaultKind: {defaultKind})"); +#endif + + // We cache all IComponentData types that we know are capable of overriding properties + if (!isShared) + m_ComponentTypeCache.UseType(typeIndex); + } + + // UsedTypes values are the ComponentType values while the keys are the same + // except with the bit flags in the high bits masked off. + // The HybridRenderer packs ComponentTypeHandles by the order they show up + // in the value array from the hashmap. + var types = m_ComponentTypeCache.UsedTypes.GetValueArray(Allocator.Temp); + var ctypes = worldBlackboardEntity.GetBuffer().Reinterpret(); + ctypes.ResizeUninitialized(types.Length); + for (int i = 0; i < types.Length; i++) + ctypes[i] = ComponentType.ReadOnly(types[i]); + + s_TypeToPropertyMappings.Clear(); + } + + bool HasShaderReflectionChanged + { + get + { +#if UNITY_EDITOR + return m_ShaderReflectionChangesSystem.HasReflectionChanged; +#else + return false; +#endif + } + } + + void AlignWithShaderReflectionChanges() + { +#if UNITY_EDITOR + // Reflection changing can imply that shader compile error status + // has also changed, so flush our cache. + if (HasShaderReflectionChanged) + { + m_ShaderHasCompileErrors = new Dictionary(); + } +#endif + } + + void UpdateHybridV2Batches(out int totalChunks) + { + if (m_FirstFrameAfterInit) + { + OnFirstFrame(); + m_FirstFrameAfterInit = false; + } + + AlignWithShaderReflectionChanges(); + + UpdateSpecCubeHDRDecode(ReflectionProbe.defaultTextureHDRDecodeValues); + + Profiler.BeginSample("UpdateAllBatches"); + using (var hybridChunks = + m_HybridRenderedQuery.CreateArchetypeChunkArray(Allocator.TempJob)) + { + UpdateAllBatches(out totalChunks); + } + + Profiler.EndSample(); + } + + private void OnFirstFrame() + { +#if UNITY_EDITOR + m_ShaderReflectionChangesSystem = World.GetExistingSystem(); +#endif + + InitializeMaterialProperties(); + +#if DEBUG_LOG_HYBRID_V2 + Debug.Log( + $"Latios Hybrid Renderer V2 active, MaterialProperty component type count {m_ComponentTypeCache.UsedTypeCount} / {ComponentTypeCache.BurstCompatibleTypeArray.kMaxTypes}"); +#endif + } + + private void ResetIds() + { + if (m_InternalToExternalIds.IsCreated) + m_InternalToExternalIds.Dispose(); + m_InternalToExternalIds = new NativeList(kInitialMaxBatchCount, Allocator.Persistent); + ResizeWithMinusOne(m_InternalToExternalIds, kInitialMaxBatchCount); + + if (m_ExternalToInternalIds.IsCreated) + m_ExternalToInternalIds.Dispose(); + m_ExternalToInternalIds = new NativeList(kInitialMaxBatchCount, Allocator.Persistent); + ResizeWithMinusOne(m_ExternalToInternalIds, kInitialMaxBatchCount); + + m_ExternalBatchCount = 0; + m_SortedInternalIds = new SortedSet(); + + if (m_InternalIdFreelist.IsCreated) + m_InternalIdFreelist.Dispose(); + m_InternalIdFreelist = new NativeList(kInitialMaxBatchCount, Allocator.Persistent); + + for (int i = kInitialMaxBatchCount - 1; i >= 0; --i) + m_InternalIdFreelist.Add(i); + } + + private void EnsureHaveSpaceForNewBatch() + { + if (m_InternalIdFreelist.Length > 0) + return; + + Debug.Assert(m_ExternalBatchCount > 0); + Debug.Assert(kMaxBatchGrowFactor >= 1f, + "Grow factor should always be greater or equal to 1"); + + var newCapacity = (int)(kMaxBatchGrowFactor * m_ExternalBatchCount); + m_InternalIdFreelist.Capacity = newCapacity; + + for (int i = newCapacity - 1; i >= m_ExternalBatchCount; --i) + m_InternalIdFreelist.Add(i); + + ResizeWithMinusOne(m_ExternalToInternalIds, newCapacity); + ResizeWithMinusOne(m_InternalToExternalIds, newCapacity); + m_BatchAABBs.Resize(newCapacity * (int)HybridChunkUpdater.kFloatsPerAABB, NativeArrayOptions.ClearMemory); + m_BatchInfos.Resize(newCapacity, NativeArrayOptions.ClearMemory); + m_BatchMotionInfos.Resize(newCapacity, NativeArrayOptions.ClearMemory); + +#if USE_PICKING_MATRICES + m_BatchPickingMatrices.Resize(newCapacity, NativeArrayOptions.ClearMemory); +#endif + } + + private int AllocateInternalId() + { + EnsureHaveSpaceForNewBatch(); + + int id = m_InternalIdFreelist[m_InternalIdFreelist.Length - 1]; + m_InternalIdFreelist.Resize(m_InternalIdFreelist.Length - 1, NativeArrayOptions.UninitializedMemory); + Debug.Assert(!m_SortedInternalIds.Contains(id), "Freshly allocated batch id found in list of used ids"); + m_SortedInternalIds.Add(id); + return id; + } + + private void ReleaseInternalId(int id) + { + if (!(id >= 0 && id < m_InternalToExternalIds.Length)) + Debug.Assert(false, $"Attempted to release invalid batch id {id}"); + if (!m_SortedInternalIds.Contains(id)) + Debug.Assert(false, $"Attempted to release an unused id {id}"); + m_SortedInternalIds.Remove(id); + m_InternalIdFreelist.Add(id); + } + + private void RemoveExternalIdSwapWithBack(int externalId) + { + // Mimic the swap back and erase that BatchRendererGroup does + + int internalIdOfRemoved = m_ExternalToInternalIds[externalId]; + int lastExternalId = m_ExternalBatchCount - 1; + + if (lastExternalId != externalId) + { + int internalIdOfLast = m_ExternalToInternalIds[lastExternalId]; + int newExternalIdOfLast = externalId; + + m_InternalToExternalIds[internalIdOfLast] = newExternalIdOfLast; + m_ExternalToInternalIds[newExternalIdOfLast] = internalIdOfLast; + + m_InternalToExternalIds[internalIdOfRemoved] = -1; + m_ExternalToInternalIds[lastExternalId] = -1; + } + else + { + m_InternalToExternalIds[internalIdOfRemoved] = -1; + m_ExternalToInternalIds[externalId] = -1; + } + } + + private int AddBatchIndex(int externalId) + { + int internalId = AllocateInternalId(); + m_InternalToExternalIds[internalId] = externalId; + m_ExternalToInternalIds[externalId] = internalId; + m_ExistingBatchInternalIndices[internalId] = internalId; + ++m_ExternalBatchCount; + return internalId; + } + + private void RemoveBatchIndex(int internalId, int externalId) + { + if (!(m_ExternalBatchCount > 0)) + Debug.Assert(false, $"Attempted to release an invalid BatchRendererGroup id {externalId}"); + m_ExistingBatchInternalIndices.Remove(internalId); + RemoveExternalIdSwapWithBack(externalId); + ReleaseInternalId(internalId); + --m_ExternalBatchCount; + } + + private int InternalIndexRange => m_SortedInternalIds.Max + 1; + + private void Dispose() + { + m_BatchRendererGroup.Dispose(); + m_MaterialPropertyTypes.Dispose(); + m_MaterialPropertyTypesShared.Dispose(); + m_SharedComponentOverrideTypeIndices.Dispose(); + m_GPUPersistentAllocator.Dispose(); + m_ChunkMetadataAllocator.Dispose(); + + m_BatchInfos.Dispose(); + m_BatchMotionInfos.Dispose(); +#if USE_PICKING_MATRICES + m_BatchPickingMatrices.Dispose(); +#endif + m_ChunkProperties.Dispose(); + m_ExistingBatchInternalIndices.Dispose(); + m_DefaultValueBlits.Dispose(); + m_ComponentTypeCache.Dispose(); + + m_BatchAABBs.Dispose(); + + if (m_InternalToExternalIds.IsCreated) + m_InternalToExternalIds.Dispose(); + if (m_ExternalToInternalIds.IsCreated) + m_ExternalToInternalIds.Dispose(); + if (m_InternalIdFreelist.IsCreated) + m_InternalIdFreelist.Dispose(); + m_ExternalBatchCount = 0; + m_SortedInternalIds = null; + +#if USE_UNITY_OCCLUSION + m_OcclusionCulling.Dispose(); +#endif + + DisposeSharedOverrideCache(); + + m_AABBsCleared = new JobHandle(); + m_AABBClearKicked = false; + } + + private void DisposeSharedOverrideCache() + { + var infos = m_ArchetypeSharedOverrideInfos.GetValueArray(Allocator.Temp); + for (int i = 0; i < infos.Length; ++i) + { + var indices = infos[i].SharedOverrideTypeIndices; + if (indices.IsCreated) + indices.Dispose(); + } + + infos.Dispose(); + m_ArchetypeSharedOverrideInfos.Dispose(); + } + +#if UNITY_ANDROID && !UNITY_64 + // There is a crash bug on ARMv7 potentially related to a compiler bug in the tool chain. + // We will have to leave this function without optimizations on that platform. + [MethodImpl(MethodImplOptions.NoOptimization)] +#endif + private void UpdateAllBatches(out int totalChunks) + { + Profiler.BeginSample("GetComponentTypes"); + + var hybridRenderedChunkTypeRO = GetComponentTypeHandle(true); + var hybridRenderedChunkType = GetComponentTypeHandle(false); + var chunkHeadersRO = GetComponentTypeHandle(true); + var chunkWorldRenderBoundsRO = GetComponentTypeHandle(true); + var localToWorldsRO = GetComponentTypeHandle(true); + var lodRangesRO = GetComponentTypeHandle(true); + var rootLodRangesRO = GetComponentTypeHandle(true); + var chunkPropertyDirtyMask = GetComponentTypeHandle(false); + + m_ComponentTypeCache.FetchTypeHandles(this); + Profiler.EndSample(); + + var numNewChunksArray = new NativeArray(1, Allocator.TempJob); + totalChunks = m_HybridRenderedQuery.CalculateChunkCount(); + var newChunks = new NativeArray( + totalChunks, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + + Dependency = new ClassifyNewChunksJob + { + HybridChunkInfo = hybridRenderedChunkTypeRO, + ChunkHeader = chunkHeadersRO, + NumNewChunks = numNewChunksArray, + NewChunks = newChunks + } + .Schedule(m_MetaEntitiesForHybridRenderableChunks, Dependency); + + const int kNumBitsPerLong = sizeof(long) * 8; + var unreferencedInternalIndices = new NativeArray( + (InternalIndexRange + kNumBitsPerLong) / kNumBitsPerLong, + + Allocator.TempJob, + NativeArrayOptions.ClearMemory); + + JobHandle initializedUnreferenced = default; + var existingKeys = m_ExistingBatchInternalIndices.GetKeyArray(Allocator.TempJob); + initializedUnreferenced = new InitializeUnreferencedIndicesScatterJob + { + ExistingInternalIndices = existingKeys, + UnreferencedInternalIndices = unreferencedInternalIndices, + }.Schedule(existingKeys.Length, kNumScatteredIndicesPerThread); + existingKeys.Dispose(initializedUnreferenced); + + Dependency = JobHandle.CombineDependencies(Dependency, initializedUnreferenced); + + uint lastSystemVersion = LastSystemVersion; + + if (HybridEditorTools.DebugSettings.ForceInstanceDataUpload) + { + Debug.Log("Reuploading all Hybrid Renderer instance data to GPU"); + lastSystemVersion = 0; + } + + CompleteDependency(); + int numNewChunks = numNewChunksArray[0]; + + var maxBatchCount = math.max(kInitialMaxBatchCount, InternalIndexRange + numNewChunks); + + // Integer division with round up + var maxBatchLongCount = (maxBatchCount + kNumBitsPerLong - 1) / kNumBitsPerLong; + + var batchRequiresUpdates = new NativeArray( + maxBatchLongCount, + Allocator.TempJob, + NativeArrayOptions.ClearMemory); + + var batchHadMovingEntities = new NativeArray( + maxBatchLongCount, + Allocator.TempJob, + NativeArrayOptions.ClearMemory); + + var hybridChunkUpdater = new HybridChunkUpdater + { + ComponentTypes = m_ComponentTypeCache.ToBurstCompatible(Allocator.TempJob), + UnreferencedInternalIndices = unreferencedInternalIndices, + BatchRequiresUpdates = batchRequiresUpdates, + BatchHadMovingEntities = batchHadMovingEntities, + ChunkProperties = m_ChunkProperties, + BatchMotionInfos = m_BatchMotionInfos, + BatchAABBs = m_BatchAABBs, + LastSystemVersion = lastSystemVersion, + lastSystemVersionForProperties = m_lastSystemVersionForProperties, + PreviousBatchIndex = -1, + + LocalToWorldType = TypeManager.GetTypeIndex(), + WorldToLocalType = TypeManager.GetTypeIndex(), + PrevWorldToLocalType = TypeManager.GetTypeIndex(), + +#if PROFILE_BURST_JOB_INTERNALS + ProfileAddUpload = new ProfilerMarker("AddUpload"), + ProfilePickingMatrices = new ProfilerMarker("EditorPickingMatrices"), +#endif +#if USE_PICKING_MATRICES + BatchPickingMatrices = m_BatchPickingMatrices, +#endif + }; + + var updateOldJob = new UpdateOldHybridChunksJob + { + HybridChunkInfo = hybridRenderedChunkType, + ChunkWorldRenderBounds = chunkWorldRenderBoundsRO, + ChunkHeader = chunkHeadersRO, + LocalToWorld = localToWorldsRO, + LodRange = lodRangesRO, + RootLodRange = rootLodRangesRO, + HybridChunkUpdater = hybridChunkUpdater, + chunkPropertyDirtyMaskHandle = chunkPropertyDirtyMask + }; + + Dependency = JobHandle.CombineDependencies(Dependency, EnsureAABBsCleared()); + + // We need to wait for the job to complete here so we can process the new chunks + Dependency = updateOldJob.Schedule(m_MetaEntitiesForHybridRenderableChunks, Dependency); + CompleteDependency(); + + // Garbage collect deleted batches before adding new ones to minimize peak memory use. + Profiler.BeginSample("GarbageCollectUnreferencedBatches"); + int numRemoved = GarbageCollectUnreferencedBatches(unreferencedInternalIndices); + Profiler.EndSample(); + + if (numNewChunks > 0) + { + Profiler.BeginSample("AddNewChunks"); + int numValidNewChunks = AddNewChunks(newChunks.GetSubArray(0, numNewChunks)); + Profiler.EndSample(); + + hybridChunkUpdater.PreviousBatchIndex = -1; + + var updateNewChunksJob = new UpdateNewHybridChunksJob + { + NewChunks = newChunks, + HybridChunkInfo = hybridRenderedChunkType, + ChunkWorldRenderBounds = chunkWorldRenderBoundsRO, + HybridChunkUpdater = hybridChunkUpdater, + chunkPropertyDirtyMaskHandle = chunkPropertyDirtyMask + }; + +#if DEBUG_LOG_INVALID_CHUNKS + if (numValidNewChunks != numNewChunks) + Debug.Log($"Tried to add {numNewChunks} new chunks, but only {numValidNewChunks} were valid, {numNewChunks - numValidNewChunks} were invalid"); +#endif + + Dependency = updateNewChunksJob.Schedule(numValidNewChunks, kNumNewChunksPerThread, Dependency); + } + + hybridChunkUpdater.ComponentTypes.Dispose(Dependency); + newChunks.Dispose(Dependency); + numNewChunksArray.Dispose(Dependency); + + // TODO: Need to wait for new chunk updating to complete, so there are no more jobs writing to the bitfields. + // This could be optimized by splitting the memcpy (time consuming part) out from the jobs, because this + // part would only need to wait for the metadata checking, not the memcpys. + CompleteDependency(); + + BlitGlobalValues(); + + StartUpdate(); + + Profiler.BeginSample("UpdateBatchProperties"); + UpdateBatchProperties(batchRequiresUpdates, batchHadMovingEntities); + Profiler.EndSample(); + +#if DEBUG_LOG_CHUNK_CHANGES + if (numNewChunks > 0 || numRemoved > 0) + Debug.Log( + $"Chunks changed, new chunks: {numNewChunks}, removed batches: {numRemoved}, batch count: {m_ExistingBatchInternalIndices.Count()}, chunk count: {m_MetaEntitiesForHybridRenderableChunks.CalculateEntityCount()}"); +#endif + + // Kick the job that clears the batch AABBs for the next frame, so it + // will be done by the time we update on the next frame. + KickAABBClear(); + + unreferencedInternalIndices.Dispose(); + batchRequiresUpdates.Dispose(); + batchHadMovingEntities.Dispose(); + } + + private int GarbageCollectUnreferencedBatches(NativeArray unreferencedInternalIndices) + { + int numRemoved = 0; + + int firstInQw = 0; + for (int i = 0; i < unreferencedInternalIndices.Length; ++i) + { + long qw = unreferencedInternalIndices[i]; + while (qw != 0) + { + int setBit = math.tzcnt(qw); + long mask = ~(1L << setBit); + int internalIndex = firstInQw + setBit; + + RemoveBatch(internalIndex); + ++numRemoved; + + qw &= mask; + } + + firstInQw += (int)AtomicHelpers.kNumBitsInLong; + } + +#if DEBUG_LOG_TOP_LEVEL + Debug.Log($"GarbageCollectUnreferencedBatches(removed: {numRemoved})"); +#endif + + return numRemoved; + } + + private void UpdateBatchProperties( + NativeArray batchRequiresUpdates, + NativeArray batchHadMovingEntities) + { + BatchUpdateStatistics updateStatistics = default; + + int firstInQw = 0; + for (int i = 0; i < batchRequiresUpdates.Length; ++i) + { + long qw = batchRequiresUpdates[i]; + while (qw != 0) + { + int setBit = math.tzcnt(qw); + long mask = (1L << setBit); + int internalIndex = firstInQw + setBit; + + bool entitiesMoved = (batchHadMovingEntities[i] & mask) != 0; + var batchMotionInfo = (BatchMotionInfo*)m_BatchMotionInfos.GetUnsafePtr() + internalIndex; + int externalBatchIndex = m_InternalToExternalIds[internalIndex]; + + UpdateBatchMotionVectors(externalBatchIndex, batchMotionInfo, entitiesMoved, ref updateStatistics); + + if (entitiesMoved) + UpdateBatchBounds(internalIndex, externalBatchIndex, ref updateStatistics); + + qw &= ~mask; + } + + firstInQw += (int)AtomicHelpers.kNumBitsInLong; + } + +#if DEBUG_LOG_BATCH_UPDATES + if (updateStatistics.NonZero) + Debug.Log( + $"Updating batch properties. Enabled motion vectors: {updateStatistics.BatchesNeedingMotionVectors}, disabled motion vectors: {updateStatistics.BatchesWithoutMotionVectors}, updated bounds: {updateStatistics.BatchesWithChangedBounds}"); +#endif + } + + private void UpdateBatchBounds(int internalIndex, int externalBatchIndex, ref BatchUpdateStatistics updateStatistics) + { + int aabbIndex = (int)(((uint)internalIndex) * HybridChunkUpdater.kFloatsPerAABB); + float minX = m_BatchAABBs[aabbIndex + HybridChunkUpdater.kMinX]; + float minY = m_BatchAABBs[aabbIndex + HybridChunkUpdater.kMinY]; + float minZ = m_BatchAABBs[aabbIndex + HybridChunkUpdater.kMinZ]; + float maxX = m_BatchAABBs[aabbIndex + HybridChunkUpdater.kMaxX]; + float maxY = m_BatchAABBs[aabbIndex + HybridChunkUpdater.kMaxY]; + float maxZ = m_BatchAABBs[aabbIndex + HybridChunkUpdater.kMaxZ]; + + var aabb = new MinMaxAABB + { + Min = new float3(minX, minY, minZ), + Max = new float3(maxX, maxY, maxZ), + }; + + var batchBounds = (AABB)aabb; + var batchCenter = batchBounds.Center; + var batchSize = batchBounds.Size; + + m_BatchRendererGroup.SetBatchBounds( + externalBatchIndex, + new Bounds( + new Vector3(batchCenter.x, batchCenter.y, batchCenter.z), + new Vector3(batchSize.x, batchSize.y, batchSize.z))); + +#if DEBUG_LOG_BATCH_UPDATES + ++updateStatistics.BatchesWithChangedBounds; +#endif + } + + private void UpdateBatchMotionVectors(int externalBatchIndex, + BatchMotionInfo* batchMotionInfo, + bool entitiesMoved, + ref BatchUpdateStatistics updateStatistics) + { + if (batchMotionInfo->RequiresMotionVectorUpdates && + entitiesMoved != batchMotionInfo->MotionVectorFlagSet) + { +#if UNITY_2020_1_OR_NEWER + if (entitiesMoved) + { +#if DEBUG_LOG_BATCH_UPDATES + ++updateStatistics.BatchesNeedingMotionVectors; +#endif + m_BatchRendererGroup.SetBatchFlags( + externalBatchIndex, + (int)BatchFlags.NeedMotionVectorPassFlag); + + batchMotionInfo->MotionVectorFlagSet = true; + } + else + { +#if DEBUG_LOG_BATCH_UPDATES + ++updateStatistics.BatchesWithoutMotionVectors; +#endif + m_BatchRendererGroup.SetBatchFlags( + externalBatchIndex, + 0); + + batchMotionInfo->MotionVectorFlagSet = false; + } +#endif + } + } + + private void RemoveBatch(int internalBatchIndex) + { + int externalBatchIndex = m_InternalToExternalIds[internalBatchIndex]; + + var batchInfo = m_BatchInfos[internalBatchIndex]; + m_BatchInfos[internalBatchIndex] = default; + m_BatchMotionInfos[internalBatchIndex] = default; + +#if USE_PICKING_MATRICES + m_BatchPickingMatrices[internalBatchIndex] = IntPtr.Zero; +#endif + +#if DEBUG_LOG_BATCHES + Debug.Log($"RemoveBatch(internalBatchIndex: {internalBatchIndex}, externalBatchIndex: {externalBatchIndex})"); +#endif + + m_BatchRendererGroup.RemoveBatch(externalBatchIndex); + RemoveBatchIndex(internalBatchIndex, externalBatchIndex); + + ref var properties = ref batchInfo.Properties; + for (int i = 0; i < properties.Length; ++i) + { + var gpuAllocation = (properties.Ptr + i)->GPUAllocation; + if (!gpuAllocation.Empty) + { + m_GPUPersistentAllocator.Release(gpuAllocation); +#if DEBUG_LOG_MEMORY_USAGE + Debug.Log($"RELEASE; {gpuAllocation.Length}"); +#endif + } + } + + ref var metadataAllocations = ref batchInfo.ChunkMetadataAllocations; + for (int i = 0; i < metadataAllocations.Length; ++i) + { + var metadataAllocation = metadataAllocations.Ptr[i]; + if (!metadataAllocation.Empty) + { + for (ulong j = metadataAllocation.begin; j < metadataAllocation.end; ++j) + m_ChunkProperties[(int)j] = default; + + m_ChunkMetadataAllocator.Release(metadataAllocation); + } + } + + batchInfo.Dispose(); + } + + private int AddNewChunks(NativeArray newChunksX) + { + int numValidNewChunks = 0; + + Debug.Assert(newChunksX.Length > 0, "Attempted to add new chunks, but list of new chunks was empty"); + + var hybridChunkInfoType = GetComponentTypeHandle(); + // Sort new chunks by RenderMesh so we can put + // all compatible chunks inside one batch. + var batchCreateInfoFactory = new BatchCreateInfoFactory + { + EntityManager = EntityManager, + RenderMeshTypeHandle = GetSharedComponentTypeHandle(), + EditorRenderDataTypeHandle = GetSharedComponentTypeHandle(), + HybridBatchPartitionHandle = GetSharedComponentTypeHandle(), + RenderMeshFlippedWindingTagTypeHandle = GetComponentTypeHandle(), +#if UNITY_EDITOR + DefaultEditorRenderData = new EditorRenderData + { SceneCullingMask = UnityEditor.SceneManagement.EditorSceneManager.DefaultSceneCullingMask }, +#else + DefaultEditorRenderData = new EditorRenderData { SceneCullingMask = ~0UL }, +#endif + }; + var batchCompatibility = new BatchCompatibility(this, batchCreateInfoFactory, newChunksX); + // This also sorts invalid chunks to the back. + var sortedNewChunks = batchCompatibility.SortChunks(); + + int batchBegin = 0; + int numInstances = sortedNewChunks[0].Capacity; + + for (int i = 1; i <= sortedNewChunks.Length; ++i) + { + int instancesInChunk = 0; + bool breakBatch = false; + + if (i < sortedNewChunks.Length) + { + var chunk = sortedNewChunks[i]; + breakBatch = !batchCompatibility.IsCompatible(EntityManager, this, batchBegin, i); + instancesInChunk = chunk.Capacity; + } + else + { + breakBatch = true; + } + + if (numInstances + instancesInChunk > kMaxEntitiesPerBatch) + breakBatch = true; + + if (breakBatch) + { + int numChunks = i - batchBegin; + + var createInfo = batchCompatibility.CreateInfoFor(batchBegin); + bool valid = AddNewBatch(ref createInfo, ref hybridChunkInfoType, + sortedNewChunks.GetSubArray(batchBegin, numChunks), numInstances); + + // As soon as we encounter an invalid chunk, we know that all the rest are invalid + // too. + if (valid) + numValidNewChunks += numChunks; + else + { + NativeArray.Copy(sortedNewChunks, newChunksX, sortedNewChunks.Length); + return numValidNewChunks; + } + + batchBegin = i; + numInstances = instancesInChunk; + } + else + { + numInstances += instancesInChunk; + } + } + + batchCompatibility.Dispose(); + sortedNewChunks.Dispose(); + + return numValidNewChunks; + } + + private BatchInfo CreateBatchInfo(ref BatchCreateInfo createInfo, NativeArray chunks, + int numInstances, Material material = null) + { + BatchInfo batchInfo = default; + + if (material == null) + material = createInfo.RenderMesh.material; + + if (material == null || material.shader == null) + return batchInfo; + +#if UNITY_2020_1_OR_NEWER + var shaderProperties = HybridV2ShaderReflection.GetDOTSInstancingProperties(material.shader); + + ref var properties = ref batchInfo.Properties; + ref var overrideComponents = ref batchInfo.OverrideComponents; + ref var sharedOverrideComponents = ref batchInfo.OverrideSharedComponents; + // TODO: This can be made a Temp allocation if the to-be-released GPU allocations are stored separately + // and preferably batched into one allocation + properties = new UnsafeList( + shaderProperties.Length, + Allocator.Persistent, + NativeArrayOptions.ClearMemory); + overrideComponents = new UnsafeList( + shaderProperties.Length, + Allocator.Temp, + NativeArrayOptions.ClearMemory); + sharedOverrideComponents = new UnsafeList( + shaderProperties.Length, + Allocator.Temp, + NativeArrayOptions.ClearMemory); + batchInfo.ChunkMetadataAllocations = new UnsafeList( + shaderProperties.Length, + Allocator.Persistent, + NativeArrayOptions.ClearMemory); + + float4x4 defaultValue = default; + + for (int i = 0; i < shaderProperties.Length; ++i) + { + var shaderProperty = shaderProperties[i]; + int nameID = shaderProperty.ConstantNameID; + BatchPropertyOverrideStatus overrideStatus = BatchPropertyOverrideStatus.Uninitialized; + + bool isBuiltin = shaderProperty.CbufferIndex == kBuiltinCbufferIndex; + + short sizeCPU = 0; + int overridesStartIndex = -1; + int sharedOverridesStartIndex = -1; + + { + bool foundMaterialPropertyType = m_MaterialPropertyTypes.TryGetFirstValue( + nameID, + out var materialPropertyType, + out var it); + + while (foundMaterialPropertyType) + { + // There can be multiple components that override some particular NameID, so add + // entries for all of them. + if (materialPropertyType.SizeBytesGPU == shaderProperty.SizeBytes || + materialPropertyType.SizeBytesCPU == shaderProperty.SizeBytes + ) // TODO: hack to work around the property being the real size after load + { + if (overridesStartIndex < 0) + overridesStartIndex = overrideComponents.Length; + + overrideComponents.Add(new BatchInfo.BatchOverrideComponent + { + BatchPropertyIndex = i, + TypeIndex = materialPropertyType.TypeIndex, + }); + + sizeCPU = materialPropertyType.SizeBytesCPU; + + // We cannot ask default values for builtins from the material, that causes errors. + // Instead, check whether one was registered manually when the overriding type + // was registered. In case there are several overriding types, we use the first + // one with a registered value. + if (isBuiltin && overrideStatus == BatchPropertyOverrideStatus.Uninitialized) + { + switch (materialPropertyType.DefaultValueKind) + { + case DefaultValueKind.ZeroDefault: + default: + break; + case DefaultValueKind.NonzeroDefault: + defaultValue = m_MaterialPropertyDefaultValues[materialPropertyType.TypeIndex]; + overrideStatus = BatchPropertyOverrideStatus.SharedNonzeroDefault; + break; + case DefaultValueKind.SharedBuiltin: + overrideStatus = BatchPropertyOverrideStatus.SharedBuiltin; + break; + } + } + } + else + { +#if USE_PROPERTY_ASSERTS + Debug.Log( + $"Shader \"{material.shader.name}\" expects property \"{m_MaterialPropertyNames[nameID]}\" to have size {shaderProperty.SizeBytes}, but overriding component \"{m_MaterialPropertyTypeNames[materialPropertyType.TypeIndex]}\" has size {materialPropertyType.SizeBytesGPU} instead."); +#endif + } + + foundMaterialPropertyType = + m_MaterialPropertyTypes.TryGetNextValue(out materialPropertyType, ref it); + } + } + + { + bool foundSharedMaterialPropertyType = m_MaterialPropertyTypesShared.TryGetFirstValue( + nameID, + out var sharedMaterialPropertyType, + out var it); + + while (foundSharedMaterialPropertyType) + { + if (sharedMaterialPropertyType.SizeBytesGPU == shaderProperty.SizeBytes || + sharedMaterialPropertyType.SizeBytesCPU == shaderProperty.SizeBytes + ) // TODO: hack to work around the property being the real size after load + { + if (sharedOverridesStartIndex < 0) + sharedOverridesStartIndex = sharedOverrideComponents.Length; + + if (sizeCPU == 0) + { + sizeCPU = sharedMaterialPropertyType.SizeBytesCPU; + } + else if (sizeCPU == sharedMaterialPropertyType.SizeBytesCPU) + { + sharedOverrideComponents.Add(new BatchInfo.BatchOverrideComponent + { + BatchPropertyIndex = i, + TypeIndex = sharedMaterialPropertyType.TypeIndex, + }); + } + else + { +#if USE_PROPERTY_ASSERTS + Debug.Log( + $"Shader \"{material.shader.name}\" expects property \"{m_MaterialPropertyNames[nameID]}\" to have size {sizeCPU}, but overriding shared component \"{m_MaterialPropertyTypeNames[sharedMaterialPropertyType.TypeIndex]}\" has size {sharedMaterialPropertyType.SizeBytesGPU} instead."); +#endif + } + } + else + { +#if USE_PROPERTY_ASSERTS + Debug.Log( + $"Shader \"{material.shader.name}\" expects property \"{m_MaterialPropertyNames[nameID]}\" to have size {shaderProperty.SizeBytes}, but overriding component \"{m_MaterialPropertyTypeNames[sharedMaterialPropertyType.TypeIndex]}\" has size {sharedMaterialPropertyType.SizeBytesGPU} instead."); +#endif + } + + foundSharedMaterialPropertyType = + m_MaterialPropertyTypesShared.TryGetNextValue(out sharedMaterialPropertyType, ref it); + } + } + + int sharedBuiltinOffset = -1; + // For non-builtin properties, we can always ask the material for defaults. + if (!isBuiltin) + { + var propertyDefault = DefaultValueFromMaterial(material, nameID, shaderProperty.SizeBytes); + defaultValue = propertyDefault.Value; + overrideStatus = propertyDefault.Nonzero ? + BatchPropertyOverrideStatus.SharedNonzeroDefault : + BatchPropertyOverrideStatus.SharedZeroDefault; + } + // Builtins for which nothing special has been found use a zero default + else if (overrideStatus == BatchPropertyOverrideStatus.Uninitialized) + { + overrideStatus = BatchPropertyOverrideStatus.SharedZeroDefault; + } + else if (overrideStatus == BatchPropertyOverrideStatus.SharedBuiltin) + { + if (!m_MaterialPropertySharedBuiltins.TryGetValue(nameID, out sharedBuiltinOffset)) + { +#if USE_PROPERTY_ASSERTS + Debug.Log($"Shader property \"{m_MaterialPropertyNames[nameID]}\" is configured to use a shared built-in value, but it was not found."); +#endif + + // Fall back to zero + overrideStatus = BatchPropertyOverrideStatus.SharedZeroDefault; + } + } + + properties.Add(new BatchInfo.BatchProperty + { + MetadataOffset = shaderProperty.MetadataOffset, + SizeBytesCPU = sizeCPU, + SizeBytesGPU = (short)shaderProperty.SizeBytes, + CbufferIndex = shaderProperty.CbufferIndex, + OverrideComponentsIndex = overridesStartIndex, + SharedBuiltinOffset = sharedBuiltinOffset, + OverrideStatus = overrideStatus, + DefaultValue = defaultValue, +#if USE_PROPERTY_ASSERTS + NameID = nameID, +#endif + }); + } + + // Check which properties have static component overrides in at least one chunk. + for (int i = 0; i < sharedOverrideComponents.Length; ++i) + { + var componentType = sharedOverrideComponents.Ptr + i; + var property = properties.Ptr + componentType->BatchPropertyIndex; + + var type = GetDynamicSharedComponentTypeHandle(ComponentType.ReadOnly(componentType->TypeIndex)); + + for (int j = 0; j < chunks.Length; ++j) + { + if (chunks[j].Has(type)) + { + property->OverrideStatus = BatchPropertyOverrideStatus.SharedComponentOverride; + var propertyDefault = DefaultValueFromSharedComponent(chunks[j], type); + property->DefaultValue = propertyDefault.Value; + break; + } + } + } + + // Check which properties have overrides in at least one chunk. + for (int i = 0; i < overrideComponents.Length; ++i) + { + var componentType = overrideComponents.Ptr + i; + var property = properties.Ptr + componentType->BatchPropertyIndex; + + var type = m_ComponentTypeCache.Type(componentType->TypeIndex); + + for (int j = 0; j < chunks.Length; ++j) + { + if (chunks[j].Has(type)) + { + property->OverrideStatus = BatchPropertyOverrideStatus.PerEntityOverride; + break; + } + } + } + + for (int i = 0; i < properties.Length; ++i) + { + var property = properties.Ptr + i; + + if (property->OverrideStatus == BatchPropertyOverrideStatus.Uninitialized) + Debug.Assert(false, "Batch property override status not initialized"); + + // If the property has a default value of all zeros and isn't overridden, + // we can use the global offset which contains zero bytes, so we don't need + // to upload a huge amount of unnecessary zeros. + bool needsDedicatedAllocation = PropertyRequiresAllocation(property->OverrideStatus); + if (needsDedicatedAllocation) + { + // If the property is not overridden, we only need space for a single element, the default value. + uint sizeBytes = (uint)PropertyValuesPerAllocation(property->OverrideStatus, numInstances) * (uint)property->SizeBytesGPU; + +#if DEBUG_LOG_MEMORY_USAGE + Debug.Log( + $"ALLOCATE; {m_MaterialPropertyNames[property->NameID]}; {PropertyValuesPerAllocation(property->OverrideStatus, numInstances)}; {property->SizeBytesGPU}; {sizeBytes}"); +#endif + + property->GPUAllocation = m_GPUPersistentAllocator.Allocate(sizeBytes); + if (property->GPUAllocation.Empty) + Debug.Assert(false, + $"Out of memory in the Hybrid Renderer GPU instance data buffer. Attempted to allocate {sizeBytes}, buffer size: {m_GPUPersistentAllocator.Size}, free size left: {m_GPUPersistentAllocator.FreeSpace}."); + } + } +#endif + + return batchInfo; + } + + private MaterialPropertyDefaultValue DefaultValueFromSharedComponent(ArchetypeChunk chunk, DynamicSharedComponentTypeHandle type) + { + return new MaterialPropertyDefaultValue( + (chunk.GetSharedComponentDataBoxed(type, EntityManager) as IHybridSharedComponentFloat4Override) + .GetFloat4OverrideData()); + } + + private int FindPropertyFromNameID(Shader shader, int nameID) + { + // TODO: this linear search should go away, but serialized property in shader is all string based so we can't use regular nameID sadly + var count = shader.GetPropertyCount(); + for (int i = 0; i < count; ++i) + { + var id = shader.GetPropertyNameId(i); + if (id == nameID) + return i; + } + + return -1; + } + + private MaterialPropertyDefaultValue DefaultValueFromMaterial( + Material material, int nameID, int sizeBytes) + { + MaterialPropertyDefaultValue propertyDefaultValue = default; + + switch (sizeBytes) + { + case 4: + propertyDefaultValue = new MaterialPropertyDefaultValue(material.GetFloat(nameID)); + break; + // float2 and float3 are handled as float4 here + case 8: + case 12: + case 16: + var shader = material.shader; + var i = FindPropertyFromNameID(shader, nameID); + Debug.Assert(i != -1, "Could not find property in shader"); + var type = shader.GetPropertyType(i); + var flags = shader.GetPropertyFlags(i); + // HDR colors should never be converted, they are always linear + if (type == ShaderPropertyType.Color && (flags & ShaderPropertyFlags.HDR) == 0) + { + if (QualitySettings.activeColorSpace == ColorSpace.Linear) + propertyDefaultValue = + new MaterialPropertyDefaultValue((Vector4)material.GetColor(nameID).linear); + else + propertyDefaultValue = + new MaterialPropertyDefaultValue((Vector4)material.GetColor(nameID).gamma); + } + else + { + propertyDefaultValue = new MaterialPropertyDefaultValue(material.GetVector(nameID)); + } + break; + case 64: + propertyDefaultValue = float4x4.identity; // matrix4x4 can't have default value in unity (you can't edit a matrix in a material inspector) (case 1339072) + break; + default: + Debug.LogWarning($"Unsupported size for a material property with nameID {nameID}"); + break; + } + + return propertyDefaultValue; + } + + private NativeList ChunkOverriddenProperties(ref BatchInfo batchInfo, ArchetypeChunk chunk, + int chunkStart, Allocator allocator) + { + ref var properties = ref batchInfo.Properties; + ref var overrideComponents = ref batchInfo.OverrideComponents; + + var overriddenProperties = new NativeList(properties.Length, allocator); + + int prevPropertyIndex = -1; + int numOverridesForProperty = 0; + int overrideIsFromIndex = -1; + + for (int i = 0; i < overrideComponents.Length; ++i) + { + var componentType = overrideComponents.Ptr + i; + int propertyIndex = componentType->BatchPropertyIndex; + var property = properties.Ptr + propertyIndex; + + if (property->OverrideStatus != BatchPropertyOverrideStatus.PerEntityOverride) + continue; + + if (propertyIndex != prevPropertyIndex) + numOverridesForProperty = 0; + + prevPropertyIndex = propertyIndex; + + if (property->GPUAllocation.Empty) + { + Debug.Assert(false, +#if USE_PROPERTY_ASSERTS + $"No valid GPU instance data buffer allocation for property {m_MaterialPropertyNames[property->NameID]}"); +#else + "No valid GPU instance data buffer allocation for property"); +#endif + } + + int typeIndex = componentType->TypeIndex; + var type = m_ComponentTypeCache.Type(typeIndex); + + if (chunk.Has(type)) + { + // If a chunk has multiple separate overrides for a property, it is not + // well defined and we ignore all but one of them and possibly issue an error. + if (numOverridesForProperty == 0) + { + uint sizeBytes = (uint)property->SizeBytesGPU; + uint batchBeginOffset = (uint)property->GPUAllocation.begin; + uint chunkBeginOffset = batchBeginOffset + (uint)chunkStart * sizeBytes; + + overriddenProperties.Add(new ChunkProperty + { + ComponentTypeIndex = typeIndex, + ValueSizeBytesCPU = property->SizeBytesCPU, + ValueSizeBytesGPU = property->SizeBytesGPU, + GPUDataBegin = (int)chunkBeginOffset, + }); + + overrideIsFromIndex = i; + +#if DEBUG_LOG_OVERRIDES + Debug.Log($"Property {m_MaterialPropertyNames[property->NameID]} overridden by component {m_MaterialPropertyTypeNames[componentType->TypeIndex]}"); +#endif + } + else + { +#if USE_PROPERTY_ASSERTS + Debug.Log( + $"Chunk has multiple overriding components for property \"{m_MaterialPropertyNames[property->NameID]}\". Override from component \"{m_MaterialPropertyTypeNames[overrideComponents.Ptr[overrideIsFromIndex].TypeIndex]}\" used, value from component \"{m_MaterialPropertyTypeNames[componentType->TypeIndex]}\" ignored."); +#endif + } + + ++numOverridesForProperty; + } + } + + return overriddenProperties; + } + + private bool UseErrorMaterial(ref BatchCreateInfo createInfo) + { +#if DISABLE_HYBRID_ERROR_MATERIAL + // If the error material is disabled, skip all checking. + return false; +#endif + + ref var renderMesh = ref createInfo.RenderMesh; + var material = renderMesh.material; + + // If there is no mesh, we can't use an error material at all + if (renderMesh.mesh == null) + return false; + + // If there is a mesh, but there is no material, then we use the error material + if (material == null) + return true; + + // If there is a material, and it somehow doesn't have a shader, use the error material. + if (material.shader == null) + return true; + + // If the shader being used has compile errors, always use the error material. + if (ShaderHasError(material.shader)) + return true; + + // If there is a material, check whether it's using the internal error shader, + // in that case we also use the error material. + if (material.shader == m_BuiltinErrorShader) + return true; + + // Otherwise, don't use the error material. + return false; + } + + private bool ShaderHasError(Shader shader) + { +#if UNITY_EDITOR + bool hasErrors = false; + + // ShaderUtil.ShaderHasError is very expensive, check if we have already called + // it for this shader. + if (m_ShaderHasCompileErrors.TryGetValue(shader, out hasErrors)) + return hasErrors; + + // If not, we check that status once and cache the result. + hasErrors = ShaderUtil.ShaderHasError(shader); + m_ShaderHasCompileErrors[shader] = hasErrors; + return hasErrors; +#else + // ShaderUtil is an Editor only API + return false; +#endif + } + + private bool AddNewBatch(ref BatchCreateInfo createInfo, + ref ComponentTypeHandle hybridChunkInfoTypeHandle, + NativeArray batchChunks, + int numInstances) + { + var material = createInfo.RenderMesh.material; + var cachedMaterial = material; + + if (UseErrorMaterial(ref createInfo)) + { + material = m_ErrorMaterial; + + if (material != null) + Debug.LogWarning("WARNING: Hybrid Renderer using error shader for batch with an erroneous material: " + cachedMaterial, cachedMaterial); + } + else if (!createInfo.Valid) + { + Debug.LogWarning("WARNING: Hybrid Renderer skipping a batch due to invalid RenderMesh."); + return false; + } + + // Double check for null material, this can happen if there is a problem with + // the error material. + if (material == null) + { + Debug.LogWarning("WARNING: Hybrid Renderer skipping a batch due to no valid material."); + return false; + } + + ref var renderMesh = ref createInfo.RenderMesh; + + int externalBatchIndex = m_BatchRendererGroup.AddBatch( + renderMesh.mesh, + renderMesh.subMesh, + material, + renderMesh.layer, + renderMesh.castShadows, + renderMesh.receiveShadows, + createInfo.FlippedWinding, + createInfo.Bounds, + numInstances, + null, + createInfo.EditorRenderData.PickableObject, + createInfo.EditorRenderData.SceneCullingMask, + renderMesh.layerMask); + int internalBatchIndex = AddBatchIndex(externalBatchIndex); + +#if UNITY_2020_1_OR_NEWER + if (renderMesh.needMotionVectorPass) + m_BatchRendererGroup.SetBatchFlags(externalBatchIndex, (int)BatchFlags.NeedMotionVectorPassFlag); +#endif + + var batchInfo = CreateBatchInfo(ref createInfo, batchChunks, numInstances, material); + var batchMotionInfo = new BatchMotionInfo + { + RequiresMotionVectorUpdates = renderMesh.needMotionVectorPass, + MotionVectorFlagSet = renderMesh.needMotionVectorPass, + }; + +#if DEBUG_LOG_BATCHES + Debug.Log( + $"AddBatch(internalBatchIndex: {internalBatchIndex}, externalBatchIndex: {externalBatchIndex}, properties: {batchInfo.Properties.Length}, chunks: {batchChunks.Length}, numInstances: {numInstances}, mesh: {renderMesh.mesh}, material: {material})"); +#endif + + SetBatchMetadata(externalBatchIndex, ref batchInfo, material); + AddBlitsForSharedDefaults(ref batchInfo); + +#if USE_PICKING_MATRICES + // Picking currently uses a built-in shader that renders using traditional instancing, + // and expects matrices in an instancing array, which is how Hybrid V1 always works. + // To support picking, we cache a pointer into the instancing matrix array of each + // batch, and refresh the contents whenever the DOTS side matrices change. + // This approach relies on the instancing matrices being permanently allocated (i.e. + // not temp allocated), which is the case at the time of writing. + var matrixArray = m_BatchRendererGroup.GetBatchMatrices(externalBatchIndex); + m_BatchPickingMatrices[internalBatchIndex] = (IntPtr)matrixArray.GetUnsafePtr(); +#endif + + CullingComponentTypes batchCullingComponentTypes = new CullingComponentTypes + { + RootLODRanges = GetComponentTypeHandle(true), + RootLODWorldReferencePoints = GetComponentTypeHandle(true), + LODRanges = GetComponentTypeHandle(true), + LODWorldReferencePoints = GetComponentTypeHandle(true), + PerInstanceCullingTag = GetComponentTypeHandle(true) + }; + + ref var metadataAllocations = ref batchInfo.ChunkMetadataAllocations; + + int chunkStart = 0; + for (int i = 0; i < batchChunks.Length; ++i) + { + var chunk = batchChunks[i]; + AddBlitsForNotOverriddenProperties(ref batchInfo, chunk, chunkStart); + var overriddenProperties = ChunkOverriddenProperties(ref batchInfo, chunk, chunkStart, Allocator.Temp); + HeapBlock metadataAllocation = default; + if (overriddenProperties.Length > 0) + { + metadataAllocation = m_ChunkMetadataAllocator.Allocate((ulong)overriddenProperties.Length); + Debug.Assert(!metadataAllocation.Empty, "Failed to allocate space for chunk property metadata"); + metadataAllocations.Add(metadataAllocation); + } + + var chunkInfo = new HybridChunkInfo + { + InternalIndex = internalBatchIndex, + ChunkTypesBegin = (int)metadataAllocation.begin, + ChunkTypesEnd = (int)metadataAllocation.end, + CullingData = ComputeChunkCullingData(ref batchCullingComponentTypes, chunk, chunkStart), + Valid = true, + }; + + if (overriddenProperties.Length > 0) + { + UnsafeUtility.MemCpy( + (ChunkProperty*)m_ChunkProperties.GetUnsafePtr() + chunkInfo.ChunkTypesBegin, + overriddenProperties.GetUnsafeReadOnlyPtr(), + overriddenProperties.Length * sizeof(ChunkProperty)); + } + + chunk.SetChunkComponentData(hybridChunkInfoTypeHandle, chunkInfo); + +#if DEBUG_LOG_CHUNKS + Debug.Log($"AddChunk(chunk: {chunk.Count}, chunkStart: {chunkStart}, overriddenProperties: {overriddenProperties.Length})"); +#endif + + chunkStart += chunk.Capacity; + } + + batchInfo.OverrideComponents.Dispose(); + batchInfo.OverrideComponents = default; + batchInfo.OverrideSharedComponents.Dispose(); + batchInfo.OverrideSharedComponents = default; + + m_BatchInfos[internalBatchIndex] = batchInfo; + m_BatchMotionInfos[internalBatchIndex] = batchMotionInfo; + + return true; + } + + private void SetBatchMetadata(int externalBatchIndex, ref BatchInfo batchInfo, Material material) + { +#if UNITY_2020_1_OR_NEWER + var metadataCbuffers = HybridV2ShaderReflection.GetDOTSInstancingCbuffers(material.shader); + + var metadataCbufferStarts = new NativeArray( + metadataCbuffers.Length, + Allocator.Temp, + NativeArrayOptions.UninitializedMemory); + var metadataCbufferLengths = new NativeArray( + metadataCbuffers.Length, + Allocator.Temp, + NativeArrayOptions.UninitializedMemory); + + int totalSizeInts = 0; + + for (int i = 0; i < metadataCbuffers.Length; ++i) + { + int sizeInts = (int)((uint)metadataCbuffers[i].SizeBytes / sizeof(int)); + metadataCbufferStarts[i] = totalSizeInts; + metadataCbufferLengths[i] = sizeInts; + totalSizeInts += sizeInts; + } + + var metadataCbufferStorage = new NativeArray( + totalSizeInts, + Allocator.Temp, + NativeArrayOptions.ClearMemory); + + ref var properties = ref batchInfo.Properties; + for (int i = 0; i < properties.Length; ++i) + { + var property = properties.Ptr + i; + int offsetInInts = property->MetadataOffset / sizeof(int); + int metadataIndex = metadataCbufferStarts[property->CbufferIndex] + offsetInInts; + + HeapBlock allocation = property->GPUAllocation; + if (PropertyIsZero(property->OverrideStatus)) + { + allocation = m_SharedZeroAllocation; + } + else if (property->OverrideStatus == BatchPropertyOverrideStatus.SharedBuiltin) + { + allocation.begin = (ulong)property->SharedBuiltinOffset; + } + + uint metadataForProperty = PropertyHasPerEntityValues(property->OverrideStatus) ? + 0x80000000 : + 0; + metadataForProperty |= (uint)allocation.begin & 0x7fffffff; + metadataCbufferStorage[metadataIndex] = (int)metadataForProperty; + +#if DEBUG_LOG_PROPERTIES + Debug.Log( + $"Property(internalBatchIndex: {m_ExternalToInternalIds[externalBatchIndex]}, externalBatchIndex: {externalBatchIndex}, property: {i}, elementSize: {property->SizeBytesCPU}, cbuffer: {property->CbufferIndex}, metadataOffset: {property->MetadataOffset}, metadata: {metadataForProperty:x8})"); +#endif + } + +#if DEBUG_LOG_BATCHES + Debug.Log( + $"SetBatchPropertyMetadata(internalBatchIndex: {m_ExternalToInternalIds[externalBatchIndex]}, externalBatchIndex: {externalBatchIndex}, numCbuffers: {metadataCbufferLengths.Length}, numMetadataInts: {metadataCbufferStorage.Length})"); +#endif + + m_BatchRendererGroup.SetBatchPropertyMetadata(externalBatchIndex, metadataCbufferLengths, + metadataCbufferStorage); +#endif + } + + private HybridChunkCullingData ComputeChunkCullingData( + ref CullingComponentTypes cullingComponentTypes, + ArchetypeChunk chunk, int chunkStart) + { + var hasLodData = chunk.Has(cullingComponentTypes.RootLODRanges) && + chunk.Has(cullingComponentTypes.LODRanges); + var hasPerInstanceCulling = !hasLodData || chunk.Has(cullingComponentTypes.PerInstanceCullingTag); + + return new HybridChunkCullingData + { + Flags = (byte) + ((hasLodData ? HybridChunkCullingData.kFlagHasLodData : 0) | + (hasPerInstanceCulling ? HybridChunkCullingData.kFlagInstanceCulling : 0)), + BatchOffset = (short)chunkStart, + InstanceLodEnableds = default + }; + } + + private void AddBlitsForSharedDefaults(ref BatchInfo batchInfo) + { + ref var properties = ref batchInfo.Properties; + for (int i = 0; i < properties.Length; ++i) + { + var property = properties.Ptr + i; + + // If the property is overridden, the batch cannot use a single shared default + // value, as there is only a single pointer for the entire batch. + // If the default value can be shared, but is known to be zero, we will use the + // global offset zero, so no need to upload separately for each property. + if (!PropertyRequiresBlit(property->OverrideStatus)) + continue; + +#if DEBUG_LOG_OVERRIDES + Debug.Log($"Property {m_MaterialPropertyNames[property->NameID]} not overridden in batch, OverrideStatus: {property->OverrideStatus}"); +#endif + + uint sizeBytes = (uint)property->SizeBytesGPU; + uint batchBeginOffset = (uint)property->GPUAllocation.begin; + + m_DefaultValueBlits.Add(new DefaultValueBlitDescriptor + { + DefaultValue = property->DefaultValue, + DestinationOffset = batchBeginOffset, + Count = 1, + ValueSizeBytes = sizeBytes, + }); + } + } + + private void AddBlitsForNotOverriddenProperties(ref BatchInfo batchInfo, ArchetypeChunk chunk, int chunkStart) + { + ref var properties = ref batchInfo.Properties; + ref var overrideComponents = ref batchInfo.OverrideComponents; + + for (int i = 0; i < properties.Length; ++i) + { + var property = properties.Ptr + i; + + // If the property is not overridden in the batch at all, it is handled by + // AddBlitsForSharedDefaults(). + if (property->OverrideStatus != BatchPropertyOverrideStatus.PerEntityOverride) + continue; + + // Loop through all components that could potentially override this property, which + // are guaranteed to be contiguous in the array. + int overrideIndex = property->OverrideComponentsIndex; + bool isOverridden = false; + + if (property->GPUAllocation.Empty) + { + Debug.Assert(false, +#if USE_PROPERTY_ASSERTS + $"No valid GPU instance data buffer allocation for property {m_MaterialPropertyNames[property->NameID]}"); +#else + "No valid GPU instance data buffer allocation for property"); +#endif + } + + Debug.Assert(overrideIndex >= 0, "Expected a valid array index"); + + while (overrideIndex < overrideComponents.Length) + { + var componentType = overrideComponents.Ptr + overrideIndex; + if (componentType->BatchPropertyIndex != i) + break; + + int typeIndex = componentType->TypeIndex; + var type = m_ComponentTypeCache.Type(typeIndex); + + if (chunk.Has(type)) + { +#if DEBUG_LOG_OVERRIDES + Debug.Log($"Property {m_MaterialPropertyNames[property->NameID]} IS overridden in chunk, NOT uploading default"); +#endif + + isOverridden = true; + break; + } + + ++overrideIndex; + } + + if (!isOverridden) + { +#if DEBUG_LOG_OVERRIDES + Debug.Log($"Property {m_MaterialPropertyNames[property->NameID]} NOT overridden in chunk, uploading default"); +#endif + + uint sizeBytes = (uint)property->SizeBytesGPU; + uint batchBeginOffset = (uint)property->GPUAllocation.begin; + uint chunkBeginOffset = (uint)chunkStart * sizeBytes; + + m_DefaultValueBlits.Add(new DefaultValueBlitDescriptor + { + DefaultValue = property->DefaultValue, + DestinationOffset = batchBeginOffset + chunkBeginOffset, + Count = (uint)chunk.Count, + ValueSizeBytes = sizeBytes, + }); + } + } + } + + // Return a JobHandle that completes when the AABBs are clear. If the job + // hasn't been kicked (i.e. it's the first frame), then do it now. + private JobHandle EnsureAABBsCleared() + { + if (!m_AABBClearKicked) + KickAABBClear(); + + return m_AABBsCleared; + } + + private void KickAABBClear() + { + m_AABBsCleared = new AABBClearJob + { + BatchAABBs = m_BatchAABBs, + }.Schedule(m_ExternalBatchCount, 64); + + m_AABBClearKicked = true; + } + + private void CompleteJobs() + { + m_AABBsCleared.Complete(); + } + + private void StartUpdate() + { + var persistanceBytes = m_GPUPersistentAllocator.OnePastHighestUsedAddress; + if (persistanceBytes > m_PersistentInstanceDataSize) + { + while (m_PersistentInstanceDataSize < persistanceBytes) + { + m_PersistentInstanceDataSize *= 2; + } + + if (m_PersistentInstanceDataSize > kGPUBufferSizeMax) + { + m_PersistentInstanceDataSize = kGPUBufferSizeMax; // Some backends fails at loading 1024 MiB, but 1023 is fine... This should ideally be a device cap. + } + + if (persistanceBytes > kGPUBufferSizeMax) + Debug.LogError( + "Hybrid Renderer: Current loaded scenes need more than 1GiB of persistent GPU memory. This is more than some GPU backends can allocate. Try to reduce amount of loaded data."); + } + } + + internal void ResizeWithMinusOne(NativeList list, int newLength) + { + Debug.Assert(newLength > 0, "Invalid newLength argument"); + + var currentLength = list.Length; + + if (newLength > currentLength) + { + list.Resize(newLength, NativeArrayOptions.ClearMemory); + + for (int i = currentLength; i < newLength; ++i) + { + list[i] = -1; + } + } + } + + static NativeList NewNativeListResized(int length, Allocator allocator, NativeArrayOptions resizeOptions = NativeArrayOptions.ClearMemory) where T : unmanaged + { + var list = new NativeList(length, allocator); + list.Resize(length, resizeOptions); + + return list; + } + + private void BlitGlobalValues() + { + if (m_GlobalAmbientProbeDirty) + { + var probe = m_GlobalAmbientProbe; + BlitBytes(m_SharedAmbientProbeAllocation, &probe, UnsafeUtility.SizeOf()); + m_GlobalAmbientProbeDirty = false; + } + + if (m_GlobalSpecCubeDecodeDirty) + { + var decodeF4 = m_GlobalSpecCubeDecode; + BlitBytes(m_SharedSpecCubeDecodeAllocation, &decodeF4, UnsafeUtility.SizeOf()); + m_GlobalSpecCubeDecodeDirty = false; + } + } + + internal void UpdateSpecCubeHDRDecode(Vector4 specCubeHDRDecode) + { + if (!s_HybridRendererEnabled) + return; + + float4 decodeF4 = specCubeHDRDecode; + m_GlobalSpecCubeDecodeDirty = + m_GlobalSpecCubeDecodeDirty || + math.any(decodeF4 != m_GlobalSpecCubeDecode); + +#if DEBUG_LOG_SPECULAR_DECODE + if (m_GlobalSpecCubeDecodeDirty) + { + Debug.Log($"Global specular probe decode: {decodeF4}"); + } +#endif + m_GlobalSpecCubeDecode = decodeF4; + } + + private void UpdateGlobalAmbientProbe(SHProperties globalAmbientProbe) + { + if (!s_HybridRendererEnabled) + return; + m_GlobalAmbientProbeDirty = m_GlobalAmbientProbeDirty || globalAmbientProbe != m_GlobalAmbientProbe; +#if DEBUG_LOG_AMBIENT_PROBE + if (m_GlobalAmbientProbeDirty) + { + Debug.Log( + $"Global Ambient probe: {globalAmbientProbe.SHAr} {globalAmbientProbe.SHAg} {globalAmbientProbe.SHAb} {globalAmbientProbe.SHBr} {globalAmbientProbe.SHBg} {globalAmbientProbe.SHBb} {globalAmbientProbe.SHC}"); + } +#endif + m_GlobalAmbientProbe = globalAmbientProbe; + } + + #endregion + + #region Callbacks + protected override void OnCreate() + { + // If all graphics rendering has been disabled, early out from all HR functionality +#if HYBRID_RENDERER_DISABLED + s_HybridRendererEnabled = false; +#else + s_HybridRendererEnabled = HybridUtils.IsHybridSupportedOnSystem(); +#endif + if (!s_HybridRendererEnabled) + { +#if !DISABLE_HYBRID_V2_SRP_LOGS + Debug.Log("No SRP present, no compute shader support, or running with -nographics. Hybrid Renderer disabled"); +#endif + return; + } + m_cullingSuperSystem = World.GetOrCreateSystem(); + worldBlackboardEntity.AddComponent(); + worldBlackboardEntity.AddBuffer(); + worldBlackboardEntity.AddCollectionComponent(new BrgCullingContext()); + worldBlackboardEntity.AddBuffer(); + worldBlackboardEntity.AddCollectionComponent(new MaterialPropertiesUploadContext()); + + m_PersistentInstanceDataSize = kGPUBufferSizeInitial; + + m_HybridRenderedQuery = GetEntityQuery(HybridUtils.GetHybridRenderedQueryDesc()); + + m_BatchRendererGroup = new BatchRendererGroup(OnPerformCulling); + + m_GPUPersistentAllocator = new HeapAllocator(kMaxGPUAllocatorMemory, 16); + m_ChunkMetadataAllocator = new HeapAllocator(kMaxChunkMetadata); + + m_BatchInfos = NewNativeListResized(kInitialMaxBatchCount, Allocator.Persistent); + m_BatchMotionInfos = NewNativeListResized(kInitialMaxBatchCount, Allocator.Persistent); +#if USE_PICKING_MATRICES + m_BatchPickingMatrices = NewNativeListResized(kInitialMaxBatchCount, Allocator.Persistent); +#endif + m_ChunkProperties = new NativeArray(kMaxChunkMetadata, Allocator.Persistent); + m_ExistingBatchInternalIndices = new NativeParallelHashMap(128, Allocator.Persistent); + m_ComponentTypeCache = new ComponentTypeCache(128); + + m_BatchAABBs = NewNativeListResized(kInitialMaxBatchCount * (int)HybridChunkUpdater.kFloatsPerAABB, Allocator.Persistent); + + m_DefaultValueBlits = new NativeList(Allocator.Persistent); + + m_AABBsCleared = new JobHandle(); + m_AABBClearKicked = false; + + // Globally allocate a single zero matrix and reuse that for all default values that are pure zero + m_SharedZeroAllocation = m_GPUPersistentAllocator.Allocate((ulong)sizeof(float4x4)); + Debug.Assert(!m_SharedZeroAllocation.Empty, "Allocation of constant-zero data failed"); + // Make sure the global zero is actually zero. + m_DefaultValueBlits.Add(new DefaultValueBlitDescriptor + { + DefaultValue = float4x4.zero, + DestinationOffset = (uint)m_SharedZeroAllocation.begin, + ValueSizeBytes = (uint)sizeof(float4x4), + Count = 1, + }); + + m_SharedAmbientProbeAllocation = m_GPUPersistentAllocator.Allocate((ulong)UnsafeUtility.SizeOf()); + Debug.Assert(!m_SharedAmbientProbeAllocation.Empty, "Allocation of the global ambient probe failed"); + UpdateGlobalAmbientProbe(new SHProperties()); + + m_SharedSpecCubeDecodeAllocation = m_GPUPersistentAllocator.Allocate((ulong)UnsafeUtility.SizeOf()); + Debug.Assert(!m_SharedSpecCubeDecodeAllocation.Empty, "Allocation of the specular decode value failed"); + UpdateSpecCubeHDRDecode(ReflectionProbe.defaultTextureHDRDecodeValues); + + ResetIds(); + + m_MetaEntitiesForHybridRenderableChunks = GetEntityQuery( + new EntityQueryDesc + + { + All = new[] + { + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + }, + }); + + // Collect all components with [MaterialProperty] attribute + m_MaterialPropertyTypes = new NativeParallelMultiHashMap(256, Allocator.Persistent); + m_MaterialPropertyTypesShared = new NativeParallelMultiHashMap(256, Allocator.Persistent); + m_SharedComponentOverrideTypeIndices = new NativeParallelHashSet(256, Allocator.Persistent); + m_MaterialPropertyNames = new Dictionary(); + m_MaterialPropertyTypeNames = new Dictionary(); + m_MaterialPropertyDefaultValues = new Dictionary(); + m_MaterialPropertySharedBuiltins = new Dictionary(); + m_ArchetypeSharedOverrideInfos = + new NativeParallelHashMap(256, Allocator.Persistent); + + // Some hardcoded mappings to avoid dependencies to Hybrid from DOTS +#if SRP_10_0_0_OR_NEWER + RegisterMaterialPropertyType( "unity_ObjectToWorld", 4 * 4 * 3); + RegisterMaterialPropertyType("unity_WorldToObject", overrideTypeSizeGPU: 4 * 4 * 3); +#else + RegisterMaterialPropertyType( "unity_ObjectToWorld", 4 * 4 * 4); + RegisterMaterialPropertyType("unity_WorldToObject", 4 * 4 * 4); +#endif + + // Explicitly use a default of all ones for probe occlusion, so stuff doesn't render as black if this isn't set. + RegisterMaterialPropertyType( + "unity_ProbesOcclusion", + defaultValue: new float4(1, 1, 1, 1)); + RegisterMaterialPropertyType( + "unity_SpecCube0_HDR", + defaultValue: new float4(1, 1, 0, 0)); + + RegisterSharedBuiltin("unity_SHAr", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHAr); + RegisterSharedBuiltin("unity_SHAg", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHAg); + RegisterSharedBuiltin("unity_SHAb", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHAb); + RegisterSharedBuiltin("unity_SHBr", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHBr); + RegisterSharedBuiltin("unity_SHBg", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHBg); + RegisterSharedBuiltin("unity_SHBb", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHBb); + RegisterSharedBuiltin("unity_SHC", (int) m_SharedAmbientProbeAllocation.begin + SHProperties.kOffsetOfSHC); + + RegisterSharedBuiltin("unity_SpecCube0_HDR", (int)m_SharedSpecCubeDecodeAllocation.begin); + UpdateSpecCubeHDRDecode(ReflectionProbe.defaultTextureHDRDecodeValues); + + foreach (var typeInfo in TypeManager.AllTypes) + { + var type = typeInfo.Type; + + bool isComponent = typeof(IComponentData).IsAssignableFrom(type); + bool isSharedComponent = typeof(ISharedComponentData).IsAssignableFrom(type); + if (isComponent || isSharedComponent) + { + var attributes = type.GetCustomAttributes(typeof(MaterialPropertyAttribute), false); + if (attributes.Length > 0) + { + var propertyAttr = (MaterialPropertyAttribute)attributes[0]; + + if (isSharedComponent) + { + Debug.Assert(typeof(IHybridSharedComponentFloat4Override).IsAssignableFrom(type), + $"Hybrid Renderer ISharedComponentData overrides must implement IHybridSharedComponentOverride. Type \"{type.Name}\" does not."); + Debug.Assert(propertyAttr.Format == MaterialPropertyFormat.Float4, + $"Hybrid Renderer ISharedComponentData overrides must have format Float4. Type \"{type.Name}\" had format {propertyAttr.Format} instead."); + } + + RegisterMaterialPropertyType(type, propertyAttr.Name, propertyAttr.OverrideSizeGPU); + } + } + } + +#if USE_UNITY_OCCLUSION + m_OcclusionCulling = new OcclusionCulling(); + m_OcclusionCulling.Create(EntityManager); +#endif + + m_FirstFrameAfterInit = true; + + // Hybrid Renderer cannot use the internal error shader because it doesn't support + // DOTS instancing, but we can check if it's set as a fallback, and use a supported + // error shader instead. + + m_BuiltinErrorShader = Shader.Find("Hidden/InternalErrorShader"); +#if UNITY_EDITOR + m_ShaderHasCompileErrors = new Dictionary(); +#endif + + Shader hybridErrorShader = null; + +#if HDRP_9_0_0_OR_NEWER + hybridErrorShader = Shader.Find("Hidden/HDRP/MaterialError"); +#endif + +#if URP_9_0_0_OR_NEWER + hybridErrorShader = Shader.Find("Hidden/Universal Render Pipeline/MaterialError"); +#endif + + // TODO: What about custom SRPs? Is it enough to just throw an error, or should + // we search for a custom shader with a specific name? + + if (hybridErrorShader != null) + m_ErrorMaterial = new Material(hybridErrorShader); + } + + protected override void OnDestroy() + { + if (!Enabled) + return; + CompleteJobs(); + Dispose(); + } + + protected override void OnUpdate() + { + if (!s_HybridRendererEnabled) + return; + + m_cullIndexThisFrame = 0; + + UpdateGlobalAmbientProbe(new SHProperties(RenderSettings.ambientProbe)); + + Profiler.BeginSample("CompleteJobs"); + CompleteDependency(); // #todo + CompleteJobs(); + Profiler.EndSample(); + + int totalChunks; + + try + { + Profiler.BeginSample("UpdateHybridV2Batches"); + UpdateHybridV2Batches(out totalChunks); + Profiler.EndSample(); + } + finally + { + } + + CompleteDependency(); + + worldBlackboardEntity.SetCollectionComponentAndDisposeOld(new MaterialPropertiesUploadContext + { + chunkProperties = m_ChunkProperties, + componentTypeCache = m_ComponentTypeCache, + defaultValueBlits = m_DefaultValueBlits, + hybridRenderedChunkCount = totalChunks, + requiredPersistentBufferSize = (int)m_PersistentInstanceDataSize + }); + + HybridEditorTools.EndFrame(); + + CompleteDependency(); + } + + public JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext batchCullingContext) + { + var batchCount = batchCullingContext.batchVisibility.Length; + if (batchCount == 0) + return new JobHandle(); + + var exposedCullingContext = new CullingContext + { + cullingMatrix = batchCullingContext.cullingMatrix, + cullIndexThisFrame = m_cullIndexThisFrame, + lodParameters = batchCullingContext.lodParameters, + nearPlane = batchCullingContext.nearPlane + }; + worldBlackboardEntity.SetComponentData(exposedCullingContext); + worldBlackboardEntity.GetBuffer().Reinterpret().CopyFrom(batchCullingContext.cullingPlanes); + + var brgCullingContext = new BrgCullingContext + { + cullingContext = batchCullingContext, + internalToExternalMappingIds = m_InternalToExternalIds + }; + + worldBlackboardEntity.SetCollectionComponentAndDisposeOld(brgCullingContext); + m_cullingSuperSystem.Update(); + + brgCullingContext = m_cullingSuperSystem.worldBlackboardEntity.GetCollectionComponent(false, out var finalHandle); + // Clear the dependency since we are not reading a container. + m_cullingSuperSystem.worldBlackboardEntity.UpdateJobDependency(finalHandle, true); + + m_cullIndexThisFrame++; + m_lastSystemVersionForProperties = GlobalSystemVersion; + return finalHandle; + } + + #endregion + + #region Jobs + [BurstCompile] + internal struct ClassifyNewChunksJob : IJobChunk + { + [ReadOnly] public ComponentTypeHandle ChunkHeader; + [ReadOnly] public ComponentTypeHandle HybridChunkInfo; + + [NativeDisableParallelForRestriction] + public NativeArray NewChunks; + [NativeDisableParallelForRestriction] + public NativeArray NumNewChunks; + + public void Execute(ArchetypeChunk metaChunk, int chunkIndex, int firstEntityIndex) + { + var chunkHeaders = metaChunk.GetNativeArray(ChunkHeader); + var hybridChunkInfos = metaChunk.GetNativeArray(HybridChunkInfo); + + for (int i = 0; i < metaChunk.Count; ++i) + { + var chunkInfo = hybridChunkInfos[i]; + var chunkHeader = chunkHeaders[i]; + + if (ShouldCountAsNewChunk(chunkInfo, chunkHeader.ArchetypeChunk)) + { + ClassifyNewChunk(chunkHeader.ArchetypeChunk); + } + } + } + + bool ShouldCountAsNewChunk(in HybridChunkInfo chunkInfo, in ArchetypeChunk chunk) + { + return !chunkInfo.Valid && !chunk.Archetype.Prefab && !chunk.Archetype.Disabled; + } + + public unsafe void ClassifyNewChunk(ArchetypeChunk chunk) + { + int* numNewChunks = (int*)NumNewChunks.GetUnsafePtr(); + int iPlus1 = System.Threading.Interlocked.Add(ref numNewChunks[0], 1); + int i = iPlus1 - 1; // C# Interlocked semantics are weird + Debug.Assert(i < NewChunks.Length, "Out of space in the NewChunks buffer"); + NewChunks[i] = chunk; + } + } + + [BurstCompile] + private struct UpdateOldHybridChunksJob : IJobChunk + { + public ComponentTypeHandle HybridChunkInfo; + public ComponentTypeHandle chunkPropertyDirtyMaskHandle; + [ReadOnly] public ComponentTypeHandle ChunkWorldRenderBounds; + [ReadOnly] public ComponentTypeHandle ChunkHeader; + [ReadOnly] public ComponentTypeHandle LocalToWorld; + [ReadOnly] public ComponentTypeHandle LodRange; + [ReadOnly] public ComponentTypeHandle RootLodRange; + public HybridChunkUpdater HybridChunkUpdater; + + public void Execute(ArchetypeChunk metaChunk, int chunkIndex, int firstEntityIndex) + { + // metaChunk is the chunk which contains the meta entities (= entities holding the chunk components) for the actual chunks + + var hybridChunkInfos = metaChunk.GetNativeArray(HybridChunkInfo); + var chunkHeaders = metaChunk.GetNativeArray(ChunkHeader); + var chunkBoundsArray = metaChunk.GetNativeArray(ChunkWorldRenderBounds); + var chunkDirtyMasks = metaChunk.GetNativeArray(chunkPropertyDirtyMaskHandle); + + for (int i = 0; i < metaChunk.Count; ++i) + { + var chunkInfo = hybridChunkInfos[i]; + var dirtyMask = chunkDirtyMasks[i]; + var chunkHeader = chunkHeaders[i]; + + var chunk = chunkHeader.ArchetypeChunk; + + ChunkWorldRenderBounds chunkBounds = chunkBoundsArray[i]; + + bool isNewChunk = !chunkInfo.Valid; + bool localToWorldChange = chunk.DidChange(LocalToWorld, HybridChunkUpdater.LastSystemVersion); + + // When LOD ranges change, we must reset the movement grace to avoid using stale data + bool lodRangeChange = + chunk.DidOrderChange(HybridChunkUpdater.LastSystemVersion) | + chunk.DidChange(LodRange, HybridChunkUpdater.LastSystemVersion) | + chunk.DidChange(RootLodRange, HybridChunkUpdater.LastSystemVersion); + + if (lodRangeChange) + chunkInfo.CullingData.MovementGraceFixed16 = 0; + + // Don't mark new chunks for updates here, they will be handled later when they have valid batch indices. + if (!isNewChunk) + HybridChunkUpdater.MarkBatchForUpdates(chunkInfo.InternalIndex, localToWorldChange); + + HybridChunkUpdater.ProcessChunk(ref chunkInfo, ref dirtyMask, chunk, chunkBounds); + hybridChunkInfos[i] = chunkInfo; + chunkDirtyMasks[i] = dirtyMask; + } + + HybridChunkUpdater.FinishExecute(); + } + } + + [BurstCompile] + private struct UpdateNewHybridChunksJob : IJobParallelFor + { + public ComponentTypeHandle HybridChunkInfo; + public ComponentTypeHandle chunkPropertyDirtyMaskHandle; + [ReadOnly] public ComponentTypeHandle ChunkWorldRenderBounds; + + public NativeArray NewChunks; + public HybridChunkUpdater HybridChunkUpdater; + + public void Execute(int index) + { + var chunk = NewChunks[index]; + var chunkInfo = chunk.GetChunkComponentData(HybridChunkInfo); + var dirtyMask = chunk.GetChunkComponentData(chunkPropertyDirtyMaskHandle); + + ChunkWorldRenderBounds chunkBounds = chunk.GetChunkComponentData(ChunkWorldRenderBounds); + + Debug.Assert(chunkInfo.Valid, "Attempted to process a chunk with uninitialized Hybrid chunk info"); + HybridChunkUpdater.MarkBatchForUpdates(chunkInfo.InternalIndex, true); + HybridChunkUpdater.ProcessValidChunk(ref chunkInfo, ref dirtyMask, chunk, chunkBounds.Value, true); + chunk.SetChunkComponentData(HybridChunkInfo, chunkInfo); + chunk.SetChunkComponentData(chunkPropertyDirtyMaskHandle, dirtyMask); + HybridChunkUpdater.FinishExecute(); + } + } + + [BurstCompile] + private struct AABBClearJob : IJobParallelFor + { + [NativeDisableParallelForRestriction] public NativeList BatchAABBs; + public void Execute(int index) + { + int aabbIndex = (int)(((uint)index) * HybridChunkUpdater.kFloatsPerAABB); + Debug.Assert(aabbIndex < BatchAABBs.Length, "AABBIndex is out of BatchAABBs bounds"); + + BatchAABBs[aabbIndex + HybridChunkUpdater.kMinX] = float.MaxValue; + BatchAABBs[aabbIndex + HybridChunkUpdater.kMinY] = float.MaxValue; + BatchAABBs[aabbIndex + HybridChunkUpdater.kMinZ] = float.MaxValue; + BatchAABBs[aabbIndex + HybridChunkUpdater.kMaxX] = float.MinValue; + BatchAABBs[aabbIndex + HybridChunkUpdater.kMaxY] = float.MinValue; + BatchAABBs[aabbIndex + HybridChunkUpdater.kMaxZ] = float.MinValue; + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/LatiosHybridRendererSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/LatiosHybridRendererSystem.cs.meta new file mode 100644 index 0000000..5c2424c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/LatiosHybridRendererSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2cd40d655c5f7c4faa61ecf0964153d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching.meta new file mode 100644 index 0000000..fd61165 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15da8ca856dea9443a8a32ee69c68a15 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ClearPerFrameCullingMasksSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ClearPerFrameCullingMasksSystem.cs new file mode 100644 index 0000000..8c97fd6 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ClearPerFrameCullingMasksSystem.cs @@ -0,0 +1,50 @@ +using Unity.Burst; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct ClearPerFrameCullingMasksSystem : ISystem + { + EntityQuery m_metaQuery; + + public void OnCreate(ref SystemState state) + { + m_metaQuery = state.Fluent().WithAll(false).WithAll(true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.Dependency = new ClearJob + { + handle = state.GetComponentTypeHandle(false), + lastSystemVersion = state.LastSystemVersion + }.ScheduleParallel(m_metaQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct ClearJob : IJobEntityBatch + { + public ComponentTypeHandle handle; + public uint lastSystemVersion; + + public unsafe void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (batchInChunk.DidChange(handle, lastSystemVersion)) + { + var ptr = batchInChunk.GetComponentDataPtrRW(ref handle); + UnsafeUtility.MemClear(ptr, sizeof(ChunkPerFrameCullingMask) * batchInChunk.Count); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ClearPerFrameCullingMasksSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ClearPerFrameCullingMasksSystem.cs.meta new file mode 100644 index 0000000..4e29be2 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ClearPerFrameCullingMasksSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 181e7d3e42961304684b77459d37bf77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/CombineExposedBonesSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/CombineExposedBonesSystem.cs new file mode 100644 index 0000000..da6c513 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/CombineExposedBonesSystem.cs @@ -0,0 +1,286 @@ +using Latios; +using Latios.Psyshock; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Jobs.LowLevel.Unsafe; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation +{ + [DisableAutoCreation] + public partial class CombineExposedBonesSystem : SubSystem + { + EntityQuery m_query; + + protected override void OnCreate() + { + m_query = Fluent.WithAll(true).WithAll(true).Build(); + + worldBlackboardEntity.AddCollectionComponent(new ExposedSkeletonBoundsArrays + { + allAabbs = new NativeList(Allocator.Persistent), + batchedAabbs = new NativeList(Allocator.Persistent) + }); + } + + protected override void OnUpdate() + { + var exposedCullingIndexManager = worldBlackboardEntity.GetCollectionComponent(true, out var cullingJH); + var boundsArrays = worldBlackboardEntity.GetCollectionComponent(false); + cullingJH.Complete(); + + var perThreadBitArrays = World.UpdateAllocator.AllocateNativeArray(JobsUtility.MaxJobThreadCount); + for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++) + perThreadBitArrays[i] = default; + + Dependency = new FindDirtyBoundsJob + { + boundsHandle = GetComponentTypeHandle(true), + indexHandle = GetComponentTypeHandle(true), + maxBitIndex = exposedCullingIndexManager.maxIndex, + perThreadBitArrays = perThreadBitArrays, + allocator = World.UpdateAllocator.ToAllocator, + lastSystemVersion = LastSystemVersion, + }.ScheduleParallel(m_query, Dependency); + + Dependency = new CollapseBitsJob + { + perThreadBitArrays = perThreadBitArrays + }.Schedule(Dependency); + + var perThreadBoundsArrays = World.UpdateAllocator.AllocateNativeArray >(JobsUtility.MaxJobThreadCount); + for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++) + perThreadBoundsArrays[i] = default; + + Dependency = new CombineBoundsPerThreadJob + { + boundsHandle = GetComponentTypeHandle(true), + indexHandle = GetComponentTypeHandle(true), + maxBitIndex = exposedCullingIndexManager.maxIndex, + perThreadBitArrays = perThreadBitArrays, + perThreadBoundsArrays = perThreadBoundsArrays, + allocator = World.UpdateAllocator.ToAllocator, + finalAabbsToResize = boundsArrays.allAabbs, + finalBatchAabbsToResize = boundsArrays.batchedAabbs + }.ScheduleParallel(m_query, Dependency); + + Dependency = new MergeThreadBoundsJob + { + perThreadBitArrays = perThreadBitArrays, + perThreadBoundsArrays = perThreadBoundsArrays, + finalAabbs = boundsArrays.allAabbs, + finalBatchAabbs = boundsArrays.batchedAabbs + }.ScheduleBatch(exposedCullingIndexManager.maxIndex.Value + 1, 32, Dependency); + } + + [BurstCompile] + struct FindDirtyBoundsJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle boundsHandle; + [ReadOnly] public ComponentTypeHandle indexHandle; + [ReadOnly] public NativeReference maxBitIndex; + [NativeDisableParallelForRestriction] public NativeArray perThreadBitArrays; + public Allocator allocator; + public uint lastSystemVersion; + + [NativeSetThreadIndex] int m_NativeThreadIndex; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (batchInChunk.DidChange(boundsHandle, lastSystemVersion) || batchInChunk.DidChange(indexHandle, lastSystemVersion)) + { + var perThreadBitArray = perThreadBitArrays[m_NativeThreadIndex]; + if (!perThreadBitArray.IsCreated) + { + perThreadBitArray = new UnsafeBitArray(CollectionHelper.Align(maxBitIndex.Value + 1, 64), + allocator, + NativeArrayOptions.ClearMemory); + perThreadBitArrays[m_NativeThreadIndex] = perThreadBitArray; + } + + var indices = batchInChunk.GetNativeArray(indexHandle); + for (int i = 0; i < batchInChunk.Count; i++) + { + perThreadBitArray.Set(indices[i].cullingIndex, true); + } + } + } + } + + [BurstCompile] + unsafe struct CollapseBitsJob : IJob + { + public NativeArray perThreadBitArrays; + + public void Execute() + { + int startFrom = -1; + for (int i = 0; i < perThreadBitArrays.Length; i++) + { + if (perThreadBitArrays[i].IsCreated) + { + startFrom = i + 1; + perThreadBitArrays[0] = perThreadBitArrays[i]; + perThreadBitArrays[i] = default; + break; + } + } + + if (startFrom == -1) + { + // This happens if no bones have changed. Unlikely but possible. + // In this case, we will need to check for this in future jobs. + return; + } + + for (int arrayIndex = startFrom; arrayIndex < perThreadBitArrays.Length; arrayIndex++) + { + if (!perThreadBitArrays[arrayIndex].IsCreated) + continue; + var dstArray = perThreadBitArrays[0]; + var dstArrayPtr = dstArray.Ptr; + var srcArrayPtr = perThreadBitArrays[arrayIndex].Ptr; + + for (int i = 0, bitCount = 0; bitCount < dstArray.Length; i++, bitCount += 64) + { + dstArrayPtr[i] |= srcArrayPtr[i]; + } + } + } + } + + [BurstCompile] + struct CombineBoundsPerThreadJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle boundsHandle; + [ReadOnly] public ComponentTypeHandle indexHandle; + [ReadOnly] public NativeReference maxBitIndex; + [ReadOnly] public NativeArray perThreadBitArrays; + [NativeDisableParallelForRestriction] public NativeArray > perThreadBoundsArrays; + public Allocator allocator; + + [NativeDisableParallelForRestriction] public NativeList finalAabbsToResize; + [NativeDisableParallelForRestriction] public NativeList finalBatchAabbsToResize; + + [NativeSetThreadIndex] int m_NativeThreadIndex; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (!perThreadBitArrays[0].IsCreated) + return; + + var perThreadBoundsArray = perThreadBoundsArrays[m_NativeThreadIndex]; + if (!perThreadBoundsArray.IsCreated) + { + perThreadBoundsArray = new UnsafeList(maxBitIndex.Value + 1, allocator, NativeArrayOptions.UninitializedMemory); + perThreadBoundsArray.Resize(maxBitIndex.Value + 1); + perThreadBoundsArrays[m_NativeThreadIndex] = perThreadBoundsArray; + for (int i = 0; i < maxBitIndex.Value + 1; i++) + { + perThreadBoundsArray[i] = new Aabb(float.MaxValue, float.MinValue); + } + } + + var indices = batchInChunk.GetNativeArray(indexHandle); + var bounds = batchInChunk.GetNativeArray(boundsHandle); + for (int i = 0; i < batchInChunk.Count; i++) + { + var index = indices[i].cullingIndex; + if (perThreadBitArrays[0].IsSet(index)) + { + perThreadBoundsArray[index] = Physics.CombineAabb(perThreadBoundsArray[index], bounds[i].bounds); + } + } + + if (batchIndex == 0) + { + // We do the resizing in this job to remove a single-threaded bubble. + int indexCount = maxBitIndex.Value + 1; + if (finalAabbsToResize.Length < indexCount) + { + finalAabbsToResize.Length = indexCount; + + int batchCount = indexCount / 16; + if (indexCount % 16 != 0) + batchCount++; + + if (finalBatchAabbsToResize.Length < batchCount) + { + finalBatchAabbsToResize.Length = batchCount; + } + } + } + } + } + + [BurstCompile] + struct MergeThreadBoundsJob : IJobParallelForBatch + { + [ReadOnly] public NativeArray perThreadBitArrays; + [ReadOnly] public NativeArray > perThreadBoundsArrays; + + [NativeDisableParallelForRestriction] public NativeList finalAabbs; + [NativeDisableParallelForRestriction] public NativeList finalBatchAabbs; + + public void Execute(int startIndex, int count) + { + if (!perThreadBitArrays[0].IsCreated) + return; + + BitField32 mergeMask = default; + FixedList4096Bytes cache = default; + Aabb batchAabb = new Aabb(float.MaxValue, float.MinValue); + for (int i = 0; i < count; i++) + { + if (perThreadBitArrays[0].IsSet(i + startIndex)) + { + mergeMask.SetBits(i, true); + cache.Add(new Aabb(float.MaxValue, float.MinValue)); + } + else + { + var aabb = new Aabb(finalAabbs[startIndex + i].Min, finalAabbs[startIndex + i].Max); + cache.Add(aabb); + batchAabb = Physics.CombineAabb(batchAabb, aabb); + } + } + + if (mergeMask.Value == 0) + return; + + for (int threadIndex = 0; threadIndex < perThreadBoundsArrays.Length; threadIndex++) + { + if (!perThreadBoundsArrays[threadIndex].IsCreated) + continue; + + var tempMask = mergeMask; + for (int i = tempMask.CountTrailingZeros(); i < count; tempMask.SetBits(i, false), i = tempMask.CountTrailingZeros()) + { + cache[i] = Physics.CombineAabb(cache[i], perThreadBoundsArrays[threadIndex][startIndex + i]); + batchAabb = Physics.CombineAabb(batchAabb, perThreadBoundsArrays[threadIndex][startIndex + i]); + } + } + + { + var tempMask = mergeMask; + for (int i = tempMask.CountTrailingZeros(); i < count; tempMask.SetBits(i, false), i = tempMask.CountTrailingZeros()) + { + finalAabbs[startIndex + i] = FromAabb(cache[i]); + } + finalBatchAabbs[startIndex / 32] = FromAabb(batchAabb); + } + } + + public static AABB FromAabb(Aabb aabb) + { + Physics.GetCenterExtents(aabb, out float3 center, out float3 extents); + return new AABB { Center = center, Extents = extents }; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/CombineExposedBonesSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/CombineExposedBonesSystem.cs.meta new file mode 100644 index 0000000..7574419 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/CombineExposedBonesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62e6988670ff10d48a1218a896a6172c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/EndPerFrameMeshSkinningBuffersUploadSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/EndPerFrameMeshSkinningBuffersUploadSystem.cs new file mode 100644 index 0000000..e223c0f --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/EndPerFrameMeshSkinningBuffersUploadSystem.cs @@ -0,0 +1,109 @@ +using Latios; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class EndPerFrameMeshSkinningBuffersUploadSystem : SubSystem + { + UnityEngine.ComputeShader m_verticesUploadShader; + UnityEngine.ComputeShader m_matricesUploadShader; + UnityEngine.ComputeShader m_bytesUploadShader; + + protected override void OnCreate() + { + m_verticesUploadShader = UnityEngine.Resources.Load("UploadVertices"); + m_matricesUploadShader = UnityEngine.Resources.Load("UploadMatrices"); + m_bytesUploadShader = UnityEngine.Resources.Load("UploadBytes"); + } + + protected override void OnUpdate() + { + var buffers = worldBlackboardEntity.GetCollectionComponent(false, out var jh); + jh.Complete(); + + if (!buffers.needsMeshCommitment && !buffers.needsBoneOffsetCommitment) + return; + + if (buffers.needsMeshCommitment) + { + buffers.verticesUploadBuffer.EndWrite(buffers.verticesUploadBufferWriteCount); + buffers.verticesUploadMetaBuffer.EndWrite(buffers.verticesUploadMetaBufferWriteCount); + buffers.weightsUploadBuffer.EndWrite(buffers.weightsUploadBufferWriteCount); + buffers.weightsUploadMetaBuffer.EndWrite(buffers.weightsUploadMetaBufferWriteCount); + buffers.bindPosesUploadBuffer.EndWrite(buffers.bindPosesUploadBufferWriteCount); + buffers.bindPosesUploadMetaBuffer.EndWrite(buffers.bindPosesUploadMetaBufferWriteCount); + + m_verticesUploadShader.SetBuffer(0, "_dst", buffers.verticesBuffer); + m_verticesUploadShader.SetBuffer(0, "_src", buffers.verticesUploadBuffer); + m_verticesUploadShader.SetBuffer(0, "_meta", buffers.verticesUploadMetaBuffer); + + for (int dispatchesRemaining = buffers.verticesUploadMetaBufferWriteCount, offset = 0; dispatchesRemaining > 0;) + { + int dispatchCount = math.min(dispatchesRemaining, 65535); + m_verticesUploadShader.SetInt("_startOffset", offset); + m_verticesUploadShader.Dispatch(0, dispatchCount, 1, 1); + offset += dispatchCount; + dispatchesRemaining -= dispatchCount; + } + + m_bytesUploadShader.SetBuffer(0, "_dst", buffers.weightsBuffer); + m_bytesUploadShader.SetBuffer(0, "_src", buffers.weightsUploadBuffer); + m_bytesUploadShader.SetBuffer(0, "_meta", buffers.weightsUploadMetaBuffer); + m_bytesUploadShader.SetInt("_elementSizeInBytes", 8); + + for (int dispatchesRemaining = buffers.weightsUploadMetaBufferWriteCount, offset = 0; dispatchesRemaining > 0;) + { + int dispatchCount = math.min(dispatchesRemaining, 65535); + m_bytesUploadShader.SetInt("_startOffset", offset); + m_bytesUploadShader.Dispatch(0, dispatchCount, 1, 1); + offset += dispatchCount; + dispatchesRemaining -= dispatchCount; + } + + m_matricesUploadShader.SetBuffer(0, "_dst", buffers.bindPosesBuffer); + m_matricesUploadShader.SetBuffer(0, "_src", buffers.bindPosesUploadBuffer); + m_matricesUploadShader.SetBuffer(0, "_meta", buffers.bindPosesUploadMetaBuffer); + + for (int dispatchesRemaining = buffers.bindPosesUploadMetaBufferWriteCount, offset = 0; dispatchesRemaining > 0;) + { + int dispatchCount = math.min(dispatchesRemaining, 65535); + m_matricesUploadShader.SetInt("_startOffset", offset); + m_matricesUploadShader.Dispatch(0, dispatchCount, 1, 1); + offset += dispatchCount; + dispatchesRemaining -= dispatchCount; + } + buffers.needsMeshCommitment = false; + } + + if (buffers.needsBoneOffsetCommitment) + { + buffers.boneOffsetsUploadBuffer.EndWrite(buffers.boneOffsetsUploadBufferWriteCount); + buffers.boneOffsetsUploadMetaBuffer.EndWrite(buffers.boneOffsetsUploadMetaBufferWriteCount); + + m_bytesUploadShader.SetBuffer(0, "_dst", buffers.boneOffsetsBuffer); + m_bytesUploadShader.SetBuffer(0, "_src", buffers.boneOffsetsUploadBuffer); + m_bytesUploadShader.SetBuffer(0, "_meta", buffers.boneOffsetsUploadMetaBuffer); + m_bytesUploadShader.SetInt("_elementSizeInBytes", 4); + + for (int dispatchesRemaining = buffers.boneOffsetsUploadMetaBufferWriteCount, offset = 0; dispatchesRemaining > 0;) + { + int dispatchCount = math.min(dispatchesRemaining, 65535); + m_bytesUploadShader.SetInt("_startOffset", offset); + m_bytesUploadShader.Dispatch(0, dispatchCount, 1, 1); + offset += dispatchCount; + dispatchesRemaining -= dispatchCount; + } + buffers.needsBoneOffsetCommitment = false; + } + + worldBlackboardEntity.SetCollectionComponentAndDisposeOld(buffers); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/EndPerFrameMeshSkinningBuffersUploadSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/EndPerFrameMeshSkinningBuffersUploadSystem.cs.meta new file mode 100644 index 0000000..baf771c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/EndPerFrameMeshSkinningBuffersUploadSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f640f4e58bf5f704094d10916da28367 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ResetPerFrameSkinningMetadataJob.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ResetPerFrameSkinningMetadataJob.cs new file mode 100644 index 0000000..3445a02 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ResetPerFrameSkinningMetadataJob.cs @@ -0,0 +1,56 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class ResetPerFrameSkinningMetadataJob : SubSystem + { + EntityQuery m_skeletonQuery; + + protected override void OnCreate() + { + m_skeletonQuery = Fluent.WithAll().Build(); + + worldBlackboardEntity.AddCollectionComponent(new BoneMatricesPerFrameBuffersManager + { + boneMatricesBuffers = new System.Collections.Generic.List() + }); + } + + protected override void OnUpdate() + { + Dependency = new ResetPerFrameMetadataJob + { + handle = GetComponentTypeHandle(false), + lastSystemVersion = LastSystemVersion + }.ScheduleParallel(m_skeletonQuery, Dependency); + + worldBlackboardEntity.GetCollectionComponent().boneMatricesBuffers.Clear(); + } + + [BurstCompile] + struct ResetPerFrameMetadataJob : IJobEntityBatch + { + public ComponentTypeHandle handle; + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (batchInChunk.DidChange(handle, lastSystemVersion)) + { + var metadata = batchInChunk.GetNativeArray(handle); + for (int i = 0; i < batchInChunk.Count; i++) + { + metadata[i] = new PerFrameSkeletonBufferMetadata { bufferId = -1, startIndexInBuffer = -1 }; + } + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ResetPerFrameSkinningMetadataJob.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ResetPerFrameSkinningMetadataJob.cs.meta new file mode 100644 index 0000000..6eb8a24 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/ResetPerFrameSkinningMetadataJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7dee62069bdf2214297dcb46b12241b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkComputeDeformMetadataSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkComputeDeformMetadataSystem.cs new file mode 100644 index 0000000..ddc9198 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkComputeDeformMetadataSystem.cs @@ -0,0 +1,88 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct UpdateChunkComputeDeformMetadataSystem : ISystem + { + EntityQuery m_query; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(true).WithAll(false, true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.GetWorldBlackboardEntity().SetComponentData(new MaxRequiredDeformVertices { verticesCount = 0 }); + + var lastSystemVersion = state.LastSystemVersion; + var blobHandle = state.GetComponentTypeHandle(true); + var metaHandle = state.GetComponentTypeHandle(false); + + state.Dependency = new UpdateChunkVertexCountsJob + { + blobHandle = blobHandle, + metaHandle = metaHandle, + lastSystemVersion = lastSystemVersion + }.ScheduleParallel(m_query, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct UpdateChunkVertexCountsJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle blobHandle; + public ComponentTypeHandle metaHandle; + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + bool needsUpdate = batchInChunk.DidChange(blobHandle, lastSystemVersion); + needsUpdate |= batchInChunk.DidOrderChange(lastSystemVersion); + if (!needsUpdate) + return; + + var blobs = batchInChunk.GetNativeArray(blobHandle); + int minVertices = int.MaxValue; + int maxVertices = int.MinValue; + + for (int i = 0; i < batchInChunk.Count; i++) + { + int c = blobs[i].skinningBlob.Value.verticesToSkin.Length; + minVertices = math.min(minVertices, c); + maxVertices = math.max(maxVertices, c); + } + + CheckVertexCountMismatch(minVertices, maxVertices); + + var metadata = batchInChunk.GetChunkComponentData(metaHandle); + if (metadata.verticesPerMesh != maxVertices || metadata.entitiesInChunk != batchInChunk.Count) + { + metadata.verticesPerMesh = maxVertices; + metadata.entitiesInChunk = batchInChunk.Count; + batchInChunk.SetChunkComponentData(metaHandle, metadata); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckVertexCountMismatch(int min, int max) + { + if (min != max) + UnityEngine.Debug.LogWarning( + "A chunk contains multiple Mesh Skinning Blobs with different vertex counts. Because Mesh Skinning Blobs are tied to their RenderMesh of which there is only one per chunk, this is likely a bug. Did you forget to change the Mesh Skinning Blob Reference when changing a Render Mesh?"); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkComputeDeformMetadataSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkComputeDeformMetadataSystem.cs.meta new file mode 100644 index 0000000..411f9dc --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkComputeDeformMetadataSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc21ce3323ad2ea48a5c0c4577e89d4c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkLinearBlendMetadataSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkLinearBlendMetadataSystem.cs new file mode 100644 index 0000000..4458d37 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkLinearBlendMetadataSystem.cs @@ -0,0 +1,88 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct UpdateChunkLinearBlendMetadataSystem : ISystem + { + EntityQuery m_query; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(true).WithAll(false, true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.GetWorldBlackboardEntity().SetComponentData(new MaxRequiredLinearBlendMatrices { matricesCount = 0 }); + + var lastSystemVersion = state.LastSystemVersion; + var blobHandle = state.GetComponentTypeHandle(true); + var metaHandle = state.GetComponentTypeHandle(false); + + state.Dependency = new UpdateChunkMatrixCountsJob + { + blobHandle = blobHandle, + metaHandle = metaHandle, + lastSystemVersion = lastSystemVersion + }.ScheduleParallel(m_query, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct UpdateChunkMatrixCountsJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle blobHandle; + public ComponentTypeHandle metaHandle; + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + bool needsUpdate = batchInChunk.DidChange(blobHandle, lastSystemVersion); + needsUpdate |= batchInChunk.DidOrderChange(lastSystemVersion); + if (!needsUpdate) + return; + + var blobs = batchInChunk.GetNativeArray(blobHandle); + int minMatrices = int.MaxValue; + int maxMatrices = int.MinValue; + + for (int i = 0; i < batchInChunk.Count; i++) + { + int c = blobs[i].skinningBlob.Value.bindPoses.Length; + minMatrices = math.min(minMatrices, c); + maxMatrices = math.max(maxMatrices, c); + } + + CheckMatrixCountMismatch(minMatrices, maxMatrices); + + var metadata = batchInChunk.GetChunkComponentData(metaHandle); + if (metadata.bonesPerMesh != maxMatrices || metadata.entitiesInChunk != batchInChunk.Count) + { + metadata.bonesPerMesh = maxMatrices; + metadata.entitiesInChunk = batchInChunk.Count; + batchInChunk.SetChunkComponentData(metaHandle, metadata); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void CheckMatrixCountMismatch(int min, int max) + { + if (min != max) + UnityEngine.Debug.LogWarning( + "A chunk contains multiple Mesh Skinning Blobs with different matrix counts. Because Mesh Skinning Blobs are tied to their RenderMesh of which there is only one per chunk, this is likely a bug. Did you forget to change the Mesh Skinning Blob Reference when changing a Render Mesh?"); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkLinearBlendMetadataSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkLinearBlendMetadataSystem.cs.meta new file mode 100644 index 0000000..2d09014 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateChunkLinearBlendMetadataSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4770765e98f8d14f9d1099c5d7d11b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateMatrixPreviousSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateMatrixPreviousSystem.cs new file mode 100644 index 0000000..42caad8 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateMatrixPreviousSystem.cs @@ -0,0 +1,103 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + // Unity's Hybrid Renderer uploads all MatrixPrevious and then updates them to LocalToWorld in the very next system. + // However, we upload material properties later, so we would have to wait until all culling is complete before updating. + // That would be fragile, so instead, we use an intermediate buffer cache component. + [DisableAutoCreation] + [BurstCompile] + public partial struct UpdateMatrixPreviousSystem : ISystem + { + EntityQuery m_query; + uint m_secondLastSystemVersion; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(true).WithAll().WithAll() + .WithAll(false, true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + int matrixPrevIndex = state.GetWorldBlackboardEntity().GetBuffer(true).Reinterpret().AsNativeArray() + .IndexOf(ComponentType.ReadOnly()); + ulong matrixPrevMaterialMaskLower = (ulong)matrixPrevIndex >= 64UL ? 0UL : (1UL << matrixPrevIndex); + ulong matrixPrevMaterialMaskUpper = (ulong)matrixPrevIndex >= 64UL ? (1UL << (matrixPrevIndex - 64)) : 0UL; + + state.Dependency = new UpdateMatricesJob + { + ltwHandle = state.GetComponentTypeHandle(true), + cacheHandle = state.GetComponentTypeHandle(false), + prevHandle = state.GetComponentTypeHandle(false), + maskHandle = state.GetComponentTypeHandle(false), + secondLastSystemVersion = m_secondLastSystemVersion, + matrixPrevMaterialMaskLower = matrixPrevMaterialMaskLower, + matrixPrevMaterialMaskUpper = matrixPrevMaterialMaskUpper + }.ScheduleParallel(m_query, state.Dependency); + m_secondLastSystemVersion = state.LastSystemVersion; + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct UpdateMatricesJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle ltwHandle; + public ComponentTypeHandle cacheHandle; + public ComponentTypeHandle prevHandle; + public ComponentTypeHandle maskHandle; + + // Even if LTW stops moving, we still need to update for one more frame for the prev + // to catch up. + public uint secondLastSystemVersion; + + // We update after LatiosHybridRendererSystem, which means property changes don't get + // seen since this is before the end of culling. So we update the mask instead. + public ulong matrixPrevMaterialMaskLower; + public ulong matrixPrevMaterialMaskUpper; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + if (!batchInChunk.DidChange(ltwHandle, secondLastSystemVersion)) + return; + + var mask = batchInChunk.GetChunkComponentData(maskHandle); + mask.lower.Value |= matrixPrevMaterialMaskLower; + mask.upper.Value |= matrixPrevMaterialMaskUpper; + batchInChunk.SetChunkComponentData(maskHandle, mask); + + var ltws = batchInChunk.GetNativeArray(ltwHandle).Reinterpret(); + var caches = batchInChunk.GetNativeArray(cacheHandle).Reinterpret(); + // Even if this is the first frame that LTW moves, we still dirty prev. + // Todo: Is there an efficient way to fix this, and is it even worth it? + var prevs = batchInChunk.GetNativeArray(prevHandle).Reinterpret(); + + for (int i = 0; i < batchInChunk.Count; i++) + { + // We pack the 3rd row of the cache in the 4th row of prev since that row + // isn't used by the uploader. This reduces memory footprint and hopefully + // increases chunk capacity a little. + var cache = caches[i]; + var ltw = ltws[i]; + var prev = prevs[i]; + prevs[i] = new float4x4(new float4(cache.c0.xy, prev.c0.w, ltw.c0.z), + new float4(cache.c1.xy, prev.c1.w, ltw.c1.z), + new float4(cache.c2.xy, prev.c2.w, ltw.c2.z), + new float4(cache.c3.xy, prev.c3.w, ltw.c3.z)); + caches[i] = new float2x4(ltw.c0.xy, ltw.c1.xy, ltw.c2.xy, ltw.c3.xy); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateMatrixPreviousSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateMatrixPreviousSystem.cs.meta new file mode 100644 index 0000000..6e7495b --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PostBatching/UpdateMatrixPreviousSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc86bc95eb70bd24798e24548ecc8f30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching.meta new file mode 100644 index 0000000..8aa14dd --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7672bcc50c98d0a408ceaacb66d7bbd7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/BeginPerFrameMeshSkinningBuffersUploadSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/BeginPerFrameMeshSkinningBuffersUploadSystem.cs new file mode 100644 index 0000000..fd0cbbe --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/BeginPerFrameMeshSkinningBuffersUploadSystem.cs @@ -0,0 +1,304 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class BeginPerFrameMeshSkinningBuffersUploadSystem : SubSystem + { + protected override void OnCreate() + { + worldBlackboardEntity.AddCollectionComponent(new ComputeBufferManager + { + pool = new ComputeBufferTrackingPool(), + }, true); + + worldBlackboardEntity.AddCollectionComponent(new GpuUploadBuffers()); + } + + protected override void OnUpdate() + { + var meshGpuManager = worldBlackboardEntity.GetCollectionComponent(false, out var meshGpuJH); + var boneOffsetsGpuManager = worldBlackboardEntity.GetCollectionComponent(false, out var boneOffsetsGpuJH); + var bufferManager = worldBlackboardEntity.GetCollectionComponent(false, out var computeBufferJH); + worldBlackboardEntity.GetCollectionComponent(false, out var meshUploadJH); + + bufferManager.pool.Update(); + var jhs = new NativeList(4, Allocator.Temp); + jhs.Add(meshGpuJH); + jhs.Add(boneOffsetsGpuJH); + jhs.Add(computeBufferJH); + jhs.Add(meshUploadJH); + CompleteDependency(); + JobHandle.CompleteAll(jhs); + + JobHandle jhv = default; + JobHandle jhw = default; + JobHandle jhb = default; + + GpuUploadBuffers newBuffers = default; + + var requiredSizes = meshGpuManager.requiredBufferSizes.Value; + newBuffers.verticesBuffer = bufferManager.pool.GetMeshVerticesBuffer(requiredSizes.requiredVertexBufferSize); + newBuffers.weightsBuffer = bufferManager.pool.GetMeshWeightsBuffer(requiredSizes.requiredWeightBufferSize); + newBuffers.bindPosesBuffer = bufferManager.pool.GetMeshBindPosesBuffer(requiredSizes.requiredBindPoseBufferSize); + + if (!meshGpuManager.uploadCommands.IsEmpty) + { + var uploadCount = meshGpuManager.uploadCommands.Length; + newBuffers.verticesUploadBuffer = bufferManager.pool.GetMeshVerticesUploadBuffer(requiredSizes.requiredVertexUploadSize); + newBuffers.weightsUploadBuffer = bufferManager.pool.GetMeshWeightsUploadBuffer(requiredSizes.requiredWeightUploadSize); + newBuffers.bindPosesUploadBuffer = bufferManager.pool.GetMeshBindPosesUploadBuffer(requiredSizes.requiredBindPoseUploadSize); + newBuffers.verticesUploadMetaBuffer = bufferManager.pool.GetUploadMetaBuffer(uploadCount); + newBuffers.weightsUploadMetaBuffer = bufferManager.pool.GetUploadMetaBuffer(uploadCount); + newBuffers.bindPosesUploadMetaBuffer = bufferManager.pool.GetUploadMetaBuffer(uploadCount); + newBuffers.verticesUploadBufferWriteCount = requiredSizes.requiredVertexUploadSize; + newBuffers.weightsUploadBufferWriteCount = requiredSizes.requiredWeightUploadSize; + newBuffers.bindPosesUploadBufferWriteCount = requiredSizes.requiredBindPoseUploadSize; + newBuffers.verticesUploadMetaBufferWriteCount = uploadCount; + newBuffers.weightsUploadMetaBufferWriteCount = uploadCount; + newBuffers.bindPosesUploadMetaBufferWriteCount = uploadCount; + var mappedVertices = newBuffers.verticesUploadBuffer.BeginWrite(0, requiredSizes.requiredVertexUploadSize); + var mappedWeights = newBuffers.weightsUploadBuffer.BeginWrite(0, requiredSizes.requiredWeightUploadSize); // Unity uses T for sizing so we don't need to *2 here. + var mappedBindPoses = newBuffers.bindPosesUploadBuffer.BeginWrite(0, requiredSizes.requiredBindPoseUploadSize); + var mappedVerticesMeta = newBuffers.verticesUploadMetaBuffer.BeginWrite(0, uploadCount); + var mappedWeightsMeta = newBuffers.weightsUploadMetaBuffer.BeginWrite(0, uploadCount); + var mappedBindPosesMeta = newBuffers.bindPosesUploadMetaBuffer.BeginWrite(0, uploadCount); + newBuffers.needsMeshCommitment = true; + + ref var allocator = ref World.UpdateAllocator; + var verticesSums = allocator.AllocateNativeArray(meshGpuManager.uploadCommands.Length); + var weightsSums = allocator.AllocateNativeArray(meshGpuManager.uploadCommands.Length); + var bindPosesSums = allocator.AllocateNativeArray(meshGpuManager.uploadCommands.Length); + jhv = new PrefixSumVerticesCountsJob + { + commands = meshGpuManager.uploadCommands, + sums = verticesSums + }.Schedule(); + jhw = new PrefixSumWeightsCountsJob + { + commands = meshGpuManager.uploadCommands, + sums = weightsSums + }.Schedule(); + jhb = new PrefixSumBindPosesCountsJob + { + commands = meshGpuManager.uploadCommands, + sums = bindPosesSums + }.Schedule(); + jhv = new UploadMeshesVerticesJob + { + commands = meshGpuManager.uploadCommands, + prefixSums = verticesSums, + mappedVertices = mappedVertices, + mappedMeta = mappedVerticesMeta + }.ScheduleParallel(uploadCount, 1, jhv); + jhw = new UploadMeshesWeightsJob + { + commands = meshGpuManager.uploadCommands, + prefixSums = weightsSums, + mappedWeights = mappedWeights, + mappedMeta = mappedWeightsMeta + }.ScheduleParallel(uploadCount, 1, jhw); + jhb = new UploadMeshesBindPosesJob + { + commands = meshGpuManager.uploadCommands, + prefixSums = bindPosesSums, + mappedBindPoses = mappedBindPoses, + mappedMeta = mappedBindPosesMeta + }.ScheduleParallel(uploadCount, 1, jhb); + } + + JobHandle jho = default; + + int boneOffsetsSize = boneOffsetsGpuManager.offsets.Length; + int boneOffsetsGpuSize = boneOffsetsSize / 2; + newBuffers.boneOffsetsBuffer = bufferManager.pool.GetBoneOffsetsBuffer(boneOffsetsGpuSize); + + if (boneOffsetsGpuManager.isDirty.Value) + { + int metaCount = (int)math.ceil(boneOffsetsGpuSize / 64f); + newBuffers.boneOffsetsUploadBuffer = bufferManager.pool.GetBoneOffsetsUploadBuffer(boneOffsetsGpuSize); + newBuffers.boneOffsetsUploadMetaBuffer = bufferManager.pool.GetUploadMetaBuffer(boneOffsetsGpuSize); + newBuffers.boneOffsetsUploadBufferWriteCount = boneOffsetsGpuSize; + newBuffers.boneOffsetsUploadMetaBufferWriteCount = metaCount; + var mappedBoneOffsets = newBuffers.boneOffsetsUploadBuffer.BeginWrite(0, boneOffsetsGpuSize); + var mappedBoneOffsetsMeta = newBuffers.boneOffsetsUploadMetaBuffer.BeginWrite(0, metaCount); + newBuffers.needsBoneOffsetCommitment = true; + + jho = new UploadBoneOffsetsJob + { + mappedBoneOffsets = mappedBoneOffsets, + mappedMeta = mappedBoneOffsetsMeta, + offsets = boneOffsetsGpuManager.offsets + }.ScheduleBatch(boneOffsetsGpuManager.offsets.Length, 128); + } + + jhs.Clear(); + jhs.Add(jhv); + jhs.Add(jhw); + jhs.Add(jhb); + jhs.Add(jho); + Dependency = JobHandle.CombineDependencies(jhs); + + if (newBuffers.needsMeshCommitment || newBuffers.needsBoneOffsetCommitment) + { + worldBlackboardEntity.SetCollectionComponentAndDisposeOld(newBuffers); + Dependency = new ClearCommandsJob { commands = meshGpuManager.uploadCommands, isDirty = boneOffsetsGpuManager.isDirty }.Schedule(Dependency); + } + } + + [BurstCompile] + struct PrefixSumVerticesCountsJob : IJob + { + [ReadOnly] public NativeArray commands; + public NativeArray sums; + + public void Execute() + { + int s = 0; + for (int i = 0; i < commands.Length; i++) + { + sums[i] = s; + s += commands[i].blob.Value.verticesToSkin.Length; + } + } + } + + [BurstCompile] + struct PrefixSumWeightsCountsJob : IJob + { + [ReadOnly] public NativeArray commands; + public NativeArray sums; + + public void Execute() + { + int s = 0; + for (int i = 0; i < commands.Length; i++) + { + sums[i] = s; + s += commands[i].blob.Value.boneWeights.Length; + } + } + } + + [BurstCompile] + struct PrefixSumBindPosesCountsJob : IJob + { + [ReadOnly] public NativeArray commands; + public NativeArray sums; + + public void Execute() + { + int s = 0; + for (int i = 0; i < commands.Length; i++) + { + sums[i] = s; + s += commands[i].blob.Value.bindPoses.Length; + } + } + } + + [BurstCompile] + struct UploadMeshesVerticesJob : IJobFor + { + [ReadOnly] public NativeArray commands; + [ReadOnly] public NativeArray prefixSums; + public NativeArray mappedVertices; + public NativeArray mappedMeta; + + public unsafe void Execute(int index) + { + int size = commands[index].blob.Value.verticesToSkin.Length; + mappedMeta[index] = (uint3) new int3(prefixSums[index], commands[index].verticesIndex, size); + var blobData = commands[index].blob.Value.verticesToSkin.GetUnsafePtr(); + var subArray = mappedVertices.GetSubArray(prefixSums[index], size); + UnsafeUtility.MemCpy(subArray.GetUnsafePtr(), blobData, size * sizeof(VertexToSkin)); + } + } + + [BurstCompile] + struct UploadMeshesWeightsJob : IJobFor + { + [ReadOnly] public NativeArray commands; + [ReadOnly] public NativeArray prefixSums; + public NativeArray mappedWeights; + public NativeArray mappedMeta; + + public unsafe void Execute(int index) + { + int size = commands[index].blob.Value.boneWeights.Length; + mappedMeta[index] = (uint3) new int3(prefixSums[index], commands[index].weightsIndex, size); + var blobData = commands[index].blob.Value.boneWeights.GetUnsafePtr(); + var subArray = mappedWeights.GetSubArray(prefixSums[index], size); + UnsafeUtility.MemCpy(subArray.GetUnsafePtr(), blobData, size * sizeof(BoneWeightLinkedList)); + } + } + + [BurstCompile] + struct UploadMeshesBindPosesJob : IJobFor + { + [ReadOnly] public NativeArray commands; + [ReadOnly] public NativeArray prefixSums; + public NativeArray mappedBindPoses; + public NativeArray mappedMeta; + + public unsafe void Execute(int index) + { + int size = commands[index].blob.Value.bindPoses.Length; + mappedMeta[index] = (uint3) new int3(prefixSums[index], commands[index].bindPosesIndex, size); + ref var blobData = ref commands[index].blob.Value.bindPoses; + var subArray = mappedBindPoses.GetSubArray(prefixSums[index], size); + + for (int i = 0; i < size; i++) + { + subArray[i] = Shrink(blobData[i]); + } + } + + float3x4 Shrink(float4x4 a) + { + return new float3x4(a.c0.xyz, a.c1.xyz, a.c2.xyz, a.c3.xyz); + } + } + + [BurstCompile] + struct UploadBoneOffsetsJob : IJobParallelForBatch + { + [ReadOnly] public NativeArray offsets; + [NativeDisableParallelForRestriction] public NativeArray mappedBoneOffsets; + [NativeDisableParallelForRestriction] public NativeArray mappedMeta; + + public void Execute(int startIndex, int count) + { + int index = startIndex / 128; + mappedMeta[index] = (uint3) new int3(startIndex / 2, startIndex / 2, count / 2); + for (int i = startIndex; i < startIndex + count; i += 2) + { +#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand + uint packed = (((uint)offsets[i + 1]) << 16) | ((uint)offsets[i]); +#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand + mappedBoneOffsets[i / 2] = packed; + } + } + } + + [BurstCompile] + struct ClearCommandsJob : IJob + { + public NativeList commands; + public NativeReference isDirty; + + public void Execute() + { + commands.Clear(); + isDirty.Value = false; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/BeginPerFrameMeshSkinningBuffersUploadSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/BeginPerFrameMeshSkinningBuffersUploadSystem.cs.meta new file mode 100644 index 0000000..6168119 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/BeginPerFrameMeshSkinningBuffersUploadSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60e0247e4734f27498fd0f889586875b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkeletonBoundsSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkeletonBoundsSystem.cs new file mode 100644 index 0000000..b54dacb --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkeletonBoundsSystem.cs @@ -0,0 +1,196 @@ +using Latios.Psyshock; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct UpdateSkeletonBoundsSystem : ISystem + { + EntityQuery m_exposedBonesQuery; + EntityQuery m_optimizedSkeletonsQuery; + + public void OnCreate(ref SystemState state) + { + m_exposedBonesQuery = state.Fluent().WithAll(true).WithAll(false).WithAll(false, true) + .WithAll( true).Build(); + + m_optimizedSkeletonsQuery = state.Fluent().WithAll(true).WithAll(true).WithAll(false) + .WithAll(false, true).WithAll(true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + var ltwHandle = state.GetComponentTypeHandle(true); + var lastSystemVersion = state.LastSystemVersion; + + state.Dependency = new ExposedBoneBoundsJob + { + boneBoundsHandle = state.GetComponentTypeHandle(true), + ltwHandle = ltwHandle, + boneWorldBoundsReadOnlyHandle = state.GetComponentTypeHandle(true), + boneWorldBoundsHandle = state.GetComponentTypeHandle(false), + chunkBoneWorldBoundsHandle = state.GetComponentTypeHandle(false), + lastSystemVersion = lastSystemVersion + }.ScheduleParallel(m_exposedBonesQuery, state.Dependency); + + // Todo: Increase batches per chunk? + state.Dependency = new OptimizedBoneBoundsJob + { + boneBoundsHandle = state.GetBufferTypeHandle(true), + boneToRootHandle = state.GetBufferTypeHandle(true), + ltwHandle = ltwHandle, + skeletonWorldBoundsReadOnlyHandle = state.GetComponentTypeHandle(true), + skeletonWorldBoundsHandle = state.GetComponentTypeHandle(false), + chunkSkeletonWorldBoundsHandle = state.GetComponentTypeHandle(false), + lastSystemVersion = lastSystemVersion + }.ScheduleParallel(m_optimizedSkeletonsQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct ExposedBoneBoundsJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle boneBoundsHandle; + [ReadOnly] public ComponentTypeHandle ltwHandle; + [ReadOnly] public ComponentTypeHandle boneWorldBoundsReadOnlyHandle; + [NativeDisableContainerSafetyRestriction] public ComponentTypeHandle boneWorldBoundsHandle; + public ComponentTypeHandle chunkBoneWorldBoundsHandle; + + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + bool needsUpdate = batchInChunk.DidChange(boneBoundsHandle, lastSystemVersion); + needsUpdate |= batchInChunk.DidChange(ltwHandle, lastSystemVersion); + if (!needsUpdate && batchInChunk.DidOrderChange(lastSystemVersion)) + { + //A structural change happened but no component of concern changed, meaning we need to recalculate the chunk component but nothing else (rare). + var worldBoundsRO = batchInChunk.GetNativeArray(boneWorldBoundsReadOnlyHandle); + var aabb = worldBoundsRO[0].bounds; + for (int i = 1; i < batchInChunk.Count; i++) + aabb = + Physics.CombineAabb(aabb, worldBoundsRO[i].bounds); + batchInChunk.SetChunkComponentData(chunkBoneWorldBoundsHandle, new ChunkBoneWorldBounds { chunkBounds = FromAabb(aabb) }); + return; + } + if (!needsUpdate) + return; + var boneBounds = batchInChunk.GetNativeArray(boneBoundsHandle); + var ltws = batchInChunk.GetNativeArray(ltwHandle); + var worldBounds = batchInChunk.GetNativeArray(boneWorldBoundsHandle); + + Aabb chunkBounds = ComputeBounds(boneBounds[0].radialOffsetInBoneSpace, ltws[0].Value); + worldBounds[0] = new BoneWorldBounds { bounds = chunkBounds }; + for (int i = 1; i < batchInChunk.Count; i++) + { + var newBounds = ComputeBounds(boneBounds[i].radialOffsetInBoneSpace, ltws[i].Value); + newBounds.min -= boneBounds[i].radialOffsetInWorldSpace; + newBounds.max += boneBounds[i].radialOffsetInWorldSpace; + worldBounds[i] = new BoneWorldBounds { bounds = newBounds }; + chunkBounds = Physics.CombineAabb(chunkBounds, newBounds); + } + + batchInChunk.SetChunkComponentData(chunkBoneWorldBoundsHandle, new ChunkBoneWorldBounds { chunkBounds = FromAabb(chunkBounds) }); + } + + Aabb ComputeBounds(float radius, float4x4 ltw) + { + float3 extents = LatiosMath.RotateExtents(radius, ltw.c0.xyz, ltw.c1.xyz, ltw.c2.xyz); + return new Aabb(ltw.c3.xyz - extents, ltw.c3.xyz + extents); + } + } + + [BurstCompile] + struct OptimizedBoneBoundsJob : IJobEntityBatch + { + [ReadOnly] public BufferTypeHandle boneBoundsHandle; + [ReadOnly] public BufferTypeHandle boneToRootHandle; + [ReadOnly] public ComponentTypeHandle ltwHandle; + [ReadOnly] public ComponentTypeHandle shaderBoundsHandle; + [ReadOnly] public ComponentTypeHandle skeletonWorldBoundsReadOnlyHandle; + [NativeDisableContainerSafetyRestriction] public ComponentTypeHandle skeletonWorldBoundsHandle; + public ComponentTypeHandle chunkSkeletonWorldBoundsHandle; + + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + bool needsUpdate = batchInChunk.DidChange(boneBoundsHandle, lastSystemVersion); + needsUpdate |= batchInChunk.DidChange(boneToRootHandle, lastSystemVersion); + needsUpdate |= batchInChunk.DidChange(ltwHandle, lastSystemVersion); + if (!needsUpdate && batchInChunk.DidOrderChange(lastSystemVersion)) + { + //A structural change happened but no component of concern changed, meaning we need to recalculate the chunk component but nothing else (rare). + var worldBoundsRO = batchInChunk.GetNativeArray(skeletonWorldBoundsReadOnlyHandle); + var aabb = new Aabb( worldBoundsRO[0].bounds.Min, worldBoundsRO[0].bounds.Max); + for (int i = 1; i < batchInChunk.Count; i++) + aabb = + Physics.CombineAabb(aabb, new Aabb(worldBoundsRO[i].bounds.Min, worldBoundsRO[i].bounds.Max)); + batchInChunk.SetChunkComponentData(chunkSkeletonWorldBoundsHandle, new ChunkSkeletonWorldBounds { chunkBounds = FromAabb(aabb) }); + return; + } + if (!needsUpdate) + return; + + var boneBounds = batchInChunk.GetBufferAccessor(boneBoundsHandle); + var boneToRoots = batchInChunk.GetBufferAccessor(boneToRootHandle); + var ltws = batchInChunk.GetNativeArray(ltwHandle); + var shaderBounds = batchInChunk.GetNativeArray(shaderBoundsHandle); + var worldBounds = batchInChunk.GetNativeArray(skeletonWorldBoundsHandle); + + Aabb chunkBounds = ComputeBounds(boneBounds[0], boneToRoots[0], ltws[0].Value); + worldBounds[0] = new SkeletonWorldBounds { bounds = FromAabb(chunkBounds) }; + for (int i = 1; i < batchInChunk.Count; i++) + { + var newBounds = ComputeBounds(boneBounds[i], boneToRoots[i], ltws[i].Value); + newBounds.min -= shaderBounds[i].radialBoundsInWorldSpace; + newBounds.max += shaderBounds[i].radialBoundsInWorldSpace; + worldBounds[i] = new SkeletonWorldBounds { bounds = FromAabb(newBounds) }; + chunkBounds = Physics.CombineAabb(chunkBounds, newBounds); + } + + batchInChunk.SetChunkComponentData(chunkSkeletonWorldBoundsHandle, new ChunkSkeletonWorldBounds { chunkBounds = FromAabb(chunkBounds) }); + } + + Aabb ComputeBounds(DynamicBuffer bounds, DynamicBuffer boneToRoots, float4x4 ltw) + { + var boundsArray = bounds.Reinterpret().AsNativeArray(); + var boneToRootsArray = boneToRoots.Reinterpret().AsNativeArray(); + + // Todo: Assert that buffers are not empty? + var aabb = new Aabb(float.MaxValue, float.MinValue); + for (int i = 0; i < boundsArray.Length; i++) + { + aabb = Physics.CombineAabb(aabb, ComputeBounds(boundsArray[i], boneToRootsArray[i], ltw)); + } + return aabb; + } + + Aabb ComputeBounds(float radius, float4x4 ltr, float4x4 rtw) + { + float3 extents = LatiosMath.RotateExtents(radius, ltr.c0.xyz, ltr.c1.xyz, ltr.c2.xyz); + extents = LatiosMath.RotateExtents(extents, rtw.c0.xyz, rtw.c1.xyz, rtw.c2.xyz); + float3 center = math.transform(rtw, ltr.c3.xyz); + return new Aabb(center - extents, center + extents); + } + } + + public static AABB FromAabb(Aabb aabb) + { + Physics.GetCenterExtents(aabb, out float3 center, out float3 extents); + return new AABB { Center = center, Extents = extents }; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkeletonBoundsSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkeletonBoundsSystem.cs.meta new file mode 100644 index 0000000..dc31576 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkeletonBoundsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e0f3bd0c81d0774ca4887d59ccfd333 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkinnedMeshChunkBoundsSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkinnedMeshChunkBoundsSystem.cs new file mode 100644 index 0000000..1b87101 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkinnedMeshChunkBoundsSystem.cs @@ -0,0 +1,120 @@ +using Latios.Psyshock; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + // This exists because setting the chunk bounds to an extreme value breaks shadows. + // Instead we calculate the combined chunk bounds for all skeletons and then write them to all skinned mesh chunk bounds. + [DisableAutoCreation] + [BurstCompile] + public partial struct UpdateSkinnedMeshChunkBoundsSystem : ISystem + { + EntityQuery m_exposedMetaQuery; + EntityQuery m_optimizedMetaQuery; + EntityQuery m_skinnedMeshMetaQuery; + + public void OnCreate(ref SystemState state) + { + m_exposedMetaQuery = state.Fluent().WithAll(true).WithAll(true).Build(); + m_optimizedMetaQuery = state.Fluent().WithAll(true).WithAll(true).Build(); + m_skinnedMeshMetaQuery = state.Fluent().WithAll(true).WithAll(false) + .WithAny(true).WithAny(true).Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + var combinedBounds = new NativeReference(state.WorldUnmanaged.UpdateAllocator.ToAllocator); + combinedBounds.Value = new Aabb(float.MaxValue, float.MinValue); + + state.Dependency = new CombineExposedJob + { + handle = state.GetComponentTypeHandle(true), + combinedBounds = combinedBounds + }.Schedule(m_exposedMetaQuery, state.Dependency); + + state.Dependency = new CombineOptimizedJob + { + handle = state.GetComponentTypeHandle(true), + combinedBounds = combinedBounds + }.Schedule(m_optimizedMetaQuery, state.Dependency); + + state.Dependency = new ApplyChunkBoundsToSkinnedMeshesJob + { + handle = state.GetComponentTypeHandle(), + combinedBounds = combinedBounds + }.Schedule(m_skinnedMeshMetaQuery, state.Dependency); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + + [BurstCompile] + struct CombineExposedJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle handle; + public NativeReference combinedBounds; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + Aabb aabb = new Aabb(float.MaxValue, float.MinValue); + var bounds = batchInChunk.GetNativeArray(handle); + for (int i = 0; i < batchInChunk.Count; i++) + { + var b = bounds[i].chunkBounds; + aabb = Physics.CombineAabb(aabb, new Aabb(b.Min, b.Max)); + } + combinedBounds.Value = Physics.CombineAabb(combinedBounds.Value, aabb); + } + } + + [BurstCompile] + struct CombineOptimizedJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle handle; + public NativeReference combinedBounds; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + Aabb aabb = new Aabb(float.MaxValue, float.MinValue); + var bounds = batchInChunk.GetNativeArray(handle); + for (int i = 0; i < batchInChunk.Count; i++) + { + var b = bounds[i].chunkBounds; + aabb = Physics.CombineAabb(aabb, new Aabb(b.Min, b.Max)); + } + combinedBounds.Value = Physics.CombineAabb(combinedBounds.Value, aabb); + } + } + + [BurstCompile] + struct ApplyChunkBoundsToSkinnedMeshesJob : IJobEntityBatch + { + [ReadOnly] public NativeReference combinedBounds; + public ComponentTypeHandle handle; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var aabb = new ChunkWorldRenderBounds { Value = FromAabb(combinedBounds.Value) }; + var bounds = batchInChunk.GetNativeArray(handle); + for (int i = 0; i < batchInChunk.Count; i++) + { + bounds[i] = aabb; + } + } + + public static AABB FromAabb(Aabb aabb) + { + Physics.GetCenterExtents(aabb, out float3 center, out float3 extents); + return new AABB { Center = center, Extents = extents }; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkinnedMeshChunkBoundsSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkinnedMeshChunkBoundsSystem.cs.meta new file mode 100644 index 0000000..845321f --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/PreBatching/UpdateSkinnedMeshChunkBoundsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4146a1498d05ab34d8ee7265ef2cd01b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint.meta new file mode 100644 index 0000000..2485aef --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2162983ff07e6814ebd232b653aaf305 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMasksSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMasksSystem.cs new file mode 100644 index 0000000..f9bb98d --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMasksSystem.cs @@ -0,0 +1,54 @@ +using Unity.Burst; +using Unity.Entities; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct AddMissingMasksSystem : ISystem + { + EntityQuery m_computeQuery; + EntityQuery m_linearBlendQuery; + EntityQuery m_copyQuery; + EntityQuery m_baseQuery; + + public void OnCreate(ref SystemState state) + { + m_computeQuery = state.Fluent().WithAll(true).WithAll().Without().Without( + true) + .IncludePrefabs().IncludeDisabled().Build(); + m_linearBlendQuery = + state.Fluent().WithAll(true).WithAll().Without().Without( + true) + .IncludePrefabs().IncludeDisabled().Build(); + m_copyQuery = state.Fluent().WithAll(true).Without(true).IncludePrefabs().IncludeDisabled().Build(); + m_baseQuery = state.Fluent().WithAll(true).Without(true).IncludePrefabs().IncludeDisabled().Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.EntityManager.AddComponent(m_computeQuery, new ComponentTypes(ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent())); + state.EntityManager.AddComponent(m_linearBlendQuery, new ComponentTypes(ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent())); + state.EntityManager.AddComponent(m_copyQuery, new ComponentTypes(ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent())); + state.EntityManager.AddComponent(m_baseQuery, new ComponentTypes(ComponentType.ChunkComponent(), + ComponentType.ChunkComponent(), + ComponentType.ChunkComponent())); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMasksSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMasksSystem.cs.meta new file mode 100644 index 0000000..14d5c47 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMasksSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 46ce97c9e16ade84b8d22744c1d049c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMatrixCacheSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMatrixCacheSystem.cs new file mode 100644 index 0000000..72e0077 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMatrixCacheSystem.cs @@ -0,0 +1,29 @@ +using Unity.Burst; +using Unity.Entities; +using Unity.Rendering; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + [BurstCompile] + public partial struct AddMissingMatrixCacheSystem : ISystem + { + EntityQuery m_query; + + public void OnCreate(ref SystemState state) + { + m_query = state.Fluent().WithAll(true).Without().IncludeDisabled().IncludePrefabs().Build(); + } + + [BurstCompile] + public void OnUpdate(ref SystemState state) + { + state.EntityManager.AddComponent(m_query); + } + + [BurstCompile] + public void OnDestroy(ref SystemState state) { + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMatrixCacheSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMatrixCacheSystem.cs.meta new file mode 100644 index 0000000..e772477 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/AddMissingMatrixCacheSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2bc2b931d003414dbed4a208c25bebf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/SkeletonMeshBindingReactiveSystem.cs b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/SkeletonMeshBindingReactiveSystem.cs new file mode 100644 index 0000000..a8ef010 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/SkeletonMeshBindingReactiveSystem.cs @@ -0,0 +1,1603 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; +using Unity.Transforms; + +namespace Latios.Kinemation.Systems +{ + [DisableAutoCreation] + public partial class SkeletonMeshBindingReactiveSystem : SubSystem + { + EntityQuery m_newMeshesQuery; + EntityQuery m_bindableMeshesQuery; + EntityQuery m_deadMeshesQuery; + EntityQuery m_newSkeletonsQuery; + EntityQuery m_deadSkeletonsQuery; + EntityQuery m_newExposedSkeletonsQuery; + EntityQuery m_syncableExposedSkeletonsQuery; + EntityQuery m_deadExposedSkeletonsQuery; + EntityQuery m_deadExposedSkeletonsQuery2; + EntityQuery m_newOptimizedSkeletonsQuery; + EntityQuery m_deadOptimizedSkeletonsQuery; + EntityQuery m_deadOptimizedSkeletonsQuery2; + EntityQuery m_cullableExposedBonesQuery; + EntityQuery m_aliveSkeletonsQuery; + + Entity m_failedBindingEntity; + + protected override void OnCreate() + { + m_newMeshesQuery = Fluent.WithAll(true).WithAll(true) + .Without() + .WithAny(true).WithAny(true).Build(); + + m_bindableMeshesQuery = Fluent.WithAll().WithAll(true).WithAll(true).WithAll() + .WithAny(true).WithAny(true).Build(); + + m_deadMeshesQuery = Fluent.WithAll().Without().Without() + .Without().Without().Build(); + + m_newSkeletonsQuery = Fluent.WithAll(true).Without().Build(); + m_deadSkeletonsQuery = Fluent.WithAll().Without().Build(); + m_aliveSkeletonsQuery = Fluent.WithAll(true).Build(); + m_newExposedSkeletonsQuery = Fluent.WithAll(true).Without().WithAll(true).Build(); + m_syncableExposedSkeletonsQuery = Fluent.WithAll(true).WithAll(true).Build(); + m_deadExposedSkeletonsQuery = Fluent.Without().WithAll().Build(); + m_deadExposedSkeletonsQuery2 = Fluent.Without().WithAll().Build(); + m_newOptimizedSkeletonsQuery = Fluent.WithAll(true).WithAll(true).Without().Build(); + m_deadOptimizedSkeletonsQuery = Fluent.WithAll(true).Without().Build(); + m_deadOptimizedSkeletonsQuery2 = Fluent.WithAll(true).Without().Build(); + m_cullableExposedBonesQuery = Fluent.WithAll().Build(); + + worldBlackboardEntity.AddCollectionComponent(new MeshGpuManager + { + blobIndexMap = new NativeParallelHashMap, int>(128, Allocator.Persistent), + entries = new NativeList(Allocator.Persistent), + indexFreeList = new NativeList(Allocator.Persistent), + verticesGaps = new NativeList(Allocator.Persistent), + weightsGaps = new NativeList(Allocator.Persistent), + bindPosesGaps = new NativeList(Allocator.Persistent), + requiredBufferSizes = new NativeReference(Allocator.Persistent), + uploadCommands = new NativeList(Allocator.Persistent) + }); + + worldBlackboardEntity.AddCollectionComponent(new BoneOffsetsGpuManager + { + entries = new NativeList(Allocator.Persistent), + indexFreeList = new NativeList(Allocator.Persistent), + offsets = new NativeList(Allocator.Persistent), + gaps = new NativeList(Allocator.Persistent), + isDirty = new NativeReference(Allocator.Persistent), + hashToEntryMap = new NativeParallelHashMap(128, Allocator.Persistent), + pathPairToEntryMap = new NativeParallelHashMap(128, Allocator.Persistent) + }); + + worldBlackboardEntity.AddCollectionComponent(new ExposedCullingIndexManager + { + skeletonToCullingIndexMap = new NativeParallelHashMap(128, Allocator.Persistent), + indexFreeList = new NativeList(Allocator.Persistent), + maxIndex = new NativeReference(Allocator.Persistent), + cullingIndexToSkeletonMap = new NativeParallelHashMap >(128, Allocator.Persistent) + }); + } + + protected override void OnUpdate() + { + // Todo: It may be possible to defer all structural changes to a sync point. + // Whether or not that is worth pursuing remains to be seen. + bool haveNewMeshes = !m_newMeshesQuery.IsEmptyIgnoreFilter; + bool haveBindableMeshes = !m_bindableMeshesQuery.IsEmptyIgnoreFilter; + bool haveDeadMeshes = !m_deadMeshesQuery.IsEmptyIgnoreFilter; + bool haveNewSkeletons = !m_newSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveAliveSkeletons = !m_aliveSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveDeadSkeletons = !m_deadSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveNewExposedSkeletons = !m_newExposedSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveSyncableExposedSkeletons = !m_syncableExposedSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveDeadExposedSkeletons = !m_deadExposedSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveDeadExposedSkeletons2 = !m_deadExposedSkeletonsQuery2.IsEmptyIgnoreFilter; + bool haveNewOptimizedSkeletons = !m_newOptimizedSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveDeadOptimizedSkeletons = !m_deadOptimizedSkeletonsQuery.IsEmptyIgnoreFilter; + bool haveDeadOptimizedSkeletons2 = !m_deadOptimizedSkeletonsQuery2.IsEmptyIgnoreFilter; + bool haveCullableExposedBones = !m_cullableExposedBonesQuery.IsEmptyIgnoreFilter; + + //bool needsCullingJob = haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedskeletons2; + //bool needsMeshJob = haveNewMeshes | haveDeadMeshes | haveBindableMeshes; + //bool needsMeshStateOpsJob = haveNewMeshes | haveBindableMeshes; + //bool needsBindingOpsJob = needsMeshJob; + //bool needsCullingOpsJob = needsCullingJob | haveSyncableExposedSkeletons; + + // The '2' variants are covered by the base dead skeletons + bool requiresStructuralChange = haveNewMeshes | haveDeadMeshes | haveNewSkeletons | haveDeadSkeletons | + haveNewExposedSkeletons | haveDeadExposedSkeletons | haveNewOptimizedSkeletons | haveDeadOptimizedSkeletons; + + bool requiresManagers = haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedSkeletons2 | haveNewMeshes | haveDeadMeshes | haveBindableMeshes; + + ref var allocatorHandle = ref World.UpdateAllocator; + var allocator = allocatorHandle.ToAllocator; + + UnsafeParallelBlockList bindingOpsBlockList = default; + UnsafeParallelBlockList meshAddOpsBlockList = default; + UnsafeParallelBlockList meshRemoveOpsBlockList = default; + if (haveNewMeshes | haveDeadMeshes | haveBindableMeshes) + { + bindingOpsBlockList = new UnsafeParallelBlockList(UnsafeUtility.SizeOf(), 128, Allocator.TempJob); + meshAddOpsBlockList = new UnsafeParallelBlockList(UnsafeUtility.SizeOf(), 128, Allocator.TempJob); + meshRemoveOpsBlockList = new UnsafeParallelBlockList(UnsafeUtility.SizeOf(), 128, Allocator.TempJob); + } + + MeshGpuManager meshGpuManager = default; + BoneOffsetsGpuManager boneOffsetsGpuManager = default; + ExposedCullingIndexManager cullingIndicesManager = default; + if (requiresManagers) + { + meshGpuManager = worldBlackboardEntity.GetCollectionComponent(false, out var meshGpuManagerJH); + boneOffsetsGpuManager = worldBlackboardEntity.GetCollectionComponent(false, out var boneOffsetsGpuManagerJH); + cullingIndicesManager = worldBlackboardEntity.GetCollectionComponent(false, out var cullingIndicesManagerJH); + var jhs = new NativeList(4, Allocator.Temp); + jhs.Add(Dependency); + jhs.Add(meshGpuManagerJH); + jhs.Add(boneOffsetsGpuManagerJH); + jhs.Add(cullingIndicesManagerJH); + Dependency = JobHandle.CombineDependencies(jhs); + } + + JobHandle cullingJH = default; + NativeList cullingOps = default; + NativeReference cullingIndicesToClear = default; + if (haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedSkeletons2 | haveBindableMeshes) + { + cullingOps = new NativeList(0, allocatorHandle.Handle); + cullingIndicesToClear = new NativeReference(allocatorHandle.Handle); + } + + var lastSystemVersion = LastSystemVersion; + var globalSystemVersion = GlobalSystemVersion; + + if (haveDeadSkeletons) + { + Dependency = new FindDeadSkeletonsJob + { + dependentsHandle = GetBufferTypeHandle(true), + meshStateCdfe = GetComponentDataFromEntity(false) + }.ScheduleParallel(m_deadSkeletonsQuery, Dependency); + } + if (haveDeadMeshes) + { + Dependency = new FindDeadMeshesJob + { + entityHandle = GetEntityTypeHandle(), + depsHandle = GetComponentTypeHandle(true), + bindingOpsBlockList = bindingOpsBlockList, + meshRemoveOpsBlockList = meshRemoveOpsBlockList + }.ScheduleParallel(m_deadMeshesQuery, Dependency); + } + + if (haveNewMeshes || haveBindableMeshes) + { + var newMeshJob = new FindNewMeshesJob + { + allocator = allocator, + bindingOpsBlockList = bindingOpsBlockList, + bindSkeletonRootCdfe = GetComponentDataFromEntity(true), + boneOwningSkeletonReferenceCdfe = GetComponentDataFromEntity(true), + entityHandle = GetEntityTypeHandle(), + meshAddOpsBlockList = meshAddOpsBlockList, + needsBindingHandle = GetComponentTypeHandle(false), + overrideBonesHandle = GetBufferTypeHandle(true), + pathBindingsBlobRefHandle = GetComponentTypeHandle(true), + skeletonBindingPathsBlobRefCdfe = GetComponentDataFromEntity(true), + skeletonRootTagCdfe = GetComponentDataFromEntity(true), + skinningBlobRefHandle = GetComponentTypeHandle(true), + radialBoundsHandle = GetComponentTypeHandle(true), + rootRefHandle = GetComponentTypeHandle(true) + }; + + if (haveNewMeshes) + { + Dependency = newMeshJob.ScheduleParallel(m_newMeshesQuery, Dependency); + } + if (haveBindableMeshes) + { + Dependency = new FindRebindMeshesJob + { + depsHandle = GetComponentTypeHandle(false), + lastSystemVersion = lastSystemVersion, + meshRemoveOpsBlockList = meshRemoveOpsBlockList, + newMeshesJob = newMeshJob + }.ScheduleParallel(m_bindableMeshesQuery, Dependency); + } + } + + if (haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedSkeletons2) + { + int newExposedSkeletonsCount = m_newExposedSkeletonsQuery.CalculateEntityCountWithoutFiltering(); + int deadExposedSkeletonsCount = m_deadExposedSkeletonsQuery.CalculateEntityCountWithoutFiltering(); + int deadExposedSkeletonsCount2 = m_deadExposedSkeletonsQuery2.CalculateEntityCountWithoutFiltering(); + + var newSkeletonsArray = allocatorHandle.AllocateNativeArray(newExposedSkeletonsCount); + var deadSkeletonsArray = allocatorHandle.AllocateNativeArray(deadExposedSkeletonsCount); + var deadSkeletonsArray2 = allocatorHandle.AllocateNativeArray(deadExposedSkeletonsCount2); + + var entityHandle = GetEntityTypeHandle(); + Dependency = new FindNewOrDeadDeadExposedSkeletonsJob + { + entityHandle = entityHandle, + newOrDeadArray = newSkeletonsArray + }.ScheduleParallel(m_newExposedSkeletonsQuery, Dependency); + Dependency = new FindNewOrDeadDeadExposedSkeletonsJob + { + entityHandle = entityHandle, + newOrDeadArray = deadSkeletonsArray + }.ScheduleParallel(m_deadExposedSkeletonsQuery, Dependency); + Dependency = new FindNewOrDeadDeadExposedSkeletonsJob + { + entityHandle = entityHandle, + newOrDeadArray = deadSkeletonsArray2 + }.ScheduleParallel(m_deadExposedSkeletonsQuery2, Dependency); + + cullingOps.Capacity = newExposedSkeletonsCount + m_syncableExposedSkeletonsQuery.CalculateEntityCountWithoutFiltering(); + + cullingJH = new ProcessNewAndDeadExposedSkeletonsJob + { + newExposedSkeletons = newSkeletonsArray, + deadExposedSkeletons = deadSkeletonsArray, + deadExposedSkeletons2 = deadSkeletonsArray2, + cullingManager = cullingIndicesManager, + operations = cullingOps, + indicesToClear = cullingIndicesToClear, + allocator = allocator + }.Schedule(Dependency); + } + + JobHandle meshBindingsJH = default; + NativeList meshBindingsStatesToWrite = default; + + JobHandle skeletonBindingOpsJH = default; + NativeList skeletonBindingOps = default; + NativeList skeletonBindingOpsStartsAndCounts = default; + if (haveNewMeshes || haveDeadMeshes || haveBindableMeshes) + { + int newMeshCount = m_newMeshesQuery.CalculateEntityCountWithoutFiltering(); + int deadMeshCount = m_deadMeshesQuery.CalculateEntityCountWithoutFiltering(); + int bindableMeshCount = m_bindableMeshesQuery.CalculateEntityCountWithoutFiltering(); + int aliveSkeletonsCount = m_aliveSkeletonsQuery.CalculateEntityCountWithoutFiltering(); + + meshBindingsStatesToWrite = new NativeList(newMeshCount + bindableMeshCount, allocator); + + meshBindingsJH = new ProcessMeshGpuChangesJob + { + meshAddOpsBlockList = meshAddOpsBlockList, // This is disposed here + meshRemoveOpsBlockList = meshRemoveOpsBlockList, // this is disposed here + boneManager = boneOffsetsGpuManager, + meshManager = meshGpuManager, + outputWriteOps = meshBindingsStatesToWrite + }.Schedule(Dependency); + + skeletonBindingOps = new NativeList(newMeshCount + deadMeshCount + bindableMeshCount, allocatorHandle.Handle); + skeletonBindingOpsStartsAndCounts = new NativeList(aliveSkeletonsCount, allocatorHandle.Handle); + + skeletonBindingOpsJH = new BatchBindingOpsJob + { + bindingsBlockList = bindingOpsBlockList, // This is disposed here + operations = skeletonBindingOps, + startsAndCounts = skeletonBindingOpsStartsAndCounts + }.Schedule(Dependency); + } + + if (requiresStructuralChange) + { + // Kick the jobs so that the sorting happens while we do structural changes. + // Todo: Does Complete already do this? + JobHandle.ScheduleBatchedJobs(); + + CompleteDependency(); + + if (!EntityManager.Exists(m_failedBindingEntity)) + { + m_failedBindingEntity = EntityManager.CreateEntity(); + EntityManager.AddComponents(m_failedBindingEntity, new ComponentTypes(typeof(LocalToWorld), typeof(FailedBindingsRootTag))); + EntityManager.SetName(m_failedBindingEntity, "Failed Bindings Root"); + } + + var optimizedTypes = new FixedList128Bytes(); + optimizedTypes.Add(typeof(PerFrameSkeletonBufferMetadata)); + optimizedTypes.Add(typeof(OptimizedBoneToRoot)); + optimizedTypes.Add(typeof(OptimizedSkeletonTag)); + optimizedTypes.Add(typeof(SkeletonShaderBoundsOffset)); + optimizedTypes.Add(typeof(SkeletonWorldBounds)); + optimizedTypes.Add(typeof(OptimizedBoneBounds)); + optimizedTypes.Add(ComponentType.ChunkComponent()); + optimizedTypes.Add(ComponentType.ChunkComponent()); + + EntityManager.RemoveComponent( m_deadMeshesQuery); + EntityManager.RemoveComponent( m_deadOptimizedSkeletonsQuery, new ComponentTypes(optimizedTypes)); + EntityManager.RemoveComponent( m_deadExposedSkeletonsQuery, + new ComponentTypes(typeof(ExposedSkeletonCullingIndex), typeof(PerFrameSkeletonBufferMetadata), + ComponentType.ChunkComponent())); + EntityManager.RemoveComponent( m_deadSkeletonsQuery); + + var transformComponentsThatWriteToLocalToParent = new FixedList128Bytes(); + // Having both causes the mesh to not render in some circumstances. Still need to investigate how this happens. + transformComponentsThatWriteToLocalToParent.Add(typeof(CopyLocalToParentFromBone)); + transformComponentsThatWriteToLocalToParent.Add(typeof(Translation)); + transformComponentsThatWriteToLocalToParent.Add(typeof(Rotation)); + transformComponentsThatWriteToLocalToParent.Add(typeof(Scale)); + transformComponentsThatWriteToLocalToParent.Add(typeof(NonUniformScale)); + transformComponentsThatWriteToLocalToParent.Add(typeof(ParentScaleInverse)); + transformComponentsThatWriteToLocalToParent.Add(typeof(CompositeRotation)); + transformComponentsThatWriteToLocalToParent.Add(typeof(CompositeScale)); + EntityManager.RemoveComponent(m_newMeshesQuery, new ComponentTypes(transformComponentsThatWriteToLocalToParent)); + EntityManager.AddComponent(m_newMeshesQuery, new ComponentTypes(typeof(SkeletonDependent), typeof(LocalToParent), typeof(Parent))); + + optimizedTypes.Add(typeof(DependentSkinnedMesh)); + + EntityManager.AddComponent(m_newOptimizedSkeletonsQuery, new ComponentTypes(optimizedTypes)); + EntityManager.AddComponent(m_newExposedSkeletonsQuery, + new ComponentTypes(typeof(DependentSkinnedMesh), typeof(PerFrameSkeletonBufferMetadata), + typeof(ExposedSkeletonCullingIndex), ComponentType.ChunkComponent())); + + EntityManager.AddComponent(m_newSkeletonsQuery); + } + + if (haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedSkeletons2 | haveNewMeshes | haveBindableMeshes | haveDeadMeshes) + { + var jhs = new NativeList(4, Allocator.Temp); + jhs.Add(cullingJH); + jhs.Add(meshBindingsJH); + jhs.Add(skeletonBindingOpsJH); + jhs.Add(Dependency); + Dependency = JobHandle.CombineDependencies(jhs); + } + + if (haveNewMeshes | haveBindableMeshes) + { + Dependency = new ProcessMeshStateOpsJob + { + failedBindingEntity = m_failedBindingEntity, + ops = meshBindingsStatesToWrite.AsDeferredJobArray(), + parentCdfe = GetComponentDataFromEntity(false), + stateCdfe = GetComponentDataFromEntity(false) + }.Schedule(meshBindingsStatesToWrite, 16, Dependency); + } + + if (haveNewMeshes | haveBindableMeshes | haveDeadMeshes) + { + Dependency = new ProcessBindingOpsJob + { + boneBoundsCdfe = GetComponentDataFromEntity(false), + boneOffsetsGpuManager = boneOffsetsGpuManager, + boneRefsBfe = GetBufferFromEntity(true), + boneToRootsBfe = GetBufferFromEntity(true), + dependentsBfe = GetBufferFromEntity(false), + meshGpuManager = meshGpuManager, + meshStateCdfe = GetComponentDataFromEntity(true), + operations = skeletonBindingOps.AsDeferredJobArray(), + optimizedBoundsBfe = GetBufferFromEntity(false), + optimizedShaderBoundsCdfe = GetComponentDataFromEntity(false), + startsAndCounts = skeletonBindingOpsStartsAndCounts.AsDeferredJobArray() + }.Schedule(skeletonBindingOpsStartsAndCounts, 1, Dependency); + } + + if (haveSyncableExposedSkeletons && haveCullableExposedBones) + { + if (!(haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedSkeletons2)) + { + Dependency = Job.WithReadOnly(cullingIndicesManager).WithCode(() => + { + cullingIndicesToClear.Value = new UnsafeBitArray(cullingIndicesManager.maxIndex.Value + 1, allocator); + }).Schedule(Dependency); + } + + Dependency = new FindExposedSkeletonsToUpdateJob + { + dirtyFlagHandle = GetComponentTypeHandle(false), + entityHandle = GetEntityTypeHandle(), + indicesToClear = cullingIndicesToClear, + lastSystemVersion = lastSystemVersion, + manager = cullingIndicesManager, + operations = cullingOps + }.Schedule(m_syncableExposedSkeletonsQuery, Dependency); + } + + if ((haveSyncableExposedSkeletons | haveNewExposedSkeletons | haveDeadExposedSkeletons | haveDeadExposedSkeletons2) && haveCullableExposedBones) + { + Dependency = new ResetExposedBonesJob + { + indexHandle = GetComponentTypeHandle(false), + indicesToClear = cullingIndicesToClear + }.ScheduleParallel(m_cullableExposedBonesQuery, Dependency); + } + + if (haveSyncableExposedSkeletons | haveNewExposedSkeletons) + { + Dependency = new SetExposedSkeletonCullingIndicesJob + { + boneCullingIndexCdfe = GetComponentDataFromEntity(false), + boneIndexCdfe = GetComponentDataFromEntity(false), + boneRefsBfe = GetBufferFromEntity(true), + operations = cullingOps.AsDeferredJobArray(), + skeletonCullingIndexCdfe = GetComponentDataFromEntity(false), + skeletonReferenceCdfe = GetComponentDataFromEntity(false) + }.Schedule(cullingOps, 1, Dependency); + } + } + + #region Types + struct BindUnbindOperation : IComparable + { + public enum OpType : byte + { + Unbind = 0, + Bind = 1, + } + + public Entity targetEntity; + public Entity meshEntity; + public OpType opType; + + public int CompareTo(BindUnbindOperation other) + { + var compare = targetEntity.CompareTo(other.targetEntity); + if (compare == 0) + { + compare = ((byte)opType).CompareTo((byte)other.opType); + if (compare == 0) + compare = meshEntity.CompareTo(other.meshEntity); + } + return compare; + } + } + + struct MeshAddOperation : IComparable + { + public Entity meshEntity; + public EntityWith root; + public BlobAssetReference skinningBlob; + public BlobAssetReference meshBindingPathsBlob; + public BlobAssetReference skeletonBindingPathsBlob; + public UnsafeList overrideBoneBindings; + public float shaderEffectRadialBounds; + + public int CompareTo(MeshAddOperation other) => meshEntity.CompareTo(other.meshEntity); + } + + struct MeshRemoveOperation : IComparable + { + public Entity meshEntity; + public SkeletonDependent oldState; + + public int CompareTo(MeshRemoveOperation other) => meshEntity.CompareTo(other.meshEntity); + } + + struct MeshWriteStateOperation + { + public EntityWith meshEntity; + public SkeletonDependent state; + } + + struct ExposedSkeletonCullingIndexOperation + { + public Entity skeletonEntity; + public int index; + } + #endregion + + #region PreSync Jobs + [BurstCompile] + struct FindDeadSkeletonsJob : IJobEntityBatch + { + [ReadOnly] public BufferTypeHandle dependentsHandle; + + [NativeDisableParallelForRestriction] public ComponentDataFromEntity meshStateCdfe; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var buffers = batchInChunk.GetBufferAccessor(dependentsHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + var buffer = buffers[i].AsNativeArray(); + for (int j = 0; j < buffer.Length; j++) + { + var entity = buffer[j].skinnedMesh; + var state = meshStateCdfe[entity]; + state.root = Entity.Null; + meshStateCdfe[entity] = state; + } + } + } + } + + [BurstCompile] + struct FindDeadMeshesJob : IJobEntityBatch + { + [ReadOnly] public EntityTypeHandle entityHandle; + [ReadOnly] public ComponentTypeHandle depsHandle; + + public UnsafeParallelBlockList bindingOpsBlockList; + public UnsafeParallelBlockList meshRemoveOpsBlockList; + [NativeSetThreadIndex] int m_nativeThreadIndex; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var entities = batchInChunk.GetNativeArray(entityHandle); + var deps = batchInChunk.GetNativeArray(depsHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + // If the mesh is in a valid state, this is not null. + if (deps[i].skinningBlob != BlobAssetReference.Null) + { + // However, the mesh could still have an invalid skeleton if the skeleton died. + var target = deps[i].root; + if (target != Entity.Null) + { + bindingOpsBlockList.Write(new BindUnbindOperation { targetEntity = target, meshEntity = entities[i], opType = BindUnbindOperation.OpType.Unbind }, + m_nativeThreadIndex); + } + meshRemoveOpsBlockList.Write(new MeshRemoveOperation { meshEntity = entities[i], oldState = deps[i] }, m_nativeThreadIndex); + } + } + } + } + + [BurstCompile] + struct FindNewMeshesJob : IJobEntityBatch + { + [ReadOnly] public EntityTypeHandle entityHandle; + [ReadOnly] public ComponentTypeHandle rootRefHandle; + [ReadOnly] public ComponentTypeHandle skinningBlobRefHandle; + [ReadOnly] public ComponentDataFromEntity skeletonRootTagCdfe; + [ReadOnly] public ComponentDataFromEntity bindSkeletonRootCdfe; + [ReadOnly] public ComponentDataFromEntity boneOwningSkeletonReferenceCdfe; + [ReadOnly] public ComponentDataFromEntity skeletonBindingPathsBlobRefCdfe; + + // Optional + [ReadOnly] public ComponentTypeHandle pathBindingsBlobRefHandle; + [ReadOnly] public BufferTypeHandle overrideBonesHandle; + [ReadOnly] public ComponentTypeHandle radialBoundsHandle; + public ComponentTypeHandle needsBindingHandle; + + public UnsafeParallelBlockList bindingOpsBlockList; + public UnsafeParallelBlockList meshAddOpsBlockList; + [NativeSetThreadIndex] int m_nativeThreadIndex; + + public Allocator allocator; + + public unsafe void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var lower = new BitField64(~0UL); + var upper = new BitField64(~0UL); + if (batchInChunk.Has(needsBindingHandle)) + { + var needs = batchInChunk.GetNativeArray(needsBindingHandle); + for (int i = 0; i < math.min(batchInChunk.Count, 64); i++) + { + lower.SetBits(i, needs[i].needsBinding); + needs[i] = new NeedsBindingFlag { needsBinding = false }; + } + for (int i = 0; i + 64 < batchInChunk.Count; i++) + { + upper.SetBits(i, needs[i + 64].needsBinding); + needs[i + 64] = new NeedsBindingFlag { needsBinding = false }; + } + if ((lower.Value | upper.Value) == 0) + return; + } + + var entities = batchInChunk.GetNativeArray(entityHandle); + var rootRefs = batchInChunk.GetNativeArray(rootRefHandle); + var skinningBlobs = batchInChunk.GetNativeArray(skinningBlobRefHandle); + + var hasPathBindings = batchInChunk.Has(pathBindingsBlobRefHandle); + var hasOverrideBones = batchInChunk.Has(overrideBonesHandle); + var hasRadialBounds = batchInChunk.Has(radialBoundsHandle); + var pathBindings = hasPathBindings ? batchInChunk.GetNativeArray(pathBindingsBlobRefHandle) : default; + var overrideBones = hasOverrideBones ? batchInChunk.GetBufferAccessor(overrideBonesHandle) : default; + var radialBounds = hasRadialBounds ? batchInChunk.GetNativeArray(radialBoundsHandle) : default; + + for (int i = 0; i < batchInChunk.Count; i++) + { + if (!(i >= 64 ? upper.IsSet(i - 64) : lower.IsSet(i))) + continue; + + var entity = entities[i]; + + var root = rootRefs[i].root; + if (root == Entity.Null) + continue; + + if (!root.IsValid(skeletonRootTagCdfe)) + { + bool found = false; + + if (boneOwningSkeletonReferenceCdfe.HasComponent(root)) + { + var skelRef = boneOwningSkeletonReferenceCdfe[root]; + if (skelRef.skeletonRoot != Entity.Null) + { + found = true; + root = skelRef.skeletonRoot; + } + } + + if (!found && bindSkeletonRootCdfe.HasComponent(root)) + { + var skelRef = bindSkeletonRootCdfe[root]; + if (skelRef.root != Entity.Null) + { + found = true; + root = skelRef.root; + } + } + + if (!found) + { + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {entity} attempted to bind to entity {root.entity}, but the latter is not a skeleton nor references one."); + continue; + } + } + + var skinningBlob = skinningBlobs[i].blob; + if (skinningBlob == BlobAssetReference.Null) + continue; + + var radial = hasRadialBounds ? radialBounds[i].radialBounds : 0f; + + var pathsBlob = BlobAssetReference.Null; + var skeletonPathsBlob = BlobAssetReference.Null; + if (hasPathBindings) + { + pathsBlob = pathBindings[i].blob; + if (pathsBlob != BlobAssetReference.Null && !hasOverrideBones) + { + var numPaths = pathsBlob.Value.pathsInReversedNotation.Length; + var numPoses = skinningBlob.Value.bindPoses.Length; + if (numPaths != numPoses) + { + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {entity} has incompatible MeshSkinningBlob and MeshBindingPathsBlob. The following should be equal: Bindposes: {numPoses}, Paths: {numPaths}"); + continue; + } + + if (skeletonBindingPathsBlobRefCdfe.HasComponent(root)) + { + skeletonPathsBlob = skeletonBindingPathsBlobRefCdfe[root].blob; + if (skeletonPathsBlob == BlobAssetReference.Null) + { + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {entity} attempted to bind to entity {root.entity}, but the latter has a null SkeletonBindingPathsBlob and the skinned mesh entity does not have OverrideSkinningBoneIndex."); + continue; + } + } + else + { + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {entity} attempted to bind to entity {root.entity}, but the latter does not have a SkeletonBindingPathsBlob and the skinned mesh entity does not have OverrideSkinningBoneIndex."); + } + } + else if (!hasOverrideBones) + continue; + } + + UnsafeList bonesList = default; + if (hasOverrideBones) + { + var bonesBuffer = overrideBones[i]; + var numPoses = skinningBlob.Value.bindPoses.Length; + if (bonesBuffer.Length != numPoses) + { + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {entity} does not have the required number of override bones. Has: {bonesBuffer.Length}, Requires: {numPoses}"); + continue; + } + + bonesList = new UnsafeList(numPoses, allocator); + bonesList.AddRangeNoResize(bonesBuffer.GetUnsafeReadOnlyPtr(), numPoses); + } + + meshAddOpsBlockList.Write(new MeshAddOperation + { + meshEntity = entity, + root = root, + skinningBlob = skinningBlob, + meshBindingPathsBlob = pathsBlob, + overrideBoneBindings = bonesList, + shaderEffectRadialBounds = radial, + skeletonBindingPathsBlob = skeletonPathsBlob, + }, m_nativeThreadIndex); + + bindingOpsBlockList.Write(new BindUnbindOperation + { + meshEntity = entity, + targetEntity = root, + opType = BindUnbindOperation.OpType.Bind + }, m_nativeThreadIndex); + } + } + } + + [BurstCompile] + struct FindRebindMeshesJob : IJobEntityBatch + { + public FindNewMeshesJob newMeshesJob; + public ComponentTypeHandle depsHandle; + + public UnsafeParallelBlockList meshRemoveOpsBlockList; + + public uint lastSystemVersion; + + [NativeSetThreadIndex] int m_nativeThreadIndex; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + // The general strategy here is to unbind anything requesting a rebind + // and then to treat it like a new mesh using that job's struct. + if (!batchInChunk.DidChange(newMeshesJob.needsBindingHandle, lastSystemVersion)) + return; + + { + // New scope so that the compiler doesn't keep these variables on the stack when running the NewMesh job. + var entities = batchInChunk.GetNativeArray(newMeshesJob.entityHandle); + var deps = batchInChunk.GetNativeArray(depsHandle); + var needs = batchInChunk.GetNativeArray(newMeshesJob.needsBindingHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + // If the mesh is in a valid state, this is not null. + if (deps[i].skinningBlob != BlobAssetReference.Null && needs[i].needsBinding) + { + // However, the mesh could still have an invalid skeleton if the skeleton died. + var target = deps[i].root; + if (target != Entity.Null) + { + newMeshesJob.bindingOpsBlockList.Write(new BindUnbindOperation { + targetEntity = target, meshEntity = entities[i], opType = BindUnbindOperation.OpType.Unbind + }, + m_nativeThreadIndex); + } + meshRemoveOpsBlockList.Write(new MeshRemoveOperation { meshEntity = entities[i], oldState = deps[i] }, m_nativeThreadIndex); + + // We need to wipe our state clean in case the rebinding fails. + deps[i] = default; + } + } + } + + newMeshesJob.Execute(batchInChunk, batchIndex); + } + } + + [BurstCompile] + struct FindNewOrDeadDeadExposedSkeletonsJob : IJobEntityBatchWithIndex + { + [ReadOnly] public EntityTypeHandle entityHandle; + public NativeArray newOrDeadArray; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex, int indexOfFirstEntityInQuery) + { + var entities = batchInChunk.GetNativeArray(entityHandle); + NativeArray.Copy(entities, 0, newOrDeadArray, indexOfFirstEntityInQuery, batchInChunk.Count); + } + } + #endregion + + #region Sync Jobs + [BurstCompile] + struct ProcessNewAndDeadExposedSkeletonsJob : IJob + { + [ReadOnly] public NativeArray newExposedSkeletons; + [ReadOnly] public NativeArray deadExposedSkeletons; + [ReadOnly] public NativeArray deadExposedSkeletons2; + public ExposedCullingIndexManager cullingManager; + public NativeList operations; + public NativeReference indicesToClear; + + public Allocator allocator; + + public void Execute() + { + // We never shrink indices, so we can safely set indicesToClear now. + indicesToClear.Value = new UnsafeBitArray(cullingManager.maxIndex.Value + 1, allocator); + + for (int i = 0; i < deadExposedSkeletons.Length; i++) + { + var entity = deadExposedSkeletons[i]; + int index = cullingManager.skeletonToCullingIndexMap[entity]; + cullingManager.indexFreeList.Add(index); + cullingManager.skeletonToCullingIndexMap.Remove(entity); + cullingManager.cullingIndexToSkeletonMap.Remove(index); + indicesToClear.Value.Set(index, true); + } + for (int i = 0; i < deadExposedSkeletons2.Length; i++) + { + var entity = deadExposedSkeletons2[i]; + if (!cullingManager.skeletonToCullingIndexMap.ContainsKey(entity)) + continue; + int index = cullingManager.skeletonToCullingIndexMap[entity]; + cullingManager.indexFreeList.Add(index); + cullingManager.skeletonToCullingIndexMap.Remove(entity); + cullingManager.cullingIndexToSkeletonMap.Remove(index); + indicesToClear.Value.Set(index, true); + } + + for (int i = 0; i < newExposedSkeletons.Length; i++) + { + int index; + if (!cullingManager.indexFreeList.IsEmpty) + { + index = cullingManager.indexFreeList[0]; + cullingManager.indexFreeList.RemoveAtSwapBack(0); + indicesToClear.Value.Set(index, true); // No harm in doing this in case something stupid happens with enabled states. + } + else + { + // Index 0 is reserved for prefabs and orphaned bones + index = cullingManager.maxIndex.Value + 1; + cullingManager.maxIndex.Value++; + } + cullingManager.skeletonToCullingIndexMap.Add(newExposedSkeletons[i], index); + cullingManager.cullingIndexToSkeletonMap.Add(index, newExposedSkeletons[i]); + + operations.Add(new ExposedSkeletonCullingIndexOperation + { + skeletonEntity = newExposedSkeletons[i], + index = index + }); + } + } + } + + [BurstCompile] + struct BatchBindingOpsJob : IJob + { + [NativeDisableUnsafePtrRestriction] public UnsafeParallelBlockList bindingsBlockList; + public NativeList operations; + public NativeList startsAndCounts; + + public void Execute() + { + var count = bindingsBlockList.Count(); + operations.ResizeUninitialized(count); + bindingsBlockList.GetElementValues(operations.AsArray()); + operations.Sort(); + Entity lastEntity = Entity.Null; + int2 nullCounts = default; + ref var currentStartCount = ref nullCounts; + for (int i = 0; i < count; i++) + { + if (operations[i].targetEntity != lastEntity) + { + startsAndCounts.Add(new int2(i, 1)); + currentStartCount = ref startsAndCounts.ElementAt(startsAndCounts.Length - 1); + lastEntity = operations[i].targetEntity; + } + else + currentStartCount.y++; + } + bindingsBlockList.Dispose(); + } + } + + [BurstCompile] + struct ProcessMeshGpuChangesJob : IJob + { + public UnsafeParallelBlockList meshAddOpsBlockList; + public UnsafeParallelBlockList meshRemoveOpsBlockList; + public MeshGpuManager meshManager; + public BoneOffsetsGpuManager boneManager; + + public NativeList outputWriteOps; + + public unsafe void Execute() + { + int removeCount = meshRemoveOpsBlockList.Count(); + var removeOps = new NativeArray(removeCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + meshRemoveOpsBlockList.GetElementValues(removeOps); + meshRemoveOpsBlockList.Dispose(); + removeOps.Sort(); + + MeshGpuRequiredSizes* requiredGpuSizes = (MeshGpuRequiredSizes*)meshManager.requiredBufferSizes.GetUnsafePtr(); + + bool madeMeshGaps = false; + bool madeBoneOffsetGaps = false; + + for (int i = 0; i < removeCount; i++) + { + var op = removeOps[i]; + + { + ref var entry = ref meshManager.entries.ElementAt(op.oldState.meshEntryIndex); + entry.referenceCount--; + if (entry.referenceCount == 0) + { + var blob = entry.blob; + int vertices = blob.Value.verticesToSkin.Length; + int weights = blob.Value.boneWeights.Length; + int bindPoses = blob.Value.bindPoses.Length; + meshManager.verticesGaps.Add(new int2(entry.verticesStart, vertices)); + meshManager.weightsGaps.Add(new int2(entry.weightsStart, weights)); + meshManager.bindPosesGaps.Add(new int2(entry.bindPosesStart, bindPoses)); + meshManager.indexFreeList.Add(op.oldState.meshEntryIndex); + entry = default; + + madeMeshGaps = true; + + meshManager.blobIndexMap.Remove(blob); + + if (!meshManager.uploadCommands.IsEmpty) + { + // This only happens if this system was invoked multiple times between renders and a mesh was added and removed in that time. + for (int j = 0; j < meshManager.uploadCommands.Length; j++) + { + if (meshManager.uploadCommands[j].blob == blob) + { + requiredGpuSizes->requiredVertexUploadSize -= vertices; + requiredGpuSizes->requiredWeightUploadSize -= weights; + requiredGpuSizes->requiredBindPoseUploadSize -= bindPoses; + + meshManager.uploadCommands.RemoveAtSwapBack(j); + j--; + } + } + } + } + } + + { + ref var entry = ref boneManager.entries.ElementAt(op.oldState.boneOffsetEntryIndex); + if (op.oldState.meshBindingBlob != BlobAssetReference.Null) + { + entry.pathsReferences--; + if (entry.pathsReferences == 0) + { + boneManager.pathPairToEntryMap.Remove(new PathMappingPair { + meshPaths = op.oldState.meshBindingBlob, skeletonPaths = op.oldState.skeletonBindingBlob + }); + } + } + else + entry.overridesReferences--; + + if (entry.pathsReferences == 0 && entry.overridesReferences == 0) + { + boneManager.gaps.Add(new int2(entry.start, entry.gpuCount)); + boneManager.hashToEntryMap.Remove(entry.hash); + boneManager.indexFreeList.Add(op.oldState.boneOffsetEntryIndex); + + entry = default; + madeBoneOffsetGaps = true; + } + } + } + + // coellesce gaps + if (madeMeshGaps) + { + requiredGpuSizes->requiredVertexBufferSize = CoellesceGaps(meshManager.verticesGaps, requiredGpuSizes->requiredVertexBufferSize); + requiredGpuSizes->requiredWeightBufferSize = CoellesceGaps(meshManager.weightsGaps, requiredGpuSizes->requiredWeightBufferSize); + requiredGpuSizes->requiredBindPoseBufferSize = CoellesceGaps(meshManager.bindPosesGaps, requiredGpuSizes->requiredBindPoseBufferSize); + } + if (madeBoneOffsetGaps) + { + boneManager.offsets.Length = CoellesceGaps(boneManager.gaps, boneManager.offsets.Length); + } + + int addCount = meshAddOpsBlockList.Count(); + var addOps = new NativeArray(addCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + meshAddOpsBlockList.GetElementValues(addOps); + meshAddOpsBlockList.Dispose(); + addOps.Sort(); + outputWriteOps.Capacity = addOps.Length; + NativeList newOffsetsCache = new NativeList(Allocator.Temp); + for (int i = 0; i < addCount; i++) + { + var op = addOps[i]; + var blob = op.skinningBlob; + + SkeletonDependent resultState = default; + + // Binding analysis + { + if (op.overrideBoneBindings.IsCreated) + { + var hash = xxHash3.Hash64(op.overrideBoneBindings.Ptr, op.overrideBoneBindings.Length * sizeof(short)); + if (boneManager.hashToEntryMap.TryGetValue(hash, out var entryIndex)) + { + ref var entry = ref boneManager.entries.ElementAt(entryIndex); + entry.overridesReferences++; + } + else + { + var boneOffsetsEntry = new BoneOffsetsEntry + { + count = (short)op.overrideBoneBindings.Length, + isValid = true, + hash = hash, + overridesReferences = 1, + pathsReferences = 0, + }; + + // Assume we only have 32bit indexing + if ((op.overrideBoneBindings.Length & 0x1) == 1) + boneOffsetsEntry.gpuCount = (short)(op.overrideBoneBindings.Length + 1); + else + boneOffsetsEntry.gpuCount = (short)op.overrideBoneBindings.Length; + + if (AllocateInGap(boneManager.gaps, boneOffsetsEntry.gpuCount, out int offsetWriteIndex)) + { + boneOffsetsEntry.start = offsetWriteIndex; + for (int j = 0; j < boneOffsetsEntry.count; j++) + boneManager.offsets[j + offsetWriteIndex] = op.overrideBoneBindings[j]; + + if (boneOffsetsEntry.count != boneOffsetsEntry.gpuCount) + boneManager.offsets[offsetWriteIndex + boneOffsetsEntry.count] = 0; + } + else + { + boneOffsetsEntry.start = boneManager.offsets.Length; + boneManager.offsets.AddRange(op.overrideBoneBindings.Ptr, boneOffsetsEntry.count); + if (boneOffsetsEntry.count != boneOffsetsEntry.gpuCount) + boneManager.offsets.Add(0); + } + + if (boneManager.indexFreeList.IsEmpty) + { + entryIndex = boneManager.entries.Length; + boneManager.entries.Add(boneOffsetsEntry); + } + else + { + entryIndex = boneManager.indexFreeList[0]; + boneManager.indexFreeList.RemoveAtSwapBack(0); + } + + boneManager.hashToEntryMap.Add(hash, entryIndex); + boneManager.isDirty.Value = true; + } + + op.overrideBoneBindings.Dispose(); + resultState.skeletonBindingBlob = default; + resultState.meshBindingBlob = default; + resultState.boneOffsetEntryIndex = entryIndex; + } + else + { + var pathPair = new PathMappingPair { meshPaths = op.meshBindingPathsBlob, skeletonPaths = op.skeletonBindingPathsBlob }; + if (boneManager.pathPairToEntryMap.TryGetValue(pathPair, out int entryIndex)) + { + ref var entry = ref boneManager.entries.ElementAt(entryIndex); + entry.pathsReferences++; + } + else + { + if (!BindingUtilities.TrySolveBindings(op.meshBindingPathsBlob, op.skeletonBindingPathsBlob, newOffsetsCache, out int failedMeshIndex)) + { + FixedString4096Bytes failedPath = default; + failedPath.Append((byte*)op.meshBindingPathsBlob.Value.pathsInReversedNotation[failedMeshIndex].GetUnsafePtr(), + op.meshBindingPathsBlob.Value.pathsInReversedNotation[failedMeshIndex].Length); + UnityEngine.Debug.LogError( + $"Cannot bind entity {op.meshEntity} to {op.root.entity}. No match for index {failedMeshIndex} requesting path: {failedPath}"); + + outputWriteOps.Add(new MeshWriteStateOperation { meshEntity = op.meshEntity, state = default }); + continue; + } + + var hash = xxHash3.Hash64(newOffsetsCache.GetUnsafeReadOnlyPtr(), newOffsetsCache.Length * sizeof(short)); + if (boneManager.hashToEntryMap.TryGetValue(hash, out entryIndex)) + { + ref var entry = ref boneManager.entries.ElementAt(entryIndex); + entry.pathsReferences++; + } + else + { + var boneOffsetsEntry = new BoneOffsetsEntry + { + count = (short)newOffsetsCache.Length, + isValid = true, + hash = hash, + overridesReferences = 0, + pathsReferences = 1, + }; + + // Assume we only have 32bit indexing + if ((newOffsetsCache.Length & 0x1) == 1) + boneOffsetsEntry.gpuCount = (short)(newOffsetsCache.Length + 1); + else + boneOffsetsEntry.gpuCount = (short)newOffsetsCache.Length; + + if (AllocateInGap(boneManager.gaps, boneOffsetsEntry.gpuCount, out int offsetWriteIndex)) + { + boneOffsetsEntry.start = offsetWriteIndex; + for (int j = 0; j < boneOffsetsEntry.count; j++) + boneManager.offsets[j + offsetWriteIndex] = newOffsetsCache[j]; + + if (boneOffsetsEntry.count != boneOffsetsEntry.gpuCount) + boneManager.offsets[offsetWriteIndex + boneOffsetsEntry.count] = 0; + } + else + { + boneOffsetsEntry.start = boneManager.offsets.Length; + boneManager.offsets.AddRange(newOffsetsCache); + if (boneOffsetsEntry.count != boneOffsetsEntry.gpuCount) + boneManager.offsets.Add(0); + } + + if (boneManager.indexFreeList.IsEmpty) + { + entryIndex = boneManager.entries.Length; + boneManager.entries.Add(boneOffsetsEntry); + } + else + { + entryIndex = boneManager.indexFreeList[0]; + boneManager.indexFreeList.RemoveAtSwapBack(0); + } + + boneManager.hashToEntryMap.Add(hash, entryIndex); + boneManager.isDirty.Value = true; + } + + boneManager.pathPairToEntryMap.Add(new PathMappingPair { meshPaths = op.meshBindingPathsBlob, skeletonPaths = op.skeletonBindingPathsBlob }, + entryIndex); + resultState.skeletonBindingBlob = op.skeletonBindingPathsBlob; + resultState.meshBindingBlob = op.meshBindingPathsBlob; + } + resultState.boneOffsetEntryIndex = entryIndex; + } + } + + if (meshManager.blobIndexMap.TryGetValue(blob, out int meshIndex)) + { + meshManager.entries.ElementAt(meshIndex).referenceCount++; + } + else + { + if (!meshManager.indexFreeList.IsEmpty) + { + meshIndex = meshManager.indexFreeList[0]; + meshManager.indexFreeList.RemoveAtSwapBack(0); + } + else + { + meshIndex = meshManager.entries.Length; + meshManager.entries.Add(default); + } + + int verticesNeeded = blob.Value.verticesToSkin.Length; + int verticesGpuStart; + if (AllocateInGap(meshManager.verticesGaps, verticesNeeded, out int verticesWriteIndex)) + { + verticesGpuStart = verticesWriteIndex; + } + else + { + verticesGpuStart = requiredGpuSizes->requiredVertexBufferSize; + requiredGpuSizes->requiredVertexBufferSize += verticesNeeded; + } + + int weightsNeeded = blob.Value.boneWeights.Length; + int weightsGpuStart; + if (AllocateInGap(meshManager.weightsGaps, verticesNeeded, out int weightsWriteIndex)) + { + weightsGpuStart = weightsWriteIndex; + } + else + { + weightsGpuStart = requiredGpuSizes->requiredWeightBufferSize; + requiredGpuSizes->requiredWeightBufferSize += weightsNeeded; + } + + int bindPosesNeeded = blob.Value.bindPoses.Length; + int bindPosesGpuStart; + if (AllocateInGap(meshManager.bindPosesGaps, verticesNeeded, out int bindPosesWriteIndex)) + { + bindPosesGpuStart = bindPosesWriteIndex; + } + else + { + bindPosesGpuStart = requiredGpuSizes->requiredBindPoseBufferSize; + requiredGpuSizes->requiredBindPoseBufferSize += bindPosesNeeded; + } + + meshManager.blobIndexMap.Add(blob, meshIndex); + ref var meshEntry = ref meshManager.entries.ElementAt(meshIndex); + meshEntry.referenceCount++; + meshEntry.verticesStart = verticesGpuStart; + meshEntry.weightsStart = weightsGpuStart; + meshEntry.bindPosesStart = bindPosesGpuStart; + meshEntry.blob = blob; + + requiredGpuSizes->requiredVertexUploadSize += blob.Value.verticesToSkin.Length; + requiredGpuSizes->requiredWeightUploadSize += blob.Value.boneWeights.Length; + requiredGpuSizes->requiredBindPoseUploadSize += blob.Value.bindPoses.Length; + //meshManager.requiredVertexWeightsbufferSizesAndUploadSizes.Value += new int4(0, 0, blob.Value.verticesToSkin.Length, blob.Value.boneWeights.Length); + meshManager.uploadCommands.Add(new MeshGpuUploadCommand + { + blob = blob, + verticesIndex = verticesGpuStart, + weightsIndex = weightsGpuStart, + bindPosesIndex = bindPosesGpuStart + }); + } + resultState.meshEntryIndex = meshIndex; + resultState.skinningBlob = blob; + resultState.root = op.root; + resultState.shaderEffectRadialBounds = op.shaderEffectRadialBounds; + outputWriteOps.Add(new MeshWriteStateOperation { meshEntity = op.meshEntity, state = resultState }); + } + } + + int CoellesceGaps(NativeList gaps, int oldSize) + { + gaps.Sort(new GapSorter()); + int dst = 1; + var array = gaps.AsArray(); + for (int j = 1; j < array.Length; j++) + { + array[dst] = array[j]; + var prev = array[dst - 1]; + if (prev.x + prev.y == array[j].x) + { + prev.y += array[j].x; + array[dst - 1] = prev; + } + else + dst++; + } + gaps.Length = dst; + + if (!gaps.IsEmpty) + { + var backItem = gaps[gaps.Length - 1]; + if (backItem.x + backItem.y == oldSize) + { + boneManager.gaps.Length--; + return backItem.x; + } + } + + return oldSize; + } + + bool AllocateInGap(NativeList gaps, int countNeeded, out int foundIndex) + { + int bestIndex = -1; + int bestCount = int.MaxValue; + + for (int i = 0; i < gaps.Length; i++) + { + if (gaps[i].y >= countNeeded && gaps[i].y < bestCount) + { + bestIndex = i; + bestCount = gaps[i].y; + } + } + + if (bestIndex < 0) + { + foundIndex = -1; + return false; + } + + if (bestCount == countNeeded) + { + foundIndex = gaps[bestIndex].x; + gaps.RemoveAtSwapBack(bestIndex); + return true; + } + + foundIndex = gaps[bestIndex].x; + gaps[bestIndex] += new int2(countNeeded, -countNeeded); + return true; + } + + struct GapSorter : IComparer + { + public int Compare(int2 a, int2 b) + { + return a.x.CompareTo(b.x); + } + } + } + #endregion + + #region Post Sync Jobs + [BurstCompile] + struct ProcessMeshStateOpsJob : IJobParallelForDefer + { + [NativeDisableParallelForRestriction] public ComponentDataFromEntity stateCdfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity parentCdfe; + + [ReadOnly] public NativeArray ops; + public Entity failedBindingEntity; + + public void Execute(int index) + { + var op = ops[index]; + op.meshEntity[stateCdfe] = op.state; + if (op.state.root == Entity.Null) + parentCdfe[op.meshEntity] = new Parent { Value = failedBindingEntity }; + else + parentCdfe[op.meshEntity] = new Parent { Value = op.state.root }; + } + } + + [BurstCompile] + struct ProcessBindingOpsJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray operations; + [ReadOnly] public NativeArray startsAndCounts; + [ReadOnly] public ComponentDataFromEntity meshStateCdfe; + [ReadOnly] public BufferFromEntity boneToRootsBfe; + [ReadOnly] public BufferFromEntity boneRefsBfe; + [ReadOnly] public MeshGpuManager meshGpuManager; + [ReadOnly] public BoneOffsetsGpuManager boneOffsetsGpuManager; + + [NativeDisableParallelForRestriction] public BufferFromEntity dependentsBfe; + [NativeDisableParallelForRestriction] public BufferFromEntity optimizedBoundsBfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity boneBoundsCdfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity optimizedShaderBoundsCdfe; + + [NativeDisableContainerSafetyRestriction, NoAlias] NativeList boundsCache; + + public void Execute(int index) + { + int2 startAndCount = startsAndCounts[index]; + var opsArray = operations.GetSubArray(startAndCount.x, startAndCount.y); + + Entity skeletonEntity = opsArray[0].targetEntity; + var depsBuffer = dependentsBfe[skeletonEntity]; + bool needsFullBoundsUpdate = false; + bool needsAddBoundsUpdate = false; + + // Todo: This might be really slow + int i = 0; + for (; i < opsArray.Length && opsArray[i].opType == BindUnbindOperation.OpType.Unbind; i++) + { + for (int j = 0; j < depsBuffer.Length; j++) + { + if (depsBuffer[j].skinnedMesh == opsArray[i].meshEntity) + { + depsBuffer.RemoveAtSwapBack(j); + needsFullBoundsUpdate = true; + break; + } + } + } + + int addStart = i; + for (; i < opsArray.Length && opsArray[i].opType == BindUnbindOperation.OpType.Bind; i++) + { + var meshState = meshStateCdfe[opsArray[i].meshEntity]; + var meshEntry = meshGpuManager.entries[meshState.meshEntryIndex]; + var boneOffsetsEntry = boneOffsetsGpuManager.entries[meshState.boneOffsetEntryIndex]; + depsBuffer.Add(new DependentSkinnedMesh + { + skinnedMesh = opsArray[i].meshEntity, + meshVerticesStart = meshEntry.verticesStart, + meshVerticesCount = meshEntry.blob.Value.verticesToSkin.Length, + meshWeightsStart = meshEntry.weightsStart, + meshBindPosesStart = meshEntry.bindPosesStart, + meshBindPosesCount = meshEntry.blob.Value.bindPoses.Length, + boneOffsetsStart = boneOffsetsEntry.start, + }); + needsAddBoundsUpdate = true; + } + + int changeStart = i; + if (needsFullBoundsUpdate) + { + ApplyMeshBounds(skeletonEntity, opsArray, 0, opsArray.Length, true); + } + else if (needsAddBoundsUpdate) + { + ApplyMeshBounds(skeletonEntity, opsArray, addStart, opsArray.Length - addStart, false); + } + else if (needsAddBoundsUpdate) + { + ApplyMeshBounds(skeletonEntity, opsArray, addStart, changeStart - addStart, false); + } + } + + void ApplyMeshBounds(Entity skeletonEntity, NativeArray ops, int start, int count, bool reset) + { + if (boneToRootsBfe.HasComponent(skeletonEntity)) + { + // Optimized skeleton path + bool needsCollapse = reset; + var boundsBuffer = optimizedBoundsBfe[skeletonEntity]; + if (boundsBuffer.IsEmpty) + { + needsCollapse = true; + boundsBuffer.ResizeUninitialized(boneToRootsBfe[skeletonEntity].Length); + } + var boundsArray = boundsBuffer.Reinterpret().AsNativeArray(); + if (needsCollapse) + { + var arr = boundsBuffer.Reinterpret().AsNativeArray(); + for (int i = 0; i < arr.Length; i++) + arr[i] = 0f; + } + + float shaderBounds = 0f; + for (int i = start; i < start + count; i++) + { + var meshState = meshStateCdfe[ops[i].meshEntity]; + var boneOffsetsEntry = boneOffsetsGpuManager.entries[meshState.boneOffsetEntryIndex]; + var boneOffsets = boneOffsetsGpuManager.offsets.AsArray().GetSubArray(boneOffsetsEntry.start, boneOffsetsEntry.count); + + needsCollapse = false; + ref var blobBounds = ref meshState.skinningBlob.Value.maxRadialOffsetsInBoneSpaceByBone; + short k = 0; + foreach (var j in boneOffsets) + { + if (j >= boundsArray.Length) + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {ops[i].meshEntity} specifies a boneSkinningIndex of {j} but OptimizedBoneToRoot buffer on Entity {skeletonEntity} only has {boundsArray.Length} elements."); + else + boundsArray[j] = math.max(boundsArray[j], blobBounds[k]); + k++; + } + shaderBounds = math.max(shaderBounds, meshState.shaderEffectRadialBounds); + } + if (shaderBounds > 0f) + { + optimizedShaderBoundsCdfe[skeletonEntity] = new SkeletonShaderBoundsOffset + { + radialBoundsInWorldSpace = math.max(shaderBounds, optimizedShaderBoundsCdfe[skeletonEntity].radialBoundsInWorldSpace) + }; + } + + if (needsCollapse) + { + // Nothing valid is bound anymore. Shrink the buffer. + boundsBuffer.Clear(); + } + } + else + { + // Exposed skeleton path + if (!boundsCache.IsCreated) + { + boundsCache = new NativeList(Allocator.Temp); + } + var boneRefs = boneRefsBfe[skeletonEntity].Reinterpret().AsNativeArray(); + boundsCache.Clear(); + boundsCache.Resize(boneRefs.Length, NativeArrayOptions.ClearMemory); + var boundsArray = boundsCache.AsArray(); + + float shaderBounds = 0f; + for (int i = start; i < start + count; i++) + { + var meshState = meshStateCdfe[ops[i].meshEntity]; + var boneOffsetsEntry = boneOffsetsGpuManager.entries[meshState.boneOffsetEntryIndex]; + var boneOffsets = boneOffsetsGpuManager.offsets.AsArray().GetSubArray(boneOffsetsEntry.start, boneOffsetsEntry.count); + + ref var blobBounds = ref meshState.skinningBlob.Value.maxRadialOffsetsInBoneSpaceByBone; + short k = 0; + foreach (var j in boneOffsets) + { + if (j >= boundsArray.Length) + UnityEngine.Debug.LogError( + $"Skinned Mesh Entity {ops[i].meshEntity} specifies a boneSkinningIndex of {j} but BoneReference buffer on Entity {skeletonEntity} only has {boundsArray.Length} elements."); + else + boundsArray[j] = math.max(boundsArray[j], blobBounds[k]); + k++; + } + shaderBounds = math.max(shaderBounds, meshState.shaderEffectRadialBounds); + } + + if (reset) + { + // Overwrite the bounds + for (int i = 0; i < boundsArray.Length; i++) + { + boneBoundsCdfe[boneRefs[i]] = new BoneBounds { radialOffsetInBoneSpace = boundsArray[i], radialOffsetInWorldSpace = shaderBounds }; + } + } + else + { + // Merge with new values + for (int i = 0; i < boundsArray.Length; i++) + { + var storedBounds = boneBoundsCdfe[boneRefs[i]]; + boneBoundsCdfe[boneRefs[i]] = new BoneBounds + { + radialOffsetInBoneSpace = math.max(boundsArray[i], storedBounds.radialOffsetInBoneSpace), + radialOffsetInWorldSpace = math.max(shaderBounds, storedBounds.radialOffsetInWorldSpace) + }; + } + } + } + } + } + + // Schedule single + [BurstCompile] + struct FindExposedSkeletonsToUpdateJob : IJobEntityBatch + { + public NativeList operations; + public ExposedCullingIndexManager manager; + public NativeReference indicesToClear; + + [ReadOnly] public EntityTypeHandle entityHandle; + public ComponentTypeHandle dirtyFlagHandle; + + public uint lastSystemVersion; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var entities = batchInChunk.GetNativeArray(entityHandle); + var dirtyFlags = batchInChunk.GetNativeArray(dirtyFlagHandle); + + for (int i = 0; i < batchInChunk.Count; i++) + { + if (dirtyFlags[i].isDirty) + { + var index = manager.skeletonToCullingIndexMap[entities[i]]; + if (index < indicesToClear.Value.Length) + indicesToClear.Value.Set(index, true); + operations.Add(new ExposedSkeletonCullingIndexOperation { index = index, skeletonEntity = entities[i] }); + dirtyFlags[i] = new BoneReferenceIsDirtyFlag { isDirty = false }; + } + } + } + } + + [BurstCompile] + struct ResetExposedBonesJob : IJobEntityBatch + { + [ReadOnly] public NativeReference indicesToClear; + public ComponentTypeHandle indexHandle; + + public void Execute(ArchetypeChunk batchInChunk, int batchIndex) + { + var indices = batchInChunk.GetNativeArray(indexHandle).Reinterpret(); + for (int i = 0; i < batchInChunk.Count; i++) + { + bool needsClearing = indices[i] < indicesToClear.Value.Length ? indicesToClear.Value.IsSet(indices[i]) : false; + indices[i] = math.select(indices[i], 0, needsClearing); + } + } + } + + [BurstCompile] + struct SetExposedSkeletonCullingIndicesJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray operations; + [ReadOnly] public BufferFromEntity boneRefsBfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity skeletonCullingIndexCdfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity boneCullingIndexCdfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity boneIndexCdfe; + [NativeDisableParallelForRestriction] public ComponentDataFromEntity skeletonReferenceCdfe; + + public void Execute(int index) + { + var op = operations[index]; + var bones = boneRefsBfe[op.skeletonEntity].AsNativeArray(); + for (short i = 0; i < bones.Length; i++) + { + if (boneCullingIndexCdfe.HasComponent(bones[i].bone)) + boneCullingIndexCdfe[bones[i].bone] = new BoneCullingIndex { cullingIndex = op.index }; + if (boneIndexCdfe.HasComponent(bones[i].bone)) + boneIndexCdfe[bones[i].bone] = new BoneIndex { index = i }; + if (skeletonReferenceCdfe.HasComponent(bones[i].bone)) + skeletonReferenceCdfe[bones[i].bone] = new BoneOwningSkeletonReference { skeletonRoot = op.skeletonEntity }; + } + skeletonCullingIndexCdfe[op.skeletonEntity] = new ExposedSkeletonCullingIndex { cullingIndex = op.index }; + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/SkeletonMeshBindingReactiveSystem.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/SkeletonMeshBindingReactiveSystem.cs.meta new file mode 100644 index 0000000..7d61705 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Systems/SyncPoint/SkeletonMeshBindingReactiveSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b9cbbf9a4f86444abbf2f2c3dd25aa5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities.meta b/Packages/com.latios.latios-framework/Kinemation/Utilities.meta new file mode 100644 index 0000000..7862f87 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d0f0e1e4d2c286242a219c48e539ea09 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/BindingUtilities.cs b/Packages/com.latios.latios-framework/Kinemation/Utilities/BindingUtilities.cs new file mode 100644 index 0000000..a6fad93 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/BindingUtilities.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Rendering; + +namespace Latios.Kinemation +{ + public static class BindingUtilities + { + //public struct PathBindingWorkspace : INativeDisposable + //{ + // internal NativeList indices; + // + // public PathBindingWorkspace(Allocator allocator) => indices = new NativeList(allocator); + // public void Dispose() => indices.Dispose(); + // public JobHandle Dispose(JobHandle inputDeps) => indices.Dispose(inputDeps); + //} + + public static unsafe bool TrySolveBindings(BlobAssetReference meshPaths, + BlobAssetReference skeletonPaths, + NativeList outSolvedBindings, + out int failedMeshIndex) + { + // Todo: Use sort or some replacement so that we can use something better than O(n^2) + outSolvedBindings.Clear(); + for (int i = 0; i < meshPaths.Value.pathsInReversedNotation.Length; i++) + { + bool found = false; + for (short j = 0; j < skeletonPaths.Value.pathsInReversedNotation.Length; j++) + { + var meshPtr = meshPaths.Value.pathsInReversedNotation[i].GetUnsafePtr(); + var skeletonPtr = skeletonPaths.Value.pathsInReversedNotation[j].GetUnsafePtr(); + var length = meshPaths.Value.pathsInReversedNotation[i].Length; + + if (length > skeletonPaths.Value.pathsInReversedNotation[j].Length) + continue; + + if (UnsafeUtility.MemCmp(meshPtr, skeletonPtr, length) == 0) + { + outSolvedBindings.Add(j); + found = true; + break; + } + } + if (!found) + { + failedMeshIndex = outSolvedBindings.Length; + return false; + } + } + failedMeshIndex = -1; + return true; + } + + //unsafe struct PathComparer : IComparer + //{ + // public BlobAssetReference meshPaths; + // public BlobAssetReference skeletonPaths; + // + // public int Compare(int x, int y) + // { + // throw new NotImplementedException(); + // } + //} + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/BindingUtilities.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Utilities/BindingUtilities.cs.meta new file mode 100644 index 0000000..a8e1d8a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/BindingUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f2847237d3f5b04faed4bf67f4ac565 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/Blenders.cs b/Packages/com.latios.latios-framework/Kinemation/Utilities/Blenders.cs new file mode 100644 index 0000000..0633d0c --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/Blenders.cs @@ -0,0 +1,282 @@ +using System; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Kinemation +{ + /// + /// A struct used to blend a full OptimizedBoneToRoot buffer. Rotations use nlerp blending. + /// + /// + /// A BufferPoseBlender reinterprets an OptimizedBoneToRoot as temporary storage to sample + /// and accumulate local space BoneTransforms. The first pose sampled for a given instance + /// will overwrite the storage. Additional samples will perform additive blending instead. + /// + /// To discard existing sampled poses and begin sampling new poses, simply create a new + /// instance of BufferPoseBlender using the same OptimizedBoneToRoot buffer. + /// + /// To finish sampling, call NormalizeRotations(). You can then get a view of the BoneTransforms + /// using GetLocalTransformsView(). + /// + /// If you leave the buffer in this state, a new BufferPoseBlender instance can recover this + /// view by immediately calling GetLocalTransformsView(). This allows you to separate sampling + /// and IK into separate jobs. + /// + /// When you are done performing any sampling or BoneTransform manipulation, call + /// ApplyBoneHierarchyAndFinish(). + /// + public struct BufferPoseBlender + { + internal NativeArray bufferAs4x4; + internal NativeArray bufferAsQvv; + internal bool sampledFirst; + + /// + /// Creates a blender for blending sampled poses into the buffer. The buffer's matrix values are invalidated. + /// + /// The buffer used for blending operations. Its memory is temporarily repurposed for accumulating poses. No resizing is performed. + public BufferPoseBlender(DynamicBuffer boneToRootBuffer) + { + bufferAs4x4 = boneToRootBuffer.Reinterpret().AsNativeArray(); + var boneCount = bufferAs4x4.Length; + var bufferAsFloat4 = bufferAs4x4.Reinterpret(64); + bufferAsQvv = bufferAsFloat4.GetSubArray(bufferAsFloat4.Length - 3 * boneCount, 3 * boneCount).Reinterpret(16); + sampledFirst = false; + } + + /// + /// Gets a view of the buffer as local transforms. + /// The contents in this view are not valid until the first pose has been sampled. + /// This is effectively the buffer used to accumulate blended poses, so data may not be normalized. + /// + /// + public NativeArray GetLocalTransformsView() + { + return bufferAsQvv.Reinterpret(); + } + + /// + /// Normalizes the rotations to be valid quaternion rotations. Call this once after sampling all blended poses. + /// The result of GetLocalTransformsView() will contain valid quaternion rotations after calling this method. + /// You can perform IK operations after calling this method but before calling ApplyBoneHierarchyAndFinish(). + /// + public unsafe void NormalizeRotations() + { + var bufferPtr = (BoneTransform*)bufferAsQvv.GetUnsafePtr(); + for (int i = 0; i < bufferAsQvv.Length; i++, bufferPtr++) + { + bufferPtr->rotation = math.normalize(bufferPtr->rotation); + } + } + + /// + /// Computes a BoneToRoot matrix for a local space BoneTransform. The result can be multiplied with the skeleton's + /// LocalToWorld to obtain a LocalToWorld for the bone. This method does not modify state. + /// + /// Warning: This method is only valid after calling NormalizeRotations(). + /// + /// The bone index to compute a BoneToRoot matrix for + /// The skeleton hierarchy used to compute the bone's ancestors + /// A skeleton-space matrix representing the transform of boneIndex + public float4x4 ComputeBoneToRoot(int boneIndex, BlobAssetReference hierarchy) + { + if (!hierarchy.Value.hasAnyParentScaleInverseBone) + { + // Fast path. + var parentIndex = hierarchy.Value.parentIndices[boneIndex]; + var transform = new BoneTransform(bufferAsQvv[boneIndex]); + var matrix = float4x4.TRS(transform.translation, transform.rotation, transform.scale); + while (parentIndex >= 0) + { + transform = new BoneTransform(bufferAsQvv[parentIndex]); + matrix = math.mul(float4x4.TRS(transform.translation, transform.rotation, transform.scale), matrix); + + Hint.Assume(hierarchy.Value.parentIndices[parentIndex] < parentIndex); + parentIndex = hierarchy.Value.parentIndices[parentIndex]; + } + + return matrix; + } + else + { + var currentIndex = boneIndex; + var parentIndex = hierarchy.Value.parentIndices[boneIndex]; + var matrix = float4x4.identity; + + while (currentIndex >= 0) + { + Hint.Assume(parentIndex < currentIndex); + + var transform = new BoneTransform(bufferAsQvv[currentIndex]); + + if (hierarchy.Value.hasParentScaleInverseBitmask[currentIndex / 64].IsSet(currentIndex % 64)) + { + var mat = math.mul(float4x4.Translate(transform.translation), float4x4.Scale(math.rcp(bufferAsQvv[parentIndex].scale.xyz))); + mat = math.mul(mat, new float4x4(transform.rotation, 0f)); + mat = math.mul(mat, float4x4.Scale(transform.scale)); + + matrix = math.mul(mat, matrix); + } + else + { + matrix = math.mul(float4x4.TRS(transform.translation, transform.rotation, transform.scale), matrix); + } + + parentIndex = hierarchy.Value.parentIndices[parentIndex]; + } + + return matrix; + } + } + + /// + /// Computes a BoneToRoot matrix for a local space BoneTransform. The result can be multiplied with the skeleton's + /// LocalToWorld to obtain a LocalToWorld for the bone. This method does not modify state. + /// + /// This variant uses an already known boneToRoot of an ancestor to avoid repeating calculations. + /// + /// Warning: This method is only valid after calling NormalizeRotations(). + /// + /// The bone index to compute a BoneToRoot matrix for + /// The skeleton hierarchy used to compute the bone's ancestors + /// The bone index of an ancestor whose boneToRoot is already known + /// The boneToRoot of the ancestor which is already known. + /// A skeleton-space matrix representing the transform of boneIndex + public float4x4 ComputeBoneToRoot(int boneIndex, + BlobAssetReference hierarchy, + int cachedAncestorBoneIndex, + in float4x4 cachedAncestorBoneToRoot) + { + if (!hierarchy.Value.hasAnyParentScaleInverseBone) + { + // Fast path. + var parentIndex = hierarchy.Value.parentIndices[boneIndex]; + var transform = new BoneTransform(bufferAsQvv[boneIndex]); + var matrix = float4x4.TRS(transform.translation, transform.rotation, transform.scale); + while (parentIndex > cachedAncestorBoneIndex) + { + transform = new BoneTransform(bufferAsQvv[parentIndex]); + matrix = math.mul(float4x4.TRS(transform.translation, transform.rotation, transform.scale), matrix); + + Hint.Assume(hierarchy.Value.parentIndices[parentIndex] < parentIndex); + parentIndex = hierarchy.Value.parentIndices[parentIndex]; + } + + return math.mul(cachedAncestorBoneToRoot, matrix); + } + else + { + var currentIndex = boneIndex; + var parentIndex = hierarchy.Value.parentIndices[boneIndex]; + var matrix = float4x4.identity; + + while (currentIndex > cachedAncestorBoneIndex) + { + Hint.Assume(parentIndex < currentIndex); + + var transform = new BoneTransform(bufferAsQvv[currentIndex]); + + if (hierarchy.Value.hasParentScaleInverseBitmask[currentIndex / 64].IsSet(currentIndex % 64)) + { + var mat = math.mul(float4x4.Translate(transform.translation), float4x4.Scale(math.rcp(bufferAsQvv[parentIndex].scale.xyz))); + mat = math.mul(mat, new float4x4(transform.rotation, 0f)); + mat = math.mul(mat, float4x4.Scale(transform.scale)); + + matrix = math.mul(mat, matrix); + } + else + { + matrix = math.mul(float4x4.TRS(transform.translation, transform.rotation, transform.scale), matrix); + } + + parentIndex = hierarchy.Value.parentIndices[parentIndex]; + } + + return math.mul(cachedAncestorBoneToRoot, matrix); + } + } + + /// + /// Applies the hierarchy to the bone transforms and restores the validity of the dynamic buffer. + /// Call this once after calling NormalizeRotations(). + /// The result of GetLocalTransformsView() is invalidated after calling this method. + /// + /// The hierarchy to apply. If the hierarchy is shorter than the buffer length, only the subrange will be restored. + public void ApplyBoneHierarchyAndFinish(BlobAssetReference hierarchy) + { + int boneCount = math.min(bufferAs4x4.Length, hierarchy.Value.parentIndices.Length); + + if (!hierarchy.Value.hasAnyParentScaleInverseBone) + { + // Fast path. + bufferAs4x4[0] = float4x4.identity; + + for (int i = 1; i < boneCount; i++) + { + var qvv = bufferAsQvv[i]; + var mat = float4x4.TRS(qvv.translation.xyz, qvv.rotation, qvv.scale.xyz); + Hint.Assume(hierarchy.Value.parentIndices[i] < i); + mat = math.mul(bufferAs4x4[hierarchy.Value.parentIndices[i]], mat); + bufferAs4x4[i] = mat; + } + } + else + { + // Slower path because we pack inverse scale into the fourth row of each matrix. + // We need to explicitly check for parentScaleInverse for index 0. + if (hierarchy.Value.hasChildWithParentScaleInverseBitmask[0].IsSet(0)) + { + var inverseScale = math.rcp(bufferAsQvv[0].scale); + var mat = float4x4.identity; + mat.c0.w = inverseScale.x; + mat.c1.w = inverseScale.y; + mat.c2.w = inverseScale.z; + bufferAs4x4[0] = mat; + } + + for (int i = 1; i < boneCount; i++) + { + var qvv = bufferAsQvv[i]; + Hint.Assume(hierarchy.Value.parentIndices[i] < i); + + var parentMat = bufferAs4x4[hierarchy.Value.parentIndices[i]]; + bool hasParentScaleInverse = hierarchy.Value.hasParentScaleInverseBitmask[i / 64].IsSet(i % 64); + var psi = float4x4.Scale(math.select(1f, new float3(parentMat.c0.w, parentMat.c1.w, parentMat.c2.w), hasParentScaleInverse)); + parentMat.c0.w = 0f; + parentMat.c1.w = 0f; + parentMat.c2.w = 0f; + var mat = math.mul(float4x4.Translate(qvv.translation.xyz), psi); + mat = math.mul(mat, new float4x4(qvv.rotation, 0f)); + mat = math.mul(mat, float4x4.Scale(qvv.scale.xyz)); + mat = math.mul(parentMat, mat); + + bool needsInverseScale = hierarchy.Value.hasChildWithParentScaleInverseBitmask[i / 64].IsSet(i % 64); + var inverseScale = math.select(0f, math.rcp(qvv.scale), needsInverseScale); + mat.c0.w = inverseScale.x; + mat.c1.w = inverseScale.y; + mat.c2.w = inverseScale.z; + bufferAs4x4[i] = mat; + } + + // Now we need to clean up the inverse scales. We wrote zeros where we didn't need them. + // So we can do a tzcnt walk. + for (int maskId = 0; maskId * 64 < boneCount; maskId++) + { + var mask = hierarchy.Value.hasChildWithParentScaleInverseBitmask[maskId]; + for (int i = mask.CountTrailingZeros(); i < 64 && maskId * 64 + i < boneCount; mask.SetBits(i, false), i = mask.CountTrailingZeros()) + { + var mat = bufferAs4x4[maskId * 64 + i]; + mat.c0.w = 0f; + mat.c1.w = 0f; + mat.c2.w = 0f; + bufferAs4x4[maskId * 64 + i] = mat; + } + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/Blenders.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Utilities/Blenders.cs.meta new file mode 100644 index 0000000..2cc4d32 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/Blenders.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a1223046b83ebc44aa96121c7c1b5c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/CullingUtilities.cs b/Packages/com.latios.latios-framework/Kinemation/Utilities/CullingUtilities.cs new file mode 100644 index 0000000..6860b6a --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/CullingUtilities.cs @@ -0,0 +1,68 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Rendering; + +namespace Latios.Kinemation +{ + public static class CullingUtilities + { + /// + /// Add these Components to any bone you want to participate in culling prior to KinemationRenderSyncPointSuperSystem + /// or a custom-placed SkeletonMeshBindingReactiveSystem prior to rendering. + /// + public static ComponentTypes GetBoneCullingComponentTypes() + { + var boneTypes = new FixedList128Bytes(); + boneTypes.Add(ComponentType.ReadWrite()); + boneTypes.Add(ComponentType.ReadWrite()); + boneTypes.Add(ComponentType.ReadWrite()); + boneTypes.Add(ComponentType.ReadWrite()); + boneTypes.Add(ComponentType.ReadWrite()); + boneTypes.Add(ComponentType.ChunkComponent()); + return new ComponentTypes(boneTypes); + } + + public static NativeArray BuildSOAPlanePackets(DynamicBuffer cullingPlanes, ref WorldUnmanaged world) + { + return BuildSOAPlanePackets(cullingPlanes.Reinterpret().AsNativeArray(), ref world); + } + + public static NativeArray BuildSOAPlanePackets(NativeArray cullingPlanes, ref WorldUnmanaged world) + { + int cullingPlaneCount = cullingPlanes.Length; + int packetCount = (cullingPlaneCount + 3) >> 2; + var planes = world.UpdateAllocator.AllocateNativeArray(packetCount); + + for (int i = 0; i < cullingPlaneCount; i++) + { + var p = planes[i >> 2]; + p.Xs[i & 3] = cullingPlanes[i].normal.x; + p.Ys[i & 3] = cullingPlanes[i].normal.y; + p.Zs[i & 3] = cullingPlanes[i].normal.z; + p.Distances[i & 3] = cullingPlanes[i].distance; + planes[i >> 2] = p; + } + + // Populate the remaining planes with values that are always "in" + for (int i = cullingPlaneCount; i < 4 * packetCount; ++i) + { + var p = planes[i >> 2]; + p.Xs[i & 3] = 1.0f; + p.Ys[i & 3] = 0.0f; + p.Zs[i & 3] = 0.0f; + + // This value was before hardcoded to 32786.0f. + // It was causing the culling system to discard the rendering of entities having a X coordinate approximately less than -32786. + // We could not find anything relying on this number, so the value has been increased to 1 billion + p.Distances[i & 3] = 1e9f; + + planes[i >> 2] = p; + } + + return planes; + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/CullingUtilities.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Utilities/CullingUtilities.cs.meta new file mode 100644 index 0000000..55eacdc --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/CullingUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 45432c16b724bb647bdc67041d368dbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/KinemationBootstrap.cs b/Packages/com.latios.latios-framework/Kinemation/Utilities/KinemationBootstrap.cs new file mode 100644 index 0000000..eff4d39 --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/KinemationBootstrap.cs @@ -0,0 +1,35 @@ +using Latios.Kinemation.Systems; +using Unity.Entities; +using Unity.Rendering; + +namespace Latios.Kinemation +{ + public static class KinemationBootstrap + { + public static void InstallKinemation(World world) + { + if (!UnityEngine.SystemInfo.supportsAsyncGPUReadback) + { + throw new System.InvalidOperationException("Kinemation only works on platforms which support Async GPU Readback."); + } + + var unityRenderer = world.GetExistingSystem(); + if (unityRenderer != null) + unityRenderer.Enabled = false; + var unitySkinning = world.GetExistingSystem(); + if (unitySkinning != null) + unitySkinning.Enabled = false; + var unityMatrixPrev = world.GetExistingSystem(); + if (unityMatrixPrev != null) + unityMatrixPrev.Enabled = false; + + BootstrapTools.InjectSystem(typeof(KinemationRenderUpdateSuperSystem), world); + BootstrapTools.InjectSystem(typeof(KinemationRenderSyncPointSuperSystem), world); + BootstrapTools.InjectSystem(typeof(KinemationFrameSyncPointSuperSystem), world); + BootstrapTools.InjectSystem(typeof(LatiosHybridRendererSystem), world); + BootstrapTools.InjectSystem(typeof(KinemationPostRenderSuperSystem), world); + BootstrapTools.InjectSystem(typeof(CopyTransformFromBoneSystem), world); + } + } +} + diff --git a/Packages/com.latios.latios-framework/Kinemation/Utilities/KinemationBootstrap.cs.meta b/Packages/com.latios.latios-framework/Kinemation/Utilities/KinemationBootstrap.cs.meta new file mode 100644 index 0000000..1b5c8de --- /dev/null +++ b/Packages/com.latios.latios-framework/Kinemation/Utilities/KinemationBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc41e81a4c492984dab9b76fb7701fcb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MathematicsExpansion.meta b/Packages/com.latios.latios-framework/MathematicsExpansion.meta new file mode 100644 index 0000000..bcb7339 --- /dev/null +++ b/Packages/com.latios.latios-framework/MathematicsExpansion.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 13755d105b82b9a47a35ab1d220b5768 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MathematicsExpansion/ExtraFunctions.cs b/Packages/com.latios.latios-framework/MathematicsExpansion/ExtraFunctions.cs new file mode 100644 index 0000000..bf70b44 --- /dev/null +++ b/Packages/com.latios.latios-framework/MathematicsExpansion/ExtraFunctions.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Unity.Mathematics +{ + public static partial class math + { + /// Returns b if c is true, a otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool select(bool a, bool b, bool c) + { + return a ^ ((a ^ b) & c); + } + + /// Returns b if c is true, a otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool2 select(bool2 a, bool2 b, bool c) + { + return a ^ ((a ^ b) & c); + } + + /// Returns b if c is true, a otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool3 select(bool3 a, bool3 b, bool c) + { + return a ^ ((a ^ b) & c); + } + + /// Returns b if c is true, a otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool4 select(bool4 a, bool4 b, bool c) + { + return a ^ ((a ^ b) & c); + } + + /// + /// Returns a componentwise selection between two bool2 vectors a and b based on a bool2 selection mask c. + /// Per component, the component from b is selected when c is true, otherwise the component from a is selected. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool2 select(bool2 a, bool2 b, bool2 c) + { + return a ^ ((a ^ b) & c); + } + + /// + /// Returns a componentwise selection between two bool3 vectors a and b based on a bool3 selection mask c. + /// Per component, the component from b is selected when c is true, otherwise the component from a is selected. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool3 select(bool3 a, bool3 b, bool3 c) + { + return a ^ ((a ^ b) & c); + } + + /// + /// Returns a componentwise selection between two bool4 vectors a and b based on a bool4 selection mask c. + /// Per component, the component from b is selected when c is true, otherwise the component from a is selected. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool4 select(bool4 a, bool4 b, bool4 c) + { + return a ^ ((a ^ b) & c); + } + } +} + diff --git a/Packages/com.latios.latios-framework/MathematicsExpansion/ExtraFunctions.cs.meta b/Packages/com.latios.latios-framework/MathematicsExpansion/ExtraFunctions.cs.meta new file mode 100644 index 0000000..788dc0a --- /dev/null +++ b/Packages/com.latios.latios-framework/MathematicsExpansion/ExtraFunctions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d11ea42410ef91048a4d14d2adbed7cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MathematicsExpansion/MathematicsExpansion.asmref b/Packages/com.latios.latios-framework/MathematicsExpansion/MathematicsExpansion.asmref new file mode 100644 index 0000000..ed6f5e6 --- /dev/null +++ b/Packages/com.latios.latios-framework/MathematicsExpansion/MathematicsExpansion.asmref @@ -0,0 +1,3 @@ +{ + "reference": "Unity.Mathematics" +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/MathematicsExpansion/MathematicsExpansion.asmref.meta b/Packages/com.latios.latios-framework/MathematicsExpansion/MathematicsExpansion.asmref.meta new file mode 100644 index 0000000..584742b --- /dev/null +++ b/Packages/com.latios.latios-framework/MathematicsExpansion/MathematicsExpansion.asmref.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2d6fb490278e7fc43b863c0eb46bbb42 +AssemblyDefinitionReferenceImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio.meta b/Packages/com.latios.latios-framework/MyriAudio.meta new file mode 100644 index 0000000..7ebbe32 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 181056049f2aee049b6d2b195f75e5b6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring.meta new file mode 100644 index 0000000..3d603c8 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f5dbc8085528a4840b62ad4927a6601b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioClipSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioClipSmartBlobberSystem.cs new file mode 100644 index 0000000..97ff371 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioClipSmartBlobberSystem.cs @@ -0,0 +1,270 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Myri.Authoring +{ + public struct AudioClipBakeData + { + public AudioClip clip; + public int numVoices; + } + + public static class AudioClipBlobberAPIExtensions + { + public static SmartBlobberHandle CreateBlob(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + AudioClipBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvert(gameObject, bakeData); + } + + public static SmartBlobberHandleUntyped CreateBlobUntyped(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + AudioClipBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvertUntyped(gameObject, bakeData); + } + } +} + +namespace Latios.Myri.Authoring.Systems +{ + [ConverterVersion("Latios", 4)] + public sealed class AudioClipSmartBlobberSystem : SmartBlobberConversionSystem + { + struct AuthoringHandlePair + { + public AudioSourceAuthoring authoring; + public SmartBlobberHandle blobHandle; + } + + List m_sourceList = new List(); + + protected override void GatherInputs() + { + m_sourceList.Clear(); + Entities.ForEach((AudioSourceAuthoring authoring) => + { + var pair = new AuthoringHandlePair { authoring = authoring }; + if (authoring.clip != null) + { + pair.blobHandle = AddToConvert(authoring.gameObject, new AudioClipBakeData { clip = authoring.clip, numVoices = authoring.voices }); + } + m_sourceList.Add(pair); + }); + } + + protected override void FinalizeOutputs() + { + foreach (var pair in m_sourceList) + { + var authoring = pair.authoring; + var blob = pair.blobHandle.IsValid ? pair.blobHandle.Resolve() : default; + + var entity = GetPrimaryEntity(authoring); + if (!authoring.looping) + { + DstEntityManager.AddComponentData(entity, new AudioSourceOneShot + { + clip = blob, + innerRange = authoring.innerRange, + outerRange = authoring.outerRange, + rangeFadeMargin = authoring.rangeFadeMargin, + volume = authoring.volume + }); + if (authoring.autoDestroyOnFinish) + { + DstEntityManager.AddComponent(entity); + } + } + else + { + DstEntityManager.AddComponentData(entity, new AudioSourceLooped + { + m_clip = blob, + innerRange = authoring.innerRange, + outerRange = authoring.outerRange, + rangeFadeMargin = authoring.rangeFadeMargin, + volume = authoring.volume, + offsetIsBasedOnSpawn = authoring.playFromBeginningAtSpawn + }); + } + if (authoring.useCone) + { + DstEntityManager.AddComponentData(entity, new AudioSourceEmitterCone + { + cosInnerAngle = math.cos(math.radians(authoring.innerAngle)), + cosOuterAngle = math.cos(math.radians(authoring.outerAngle)), + outerAngleAttenuation = authoring.outerAngleVolume + }); + } + } + } + + protected override void Filter(FilterBlobberData blobberData, ref AudioClipContext context, NativeArray inputToFilteredMapping) + { + var hashes = new NativeArray(blobberData.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < blobberData.Count; i++) + { + var input = blobberData.input[i]; + if (input.clip == null || input.clip.channels > 2) + { + if (input.clip != null && input.clip.channels > 2) + Debug.LogError($"Myri failed to convert clip {input.clip.name}. Only mono and stereo clips are supported."); + + hashes[i] = default; + inputToFilteredMapping[i] = -1; + } + else + { + DeclareAssetDependency(blobberData.associatedObject[i], input.clip); + hashes[i] = new int2(input.clip.GetInstanceID(), input.numVoices); + } + } + + new DeduplicateJob { hashes = hashes, inputToFilteredMapping = inputToFilteredMapping }.Run(); + hashes.Dispose(); + } + + protected override void PostFilter(PostFilterBlobberData blobberData, ref AudioClipContext context) + { + int sampleCount = 0; + for (int i = 0; i < blobberData.Count; i++) + { + var clip = blobberData.input[i].clip; + sampleCount += clip.samples * clip.channels; + } + + context.samples = new NativeArray(sampleCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var cache = new float[1024 * 8]; + + var converters = blobberData.converters; + + int sampleStart = 0; + for (int i = 0; i < blobberData.Count; i++) + { + var clip = blobberData.input[i].clip; + int count = clip.samples * clip.channels; + ReadClip(clip, context.samples.GetSubArray(sampleStart, count), cache); + + converters[i] = new AudioClipConverter + { + count = count, + isStereo = clip.channels == 2, + name = clip.name, + numVoices = blobberData.input[i].numVoices, + sampleRate = clip.frequency, + start = sampleStart + }; + + sampleStart += count; + } + } + + void ReadClip(AudioClip clip, NativeArray data, float[] cache) + { + int channels = clip.channels; + int stride = cache.Length; + for (int i = 0; i < data.Length; i += stride) + { + int count = math.min(data.Length - i, stride); + + // Todo: This suppresses a warning but is not ideal + if (count < cache.Length) + { + var tempCache = new float[count]; + clip.GetData(tempCache, i / channels); + NativeArray.Copy(tempCache, data.GetSubArray(i, count), count); + } + else + { + clip.GetData(cache, i / channels); + NativeArray.Copy(cache, data.GetSubArray(i, count), count); + } + } + } + + [BurstCompile] + struct DeduplicateJob : IJob + { + [ReadOnly] public NativeArray hashes; + public NativeArray inputToFilteredMapping; + + public void Execute() + { + var map = new NativeParallelHashMap(hashes.Length, Allocator.Temp); + for (int i = 0; i < hashes.Length; i++) + { + if (inputToFilteredMapping[i] < 0) + continue; + + if (map.TryGetValue(hashes[i], out int index)) + inputToFilteredMapping[i] = index; + else + map.Add(hashes[i], i); + } + } + } + } + + public struct AudioClipConverter : ISmartBlobberContextBuilder + { + internal int start; + internal int count; + internal int sampleRate; + internal int numVoices; + internal FixedString128Bytes name; + internal bool isStereo; + + public BlobAssetReference BuildBlob(int _, int index, ref AudioClipContext context) + { + var builder = new BlobBuilder(Allocator.Temp); + ref var root = ref builder.ConstructRoot(); + var blobLeft = builder.Allocate(ref root.samplesLeftOrMono, count / math.select(1, 2, isStereo)); + if (isStereo) + { + var blobRight = builder.Allocate(ref root.samplesRight, count / 2); + for (int i = 0; i < count; i++) + { + blobLeft[i / 2] = context.samples[start + i]; + i++; + blobRight[i / 2] = context.samples[start + i]; + } + } + else + { + var blobRight = builder.Allocate(ref root.samplesRight, 1); + blobRight[0] = 0f; + for (int i = 0; i < count; i++) + { + blobLeft[i] = context.samples[start + i]; + } + } + int offsetCount = math.max(numVoices, 1); + int stride = blobLeft.Length / offsetCount; + var offsets = builder.Allocate(ref root.loopedOffsets, offsetCount); + for (int i = 0; i < offsetCount; i++) + { + offsets[i] = i * stride; + } + root.sampleRate = sampleRate; + root.name = name; + + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } + + public struct AudioClipContext : System.IDisposable + { + [ReadOnly] internal NativeArray samples; + public void Dispose() => samples.Dispose(); + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioClipSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioClipSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..08c4616 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioClipSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ffbc51632e4776a4faa01291d2dedc2b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioListenerAuthoring.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioListenerAuthoring.cs new file mode 100644 index 0000000..c6d83d2 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioListenerAuthoring.cs @@ -0,0 +1,22 @@ +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Myri.Authoring +{ + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Audio (Myri)/Audio Listener")] + public class AudioListenerAuthoring : MonoBehaviour + { + [Tooltip("The raw volume applied to everything the listener hears. This value is not in decibels.")] + public float volume = 1f; + + [Tooltip("The resolution of time-based spatialization. Increasing this value incurs a higher cost but may increase the player's sense of direction.")] + [Range(0, 15)] + public int interauralTimeDifferenceResolution = 2; + + [Tooltip("A custom volume and frequency spatialization profile. If empty, a default profile will be used.")] + public ListenerProfileBuilder listenerResponseProfile; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioListenerAuthoring.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioListenerAuthoring.cs.meta new file mode 100644 index 0000000..53416ac --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioListenerAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ecd89e842ebc68e40a9cdd2462156c7b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSettingsAuthoring.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSettingsAuthoring.cs new file mode 100644 index 0000000..b36d7a2 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSettingsAuthoring.cs @@ -0,0 +1,32 @@ +using System.Collections; +using Unity.Entities; +using UnityEngine; + +namespace Latios.Myri.Authoring +{ + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Audio (Myri)/Audio Settings")] + public class AudioSettingsAuthoring : MonoBehaviour, IConvertGameObjectToEntity + { + [Tooltip("The number of additional audio frames to generate in case the main thread stalls")] + public int safetyAudioFrames = 2; + [Tooltip("Set this to the max number of audio updates which can happen in a normal visual frame")] + public int audioFramesPerUpdate = 1; + [Tooltip("If the beginning of clips are getting chopped off due to large amounts of sources, increase this value by 1")] + public int lookaheadAudioFrames = 0; + [Tooltip("If enabled, the audio thread will log when it runs out of samples. It is normal for it to log during initialization.")] + public bool logWarningIfBuffersAreStarved = false; + + public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) + { + dstManager.AddComponentData(entity, new AudioSettings + { + safetyAudioFrames = safetyAudioFrames, + audioFramesPerUpdate = audioFramesPerUpdate, + lookaheadAudioFrames = lookaheadAudioFrames, + logWarningIfBuffersAreStarved = logWarningIfBuffersAreStarved + }); + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSettingsAuthoring.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSettingsAuthoring.cs.meta new file mode 100644 index 0000000..4d95cd0 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSettingsAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 315ef3232f3a35e429def206169ab869 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSourceAuthoring.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSourceAuthoring.cs new file mode 100644 index 0000000..cb0922a --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSourceAuthoring.cs @@ -0,0 +1,44 @@ +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Myri.Authoring +{ + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Audio (Myri)/Audio Source")] + public class AudioSourceAuthoring : MonoBehaviour + { + [Tooltip("An audio clip which will be converted into a DOTS representation and played by this source")] + public AudioClip clip; + [Tooltip("Whether or not the source should play the clip in a loop")] + public bool looping; + [Tooltip("If enabled, the entity will automatically be destroyed once the clip is finished playing. This option is ignored for looping sources.")] + public bool autoDestroyOnFinish; + [Tooltip("The raw volume applied to the audio source, before spatial falloff is applied")] + public float volume = 1f; + [Tooltip("When the listener is within this distance to the source, no falloff attenuation is applied")] + public float innerRange = 5f; + [Tooltip("When the listener is outside this distance to the source, no audio is heard")] + public float outerRange = 25f; + [Tooltip("A distance from the outerRange is which the falloff attenuation is dampened towards 0 where it otherwise wouldn't")] + public float rangeFadeMargin = 1f; + [Tooltip( + "If true, the audio source begins playing from the beginning when it spawns. This option only affects looping sources. Do not use this for large amounts of looped sources.") + ] + public bool playFromBeginningAtSpawn; + [Tooltip("The number of unique voices entities instantiated from this converted Game Object may use. This option only affects looping sources.")] + public int voices; + + [Header("Cone")] + [Tooltip("If enabled, directional attenuation is applied")] + public bool useCone; + [Tooltip("The inner angle from the entity's forward direction in which no attenuation is applied")] + public float innerAngle = 30f; + [Tooltip("The outer angle from the entity's forward direction in which full attenuation is applied")] + public float outerAngle = 60f; + [Tooltip("The attenuation to apply at the outer angle. A value of 0 makes the source inaudible outside of the outer angle.")] + [Range(0f, 1f)] + public float outerAngleVolume = 0f; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSourceAuthoring.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSourceAuthoring.cs.meta new file mode 100644 index 0000000..050ef16 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/AudioSourceAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3ba3e5adc2b7534db9c106bfc26ac54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/DefaultListenerProfileBuilder.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/DefaultListenerProfileBuilder.cs new file mode 100644 index 0000000..fdbbb8c --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/DefaultListenerProfileBuilder.cs @@ -0,0 +1,35 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Myri.Authoring +{ + internal class DefaultListenerProfileBuilder : ListenerProfileBuilder + { + protected override void BuildProfile() + { + //left unblocked + AddChannel(new float2(math.PI / 2f, math.PI * 1.25f), new float2(-math.PI, math.PI), 1f, 0f, 1f, false); + //left fully blocked + var leftFilterChannel = AddChannel(new float2(-math.PI / 4f, math.PI / 4f), new float2(-math.PI, math.PI), 0f, 1f, 0f, false); + AddFilterToChannel(new FrequencyFilter + { + cutoff = 1500f, + gainInDecibels = 0f, + q = 0.707f, + type = FrequencyFilterType.Lowpass + }, leftFilterChannel); + //right unblocked + AddChannel(new float2(-math.PI / 4f, math.PI / 2f), new float2(-math.PI, math.PI), 1f, 0f, 1f, true); + //right fully blocked + var rightFilterChannel = AddChannel(new float2(math.PI * 0.75f, math.PI * 1.25f), new float2(-math.PI, math.PI), 0f, 1f, 0f, true); + AddFilterToChannel(new FrequencyFilter + { + cutoff = 1500f, + gainInDecibels = 0f, + q = 0.707f, + type = FrequencyFilterType.Lowpass + }, rightFilterChannel); + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/DefaultListenerProfileBuilder.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/DefaultListenerProfileBuilder.cs.meta new file mode 100644 index 0000000..ccc2a65 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/DefaultListenerProfileBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8eb20778121aed34c81b7722b0e12005 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileBuilder.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileBuilder.cs new file mode 100644 index 0000000..032a15e --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileBuilder.cs @@ -0,0 +1,196 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +using Hash128 = Unity.Entities.Hash128; + +namespace Latios.Myri.Authoring +{ + /// + /// An asset type which is used to define a volume and frequency spatialization profile for an audio listener + /// + public abstract class ListenerProfileBuilder : ScriptableObject + { + /// + /// Override this function to make several calls to AddChannel and AddFilterToChannel which defines a profile. + /// + protected abstract void BuildProfile(); + + /// + /// Adds a channel to the profile. A channel is a 3D radial slice where all audio sources coming from that direction + /// are subject to the channels filters. Sources between channels will interpolate between the channels. + /// + /// + /// The horizontal extremes of the slice in radians beginning from the left ear positively rotating towards forward. + /// Values between -2pi and +2pi are allowed. + /// + /// The vertical extremes of the slice in radians beginning from horizontal positively rotating upwards. + /// Values between -2pi and +2pi are allowed. + /// The amount of signal which should bypass the filters. Must be between 0 and 1. + /// The raw attenuation or amplification to apply to the input of filtered signal. Not in decibels. + /// The raw attenuation of amplification to apply to the signal bypassing the filters. Not in decibels. + /// If true, this channel uses the right ear. Otherwise, it uses the left ear. + /// A handle which can be used to add filters to the channel + protected ChannelHandle AddChannel(float2 minMaxHorizontalAngleInRadiansCounterClockwiseFromRight, + float2 minMaxVerticalAngleInRadians, + float passthroughFraction, + float filterVolume, + float passthroughVolume, + bool isRightEar) + { + CheckAllowedToGenerate("AddChannel()"); + + if (m_job.anglesPerLeftChannel.Length + m_job.anglesPerRightChannel.Length >= 127) + throw new InvalidOperationException("An IldProfile only supports up to 127 channels"); + + if (isRightEar) + { + m_job.anglesPerRightChannel.Add(new float4(minMaxHorizontalAngleInRadiansCounterClockwiseFromRight, minMaxVerticalAngleInRadians)); + m_job.passthroughFractionsPerRightChannel.Add(math.saturate(passthroughFraction)); + m_job.filterVolumesPerRightChannel.Add(math.saturate(filterVolume)); + m_job.passthroughVolumesPerRightChannel.Add(math.saturate(passthroughVolume)); + return new ChannelHandle { channelIndex = m_job.anglesPerRightChannel.Length - 1, isRightChannel = true }; + } + else + { + m_job.anglesPerLeftChannel.Add(new float4(minMaxHorizontalAngleInRadiansCounterClockwiseFromRight, minMaxVerticalAngleInRadians)); + m_job.passthroughFractionsPerLeftChannel.Add(passthroughFraction); + m_job.filterVolumesPerLeftChannel.Add(math.saturate(filterVolume)); + m_job.passthroughVolumesPerLeftChannel.Add(math.saturate(passthroughVolume)); + return new ChannelHandle { channelIndex = m_job.anglesPerLeftChannel.Length - 1, isRightChannel = false }; + } + } + + /// + /// Adds a filter to the channel. Filters are applied in the order they are added. + /// + /// The filter to apply + /// The channel handle returned from AddChannel + protected void AddFilterToChannel(FrequencyFilter filter, ChannelHandle channel) + { + CheckAllowedToGenerate("AddFilterToChannel()"); + + if (channel.isRightChannel) + { + m_job.filtersRight.Add(filter); + m_job.channelIndicesRight.Add(channel.channelIndex); + } + else + { + m_job.filtersLeft.Add(filter); + m_job.channelIndicesLeft.Add(channel.channelIndex); + } + } + + /// + /// A handle representing a channel added to the profile + /// + public struct ChannelHandle + { + internal int channelIndex; + internal bool isRightChannel; + } + + #region Internals + + FinalizeBlobJob m_job = default; + bool m_allowGenerate = false; + + void CheckAllowedToGenerate(string function) + { + if (!m_allowGenerate) + throw new InvalidOperationException($"The context is not valid. Please only call {function} from within BuildProfile() and do not call BuildProfile() yourself."); + } + + internal BlobAssetReference ComputeBlob() + { + m_job = new FinalizeBlobJob + { + filtersLeft = new NativeList(Allocator.TempJob), + filtersRight = new NativeList(Allocator.TempJob), + channelIndicesLeft = new NativeList(Allocator.TempJob), + channelIndicesRight = new NativeList(Allocator.TempJob), + anglesPerLeftChannel = new NativeList(Allocator.TempJob), + anglesPerRightChannel = new NativeList(Allocator.TempJob), + passthroughFractionsPerLeftChannel = new NativeList(Allocator.TempJob), + passthroughFractionsPerRightChannel = new NativeList(Allocator.TempJob), + filterVolumesPerLeftChannel = new NativeList(Allocator.TempJob), + filterVolumesPerRightChannel = new NativeList(Allocator.TempJob), + passthroughVolumesPerLeftChannel = new NativeList(Allocator.TempJob), + passthroughVolumesPerRightChannel = new NativeList(Allocator.TempJob), + blob = new NativeReference >(Allocator.TempJob) + }; + + m_allowGenerate = true; + BuildProfile(); + m_allowGenerate = false; + + m_job.Run(); + var blob = m_job.blob.Value; + + m_job.filtersLeft.Dispose(); + m_job.filtersRight.Dispose(); + m_job.channelIndicesLeft.Dispose(); + m_job.channelIndicesRight.Dispose(); + m_job.anglesPerLeftChannel.Dispose(); + m_job.anglesPerRightChannel.Dispose(); + m_job.passthroughFractionsPerLeftChannel.Dispose(); + m_job.passthroughFractionsPerRightChannel.Dispose(); + m_job.filterVolumesPerLeftChannel.Dispose(); + m_job.filterVolumesPerRightChannel.Dispose(); + m_job.passthroughVolumesPerLeftChannel.Dispose(); + m_job.passthroughVolumesPerRightChannel.Dispose(); + m_job.blob.Dispose(); + + return blob; + } + + [BurstCompile] + struct FinalizeBlobJob : IJob + { + public NativeList filtersLeft; + public NativeList channelIndicesLeft; + public NativeList filtersRight; + public NativeList channelIndicesRight; + + public NativeList anglesPerLeftChannel; + public NativeList anglesPerRightChannel; + public NativeList passthroughFractionsPerLeftChannel; + public NativeList passthroughFractionsPerRightChannel; + public NativeList filterVolumesPerLeftChannel; + public NativeList filterVolumesPerRightChannel; + public NativeList passthroughVolumesPerLeftChannel; + public NativeList passthroughVolumesPerRightChannel; + + public NativeReference > blob; + + public void Execute() + { + var builder = new BlobBuilder(Allocator.Temp); + ref var root = ref builder.ConstructRoot(); + builder.ConstructFromNativeArray(ref root.filtersLeft, filtersLeft); + builder.ConstructFromNativeArray(ref root.channelIndicesLeft, channelIndicesLeft); + builder.ConstructFromNativeArray(ref root.filtersRight, filtersRight); + builder.ConstructFromNativeArray(ref root.channelIndicesRight, channelIndicesRight); + + builder.ConstructFromNativeArray(ref root.anglesPerLeftChannel, anglesPerLeftChannel); + builder.ConstructFromNativeArray(ref root.anglesPerRightChannel, anglesPerRightChannel); + builder.ConstructFromNativeArray(ref root.passthroughFractionsPerLeftChannel, passthroughFractionsPerLeftChannel); + builder.ConstructFromNativeArray(ref root.passthroughFractionsPerRightChannel, passthroughFractionsPerRightChannel); + builder.ConstructFromNativeArray(ref root.filterVolumesPerLeftChannel, filterVolumesPerLeftChannel); + builder.ConstructFromNativeArray(ref root.filterVolumesPerRightChannel, filterVolumesPerRightChannel); + builder.ConstructFromNativeArray(ref root.passthroughVolumesPerLeftChannel, passthroughVolumesPerLeftChannel); + builder.ConstructFromNativeArray(ref root.passthroughVolumesPerRightChannel, passthroughVolumesPerRightChannel); + + blob.Value = builder.CreateBlobAssetReference(Allocator.Persistent); + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileBuilder.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileBuilder.cs.meta new file mode 100644 index 0000000..6493d88 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 55554995819d4d84fb7014644843cc65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileSmartBlobberSystem.cs new file mode 100644 index 0000000..a5b8f8d --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileSmartBlobberSystem.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Myri.Authoring +{ + public struct ListenerProfileBakeData + { + public ListenerProfileBuilder builder; + } + + public static class ListenerProfileBlobberAPIExtensions + { + public static SmartBlobberHandle CreateBlob(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + ListenerProfileBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvert(gameObject, bakeData); + } + + public static SmartBlobberHandleUntyped CreateBlobUntyped(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + ListenerProfileBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvertUntyped(gameObject, bakeData); + } + } +} + +namespace Latios.Myri.Authoring.Systems +{ + [ConverterVersion("Latios", 4)] + public sealed class ListenerProfileSmartBlobberSystem : SmartBlobberConversionSystem + { + struct AuthoringHandlePair + { + public AudioListenerAuthoring authoring; + public SmartBlobberHandle handle; + } + + List m_listenerList = new List(); + + DefaultListenerProfileBuilder m_defaultProfile; + + protected override void OnCreate() + { + base.OnCreate(); + m_defaultProfile = ScriptableObject.CreateInstance(); + } + + protected override void OnDestroy() + { + m_defaultProfile.DestroyDuringConversion(); + base.OnDestroy(); + } + + protected override void GatherInputs() + { + m_listenerList.Clear(); + Entities.ForEach((AudioListenerAuthoring authoring) => + { + var handle = AddToConvert(authoring.gameObject, new ListenerProfileBakeData { builder = authoring.listenerResponseProfile }); + m_listenerList.Add(new AuthoringHandlePair { authoring = authoring, handle = handle }); + }); + } + + protected override void FinalizeOutputs() + { + foreach (var listener in m_listenerList) + { + var authoring = listener.authoring; + var entity = GetPrimaryEntity(authoring); + DstEntityManager.AddComponentData(entity, new AudioListener + { + volume = authoring.volume, + itdResolution = authoring.interauralTimeDifferenceResolution, + ildProfile = listener.handle.Resolve() + }); + } + } + + protected override bool Filter(in ListenerProfileBakeData input, GameObject gameObject, out ListenerProfileConverter converter) + { + if (input.builder != null) + DeclareAssetDependency(gameObject, input.builder); + + var profile = input.builder == null ? m_defaultProfile : input.builder; + converter.blob = profile.ComputeBlob(); + return true; + } + } + + public struct ListenerProfileConverter : ISmartBlobberSimpleBuilder + { + public BlobAssetReference blob; + + public BlobAssetReference BuildBlob() => blob; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..6fae272 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Authoring/ListenerProfileSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f49e5c96f8a558e4295ce9ad7f92d28d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components.meta b/Packages/com.latios.latios-framework/MyriAudio/Components.meta new file mode 100644 index 0000000..7be43e7 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 193c4bda50933554ab4a97c73c22e132 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/AudioListenerMyri.cs b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioListenerMyri.cs new file mode 100644 index 0000000..85f967a --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioListenerMyri.cs @@ -0,0 +1,89 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Myri +{ + /// + /// A listener which captures audio in 3D space. + /// + public struct AudioListener : IComponentData + { + /// + /// A volume multiplier for all audio picked up by this listener. + /// For loud audio scenes, this only affects the relative loudness between other listeners. + /// This value is not in decibels. + /// + public float volume; + + /// + /// The resolution of time-based spatialization to apply between the range of 0 and 15. + /// Higher values are more expensive but may provide a better sense of direction for the listener. + /// + public int itdResolution; + /// + /// The profile which specifies volume and frequency-based filtering spatialization. + /// + public BlobAssetReference ildProfile; + } + + /// + /// A volume and frequency-based filtering spatialization profile. + /// A custom variant can be constructed by overriding AudioIldProfileBuilder. + /// + public struct ListenerProfileBlob + { + internal BlobArray filtersLeft; + internal BlobArray channelIndicesLeft; + internal BlobArray filtersRight; + internal BlobArray channelIndicesRight; + + internal BlobArray anglesPerLeftChannel; + internal BlobArray anglesPerRightChannel; + internal BlobArray passthroughFractionsPerLeftChannel; + internal BlobArray passthroughFractionsPerRightChannel; + internal BlobArray filterVolumesPerLeftChannel; + internal BlobArray filterVolumesPerRightChannel; + internal BlobArray passthroughVolumesPerLeftChannel; + internal BlobArray passthroughVolumesPerRightChannel; + } + + /// + /// A frequency-based filter configuration which can be applied to audio to achieve spatialization. + /// + public struct FrequencyFilter + { + /// + /// The cutoff frequency for the filter. + /// + public float cutoff; + /// + /// The quality of the filter. + /// + public float q; + /// + /// The amplification or attenuation of the filter in decibels. + /// + public float gainInDecibels; + /// + /// The type of filter. + /// + public FrequencyFilterType type; + } + + /// + /// The type of frequency-based filtering to apply. + /// + public enum FrequencyFilterType + { + Lowpass, + Highpass, + Bandpass, + Bell, + Notch, + Lowshelf, + Highshelf + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/AudioListenerMyri.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioListenerMyri.cs.meta new file mode 100644 index 0000000..88bb73e --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioListenerMyri.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cd2f44aebaf5b845b268372ca992d55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSettingsMyri.cs b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSettingsMyri.cs new file mode 100644 index 0000000..a09d663 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSettingsMyri.cs @@ -0,0 +1,30 @@ +using Unity.Entities; + +namespace Latios.Myri +{ + /// + /// Configuration data for audio to be added to the worldBlackboardEntity + /// + public struct AudioSettings : IComponentData + { + /// + /// The number of additional audio frames to generate in case the main thread stalls + /// + public int safetyAudioFrames; + /// + /// The number of audio frames expected per update. Increase this if the audio framerate is higher than the visual framerate. + /// + public int audioFramesPerUpdate; + /// + /// The number of audio frames ahead of the DSP clock that the jobs should start generating data for. + /// Increase this when the amount of sampling is heavy and the beginning of clips get cut off. + /// Increasing this value adds a delay to the audio. + /// + public int lookaheadAudioFrames; + /// + /// If enabled, warnings will be logged when the audio thread runs out of samples to process. + /// + public bool logWarningIfBuffersAreStarved; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSettingsMyri.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSettingsMyri.cs.meta new file mode 100644 index 0000000..6c5da41 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSettingsMyri.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 952e2dd6ef0a17f409800d5f3b70f24b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSourceMyri.cs b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSourceMyri.cs new file mode 100644 index 0000000..6405837 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSourceMyri.cs @@ -0,0 +1,205 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Myri +{ + /// + /// An audio source which plays in a continuous loop synchronized with the global DSP Time. + /// + public struct AudioSourceLooped : IComponentData + { + //Internal until we can detect changes. Will likely require a separate component. + internal BlobAssetReference m_clip; + internal int m_loopOffset; + /// + /// The raw volume multiplier for the source. This is not in decibels. + /// + public float volume; + /// + /// When the listener is within this distance, no distance-based attenuation is applied to this audio source. + /// + public float innerRange; + /// + /// When the listener is outside of this distance, the audio source is not heard. + /// + public float outerRange; + /// + /// As the listener nears the outerRange, additional damping is applied to help the perceived volume smoothly transition to zero. + /// This value dictates the width of the region where this damping takes affect. + /// + public float rangeFadeMargin; + internal short m_spawnBufferLow16; + internal ushort m_flags; + + /// + /// The audio clip to play on loop. Setting this value with a new clip resets the playhead. + /// + public BlobAssetReference clip + { + get => m_clip; + set + { + if (m_clip != value) + { + initialized = false; + offsetLocked = false; + m_loopOffset = 0; + m_clip = value; + } + } + } + + /// + /// Resets the clip's playhead. + /// + public void ResetPlaybackState() + { + var c = clip; + clip = default; + clip = c; + } + + /// + /// If true, the clip plays from the beginning at the time of spawn. Otherwise it plays using a random offset based on the number of voices. + /// + public bool offsetIsBasedOnSpawn + { + get => (m_flags & 0x4) != 0; + set + { + ResetPlaybackState(); + m_flags = (ushort)math.select(m_flags & ~0x4, m_flags | 0x4, value); + } + } + + internal bool initialized + { + get => (m_flags & 0x1) != 0; + set => m_flags = (ushort)math.select(m_flags & ~0x1, m_flags | 0x1, value); + } + + internal bool offsetLocked + { + get => (m_flags & 0x2) != 0; + set => m_flags = (ushort)math.select(m_flags & ~0x2, m_flags | 0x2, value); + } + } + + /// + /// An audio source which plays only once, starting from the beginning when spawned. + /// + public struct AudioSourceOneShot : IComponentData + { + internal BlobAssetReference m_clip; + internal int m_spawnedAudioFrame; + internal int m_spawnedBufferId; + + /// + /// The raw volume multiplier for the source. This is not in decibels. + /// + public float volume; + /// + /// When the listener is within this distance, no distance-based attenuation is applied to this audio source. + /// + public float innerRange; + /// + /// When the listener is outside of this distance, the audio source is not heard. + /// + public float outerRange; + /// + /// As the listener nears the outerRange, additional damping is applied to help the perceived volume smoothly transition to zero. + /// This value dictates the width of the region where this damping takes affect. + /// + public float rangeFadeMargin; + + /// + /// The audio clip to play. Setting this value with a new clip resets the playhead back to the beginning. + /// + public BlobAssetReference clip + { + get => m_clip; + set + { + if (m_clip != value) + { + m_spawnedAudioFrame = 0; + m_spawnedBufferId = 0; + m_clip = value; + } + } + } + + /// + /// Resets the clips playhead back to the beginning. + /// + public void ResetPlaybackState() + { + var c = clip; + clip = default; + clip = c; + } + + internal bool isInitialized => (m_spawnedBufferId != 0) | (m_spawnedBufferId != m_spawnedAudioFrame); + } + + /// + /// When present on an entity with an audio source, directional attenuation is applied. + /// The cone's center ray uses the audio source's transform's forward direction. + /// + public struct AudioSourceEmitterCone : IComponentData + { + /// + /// The cosine of the inner angle within which no attenuation is applied. + /// + public float cosInnerAngle; + /// + /// The cosine of the outer angle outside of which the full attenuation is applied. + /// + public float cosOuterAngle; + /// + /// The amount of attenuation to apply at or outside the outer angle. The value should be between 0 and 1. + /// + public float outerAngleAttenuation; + } + + /// + /// If present on an entity with an AudioSourceOneshot, the entity will be destroyed when the playhead passes the last sample. + /// This is conservatively computed based on values from the audio thread, so the entity may not be destroyed until multiple frames later. + /// + public struct AudioSourceDestroyOneShotWhenFinished : IComponentData { } + + /// + /// An audio clip representation accessible in Burst jobs. + /// + public struct AudioClipBlob + { + /// + /// The samples for either the left channel, or the mono channel if this is not a stereo clip. + /// + public BlobArray samplesLeftOrMono; + /// + /// The samples for the right channel. It is length 0 if this is not a stereo clip. + /// + public BlobArray samplesRight; + /// + /// These are offsets for the different voices used in looping audio. The number of offsets is equal to the number of voices. + /// + public BlobArray loopedOffsets; + /// + /// The name of the audio clip asset that created this blob asset. + /// + public FixedString128Bytes name; + /// + /// The sample rate of the audio clip. A value of 48000 would mean 48000 float samples are required for 1 second of audio. + /// + public int sampleRate; + + /// + /// If true, the audio clip is a stereo clip. Otherwise it is a mono clip. Surround is not supported. + /// + public bool isStereo => samplesRight.Length == samplesLeftOrMono.Length; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSourceMyri.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSourceMyri.cs.meta new file mode 100644 index 0000000..f3d0abe --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/AudioSourceMyri.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a84058fc1c549fa44ae710a46b3b285b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/InternalComponents.cs b/Packages/com.latios.latios-framework/MyriAudio/Components/InternalComponents.cs new file mode 100644 index 0000000..5dd8e04 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/InternalComponents.cs @@ -0,0 +1,39 @@ +using Unity.Audio; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; + +namespace Latios.Myri +{ + internal struct IldOutputConnection + { + public int ildOutputPort; + public int nodeInputPort; + public float attenuation; + public DSPNode node; + public DSPConnection connection; + } + + internal unsafe struct ListenerGraphState : ISystemStateComponentData + { + public UnsafeList nodes; + public UnsafeList connections; + public UnsafeList ildConnections; + public BlobAssetReference lastUsedProfile; + } + + internal struct EntityOutputGraphState : ISystemStateComponentData + { + public DSPConnection connection; + public int portIndex; + } + + /*internal unsafe struct MasterGraphState + { + public UnsafeList portFreelist; + public DSPGraph graph; + public DSPNode mixdownNode; + public DSPConnection driverConnection; + }*/ +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Components/InternalComponents.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Components/InternalComponents.cs.meta new file mode 100644 index 0000000..26fd2e5 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Components/InternalComponents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9c1c7173a0f3b94f80fcc54259eba77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph.meta new file mode 100644 index 0000000..14dca9d --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 33062d8cff088c04184133af8aca4aac +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/BrickwallLimiterNode.cs b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/BrickwallLimiterNode.cs new file mode 100644 index 0000000..7d751ec --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/BrickwallLimiterNode.cs @@ -0,0 +1,210 @@ +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; + +// This is a brickwall limiter with lookahead. The way such filters work is they add a delay to the signal in order to "see ahead". +// From this, they can begin gradually attenuating the signal prior to the sample that would have otherwise had a magnitude greater +// than 1.0f. The gradual attenuation prevents clicking noises that would otherwise occur from sudden attenuation spikes. + +// This initial implementation is a naive brute-force implementation which searches through all lookahead samples for the extreme +// slope for every sample. This costs 1.2 ms on my system. A better solution would be to keep a queue of slope segments. However, +// this implementation uses fixed memory allocated in the node object since the AudioKernal allocator is poorly documented. + +namespace Latios.Myri +{ + [BurstCompile(CompileSynchronously = true)] + public unsafe struct BrickwallLimiterNode : IAudioKernel + { + public enum Parameters + { + Unused + } + public enum SampleProviders + { + Unused + } + + float m_currentAttenuationDb; + float m_maxVolumeDb; + float m_releasePerSampleDb; + DelayQueue m_delayQueueL; + DelayQueue m_delayQueueR; + DelayQueue m_delayAmplitudeDb; + + public void Initialize() + { + m_currentAttenuationDb = 0f; + m_maxVolumeDb = 0f; + m_releasePerSampleDb = 10f / 60f / 1024f; // Dependent on sample rate so should be overwritten + m_delayQueueL.maxSamples = 256; + m_delayQueueR.maxSamples = 256; + m_delayAmplitudeDb.maxSamples = 256; + } + + public void Execute(ref ExecuteContext context) + { + // Assume stereo in, stereo out + if (context.Outputs.Count <= 0) + return; + var outputBuffer = context.Outputs.GetSampleBuffer(0); + if (outputBuffer.Channels <= 1) + { + ZeroSampleBuffer(outputBuffer); + return; + } + if (context.Inputs.Count <= 0) + { + ZeroSampleBuffer(outputBuffer); + return; + } + var inputBuffer = context.Inputs.GetSampleBuffer(0); + if (inputBuffer.Channels <= 1) + { + ZeroSampleBuffer(outputBuffer); + return; + } + + // Temporary: Update the release rate here until the limiter parameters are exposed. + m_releasePerSampleDb = 10f / 60f / context.DSPBufferSize; + + // Real start + var inputL = inputBuffer.GetBuffer(0); + var inputR = inputBuffer.GetBuffer(1); + var outputL = outputBuffer.GetBuffer(0); + var outputR = outputBuffer.GetBuffer(1); + int length = outputL.Length; + + int src = 0; + int dst = 0; + + // Fill the queues if they haven't filled up yet and write zeros to the output + while (dst < length && m_delayQueueL.count < m_delayQueueL.maxSamples) + { + m_delayQueueL.Enqueue(inputL[src]); + m_delayQueueR.Enqueue(inputR[src]); + float max = math.max(math.abs(inputL[src]), math.abs(inputR[src])); + m_delayAmplitudeDb.Enqueue(20f * math.log10(max)); + src++; + outputL[dst] = 0f; + outputR[dst] = 0f; + dst++; + } + + while (dst < length) + { + var leftSample = m_delayQueueL.Dequeue(); + var rightSample = m_delayQueueR.Dequeue(); + var amplitudeSampleDb = m_delayAmplitudeDb.Dequeue(); + + // Clamp the attenuation to whatever the sample needs + m_currentAttenuationDb = math.min(m_currentAttenuationDb, -amplitudeSampleDb); + + // Attenuate the sample + var currentAttenuation = math.pow(10f, m_currentAttenuationDb / 20f); + outputL[dst] = leftSample * currentAttenuation; + outputR[dst] = rightSample * currentAttenuation; + dst++; + + // Fill the gap in the queue + m_delayQueueL.Enqueue(inputL[src]); + m_delayQueueR.Enqueue(inputR[src]); + float max = math.max(math.abs(inputL[src]), math.abs(inputR[src])); + m_delayAmplitudeDb.Enqueue(20f * math.log10(max)); + src++; + + // Find the maximally decreasing attenuation slope in the lookahead queue + float slope = float.MaxValue; + for (int i = 0; i < m_delayAmplitudeDb.count; i++) + { + float newSlope = (-m_delayAmplitudeDb[i] - m_currentAttenuationDb) / (i + 1); + slope = math.min(slope, newSlope); + } + + // Update the attenuation for the next sample + m_currentAttenuationDb += math.select(slope, m_releasePerSampleDb, slope > 0f); + m_currentAttenuationDb = math.min(m_currentAttenuationDb, m_maxVolumeDb); + } + } + + void ZeroSampleBuffer(SampleBuffer sb) + { + for (int c = 0; c < sb.Channels; c++) + { + var b = sb.GetBuffer(c); + for (int i = 0; i < b.Length; i++) + { + b[i] = 0f; + } + } + } + + public void Dispose() + { + } + + unsafe struct DelayQueue + { + fixed float m_buffer[2048]; + int m_nextEnqueueIndex; + int m_nextDequeueIndex; + int m_count; + int m_maxSamples; + + public int count => m_count; + + public int maxSamples + { + get => m_maxSamples; + set + { + //dequeue all into new buffer with new max and then copy buffer + if (value != m_maxSamples) + { + DelayQueue other = default; + other.m_maxSamples = value; + while (m_count > value) + { + Dequeue(); + } + while (m_count > 0) + { + other.Enqueue(Dequeue()); + } + this = other; + } + } + } + + public void Enqueue(float newValue) + { + m_buffer[m_nextEnqueueIndex] = newValue; + m_nextEnqueueIndex++; + m_nextEnqueueIndex = math.select(m_nextEnqueueIndex, 0, m_nextEnqueueIndex >= m_maxSamples); + m_count++; + } + + public float Dequeue() + { + float result = m_buffer[m_nextDequeueIndex]; + m_nextDequeueIndex++; + m_nextDequeueIndex = math.select(m_nextDequeueIndex, 0, m_nextDequeueIndex >= m_maxSamples); + m_count--; + return result; + } + + public float this[int index] + { + get + { + int targetIndex = index + m_nextDequeueIndex; + targetIndex = math.select(targetIndex, targetIndex - m_maxSamples, targetIndex >= m_maxSamples); + return m_buffer[targetIndex]; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/BrickwallLimiterNode.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/BrickwallLimiterNode.cs.meta new file mode 100644 index 0000000..a69e61d --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/BrickwallLimiterNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76208768da55a7a459167d01907b3243 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/LatiosDspGraphDriver.cs b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/LatiosDspGraphDriver.cs new file mode 100644 index 0000000..5c2a361 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/LatiosDspGraphDriver.cs @@ -0,0 +1,64 @@ +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Media.Utilities; + +namespace Latios.Myri +{ + //This is just DefaultDSPGraphDriver from DspGraph except with the scheduling mode changed to be on-thread. + [BurstCompile(CompileSynchronously = true)] + public struct LatiosDSPGraphDriver : IAudioOutput + { + public DSPGraph Graph; + int m_ChannelCount; + [NativeDisableContainerSafetyRestriction] + NativeArray m_DeinterleavedBuffer; + private bool m_FirstMix; + + public void Initialize(int channelCount, SoundFormat format, int sampleRate, long dspBufferSize) + { + m_ChannelCount = channelCount; + m_DeinterleavedBuffer = new NativeArray((int)(dspBufferSize * channelCount), Allocator.AudioKernel, NativeArrayOptions.UninitializedMemory); + m_FirstMix = true; + } + + public void BeginMix(int frameCount) + { + if (!m_FirstMix) + return; + m_FirstMix = false; + Graph.OutputMixer.BeginMix(frameCount, DSPGraph.ExecutionMode.Synchronous); + } + + public unsafe void EndMix(NativeArray output, int frames) + { +#if UNITY_2020_2_OR_NEWER + // Interleaving happens in the output hook manager + Graph.OutputMixer.ReadMix(output, frames, m_ChannelCount); +#else + Graph.OutputMixer.ReadMix(m_DeinterleavedBuffer, frames, m_ChannelCount); + Utility.InterleaveAudioStream((float*)m_DeinterleavedBuffer.GetUnsafeReadOnlyPtr(), (float*)output.GetUnsafePtr(), frames, m_ChannelCount); +#endif + Graph.OutputMixer.BeginMix(frames, DSPGraph.ExecutionMode.Synchronous); + } + + public void Dispose() + { + //UnityEngine.Debug.Log("Driver.Dispose"); + + if (Graph.Valid) + { + //UnityEngine.Debug.Log("Disposing graph"); + Graph.Dispose(); + } + + // TODO: This currently throws, needs yet another fix in unity + if (m_DeinterleavedBuffer.IsCreated) + { + m_DeinterleavedBuffer.Dispose(); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/LatiosDspGraphDriver.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/LatiosDspGraphDriver.cs.meta new file mode 100644 index 0000000..d525774 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/LatiosDspGraphDriver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5da15eb1e6c2ae4f86b9562cec1da75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixPortsToStereoNode.cs b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixPortsToStereoNode.cs new file mode 100644 index 0000000..3053d7e --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixPortsToStereoNode.cs @@ -0,0 +1,107 @@ +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios.Myri +{ + [BurstCompile(CompileSynchronously = true)] + public unsafe struct MixPortsToStereoNode : IAudioKernel + { + public enum Parameters + { + Unused + } + public enum SampleProviders + { + Unused + } + + internal int m_leftChannelCount; + + public void Initialize() + { + } + + public void Execute(ref ExecuteContext context) + { + if (context.Outputs.Count <= 0) + return; + var mixedOutputSampleBuffer = context.Outputs.GetSampleBuffer(0); + + if (mixedOutputSampleBuffer.Channels > 0) + { + var leftBuffer = mixedOutputSampleBuffer.GetBuffer(0); + for (int i = 0; i < leftBuffer.Length; i++) + { + leftBuffer[i] = 0f; + } + for (int c = 0; c < math.min(context.Inputs.Count, m_leftChannelCount); c++) + { + var inputBuffer = context.Inputs.GetSampleBuffer(c).GetBuffer(0); + for (int i = 0; i < leftBuffer.Length; i++) + { + leftBuffer[i] += inputBuffer[i]; + } + } + } + if (mixedOutputSampleBuffer.Channels > 1) + { + var rightBuffer = mixedOutputSampleBuffer.GetBuffer(1); + for (int i = 0; i < rightBuffer.Length; i++) + { + rightBuffer[i] = 0f; + } + for (int c = m_leftChannelCount; c < context.Inputs.Count; c++) + { + var inputBuffer = context.Inputs.GetSampleBuffer(c).GetBuffer(0); + for (int i = 0; i < rightBuffer.Length; i++) + { + rightBuffer[i] += inputBuffer[i]; + } + } + } + + for (int p = 1; p < context.Outputs.Count; p++) + { + var additionalOutputSampleBuffer = context.Outputs.GetSampleBuffer(p); + if (mixedOutputSampleBuffer.Channels > 0 && additionalOutputSampleBuffer.Channels > 0) + { + var leftBuffer = mixedOutputSampleBuffer.GetBuffer(0); + var additionalLeftBuffer = additionalOutputSampleBuffer.GetBuffer(0); + for (int i = 0; i < leftBuffer.Length; i++) + { + additionalLeftBuffer[i] = leftBuffer[i]; + } + } + if (mixedOutputSampleBuffer.Channels > 1 && additionalOutputSampleBuffer.Channels > 1) + { + var rightBuffer = mixedOutputSampleBuffer.GetBuffer(1); + var additionalRightBuffer = additionalOutputSampleBuffer.GetBuffer(1); + for (int i = 0; i < rightBuffer.Length; i++) + { + additionalRightBuffer[i] = rightBuffer[i]; + } + } + } + } + + public void Dispose() + { + } + } + + [BurstCompile(CompileSynchronously = true)] + internal unsafe struct MixPortsToStereoNodeUpdate : IAudioKernelUpdate + { + public int leftChannelCount; + + public void Update(ref MixPortsToStereoNode audioKernel) + { + audioKernel.m_leftChannelCount = leftChannelCount; + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixPortsToStereoNode.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixPortsToStereoNode.cs.meta new file mode 100644 index 0000000..ab27f12 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixPortsToStereoNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85f7e3a2ae0d0e54899aa652e1e14feb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixStereoPortsNode.cs b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixStereoPortsNode.cs new file mode 100644 index 0000000..b46877f --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixStereoPortsNode.cs @@ -0,0 +1,52 @@ +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios.Myri +{ + [BurstCompile(CompileSynchronously = true)] + public unsafe struct MixStereoPortsNode : IAudioKernel + { + public enum Parameters + { + Unused + } + public enum SampleProviders + { + Unused + } + + public void Initialize() + { + } + + public void Execute(ref ExecuteContext context) + { + if (context.Outputs.Count <= 0) + return; + var outputBuffer = context.Outputs.GetSampleBuffer(0); + + for (int input = 0; input < context.Inputs.Count; input++) + { + var inputBuffer = context.Inputs.GetSampleBuffer(input); + for (int c = 0; c < math.min(outputBuffer.Channels, inputBuffer.Channels); c++) + { + var inputSamples = inputBuffer.GetBuffer(c); + var outputSamples = outputBuffer.GetBuffer(c); + for (int i = 0; i < outputSamples.Length; i++) + { + outputSamples[i] += inputSamples[i]; + } + } + } + } + + public void Dispose() + { + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixStereoPortsNode.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixStereoPortsNode.cs.meta new file mode 100644 index 0000000..22b8ece --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/MixStereoPortsNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7d5c30d665a70a14998aba82f4704c38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/ReadIldBuffersNode.cs b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/ReadIldBuffersNode.cs new file mode 100644 index 0000000..eb34e6c --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/ReadIldBuffersNode.cs @@ -0,0 +1,165 @@ +using System.Threading; +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; + +//Todo: Need to factor in the panFilterRatio +namespace Latios.Myri +{ + [BurstCompile(CompileSynchronously = true)] + internal unsafe struct ReadIldBuffersNode : IAudioKernel + { + public enum Parameters + { + Unused + } + public enum SampleProviders + { + Unused + } + + int m_currentFrame; + int m_nextUpdateFrame; + int m_lastPlayedBufferID; + IldBuffer m_ildBuffer; + + internal FixedList4096Bytes m_queuedIldBuffers; + + [NativeDisableUnsafePtrRestriction] + internal long* m_packedFrameCounterBufferId; + + public void Initialize() + { + //We start on frame 1 so that a buffer ID and frame of both 0 means uninitialized. + //The audio components use this at the time of writing this comment. + m_currentFrame = 1; + m_nextUpdateFrame = 0; + m_lastPlayedBufferID = -1; + m_ildBuffer = default; + m_queuedIldBuffers = default; + } + + public void Execute(ref ExecuteContext context) + { + bool bufferStarved = false; + + m_currentFrame++; + if (m_currentFrame >= m_nextUpdateFrame && !m_queuedIldBuffers.IsEmpty) + { + int bestIndex = -1; + for (int i = 0; i < m_queuedIldBuffers.Length; i++) + { + if (m_queuedIldBuffers[i].frame <= m_currentFrame) + { + bestIndex = i; + } + } + for (int i = 0; i < bestIndex; i++) + { + m_queuedIldBuffers.RemoveAt(0); + } + if (m_queuedIldBuffers[0].frame <= m_currentFrame) + { + m_ildBuffer = m_queuedIldBuffers[0]; + m_lastPlayedBufferID = m_ildBuffer.bufferId; //we need to report the buffer we just consumed, the audio system knows to keep that one around yet + m_nextUpdateFrame = m_ildBuffer.frame + m_ildBuffer.framesPerUpdate; + } + } + + for (int outputChannelIndex = 0; outputChannelIndex < context.Outputs.Count; outputChannelIndex++) + { + var channelOutput = context.Outputs.GetSampleBuffer(outputChannelIndex); + if (channelOutput.Channels <= 0) + continue; + var outputBuffer = channelOutput.GetBuffer(0); + if (m_ildBuffer.channelCount <= outputChannelIndex) + { + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = 0f; + } + } + else if (m_currentFrame - m_ildBuffer.frame >= m_ildBuffer.framesInBuffer) + { + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = 0f; + } + bufferStarved = true; + } + else + { + var ildBufferChannel = m_ildBuffer.bufferChannels[outputChannelIndex]; + + int bufferOffset = m_currentFrame - m_ildBuffer.frame; + bufferOffset *= outputBuffer.Length; + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = ildBufferChannel.buffer[bufferOffset + i]; + } + } + } + + if (bufferStarved && m_ildBuffer.warnIfStarved) + { + UnityEngine.Debug.LogWarning( + $"Dsp buffer starved. Kernel frame: {m_currentFrame}, IldBuffer frame: {m_ildBuffer.frame}, ildBuffer Id: {m_ildBuffer.bufferId}, frames in buffer: {m_ildBuffer.framesInBuffer}, next update frame: {m_nextUpdateFrame}, frames per update: {m_ildBuffer.framesPerUpdate}"); + } + + long packed = m_currentFrame + (((long)m_lastPlayedBufferID) << 32); + ref long location = ref UnsafeUtility.AsRef(m_packedFrameCounterBufferId); + Interlocked.Exchange(ref location, packed); + } + + public void Dispose() + { + } + } + + [BurstCompile(CompileSynchronously = true)] + internal unsafe struct ReadIldBuffersNodeUpdate : IAudioKernelUpdate + { + public IldBuffer ildBuffer; + + public void Update(ref ReadIldBuffersNode audioKernel) + { + if (audioKernel.m_queuedIldBuffers.IsEmpty) + { + audioKernel.m_queuedIldBuffers.Add(ildBuffer); + return; + } + else + { + for (int i = 0; i < audioKernel.m_queuedIldBuffers.Length; i++) + { + if (ildBuffer.frame <= audioKernel.m_queuedIldBuffers[i].frame) + { + audioKernel.m_queuedIldBuffers.Length = i; + break; + } + } + if (audioKernel.m_queuedIldBuffers.Length != audioKernel.m_queuedIldBuffers.Capacity) + { + audioKernel.m_queuedIldBuffers.Add(ildBuffer); + } + } + } + } + + [BurstCompile(CompileSynchronously = true)] + internal unsafe struct SetReadIldBuffersNodePackedFrameBufferId : IAudioKernelUpdate + { + [NativeDisableUnsafePtrRestriction] + public long* ptr; + + public void Update(ref ReadIldBuffersNode audioKernel) + { + audioKernel.m_packedFrameCounterBufferId = ptr; + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/ReadIldBuffersNode.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/ReadIldBuffersNode.cs.meta new file mode 100644 index 0000000..51e4aa7 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/ReadIldBuffersNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0dcb1b21fafbfc447b87ad863cc9a627 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/StateVariableFilterNode.cs b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/StateVariableFilterNode.cs new file mode 100644 index 0000000..e0cc13e --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/StateVariableFilterNode.cs @@ -0,0 +1,241 @@ +using System; +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +//This is a modified version of StateVariableFilter from the DSPGraph samples +namespace Latios.Myri +{ + [BurstCompile(CompileSynchronously = true)] + public struct StateVariableFilterNode : IAudioKernel + { + public enum FilterType + { + Lowpass, + Highpass, + Bandpass, + Bell, + Notch, + Lowshelf, + Highshelf + } + + public static DSPNode Create(DSPCommandBlock block, FilterType type, float cutoff, float q, float gainInDbs, int channelsPerPort) + { + var node = block.CreateDSPNode(); + block.AddInletPort(node, channelsPerPort); + block.AddOutletPort(node, channelsPerPort); + block.SetFloat(node, Parameters.FilterType, (float)type); + block.SetFloat(node, Parameters.Cutoff, cutoff); + block.SetFloat(node, Parameters.Q, q); + block.SetFloat(node, Parameters.GainInDBs, gainInDbs); + + return node; + } + + struct Coefficients + { + public float A, g, k, a1, a2, a3, m0, m1, m2; + } + + static Coefficients DesignBell(float fc, float quality, float linearGain) + { + var A = linearGain; + var g = math.tan(math.PI * fc); + var k = 1 / (quality * A); + var a1 = 1 / (1 + g * (g + k)); + var a2 = g * a1; + var a3 = g * a2; + var m0 = 1; + var m1 = k * (A * A - 1); + var m2 = 0; + return new Coefficients {A = A, g = g, k = k, a1 = a1, a2 = a2, a3 = a3, m0 = m0, m1 = m1, m2 = m2}; + } + + static Coefficients DesignLowpass(float normalizedFrequency, float Q, float linearGain) + { + var A = linearGain; + var g = math.tan(math.PI * normalizedFrequency); + var k = 1 / Q; + var a1 = 1 / (1 + g * (g + k)); + var a2 = g * a1; + var a3 = g * a2; + var m0 = 0; + var m1 = 0; + var m2 = 1; + return new Coefficients {A = A, g = g, k = k, a1 = a1, a2 = a2, a3 = a3, m0 = m0, m1 = m1, m2 = m2}; + } + + static Coefficients DesignBandpass(float normalizedFrequency, float Q, float linearGain) + { + var coefficients = Design(FilterType.Lowpass, normalizedFrequency, Q, linearGain); + coefficients.m1 = 1; + coefficients.m2 = 0; + return coefficients; + } + + static Coefficients DesignHighpass(float normalizedFrequency, float Q, float linearGain) + { + var coefficients = Design(FilterType.Lowpass, normalizedFrequency, Q, linearGain); + coefficients.m0 = 1; + coefficients.m1 = -coefficients.k; + coefficients.m2 = -1; + return coefficients; + } + + static Coefficients DesignNotch(float normalizedFrequency, float Q, float linearGain) + { + var coefficients = DesignLowpass(normalizedFrequency, Q, linearGain); + coefficients.m0 = 1; + coefficients.m1 = -coefficients.k; + coefficients.m2 = 0; + return coefficients; + } + + static Coefficients DesignLowshelf(float normalizedFrequency, float Q, float linearGain) + { + var A = linearGain; + var g = math.tan(math.PI * normalizedFrequency) / math.sqrt(A); + var k = 1 / Q; + var a1 = 1 / (1 + g * (g + k)); + var a2 = g * a1; + var a3 = g * a2; + var m0 = 1; + var m1 = k * (A - 1); + var m2 = A * A - 1; + return new Coefficients {A = A, g = g, k = k, a1 = a1, a2 = a2, a3 = a3, m0 = m0, m1 = m1, m2 = m2}; + } + + static Coefficients DesignHighshelf(float normalizedFrequency, float Q, float linearGain) + { + var A = linearGain; + var g = math.tan(math.PI * normalizedFrequency) / math.sqrt(A); + var k = 1 / Q; + var a1 = 1 / (1 + g * (g + k)); + var a2 = g * a1; + var a3 = g * a2; + var m0 = A * A; + var m1 = k * (1 - A) * A; + var m2 = 1 - A * A; + return new Coefficients {A = A, g = g, k = k, a1 = a1, a2 = a2, a3 = a3, m0 = m0, m1 = m1, m2 = m2}; + } + + static Coefficients Design(FilterType type, float normalizedFrequency, float Q, float linearGain) + { + switch (type) + { + case FilterType.Lowpass: return DesignLowpass(normalizedFrequency, Q, linearGain); + case FilterType.Highpass: return DesignHighpass(normalizedFrequency, Q, linearGain); + case FilterType.Bandpass: return DesignBandpass(normalizedFrequency, Q, linearGain); + case FilterType.Bell: return DesignBell(normalizedFrequency, Q, linearGain); + case FilterType.Notch: return DesignNotch(normalizedFrequency, Q, linearGain); + case FilterType.Lowshelf: return DesignLowshelf(normalizedFrequency, Q, linearGain); + case FilterType.Highshelf: return DesignHighshelf(normalizedFrequency, Q, linearGain); + default: + throw new ArgumentException("Unknown filter type", nameof(type)); + } + } + + static Coefficients Design(FilterType filterType, float cutoff, float Q, float gainInDBs, float sampleRate) + { + var linearGain = math.pow(10, gainInDBs / 20); + switch (filterType) + { + case FilterType.Lowpass: + return DesignLowpass(cutoff / sampleRate, Q, linearGain); + case FilterType.Highpass: + return DesignHighpass(cutoff / sampleRate, Q, linearGain); + case FilterType.Bandpass: + return DesignBandpass(cutoff / sampleRate, Q, linearGain); + case FilterType.Bell: + return DesignBell(cutoff / sampleRate, Q, linearGain); + case FilterType.Notch: + return DesignNotch(cutoff / sampleRate, Q, linearGain); + case FilterType.Lowshelf: + return DesignLowshelf(cutoff / sampleRate, Q, linearGain); + case FilterType.Highshelf: + return DesignHighshelf(cutoff / sampleRate, Q, linearGain); + default: + throw new ArgumentException("Unknown filter type", nameof(filterType)); + } + } + + public struct Channel + { + public float z1, z2; + } + + FixedList512Bytes m_channels; + + public enum Parameters + { + [ParameterDefault((float)StateVariableFilterNode.FilterType.Lowpass)] + [ParameterRange((float)StateVariableFilterNode.FilterType.Lowpass, + (float)StateVariableFilterNode.FilterType.Highshelf)] + FilterType, + [ParameterDefault(5000.0f)][ParameterRange(10.0f, 22000.0f)] + Cutoff, + [ParameterDefault(1.0f)][ParameterRange(1.0f, 100.0f)] + Q, + [ParameterDefault(0.0f)][ParameterRange(-80.0f, 0.0f)] + GainInDBs + } + + public enum Providers + { + } + + public void Initialize() + { + } + + public void Execute(ref ExecuteContext context) + { + var input = context.Inputs.GetSampleBuffer(0); + var output = context.Outputs.GetSampleBuffer(0); + var channelCount = output.Channels; + var sampleFrames = output.Samples; + + while (m_channels.Length < channelCount) + { + m_channels.Add(default); + } + + var parameters = context.Parameters; + var filterType = (FilterType)parameters.GetFloat(Parameters.FilterType, 0); + var cutoff = parameters.GetFloat(Parameters.Cutoff, 0); + var q = parameters.GetFloat(Parameters.Q, 0); + var gain = parameters.GetFloat(Parameters.GainInDBs, 0); + var coefficients = Design(filterType, cutoff, q, gain, context.SampleRate); + + for (var c = 0; c < channelCount; c++) + { + var inputBuffer = input.GetBuffer(c); + var outputBuffer = output.GetBuffer(c); + + var z1 = m_channels[c].z1; + var z2 = m_channels[c].z2; + + for (var i = 0; i < sampleFrames; ++i) + { + var x = inputBuffer[i]; + var v3 = x - z2; + var v1 = coefficients.a1 * z1 + coefficients.a2 * v3; + var v2 = z2 + coefficients.a2 * z1 + coefficients.a3 * v3; + z1 = 2 * v1 - z1; + z2 = 2 * v2 - z2; + outputBuffer[i] = coefficients.A * (coefficients.m0 * x + coefficients.m1 * v1 + coefficients.m2 * v2); + } + + m_channels[c] = new Channel {z1 = z1, z2 = z2}; + } + } + + public void Dispose() + { + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/DspGraph/StateVariableFilterNode.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/StateVariableFilterNode.cs.meta new file mode 100644 index 0000000..005dbf9 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/DspGraph/StateVariableFilterNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5e783bab7ecf64adcbaa1030afac7c31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Interop.meta b/Packages/com.latios.latios-framework/MyriAudio/Interop.meta new file mode 100644 index 0000000..cdcd080 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Interop.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c5766bc3a90f92542a3c49dde8feb07a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Interop/InteropStructures.cs b/Packages/com.latios.latios-framework/MyriAudio/Interop/InteropStructures.cs new file mode 100644 index 0000000..66ebdb7 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Interop/InteropStructures.cs @@ -0,0 +1,26 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Myri +{ + internal unsafe struct IldBufferChannel + { + public float* buffer; + } + + internal unsafe struct IldBuffer + { + [NativeDisableUnsafePtrRestriction] + public IldBufferChannel* bufferChannels; + public int channelCount; + public int frame; + public int bufferId; + public int framesInBuffer; + public int framesPerUpdate; + public bool warnIfStarved; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Interop/InteropStructures.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Interop/InteropStructures.cs.meta new file mode 100644 index 0000000..7baf575 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Interop/InteropStructures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04a8fb351bf903b4d8c55b6c85b8b479 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs.meta new file mode 100644 index 0000000..73ca986 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7c16143a749a8d44885b59f47049f603 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/Batching.cs b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Batching.cs new file mode 100644 index 0000000..fcf2009 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Batching.cs @@ -0,0 +1,151 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Myri +{ + internal static class Batching + { + internal const int INITIAL_ALLOCATION_SIZE = 1024; + + [BurstCompile] + public struct BatchOneshotsJob : IJob + { + [ReadOnly] public NativeArray emitters; + [ReadOnly] public NativeStream.Reader pairWeights; + [ReadOnly] public NativeStream.Reader listenerEmitterPairs; //int2: listener, emitter + + public NativeList clipFrameLookups; + public NativeList batchedWeights; + public NativeList targetListenerIndices; + + public void Execute() + { + var hashmap = new NativeParallelHashMap(INITIAL_ALLOCATION_SIZE, Allocator.Temp); + if (clipFrameLookups.Capacity < INITIAL_ALLOCATION_SIZE) + clipFrameLookups.Capacity = INITIAL_ALLOCATION_SIZE; + if (batchedWeights.Capacity < INITIAL_ALLOCATION_SIZE) + batchedWeights.Capacity = INITIAL_ALLOCATION_SIZE; + if (targetListenerIndices.Capacity < INITIAL_ALLOCATION_SIZE) + targetListenerIndices.Capacity = INITIAL_ALLOCATION_SIZE; + + int streamIndices = listenerEmitterPairs.ForEachCount; + for (int streamIndex = 0; streamIndex < streamIndices; streamIndex++) + { + int countInStream = listenerEmitterPairs.BeginForEachIndex(streamIndex); + pairWeights.BeginForEachIndex(streamIndex); + + for (; countInStream > 0; countInStream--) + { + int2 listenerEmitterPairIndices = listenerEmitterPairs.Read(); + var pairWeight = pairWeights.Read(); + + var e = emitters[listenerEmitterPairIndices.y]; + if (!e.source.clip.IsCreated) + continue; + + ClipFrameListener cfl = new ClipFrameListener + { + lookup = new ClipFrameLookup { clip = e.source.clip, spawnFrameOrOffset = e.source.m_spawnedAudioFrame }, + listenerIndex = listenerEmitterPairIndices.x + }; + if (hashmap.TryGetValue(cfl, out int foundIndex)) + { + ref Weights w = ref batchedWeights.ElementAt(foundIndex); + w += pairWeight; + } + else + { + hashmap.Add(cfl, clipFrameLookups.Length); + clipFrameLookups.Add(cfl.lookup); + batchedWeights.Add(pairWeight); + targetListenerIndices.Add(cfl.listenerIndex); + } + } + listenerEmitterPairs.EndForEachIndex(); + pairWeights.EndForEachIndex(); + } + } + } + + [BurstCompile] + public struct BatchLoopedJob : IJob + { + [ReadOnly] public NativeArray emitters; + [ReadOnly] public NativeStream.Reader pairWeights; + [ReadOnly] public NativeStream.Reader listenerEmitterPairs; //int2: listener, emitter + + public NativeList clipFrameLookups; + public NativeList batchedWeights; + public NativeList targetListenerIndices; + + public void Execute() + { + var hashmap = new NativeParallelHashMap(INITIAL_ALLOCATION_SIZE, Allocator.Temp); + if (clipFrameLookups.Capacity < INITIAL_ALLOCATION_SIZE) + clipFrameLookups.Capacity = INITIAL_ALLOCATION_SIZE; + if (batchedWeights.Capacity < INITIAL_ALLOCATION_SIZE) + batchedWeights.Capacity = INITIAL_ALLOCATION_SIZE; + if (targetListenerIndices.Capacity < INITIAL_ALLOCATION_SIZE) + targetListenerIndices.Capacity = INITIAL_ALLOCATION_SIZE; + + int streamIndices = listenerEmitterPairs.ForEachCount; + for (int streamIndex = 0; streamIndex < streamIndices; streamIndex++) + { + int countInStream = listenerEmitterPairs.BeginForEachIndex(streamIndex); + pairWeights.BeginForEachIndex(streamIndex); + + for (; countInStream > 0; countInStream--) + { + int2 listenerEmitterPairIndices = listenerEmitterPairs.Read(); + var pairWeight = pairWeights.Read(); + + var e = emitters[listenerEmitterPairIndices.y]; + if (!e.source.clip.IsCreated) + continue; + ClipFrameListener cfl = new ClipFrameListener + { + lookup = new ClipFrameLookup { clip = e.source.clip, spawnFrameOrOffset = e.source.m_loopOffset }, + listenerIndex = listenerEmitterPairIndices.x + }; + if (hashmap.TryGetValue(cfl, out int foundIndex)) + { + ref Weights w = ref batchedWeights.ElementAt(foundIndex); + w += pairWeight; + } + else + { + hashmap.Add(cfl, clipFrameLookups.Length); + clipFrameLookups.Add(cfl.lookup); + batchedWeights.Add(pairWeight); + targetListenerIndices.Add(cfl.listenerIndex); + } + } + listenerEmitterPairs.EndForEachIndex(); + pairWeights.EndForEachIndex(); + } + } + } + + private struct ClipFrameListener : IEquatable + { + public ClipFrameLookup lookup; + public int listenerIndex; + + public bool Equals(ClipFrameListener other) + { + return lookup.Equals(other.lookup) && listenerIndex.Equals(other.listenerIndex); + } + + public unsafe override int GetHashCode() + { + return new int3((int)((ulong)lookup.clip.GetUnsafePtr() >> 4), lookup.spawnFrameOrOffset, listenerIndex).GetHashCode(); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/Batching.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Batching.cs.meta new file mode 100644 index 0000000..5a9d979 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Batching.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d38c8d8bd2ecfc429f20c5d7d7b9944 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/CullingAndWeighting.cs b/Packages/com.latios.latios-framework/MyriAudio/Jobs/CullingAndWeighting.cs new file mode 100644 index 0000000..eab8035 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/CullingAndWeighting.cs @@ -0,0 +1,631 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Myri +{ + internal static class CullingAndWeighting + { + public const int BATCH_SIZE = 128; + + //Parallel + //The weighting algorithm is fairly pricey + [BurstCompile] + public struct OneshotsJob : IJobParallelForBatch + { + [ReadOnly] public NativeList listenersWithTransforms; + [ReadOnly] public NativeArray emitters; + [NativeDisableParallelForRestriction] public NativeStream.Writer weights; + [NativeDisableParallelForRestriction] public NativeStream.Writer listenerEmitterPairs; //int2: listener, emitter + + public void Execute(int startIndex, int count) + { + var scratchCache = new NativeList(Allocator.Temp); + + var baseWeights = new NativeArray(listenersWithTransforms.Length, Allocator.Temp, NativeArrayOptions.ClearMemory); + for (int i = 0; i < listenersWithTransforms.Length; i++) + { + int c = listenersWithTransforms[i].listener.ildProfile.Value.anglesPerLeftChannel.Length + + listenersWithTransforms[i].listener.ildProfile.Value.anglesPerRightChannel.Length; + Weights w = default; + for (int j = 0; j < c; j++) + { + w.channelWeights.Add(0f); + } + c = listenersWithTransforms[i].listener.itdResolution; + c = 2 * c + 1; + for (int j = 0; j < c; j++) + { + w.itdWeights.Add(0f); + } + baseWeights[i] = w; + } + + listenerEmitterPairs.BeginForEachIndex(startIndex / BATCH_SIZE); + weights.BeginForEachIndex(startIndex / BATCH_SIZE); + + for (int i = startIndex; i < startIndex + count; i++) + { + var emitter = emitters[i]; + for (int j = 0; j < listenersWithTransforms.Length; j++) + { + if (math.distancesq(emitter.transform.pos, + listenersWithTransforms[j].transform.pos) < emitter.source.outerRange * emitter.source.outerRange && emitter.source.clip.IsCreated) + { + var w = baseWeights[j]; + + EmitterParameters e = new EmitterParameters + { + cone = emitter.cone, + innerRange = emitter.source.innerRange, + outerRange = emitter.source.outerRange, + rangeFadeMargin = emitter.source.rangeFadeMargin, + transform = emitter.transform, + useCone = emitter.useCone, + volume = emitter.source.volume + }; + //ComputeWeights(ref w, e, in listenersWithTransforms.ElementAt(j), scratchCache); + ComputeWeights(ref w, e, listenersWithTransforms[j], scratchCache); + + weights.Write(w); + listenerEmitterPairs.Write(new int2(j, i)); + } + } + } + + listenerEmitterPairs.EndForEachIndex(); + weights.EndForEachIndex(); + } + } + + //Parallel + //The weighting algorithm is fairly pricey + [BurstCompile] + public struct LoopedJob : IJobParallelForBatch + { + [ReadOnly] public NativeList listenersWithTransforms; + [ReadOnly] public NativeArray emitters; + [NativeDisableParallelForRestriction] public NativeStream.Writer weights; + [NativeDisableParallelForRestriction] public NativeStream.Writer listenerEmitterPairs; //int2: listener, emitter + + public void Execute(int startIndex, int count) + { + var scratchCache = new NativeList(Allocator.Temp); + + var baseWeights = new NativeArray(listenersWithTransforms.Length, Allocator.Temp, NativeArrayOptions.ClearMemory); + for (int i = 0; i < listenersWithTransforms.Length; i++) + { + int c = listenersWithTransforms[i].listener.ildProfile.Value.anglesPerLeftChannel.Length + + listenersWithTransforms[i].listener.ildProfile.Value.anglesPerRightChannel.Length; + Weights w = default; + for (int j = 0; j < c; j++) + { + w.channelWeights.Add(0f); + } + c = listenersWithTransforms[i].listener.itdResolution; + c = 2 * c + 1; + for (int j = 0; j < c; j++) + { + w.itdWeights.Add(0f); + } + baseWeights[i] = w; + } + + listenerEmitterPairs.BeginForEachIndex(startIndex / BATCH_SIZE); + weights.BeginForEachIndex(startIndex / BATCH_SIZE); + + for (int i = startIndex; i < startIndex + count; i++) + { + var emitter = emitters[i]; + for (int j = 0; j < listenersWithTransforms.Length; j++) + { + if (math.distancesq(emitter.transform.pos, + listenersWithTransforms[j].transform.pos) < emitter.source.outerRange * emitter.source.outerRange && emitter.source.clip.IsCreated) + { + var w = baseWeights[j]; + + EmitterParameters e = new EmitterParameters + { + cone = emitter.cone, + innerRange = emitter.source.innerRange, + outerRange = emitter.source.outerRange, + rangeFadeMargin = emitter.source.rangeFadeMargin, + transform = emitter.transform, + useCone = emitter.useCone, + volume = emitter.source.volume + }; + ComputeWeights(ref w, e, listenersWithTransforms[j], scratchCache); + + weights.Write(w); + listenerEmitterPairs.Write(new int2(j, i)); + } + } + } + + listenerEmitterPairs.EndForEachIndex(); + weights.EndForEachIndex(); + } + } + + private struct EmitterParameters + { + public float volume; + public float innerRange; + public float outerRange; + public float rangeFadeMargin; + + public RigidTransform transform; + public AudioSourceEmitterCone cone; + public bool useCone; + } + + private static void ComputeWeights(ref Weights weights, EmitterParameters emitter, ListenerWithTransform listener, NativeList scratchCache) + { + float volume = emitter.volume * listener.listener.volume; + + var emitterInListenerSpace = math.mul(math.inverse(listener.transform), emitter.transform); + var emitterPositionNormalized = math.normalizesafe(emitterInListenerSpace.pos, float3.zero); + + //attenuation + { + float d = math.length(emitterInListenerSpace.pos); + float atten = 1f; + if (d > emitter.innerRange) + { + if (emitter.innerRange <= 0f) + { + //The offset is the distance from the innerRange minus 1 unit clamped between the innerRange and the margin. + //The minus one offset ensures the falloff is always 1 or larger, making the transition betweem the innerRange + //and the falloff region continuous (by calculus terminology). + float falloff = math.min(d, emitter.outerRange - emitter.rangeFadeMargin) - (emitter.innerRange - 1f); + atten = math.saturate(math.rcp(falloff * falloff)); + } + else + { + float falloff = math.min(d, emitter.outerRange - emitter.rangeFadeMargin) / emitter.innerRange; + atten = math.saturate(math.rcp(falloff * falloff)); + } + } + if (d > emitter.outerRange - emitter.rangeFadeMargin) + { + float factor = (d - (emitter.outerRange - emitter.rangeFadeMargin)) / emitter.rangeFadeMargin; + factor = math.saturate(factor); + atten = math.lerp(atten, 0f, factor); + } + + if (emitter.useCone) + { + float cosine = math.dot(math.forward(emitterInListenerSpace.rot), -emitterPositionNormalized); + if (cosine <= emitter.cone.cosOuterAngle) + { + atten *= emitter.cone.outerAngleAttenuation; + } + else if (cosine < emitter.cone.cosInnerAngle) + { + float factor = math.unlerp(emitter.cone.cosOuterAngle, emitter.cone.cosInnerAngle, cosine); + atten *= math.lerp(emitter.cone.outerAngleAttenuation, 1f, factor); + } + } + volume *= atten; + } + + //ITD + { + float itd = (math.dot(emitterPositionNormalized, math.right()) * 0.5f + 0.5f) * weights.itdWeights.Length; + //float frac = math.modf(itd, out float integer); + //int indexLow = math.clamp((int)integer, 0, weights.itdWeights.Length - 1); + //int indexHigh = math.clamp(indexLow + 1, 0, weights.itdWeights.Length - 1); + //weights.itdWeights[indexLow] = volume * frac; + //weights.itdWeights[indexHigh] = volume * (1f - frac); + int index = math.clamp((int)math.round(itd), 0, weights.itdWeights.Length - 1); + weights.itdWeights[index] = volume; + } + + //ILD + { + ref var profile = ref listener.listener.ildProfile.Value; + + float2 xz = math.normalizesafe(emitterPositionNormalized.xz, new float2(0f, 1f)); + float2 angles = default; + angles.x = math.atan2(xz.y, xz.x); + float2 yz = math.normalizesafe(emitterPositionNormalized.yz, new float2(1f, 0f)); + angles.y = math.atan2(yz.y, yz.x); + + //Left + //First, find if there is a perfect match + bool perfectMatch = false; + for (int i = 0; i < profile.anglesPerLeftChannel.Length; i++) + { + perfectMatch = math.all(((angles >= profile.anglesPerLeftChannel[i].xz) & + (angles <= profile.anglesPerLeftChannel[i].yw)) | + ((angles + 2f * math.PI >= profile.anglesPerLeftChannel[i].xz) & + (angles + 2f * math.PI <= profile.anglesPerLeftChannel[i].yw))); + if (perfectMatch) + { + weights.channelWeights[i] = 1f; + perfectMatch = true; + break; + } + } + + if (!perfectMatch) + { + //No perfect match. + int4 bestMinMaxXYIndices = default; //This should always be overwritten + float4 bestAngleDeltas = new float4(2f * math.PI, -2f * math.PI, 2f * math.PI, -2f * math.PI); + FixedList128Bytes candidateChannels = default; + FixedList128Bytes candidateDistances = default; + + //Find our limits + scratchCache.Clear(); + scratchCache.AddRangeFromBlob(ref profile.anglesPerLeftChannel); + var leftChannelDeltas = scratchCache.AsArray(); + FixedList512Bytes leftChannelInsides = default; + + for (int i = 0; i < leftChannelDeltas.Length; i++) + { + var delta = leftChannelDeltas[i] - angles.xxyy; + var temp = delta; + delta += math.select(0f, new float4(2f * math.PI, -2f * math.PI, 2f * math.PI, -2f * math.PI), delta * new float4(1f, -1f, 1f, -1f) < 0f); + delta -= math.select(0f, + new float4(2f * math.PI, -2f * math.PI, 2f * math.PI, -2f * math.PI), + delta * new float4(1f, -1f, 1f, -1f) >= 2f * math.PI); + temp -= math.select(0f, 2f * math.PI, temp.xxzz > 0f); + bool2 inside = temp.yw >= 0f; + leftChannelDeltas[i] = delta; + leftChannelInsides.Add(inside); + } + //By this point, any delta should be (positive, negative, positive, negative) + + //Find our search region + for (int i = 0; i < leftChannelDeltas.Length; i++) + { + bool2 inside = leftChannelInsides[i]; + var delta = leftChannelDeltas[i]; + if (inside.x) + { + //above + if (delta.z <= bestAngleDeltas.z) + { + bestAngleDeltas.z = delta.z; + bestMinMaxXYIndices.z = i; + } + //below + if (delta.w >= bestAngleDeltas.w) + { + bestAngleDeltas.w = delta.w; + bestMinMaxXYIndices.w = i; + } + } + if (inside.y) + { + //right + if (delta.x <= bestAngleDeltas.x) + { + bestAngleDeltas.x = delta.x; + bestMinMaxXYIndices.x = i; + } + //left + if (delta.y >= bestAngleDeltas.y) + { + bestAngleDeltas.y = delta.y; + bestMinMaxXYIndices.y = i; + } + } + } + + //Add our constraining indices to the pot + var bestAngleDistances = math.abs(bestAngleDeltas); + candidateChannels.Add(bestMinMaxXYIndices.x); + candidateDistances.Add(bestAngleDistances.x); + if (bestMinMaxXYIndices.x != bestMinMaxXYIndices.y) + { + candidateChannels.Add(bestMinMaxXYIndices.y); + candidateDistances.Add(bestAngleDistances.y); + } + else + candidateDistances[0] = math.min(candidateDistances[0], bestAngleDistances.y); + + if (math.all(bestMinMaxXYIndices.xy != bestMinMaxXYIndices.z)) + { + candidateChannels.Add(bestMinMaxXYIndices.z); + candidateDistances.Add(bestAngleDistances.z); + } + else if (bestMinMaxXYIndices.x == bestMinMaxXYIndices.z) + candidateDistances[0] = math.min(candidateDistances[0], bestAngleDistances.z); + else + candidateDistances[1] = math.min(candidateDistances[1], bestAngleDistances.z); + + if (math.all(bestMinMaxXYIndices.xyz != bestMinMaxXYIndices.w)) + { + candidateChannels.Add(bestMinMaxXYIndices.w); + candidateDistances.Add(bestAngleDistances.w); + } + else if (bestMinMaxXYIndices.x == bestMinMaxXYIndices.w) + candidateDistances[0] = math.min(candidateDistances[0], bestAngleDistances.w); + else if (bestMinMaxXYIndices.y == bestMinMaxXYIndices.w) + candidateDistances[1] = math.min(candidateDistances[1], bestAngleDistances.w); + else + candidateDistances[candidateDistances.Length - 1] = math.min(candidateDistances[candidateDistances.Length - 1], bestAngleDistances.w); + + //Add additional candidates + for (int i = 0; i < leftChannelDeltas.Length; i++) + { + if (math.any(i == bestMinMaxXYIndices)) + continue; + + float4 delta = leftChannelDeltas[i]; + bool added = false; + int c = candidateDistances.Length; + if (math.all(delta.xz < bestAngleDeltas.xz)) + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.xz)); + added = true; + } + if (delta.y > bestAngleDeltas.y && delta.z < bestAngleDeltas.z) + { + if (added) + { + candidateDistances[c] = math.min(candidateDistances[c], math.length(delta.yz)); + } + else + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.yz)); + added = true; + } + } + if (delta.x < bestAngleDeltas.x && delta.w < bestAngleDeltas.w) + { + if (added) + { + candidateDistances[c] = math.min(candidateDistances[c], math.length(delta.xw)); + } + else + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.xw)); + added = true; + } + } + if (math.all(delta.yw > bestAngleDeltas.yw)) + { + if (added) + { + candidateDistances[c] = math.min(candidateDistances[c], math.length(delta.yw)); + } + else + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.yw)); + } + } + } + + //Compute weights + float sum = 0f; + for (int i = 0; i < candidateDistances.Length; i++) + { + candidateDistances[i] = 1f / candidateDistances[i]; + sum += candidateDistances[i]; + } + for (int i = 0; i < candidateDistances.Length; i++) + { + weights.channelWeights[candidateChannels[i]] = candidateDistances[i] / sum; + } + } + + //Right + //First, find if there is a perfect match + perfectMatch = false; + for (int i = 0; i < profile.anglesPerRightChannel.Length; i++) + { + perfectMatch = math.all(((angles >= profile.anglesPerRightChannel[i].xz) & + (angles <= profile.anglesPerRightChannel[i].yw)) | + ((angles + 2f * math.PI >= profile.anglesPerRightChannel[i].xz) & + (angles + 2f * math.PI <= profile.anglesPerRightChannel[i].yw))); + if (perfectMatch) + { + weights.channelWeights[i + profile.anglesPerLeftChannel.Length] = 1f; + perfectMatch = true; + break; + } + } + + if (!perfectMatch) + { + //No perfect match. + int4 bestMinMaxXYIndices = default; //This should always be overwritten + float4 bestAngleDeltas = new float4(2f * math.PI, -2f * math.PI, 2f * math.PI, -2f * math.PI); + FixedList128Bytes candidateChannels = default; + FixedList128Bytes candidateDistances = default; + + //Find our limits + scratchCache.Clear(); + scratchCache.AddRangeFromBlob(ref profile.anglesPerRightChannel); + var rightChannelDeltas = scratchCache.AsArray(); + FixedList512Bytes rightChannelInsides = default; + + for (int i = 0; i < rightChannelDeltas.Length; i++) + { + var delta = rightChannelDeltas[i] - angles.xxyy; + var temp = delta; + delta += math.select(0f, new float4(2f * math.PI, -2f * math.PI, 2f * math.PI, -2f * math.PI), delta * new float4(1f, -1f, 1f, -1f) < 0f); + delta -= math.select(0f, + new float4(2f * math.PI, -2f * math.PI, 2f * math.PI, -2f * math.PI), + delta * new float4(1f, -1f, 1f, -1f) >= 2f * math.PI); + temp -= math.select(0f, 2f * math.PI, temp.xxzz > 0f); + bool2 inside = temp.yw >= 0f; + rightChannelDeltas[i] = delta; + rightChannelInsides.Add(inside); + } + //By this point, any delta should be (positive, negative, positive, negative) + + //Find our search region + for (int i = 0; i < rightChannelDeltas.Length; i++) + { + bool2 inside = rightChannelInsides[i]; + var delta = rightChannelDeltas[i]; + if (inside.x) + { + //above + if (delta.z <= bestAngleDeltas.z) + { + bestAngleDeltas.z = delta.z; + bestMinMaxXYIndices.z = i; + } + //below + if (delta.w >= bestAngleDeltas.w) + { + bestAngleDeltas.w = delta.w; + bestMinMaxXYIndices.w = i; + } + } + if (inside.y) + { + //right + if (delta.x <= bestAngleDeltas.x) + { + bestAngleDeltas.x = delta.x; + bestMinMaxXYIndices.x = i; + } + //left + if (delta.y >= bestAngleDeltas.y) + { + bestAngleDeltas.y = delta.y; + bestMinMaxXYIndices.y = i; + } + } + } + + //Add our constraining indices to the pot + var bestAngleDistances = math.abs(bestAngleDeltas); + candidateChannels.Add(bestMinMaxXYIndices.x); + candidateDistances.Add(bestAngleDistances.x); + if (bestMinMaxXYIndices.x != bestMinMaxXYIndices.y) + { + candidateChannels.Add(bestMinMaxXYIndices.y); + candidateDistances.Add(bestAngleDistances.y); + } + else + candidateDistances[0] = math.min(candidateDistances[0], bestAngleDistances.y); + + if (math.all(bestMinMaxXYIndices.xy != bestMinMaxXYIndices.z)) + { + candidateChannels.Add(bestMinMaxXYIndices.z); + candidateDistances.Add(bestAngleDistances.z); + } + else if (bestMinMaxXYIndices.x == bestMinMaxXYIndices.z) + candidateDistances[0] = math.min(candidateDistances[0], bestAngleDistances.z); + else + candidateDistances[1] = math.min(candidateDistances[1], bestAngleDistances.z); + + if (math.all(bestMinMaxXYIndices.xyz != bestMinMaxXYIndices.w)) + { + candidateChannels.Add(bestMinMaxXYIndices.w); + candidateDistances.Add(bestAngleDistances.w); + } + else if (bestMinMaxXYIndices.x == bestMinMaxXYIndices.w) + candidateDistances[0] = math.min(candidateDistances[0], bestAngleDistances.w); + else if (bestMinMaxXYIndices.y == bestMinMaxXYIndices.w) + candidateDistances[1] = math.min(candidateDistances[1], bestAngleDistances.w); + else + candidateDistances[candidateDistances.Length - 1] = math.min(candidateDistances[candidateDistances.Length - 1], bestAngleDistances.w); + + //Add additional candidates + for (int i = 0; i < rightChannelDeltas.Length; i++) + { + if (math.any(i == bestMinMaxXYIndices)) + continue; + + float4 delta = rightChannelDeltas[i]; + bool added = false; + int c = candidateDistances.Length; + if (math.all(delta.xz < bestAngleDeltas.xz)) + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.xz)); + added = true; + } + if (delta.y > bestAngleDeltas.y && delta.z < bestAngleDeltas.z) + { + if (added) + { + candidateDistances[c] = math.min(candidateDistances[c], math.length(delta.yz)); + } + else + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.yz)); + added = true; + } + } + if (delta.x < bestAngleDeltas.x && delta.w < bestAngleDeltas.w) + { + if (added) + { + candidateDistances[c] = math.min(candidateDistances[c], math.length(delta.xw)); + } + else + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.xw)); + added = true; + } + } + if (math.all(delta.yw > bestAngleDeltas.yw)) + { + if (added) + { + candidateDistances[c] = math.min(candidateDistances[c], math.length(delta.yw)); + } + else + { + candidateChannels.Add(i); + candidateDistances.Add(math.length(delta.yw)); + } + } + } + + //Compute weights + float sum = 0f; + for (int i = 0; i < candidateDistances.Length; i++) + { + candidateDistances[i] = 1f / candidateDistances[i]; + sum += candidateDistances[i]; + } + for (int i = 0; i < candidateDistances.Length; i++) + { + weights.channelWeights[candidateChannels[i] + profile.anglesPerLeftChannel.Length] = candidateDistances[i] / sum; + } + } + + //Combine left and right + float combinedWeightSum = 0f; + for (int i = 0; i < profile.anglesPerLeftChannel.Length; i++) + { + weights.channelWeights[i] *= profile.filterVolumesPerLeftChannel[i] * (1f - profile.passthroughFractionsPerLeftChannel[i]) + + profile.passthroughVolumesPerLeftChannel[i] * profile.passthroughFractionsPerLeftChannel[i]; + combinedWeightSum += weights.channelWeights[i]; + } + for (int i = 0; i < profile.anglesPerRightChannel.Length; i++) + { + weights.channelWeights[i + profile.anglesPerLeftChannel.Length] *= profile.filterVolumesPerRightChannel[i] * + (1f - profile.passthroughFractionsPerRightChannel[i]) + + profile.passthroughVolumesPerRightChannel[i] * + profile.passthroughFractionsPerRightChannel[i]; + combinedWeightSum += weights.channelWeights[i + profile.anglesPerLeftChannel.Length]; + } + for (int i = 0; i < weights.channelWeights.Length; i++) + { + weights.channelWeights[i] /= combinedWeightSum; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/CullingAndWeighting.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs/CullingAndWeighting.cs.meta new file mode 100644 index 0000000..d6c9917 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/CullingAndWeighting.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0be7b0abb2a942a47b1bb1adbb3f3c75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/GraphHandling.cs b/Packages/com.latios.latios-framework/MyriAudio/Jobs/GraphHandling.cs new file mode 100644 index 0000000..286a793 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/GraphHandling.cs @@ -0,0 +1,545 @@ +using System.Threading; +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Myri +{ + internal static class GraphHandling + { + [BurstCompile] + public struct CaptureIldFrameJob : IJob + { + public NativeReference packedFrameCounterBufferId; //MSB bufferId, LSB frame + public NativeReference audioFrame; + public NativeReference lastPlayedAudioFrame; + public NativeReference lastReadBufferId; + + public NativeQueue audioFrameHistory; + [ReadOnly] public ComponentDataFromEntity audioSettingsCdfe; + public Entity worldBlackboardEntity; + + public unsafe void Execute() + { + ref long packedRef = ref UnsafeUtility.AsRef(packedFrameCounterBufferId.GetUnsafePtr()); + long frameData = Interlocked.Read(ref packedRef); + int frame = (int)(frameData & 0xffffffff); + int bufferId = (int)(frameData >> 32); + + while (!audioFrameHistory.IsEmpty() && audioFrameHistory.Peek().bufferId < bufferId) + { + audioFrameHistory.Dequeue(); + } + int targetFrame = frame + 1 + math.max(audioSettingsCdfe[worldBlackboardEntity].lookaheadAudioFrames, 0); + if (!audioFrameHistory.IsEmpty() && audioFrameHistory.Peek().bufferId == bufferId) + { + targetFrame = math.max(audioFrameHistory.Peek().expectedNextUpdateFrame, targetFrame); + } + audioFrame.Value = targetFrame; + lastReadBufferId.Value = bufferId; + lastPlayedAudioFrame.Value = frame; + } + } + + //Temporary workaround: DSPGraph updates can't be scheduled from Burst + // [BurstCompile] + public struct UpdateListenersGraphJob : IJob + { + [ReadOnly] public NativeArray listenerEntities; + [ReadOnly] public NativeArray destroyedListenerEntities; + [ReadOnly] public ComponentDataFromEntity listenerCdfe; + public ComponentDataFromEntity listenerGraphStateCdfe; + public ComponentDataFromEntity listenerOutputGraphStateCdfe; + public EntityCommandBuffer ecb; + + [ReadOnly] public ComponentDataFromEntity audioSettingsCdfe; + public Entity worldBlackboardEntity; + + [ReadOnly] public NativeReference audioFrame; + public NativeQueue audioFrameHistory; + + public NativeList systemMixNodePortFreelist; + public NativeReference systemMixNodePortCount; + public DSPNode systemMixNode; + + public NativeReference systemIldNodePortCount; + public DSPNode systemIldNode; + + public DSPCommandBlock commandBlock; + + public NativeArray listenerBufferParameters; + public NativeList forIndexToListenerAndChannelIndices; + public NativeList outputSamplesMegaBuffer; + public NativeList outputSamplesMegaBufferChannels; + + public int samplesPerFrame; + public int bufferId; + + public unsafe void Execute() + { + bool dirty = false; + var existingEntities = new NativeList(Allocator.Temp); + var newEntities = new NativeList(Allocator.Temp); + var newOutputGraphStates = new NativeList(Allocator.Temp); + var newListenerGraphStates = new NativeList(Allocator.Temp); + int megaBufferSampleCount = 0; + var channelCounts = new NativeArray(listenerBufferParameters.Length, Allocator.Temp); + + var audioSettings = audioSettingsCdfe[worldBlackboardEntity]; + audioSettings.audioFramesPerUpdate = math.max(audioSettings.audioFramesPerUpdate, 1); + audioSettings.safetyAudioFrames = math.max(audioSettings.safetyAudioFrames, 0); + audioSettings.lookaheadAudioFrames = math.max(audioSettings.lookaheadAudioFrames, 0); + + //Destroy graph state and components of old entities + for (int i = 0; i < destroyedListenerEntities.Length; i++) + { + var entity = destroyedListenerEntities[i]; + dirty = true; + var listenerOutputGraphState = listenerOutputGraphStateCdfe[entity]; + commandBlock.Disconnect(listenerOutputGraphState.connection); + systemMixNodePortFreelist.Add(listenerOutputGraphState.portIndex); + + var listenerGraphState = listenerGraphStateCdfe[entity]; + for (int j = 0; j < listenerGraphState.connections.Length; j++) + { + commandBlock.Disconnect(listenerGraphState.connections[j]); + } + for (int j = 0; j < listenerGraphState.ildConnections.Length; j++) + { + commandBlock.Disconnect(listenerGraphState.ildConnections[j].connection); + } + for (int j = 0; j < listenerGraphState.nodes.Length; j++) + { + commandBlock.ReleaseDSPNode(listenerGraphState.nodes[j]); + } + + listenerGraphState.connections.Dispose(); + listenerGraphState.nodes.Dispose(); + listenerGraphState.ildConnections.Dispose(); + ecb.RemoveComponent( entity); + ecb.RemoveComponent(entity); + } + + //Process new and changed listeners + for (int i = 0; i < listenerEntities.Length; i++) + { + var entity = listenerEntities[i]; + + if (!listenerGraphStateCdfe.HasComponent(entity)) + { + dirty = true; + var listener = listenerCdfe[entity]; + var listenerGraphState = new ListenerGraphState + { + connections = new UnsafeList(8, Allocator.Persistent), + nodes = new UnsafeList(8, Allocator.Persistent), + ildConnections = new UnsafeList(8, Allocator.Persistent), + lastUsedProfile = listener.ildProfile + }; + + //Create the output MixPortsToStereoNode and tie it to the final mix + int mixNodePortIndex; + if (systemMixNodePortFreelist.Length > 0) + { + mixNodePortIndex = systemMixNodePortFreelist[systemMixNodePortFreelist.Length - 1]; + systemMixNodePortFreelist.RemoveAt(systemMixNodePortFreelist.Length - 1); + } + else + { + mixNodePortIndex = systemMixNodePortCount.Value; + commandBlock.AddInletPort(systemMixNode, 2); + systemMixNodePortCount.Value++; + } + var listenerMixNode = commandBlock.CreateDSPNode(); + commandBlock.AddOutletPort(listenerMixNode, 2); + + listenerGraphState.nodes.Add(listenerMixNode); + var listenerOutputGraphState = new EntityOutputGraphState + { + connection = commandBlock.Connect(listenerMixNode, 0, systemMixNode, mixNodePortIndex), + portIndex = mixNodePortIndex + }; + + ref var profile = ref listener.ildProfile.Value; + + BuildChannelGraph(ref profile, commandBlock, listenerMixNode, ref listenerGraphState); + + //Write out entity data + newEntities.Add(entity); + newOutputGraphStates.Add(listenerOutputGraphState); + newListenerGraphStates.Add(listenerGraphState); + + //Compute parameters and megabuffer allocation + int numChannels = profile.passthroughFractionsPerLeftChannel.Length + profile.passthroughFractionsPerRightChannel.Length; + listenerBufferParameters[i] = new ListenerBufferParameters + { + bufferStart = megaBufferSampleCount, + leftChannelsCount = profile.passthroughFractionsPerLeftChannel.Length, + samplesPerChannel = samplesPerFrame * (audioSettings.audioFramesPerUpdate + audioSettings.safetyAudioFrames) + }; + for (int j = 0; j < numChannels; j++) + { + forIndexToListenerAndChannelIndices.Add(new int2(i, j)); + } + megaBufferSampleCount += listenerBufferParameters[i].samplesPerChannel * numChannels; + channelCounts[i] = numChannels; + } + else + { + var listener = listenerCdfe[entity]; + ref var profile = ref listener.ildProfile.Value; + var listenerGraphState = listenerGraphStateCdfe[entity]; + if (listener.ildProfile != listenerGraphState.lastUsedProfile) + { + dirty = true; + //Swap the old MixPortsToStereoNode with a new one + var listenerOutputGraphState = listenerOutputGraphStateCdfe[entity]; + var listenerMixNode = + commandBlock.CreateDSPNode(); + commandBlock.AddOutletPort(listenerMixNode, 2); + commandBlock.Disconnect(listenerOutputGraphState.connection); + listenerOutputGraphState.connection = commandBlock.Connect(listenerMixNode, 0, systemMixNode, listenerOutputGraphState.portIndex); + + //Destroy the old graph + for (int j = 0; j < listenerGraphState.connections.Length; j++) + { + commandBlock.Disconnect(listenerGraphState.connections[j]); + } + for (int j = 0; j < listenerGraphState.ildConnections.Length; j++) + { + commandBlock.Disconnect(listenerGraphState.ildConnections[j].connection); + } + for (int j = 0; j < listenerGraphState.nodes.Length; j++) + { + commandBlock.ReleaseDSPNode(listenerGraphState.nodes[j]); + } + listenerGraphState.connections.Clear(); + listenerGraphState.nodes.Clear(); + listenerGraphState.ildConnections.Clear(); + + //Set up the new graph + listenerGraphState.lastUsedProfile = listener.ildProfile; + listenerGraphState.nodes.Add(listenerMixNode); + + BuildChannelGraph(ref profile, commandBlock, listenerMixNode, ref listenerGraphState); + + //Write out entity data + listenerGraphStateCdfe[entity] = listenerGraphState; + listenerOutputGraphStateCdfe[entity] = listenerOutputGraphState; + } + existingEntities.Add(entity); + + //Compute parameters and megabuffer allocation + int numChannels = profile.passthroughFractionsPerLeftChannel.Length + profile.passthroughFractionsPerRightChannel.Length; + listenerBufferParameters[i] = new ListenerBufferParameters + { + bufferStart = megaBufferSampleCount, + leftChannelsCount = profile.passthroughFractionsPerLeftChannel.Length, + samplesPerChannel = samplesPerFrame * (audioSettings.audioFramesPerUpdate + audioSettings.safetyAudioFrames) + }; + for (int j = 0; j < numChannels; j++) + { + forIndexToListenerAndChannelIndices.Add(new int2(i, j)); + } + megaBufferSampleCount += listenerBufferParameters[i].samplesPerChannel * numChannels; + channelCounts[i] = numChannels; + } + } + + //Rebuild ildConnections + if (dirty) + { + //Reset connections for existing entities + for (int i = 0; i < existingEntities.Length; i++) + { + var entity = existingEntities[i]; + var listenerGraphState = listenerGraphStateCdfe[entity]; + for (int j = 0; j < listenerGraphState.ildConnections.Length; j++) + { + var ildConnection = listenerGraphState.ildConnections[j]; + if (ildConnection.ildOutputPort >= 0) + { + commandBlock.Disconnect(ildConnection.connection); + ildConnection.ildOutputPort = -ildConnection.ildOutputPort - 1; + listenerGraphState.ildConnections[j] = ildConnection; + } + } + listenerGraphStateCdfe[entity] = listenerGraphState; + } + + var fakePortRealPortHashmap = new NativeParallelHashMap(8, Allocator.Temp); + int ildPortsUsed = 0; + //Build connections for existing entities + for (int i = 0; i < existingEntities.Length; i++) + { + var entity = existingEntities[i]; + var listenerGraphState = listenerGraphStateCdfe[entity]; + fakePortRealPortHashmap.Clear(); + for (int j = 0; j < listenerGraphState.ildConnections.Length; j++) + { + var ildConnection = listenerGraphState.ildConnections[j]; + if (fakePortRealPortHashmap.TryGetValue(ildConnection.ildOutputPort, out int realPort)) + { + ildConnection.ildOutputPort = realPort; + } + else + { + if (ildPortsUsed >= systemIldNodePortCount.Value) + { + commandBlock.AddOutletPort(systemIldNode, 1); + systemIldNodePortCount.Value++; + } + fakePortRealPortHashmap.Add(ildConnection.ildOutputPort, ildPortsUsed); + ildConnection.ildOutputPort = ildPortsUsed; + ildPortsUsed++; + } + ildConnection.connection = commandBlock.Connect(systemIldNode, ildConnection.ildOutputPort, ildConnection.node, ildConnection.nodeInputPort); + if (ildConnection.attenuation != 1f) + commandBlock.SetAttenuation(ildConnection.connection, ildConnection.attenuation); + listenerGraphState.ildConnections[j] = ildConnection; + } + listenerGraphStateCdfe[entity] = listenerGraphState; + } + //Build connections fo new entities + for (int i = 0; i < newEntities.Length; i++) + { + var listenerGraphState = newListenerGraphStates[i]; + fakePortRealPortHashmap.Clear(); + for (int j = 0; j < listenerGraphState.ildConnections.Length; j++) + { + var ildConnection = listenerGraphState.ildConnections[j]; + if (fakePortRealPortHashmap.TryGetValue(ildConnection.ildOutputPort, out int realPort)) + { + ildConnection.ildOutputPort = realPort; + } + else + { + if (ildPortsUsed >= systemIldNodePortCount.Value) + { + commandBlock.AddOutletPort(systemIldNode, 1); + systemIldNodePortCount.Value++; + } + fakePortRealPortHashmap.Add(ildConnection.ildOutputPort, ildPortsUsed); + ildConnection.ildOutputPort = ildPortsUsed; + ildPortsUsed++; + } + ildConnection.connection = commandBlock.Connect(systemIldNode, ildConnection.ildOutputPort, ildConnection.node, ildConnection.nodeInputPort); + if (ildConnection.attenuation != 1f) + commandBlock.SetAttenuation(ildConnection.connection, ildConnection.attenuation); + listenerGraphState.ildConnections[j] = ildConnection; + } + newListenerGraphStates[i] = listenerGraphState; + } + } + + //Add components to new entities + for (int i = 0; i < newEntities.Length; i++) + { + ecb.AddComponent(newEntities[i], newListenerGraphStates[i]); + ecb.AddComponent(newEntities[i], newOutputGraphStates[i]); + } + + //Resize megaBuffer and populate offsets + outputSamplesMegaBuffer.Resize(megaBufferSampleCount, NativeArrayOptions.ClearMemory); + var megaBuffer = outputSamplesMegaBuffer.AsArray(); + for (int i = 0; i < listenerBufferParameters.Length; i++) + { + var parameters = listenerBufferParameters[i]; + for (int j = 0; j < channelCounts[i]; j++) + { + var subBuffer = megaBuffer.GetSubArray(parameters.bufferStart + parameters.samplesPerChannel * j, parameters.samplesPerChannel); + outputSamplesMegaBufferChannels.Add(new IldBufferChannel + { + buffer = (float*)subBuffer.GetUnsafePtr() + }); + } + } + audioFrameHistory.Enqueue(new AudioFrameBufferHistoryElement + { + audioFrame = audioFrame.Value, + bufferId = bufferId, + expectedNextUpdateFrame = audioFrame.Value + audioSettings.audioFramesPerUpdate + }); + commandBlock.UpdateAudioKernel(new ReadIldBuffersNodeUpdate + { + ildBuffer = new IldBuffer + { + bufferChannels = (IldBufferChannel*)outputSamplesMegaBufferChannels.GetUnsafePtr(), + bufferId = bufferId, + framesInBuffer = 1 + audioSettings.safetyAudioFrames, + framesPerUpdate = audioSettings.audioFramesPerUpdate, + channelCount = outputSamplesMegaBufferChannels.Length, + frame = audioFrame.Value, + warnIfStarved = audioSettings.logWarningIfBuffersAreStarved + }, + }, + systemIldNode); + } + } + + [BurstCompile] + public struct SubmitToDspGraphJob : IJob + { + public DSPCommandBlock commandBlock; + + public void Execute() + { + commandBlock.Complete(); + } + } + + public static StateVariableFilterNode.FilterType ToDspFilterType(this FrequencyFilterType type) + { + switch(type) + { + case FrequencyFilterType.Lowpass: return StateVariableFilterNode.FilterType.Lowpass; + case FrequencyFilterType.Highpass: return StateVariableFilterNode.FilterType.Highpass; + case FrequencyFilterType.Bandpass: return StateVariableFilterNode.FilterType.Bandpass; + case FrequencyFilterType.Bell: return StateVariableFilterNode.FilterType.Bell; + case FrequencyFilterType.Notch: return StateVariableFilterNode.FilterType.Notch; + case FrequencyFilterType.Lowshelf: return StateVariableFilterNode.FilterType.Lowshelf; + case FrequencyFilterType.Highshelf: return StateVariableFilterNode.FilterType.Highshelf; + } + return StateVariableFilterNode.FilterType.Lowpass; + } + + public static void BuildChannelGraph(ref ListenerProfileBlob profile, DSPCommandBlock commandBlock, DSPNode listenerMixNode, ref ListenerGraphState listenerGraphState) + { + //Create the channel nodes and connections but leave the ild outputs disconnected + int listenerMixPortCount = 0; + for (int i = 0; i < profile.passthroughFractionsPerLeftChannel.Length; i++) + { + bool filterAdded = false; + DSPNode previousFilterNode = listenerMixNode; + int previousFilterPort = listenerMixPortCount; + float filterVolume = profile.filterVolumesPerLeftChannel[i] * (1f - profile.passthroughFractionsPerLeftChannel[i]); + float passthroughVolume = profile.passthroughVolumesPerLeftChannel[i] * profile.passthroughFractionsPerLeftChannel[i]; + commandBlock.AddInletPort(listenerMixNode, 1); + listenerMixPortCount++; + + if (filterVolume > 0f) + { + //We have to walk backwards to build the filter connections correctly since the ild outputs must remain disconnected + for (int j = profile.channelIndicesLeft.Length - 1; j >= 0; j--) + { + if (i == profile.channelIndicesLeft[j]) + { + var filter = profile.filtersLeft[j]; + var filterNode = StateVariableFilterNode.Create(commandBlock, + filter.type.ToDspFilterType(), + filter.cutoff, + filter.q, + filter.gainInDecibels, + 1); + listenerGraphState.nodes.Add(filterNode); + var filterConnection = commandBlock.Connect(filterNode, 0, previousFilterNode, previousFilterPort); + listenerGraphState.connections.Add(filterConnection); + previousFilterNode = filterNode; + previousFilterPort = 0; + if (!filterAdded && filterVolume < 1f) + commandBlock.SetAttenuation(filterConnection, filterVolume); + filterAdded = true; + } + } + if (filterAdded) + { + listenerGraphState.ildConnections.Add(new IldOutputConnection + { + ildOutputPort = -i - 1, //The negative value stores the intended channel in a disconnected state + nodeInputPort = previousFilterPort, + node = previousFilterNode, + attenuation = 1f + }); + } + } + if (passthroughVolume > 0f) + { + if (filterAdded) + { + previousFilterPort = listenerMixPortCount; + commandBlock.AddInletPort(listenerMixNode, 1); + listenerMixPortCount++; + } + listenerGraphState.ildConnections.Add(new IldOutputConnection + { + ildOutputPort = -i - 1, + nodeInputPort = previousFilterPort, + node = listenerMixNode, + attenuation = passthroughVolume + }); + } + } + commandBlock.UpdateAudioKernel( + new MixPortsToStereoNodeUpdate { leftChannelCount = listenerMixPortCount }, + listenerMixNode); + for (int i = 0; i < profile.passthroughFractionsPerRightChannel.Length; i++) + { + bool filterAdded = false; + DSPNode previousFilterNode = listenerMixNode; + int previousFilterPort = listenerMixPortCount; + float filterVolume = profile.filterVolumesPerRightChannel[i] * (1f - profile.passthroughFractionsPerRightChannel[i]); + float passthroughVolume = profile.passthroughVolumesPerRightChannel[i] * profile.passthroughFractionsPerRightChannel[i]; + commandBlock.AddInletPort(listenerMixNode, 1); + listenerMixPortCount++; + + if (filterVolume > 0f) + { + //We have to walk backwards to build the filter connections correctly since the ild outputs must remain disconnected + for (int j = profile.channelIndicesRight.Length - 1; j >= 0; j--) + { + if (i == profile.channelIndicesRight[j]) + { + var filter = profile.filtersRight[j]; + var filterNode = StateVariableFilterNode.Create(commandBlock, + filter.type.ToDspFilterType(), + filter.cutoff, + filter.q, + filter.gainInDecibels, + 1); + listenerGraphState.nodes.Add(filterNode); + var filterConnection = commandBlock.Connect(filterNode, 0, previousFilterNode, previousFilterPort); + listenerGraphState.connections.Add(filterConnection); + previousFilterNode = filterNode; + previousFilterPort = 0; + if (!filterAdded && filterVolume < 1f) + commandBlock.SetAttenuation(filterConnection, filterVolume); + filterAdded = true; + } + } + if (filterAdded) + { + listenerGraphState.ildConnections.Add(new IldOutputConnection + { + ildOutputPort = -i - 1 - profile.passthroughFractionsPerLeftChannel.Length, //The negative value stores the intended channel in a disconnected state + nodeInputPort = previousFilterPort, + node = previousFilterNode, + attenuation = 1f + }); + } + } + if (passthroughVolume > 0f) + { + if (filterAdded) + { + previousFilterPort = listenerMixPortCount; + commandBlock.AddInletPort(listenerMixNode, 1); + listenerMixPortCount++; + } + listenerGraphState.ildConnections.Add(new IldOutputConnection + { + ildOutputPort = -i - 1 - profile.passthroughFractionsPerLeftChannel.Length, + nodeInputPort = previousFilterPort, + node = listenerMixNode, + attenuation = passthroughVolume + }); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/GraphHandling.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs/GraphHandling.cs.meta new file mode 100644 index 0000000..bcf519c --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/GraphHandling.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee56ade8a8eea6e499420620b0ec6897 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/InitUpdateDestroy.cs b/Packages/com.latios.latios-framework/MyriAudio/Jobs/InitUpdateDestroy.cs new file mode 100644 index 0000000..6c64ec7 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/InitUpdateDestroy.cs @@ -0,0 +1,621 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Myri +{ + internal static class InitUpdateDestroy + { + //Parallel + [BurstCompile] + public struct DestroyOneshotsWhenFinishedJob : IJobEntityBatch + { + public DestroyCommandBuffer.ParallelWriter dcb; + [ReadOnly] public ComponentTypeHandle oneshotHandle; + [ReadOnly] public EntityTypeHandle entityHandle; + [ReadOnly] public NativeReference audioFrame; + [ReadOnly] public NativeReference lastPlayedAudioFrame; + [ReadOnly] public ComponentDataFromEntity settingsCdfe; + public Entity worldBlackboardEntity; + public int sampleRate; + public int samplesPerFrame; + + public void Execute(ArchetypeChunk chunk, int chunkIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var entities = chunk.GetNativeArray(entityHandle); + for (int i = 0; i < oneshots.Length; i++) + { + var os = oneshots[i]; + int playedFrames = lastPlayedAudioFrame.Value - os.m_spawnedAudioFrame; + double resampleRate = os.clip.Value.sampleRate / (double)sampleRate; + if (os.isInitialized && os.clip.Value.samplesLeftOrMono.Length < resampleRate * playedFrames * samplesPerFrame) + { + dcb.Add(entities[i], chunkIndex); + } + } + } + } + + //Single + [BurstCompile] + public struct UpdateListenersJob : IJobEntityBatch + { + [ReadOnly] public ComponentTypeHandle listenerHandle; + [ReadOnly] public ComponentTypeHandle translationHandle; + [ReadOnly] public ComponentTypeHandle rotationHandle; + [ReadOnly] public ComponentTypeHandle ltwHandle; + public NativeList listenersWithTransforms; + + public void Execute(ArchetypeChunk chunk, int chunkIndex) + { + var listeners = chunk.GetNativeArray(listenerHandle); + if (chunk.Has(ltwHandle)) + { + var ltws = chunk.GetNativeArray(ltwHandle); + for (int i = 0; i < chunk.Count; i++) + { + var l = listeners[i]; + //This culling desyncs the listener indices from the graph handling logic. + //Todo: Figure out how to bring this optimization back. + //if (l.volume > 0f) + { + l.itdResolution = math.clamp(l.itdResolution, 0, 15); + var ltw = ltws[i]; + var transform = new RigidTransform(quaternion.LookRotation(ltw.Forward, ltw.Up), ltw.Position); + listenersWithTransforms.Add(new ListenerWithTransform { listener = l, transform = transform }); + } + } + } + else + { + bool hasTranslation = chunk.Has(translationHandle); + bool hasRotation = chunk.Has(rotationHandle); + NativeArray translations = default; + NativeArray rotations = default; + if (hasTranslation) + translations = chunk.GetNativeArray(translationHandle); + if (hasRotation) + rotations = chunk.GetNativeArray(rotationHandle); + for (int i = 0; i < chunk.Count; i++) + { + var l = listeners[i]; + //This culling desyncs the listener indices from the graph handling logic. + //Todo: Figure out how to bring this optimization back. + //if (l.volume > 0f) + { + l.itdResolution = math.clamp(l.itdResolution, 0, 15); + + var transform = RigidTransform.identity; + if (hasRotation) + transform.rot = rotations[i].Value; + if (hasTranslation) + transform.pos = translations[i].Value; + + listenersWithTransforms.Add(new ListenerWithTransform { listener = l, transform = transform }); + } + } + } + } + } + + //Parallel + //Todo: It might be worth it to cull here rather than write to the emitters array. + [BurstCompile] + public struct UpdateOneshotsJob : IJobEntityBatchWithIndex + { + public ComponentTypeHandle oneshotHandle; + [ReadOnly] public ComponentTypeHandle coneHandle; + [ReadOnly] public ComponentTypeHandle translationHandle; + [ReadOnly] public ComponentTypeHandle rotationHandle; + [ReadOnly] public ComponentTypeHandle parentHandle; + [ReadOnly] public ComponentTypeHandle ltwHandle; + public NativeArray emitters; + [ReadOnly] public NativeReference audioFrame; + [ReadOnly] public NativeReference lastPlayedAudioFrame; + [ReadOnly] public NativeReference lastConsumedBufferId; + public int bufferId; + + public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + for (int i = 0; i < chunk.Count; i++) + { + var oneshot = oneshots[i]; + //There's a chance the one shot spawned last game frame but the dsp missed the audio frame. + //In such a case, we still want the one shot to start at the beginning rather than skip the first audio frame. + //This is more likely to happen in high framerate scenarios. + //This does not solve the problem where the audio frame ticks during DSP and again before the next AudioSystemUpdate. + if ((!oneshot.isInitialized) || (oneshot.m_spawnedBufferId - lastConsumedBufferId.Value > 0 && (lastPlayedAudioFrame.Value - oneshot.m_spawnedAudioFrame >= 0))) + { + oneshot.m_spawnedBufferId = bufferId; + oneshot.m_spawnedAudioFrame = audioFrame.Value; + oneshots[i] = oneshot; + } + } + + bool ltw = chunk.Has(ltwHandle); + bool p = chunk.Has(parentHandle); + bool t = chunk.Has(translationHandle); + bool r = chunk.Has(rotationHandle); + bool c = chunk.Has(coneHandle); + + int mask = math.select(0, 0x10, ltw); + mask += math.select(0, 0x8, p); + mask += math.select(0, 0x4, t); + mask += math.select(0, 0x2, r); + mask += math.select(0, 0x1, c); + + //Note: We only care about rotation if there is also a cone + switch (mask) + { + case 0x0: ProcessNoTransform(chunk, firstEntityIndex); break; + case 0x1: ProcessCone(chunk, firstEntityIndex); break; + case 0x2: ProcessNoTransform(chunk, firstEntityIndex); break; + case 0x3: ProcessRotationCone(chunk, firstEntityIndex); break; + case 0x4: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x5: ProcessTranslationCone(chunk, firstEntityIndex); break; + case 0x6: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x7: ProcessTranslationRotationCone(chunk, firstEntityIndex); break; + + case 0x8: ErrorCase(); break; + case 0x9: ErrorCase(); break; + case 0xa: ErrorCase(); break; + case 0xb: ErrorCase(); break; + case 0xc: ErrorCase(); break; + case 0xd: ErrorCase(); break; + case 0xe: ErrorCase(); break; + case 0xf: ErrorCase(); break; + + case 0x10: ProcessLtw(chunk, firstEntityIndex); break; + case 0x11: ProcessCone(chunk, firstEntityIndex); break; + case 0x12: ProcessLtw(chunk, firstEntityIndex); break; + case 0x13: ProcessRotationCone(chunk, firstEntityIndex); break; + case 0x14: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x15: ProcessTranslationCone(chunk, firstEntityIndex); break; + case 0x16: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x17: ProcessTranslationRotationCone(chunk, firstEntityIndex); break; + + case 0x18: ProcessLtw(chunk, firstEntityIndex); break; + case 0x19: ProcessLtwCone(chunk, firstEntityIndex); break; + case 0x1a: ProcessLtw(chunk, firstEntityIndex); break; + case 0x1b: ProcessLtwCone(chunk, firstEntityIndex); break; + case 0x1c: ProcessLtw(chunk, firstEntityIndex); break; + case 0x1d: ProcessLtwCone(chunk, firstEntityIndex); break; + case 0x1e: ProcessLtw(chunk, firstEntityIndex); break; + case 0x1f: ProcessLtwCone(chunk, firstEntityIndex); break; + + default: ErrorCase(); break; + } + } + + void ProcessNoTransform(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + AudioSourceEmitterCone cone = default; + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = RigidTransform.identity, + cone = cone, + useCone = false + }; + } + } + + void ProcessCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = RigidTransform.identity, + cone = cones[i], + useCone = true + }; + } + } + + void ProcessRotationCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var rotations = chunk.GetNativeArray(rotationHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = new RigidTransform(rotations[i].Value, float3.zero), + cone = cones[i], + useCone = true + }; + } + } + + void ProcessTranslation(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var translations = chunk.GetNativeArray(translationHandle); + AudioSourceEmitterCone cone = default; + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = new RigidTransform(quaternion.identity, translations[i].Value), + cone = cone, + useCone = false + }; + } + } + + void ProcessTranslationCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var translations = chunk.GetNativeArray(translationHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = new RigidTransform(quaternion.identity, translations[i].Value), + cone = cones[i], + useCone = true + }; + } + } + + void ProcessTranslationRotationCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var translations = chunk.GetNativeArray(translationHandle); + var rotations = chunk.GetNativeArray(rotationHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = new RigidTransform(rotations[i].Value, translations[i].Value), + cone = cones[i], + useCone = true + }; + } + } + + void ProcessLtw(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var ltws = chunk.GetNativeArray(ltwHandle); + AudioSourceEmitterCone cone = default; + for (int i = 0; i < chunk.Count; i++) + { + var ltw = ltws[i]; + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = new RigidTransform(quaternion.LookRotationSafe(ltw.Forward, ltw.Up), ltw.Position), + cone = cone, + useCone = false + }; + } + } + + void ProcessLtwCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var oneshots = chunk.GetNativeArray(oneshotHandle); + var ltws = chunk.GetNativeArray(ltwHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + var ltw = ltws[i]; + emitters[firstEntityIndex + i] = new OneshotEmitter + { + source = oneshots[i], + transform = new RigidTransform(quaternion.LookRotationSafe(ltw.Forward, ltw.Up), ltw.Position), + cone = cones[i], + useCone = true + }; + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void ErrorCase() + { + throw new System.InvalidOperationException("OneshotsUpdateJob received an invalid EntityQuery"); + } + } + + //Parallel + //Todo: It might be worth it to cull here rather than write to the emitters array. + [BurstCompile] + public struct UpdateLoopedsJob : IJobEntityBatchWithIndex + { + public ComponentTypeHandle loopedHandle; + [ReadOnly] public ComponentTypeHandle coneHandle; + [ReadOnly] public ComponentTypeHandle translationHandle; + [ReadOnly] public ComponentTypeHandle rotationHandle; + [ReadOnly] public ComponentTypeHandle parentHandle; + [ReadOnly] public ComponentTypeHandle ltwHandle; + public NativeArray emitters; + [ReadOnly] public NativeReference audioFrame; + [ReadOnly] public NativeReference lastConsumedBufferId; + public int bufferId; + public int sampleRate; + public int samplesPerFrame; + + public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) + { + var rng = new Rng.RngSequence(math.asuint(new int2(audioFrame.Value, chunkIndex))); + var looped = chunk.GetNativeArray(loopedHandle); + for (int i = 0; i < chunk.Count; i++) + { + var l = looped[i]; + + if (!l.initialized) + { + if (l.offsetIsBasedOnSpawn) + { + ulong samplesPlayed = (ulong)samplesPerFrame * (ulong)audioFrame.Value; + if (sampleRate == l.clip.Value.sampleRate) + { + int clipStart = (int)(samplesPlayed % (ulong)l.clip.Value.samplesLeftOrMono.Length); + l.m_loopOffset = l.clip.Value.samplesLeftOrMono.Length - clipStart; + } + else + { + double clipSampleStride = l.clip.Value.sampleRate / (double)sampleRate; + double samplesPlayedInSourceSamples = samplesPlayed * clipSampleStride; + double clipStart = samplesPlayedInSourceSamples % l.clip.Value.samplesLeftOrMono.Length; + // We can't get exact due to the mismatched rate, so we choose a rounded start point between + // the last and first sample by chopping off the fractional part + l.m_loopOffset = (int)(l.clip.Value.samplesLeftOrMono.Length - clipStart); + } + l.m_spawnBufferLow16 = (short)bufferId; + } + else + { + l.m_loopOffset = l.m_clip.Value.loopedOffsets[rng.NextInt(0, l.m_clip.Value.loopedOffsets.Length)]; + l.offsetLocked = true; + } + l.initialized = true; + looped[i] = l; + } + else if (!l.offsetLocked && l.offsetIsBasedOnSpawn) + { + if ((short)lastConsumedBufferId.Value - l.m_spawnBufferLow16 >= 0) + { + l.offsetLocked = true; + } + else + { + // This check compares if the playhead loop advanced past the target start point in the loop + ulong samplesPlayed = (ulong)samplesPerFrame * (ulong)audioFrame.Value; + double clipSampleStride = l.clip.Value.sampleRate / (double)sampleRate; + double samplesPlayedInSourceSamples = samplesPlayed * clipSampleStride; + double clipStart = (samplesPlayedInSourceSamples + l.m_loopOffset) % l.clip.Value.samplesLeftOrMono.Length; + // We add a one sample tolerance in case we are regenerating the same audio frame, in which case the old values are fine. + if (clipStart < l.clip.Value.samplesLeftOrMono.Length / 2 && clipStart > 1) + { + // We missed the buffer + clipStart = samplesPlayedInSourceSamples % l.clip.Value.samplesLeftOrMono.Length; + l.m_loopOffset = (int)(l.clip.Value.samplesLeftOrMono.Length - clipStart); + l.m_spawnBufferLow16 = (short)bufferId; + } + } + looped[i] = l; + } + } + + bool ltw = chunk.Has(ltwHandle); + bool p = chunk.Has(parentHandle); + bool t = chunk.Has(translationHandle); + bool r = chunk.Has(rotationHandle); + bool c = chunk.Has(coneHandle); + + int mask = math.select(0, 0x10, ltw); + mask += math.select(0, 0x8, p); + mask += math.select(0, 0x4, t); + mask += math.select(0, 0x2, r); + mask += math.select(0, 0x1, c); + + //Note: We only care about rotation if there is also a cone + switch (mask) + { + case 0x0: ProcessNoTransform(chunk, firstEntityIndex); break; + case 0x1: ProcessCone(chunk, firstEntityIndex); break; + case 0x2: ProcessNoTransform(chunk, firstEntityIndex); break; + case 0x3: ProcessRotationCone(chunk, firstEntityIndex); break; + case 0x4: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x5: ProcessTranslationCone(chunk, firstEntityIndex); break; + case 0x6: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x7: ProcessTranslationRotationCone(chunk, firstEntityIndex); break; + + case 0x8: ErrorCase(); break; + case 0x9: ErrorCase(); break; + case 0xa: ErrorCase(); break; + case 0xb: ErrorCase(); break; + case 0xc: ErrorCase(); break; + case 0xd: ErrorCase(); break; + case 0xe: ErrorCase(); break; + case 0xf: ErrorCase(); break; + + case 0x10: ProcessLtw(chunk, firstEntityIndex); break; + case 0x11: ProcessCone(chunk, firstEntityIndex); break; + case 0x12: ProcessLtw(chunk, firstEntityIndex); break; + case 0x13: ProcessRotationCone(chunk, firstEntityIndex); break; + case 0x14: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x15: ProcessTranslationCone(chunk, firstEntityIndex); break; + case 0x16: ProcessTranslation(chunk, firstEntityIndex); break; + case 0x17: ProcessTranslationRotationCone(chunk, firstEntityIndex); break; + + case 0x18: ProcessLtw(chunk, firstEntityIndex); break; + case 0x19: ProcessLtwCone(chunk, firstEntityIndex); break; + case 0x1a: ProcessLtw(chunk, firstEntityIndex); break; + case 0x1b: ProcessLtwCone(chunk, firstEntityIndex); break; + case 0x1c: ProcessLtw(chunk, firstEntityIndex); break; + case 0x1d: ProcessLtwCone(chunk, firstEntityIndex); break; + case 0x1e: ProcessLtw(chunk, firstEntityIndex); break; + case 0x1f: ProcessLtwCone(chunk, firstEntityIndex); break; + + default: ErrorCase(); break; + } + } + + void ProcessNoTransform(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + AudioSourceEmitterCone cone = default; + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = RigidTransform.identity, + cone = cone, + useCone = false + }; + } + } + + void ProcessCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = RigidTransform.identity, + cone = cones[i], + useCone = true + }; + } + } + + void ProcessRotationCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var rotations = chunk.GetNativeArray(rotationHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = new RigidTransform(rotations[i].Value, float3.zero), + cone = cones[i], + useCone = true + }; + } + } + + void ProcessTranslation(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var translations = chunk.GetNativeArray(translationHandle); + AudioSourceEmitterCone cone = default; + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = new RigidTransform(quaternion.identity, translations[i].Value), + cone = cone, + useCone = false + }; + } + } + + void ProcessTranslationCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var translations = chunk.GetNativeArray(translationHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = new RigidTransform(quaternion.identity, translations[i].Value), + cone = cones[i], + useCone = true + }; + } + } + + void ProcessTranslationRotationCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var translations = chunk.GetNativeArray(translationHandle); + var rotations = chunk.GetNativeArray(rotationHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = new RigidTransform(rotations[i].Value, translations[i].Value), + cone = cones[i], + useCone = true + }; + } + } + + void ProcessLtw(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var ltws = chunk.GetNativeArray(ltwHandle); + AudioSourceEmitterCone cone = default; + for (int i = 0; i < chunk.Count; i++) + { + var ltw = ltws[i]; + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = new RigidTransform(quaternion.LookRotationSafe(ltw.Forward, ltw.Up), ltw.Position), + cone = cone, + useCone = false + }; + } + } + + void ProcessLtwCone(ArchetypeChunk chunk, int firstEntityIndex) + { + var loopeds = chunk.GetNativeArray(loopedHandle); + var ltws = chunk.GetNativeArray(ltwHandle); + var cones = chunk.GetNativeArray(coneHandle); + for (int i = 0; i < chunk.Count; i++) + { + var ltw = ltws[i]; + emitters[firstEntityIndex + i] = new LoopedEmitter + { + source = loopeds[i], + transform = new RigidTransform(quaternion.LookRotationSafe(ltw.Forward, ltw.Up), ltw.Position), + cone = cones[i], + useCone = true + }; + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + void ErrorCase() + { + throw new System.InvalidOperationException("LoopedsUpdateJob received an invalid EntityQuery"); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/InitUpdateDestroy.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs/InitUpdateDestroy.cs.meta new file mode 100644 index 0000000..68347de --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/InitUpdateDestroy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a9f593fc5ddbed049a06720b571e6e2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/JobStructures.cs b/Packages/com.latios.latios-framework/MyriAudio/Jobs/JobStructures.cs new file mode 100644 index 0000000..d7c6315 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/JobStructures.cs @@ -0,0 +1,80 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Myri +{ + internal struct ClipFrameLookup : IEquatable + { + public BlobAssetReference clip; + public int spawnFrameOrOffset; + + public unsafe bool Equals(ClipFrameLookup other) + { + return ((ulong)clip.GetUnsafePtr()).Equals((ulong)other.clip.GetUnsafePtr()) && spawnFrameOrOffset == other.spawnFrameOrOffset; + } + + public unsafe override int GetHashCode() + { + return new int2((int)((ulong)clip.GetUnsafePtr() >> 4), spawnFrameOrOffset).GetHashCode(); + } + } + + internal struct Weights + { + public FixedList512Bytes channelWeights; + public FixedList128Bytes itdWeights; + + public static Weights operator + (Weights a, Weights b) + { + Weights result = a; + for (int i = 0; i < a.channelWeights.Length; i++) + { + result.channelWeights[i] += b.channelWeights[i]; + } + for (int i = 0; i < a.itdWeights.Length; i++) + { + result.itdWeights[i] += b.itdWeights[i]; + } + return result; + } + } + + internal struct ListenerBufferParameters + { + public int bufferStart; + public int leftChannelsCount; + public int samplesPerChannel; + } + + internal struct ListenerWithTransform + { + public AudioListener listener; + public RigidTransform transform; + } + + internal struct OneshotEmitter + { + public AudioSourceOneShot source; + public RigidTransform transform; + public AudioSourceEmitterCone cone; + public bool useCone; + } + + internal struct LoopedEmitter + { + public AudioSourceLooped source; + public RigidTransform transform; + public AudioSourceEmitterCone cone; + public bool useCone; + } + + internal struct AudioFrameBufferHistoryElement + { + public int bufferId; + public int audioFrame; + public int expectedNextUpdateFrame; + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/JobStructures.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs/JobStructures.cs.meta new file mode 100644 index 0000000..f88e811 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/JobStructures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 295610e3e1026f74f921f2b51cb2584b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/Sampling.cs b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Sampling.cs new file mode 100644 index 0000000..1c68910 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Sampling.cs @@ -0,0 +1,232 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Myri +{ + internal static class Sampling + { + const double ITD_TIME = 0.0007; + + //Parallel + [BurstCompile] + public struct SampleOneshotClipsJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray clipFrameLookups; + [ReadOnly] public NativeArray weights; + [ReadOnly] public NativeArray targetListenerIndices; + + [ReadOnly] public NativeArray listenerBufferParameters; + [ReadOnly] public NativeArray forIndexToListenerAndChannelIndices; + + [ReadOnly] public NativeReference audioFrame; + + [NativeDisableParallelForRestriction] public NativeArray outputSamplesMegaBuffer; + + public int sampleRate; + + public int samplesPerFrame; + + public void Execute(int forIndex) + { + int listenerIndex = forIndexToListenerAndChannelIndices[forIndex].x; + int channelIndex = forIndexToListenerAndChannelIndices[forIndex].y; + var targetListenerParameters = listenerBufferParameters[listenerIndex]; + bool isRightChannel = targetListenerParameters.leftChannelsCount <= channelIndex; + + var outputSamples = outputSamplesMegaBuffer.GetSubArray(targetListenerParameters.bufferStart + targetListenerParameters.samplesPerChannel * channelIndex, + targetListenerParameters.samplesPerChannel); + + for (int clipIndex = 0; clipIndex < clipFrameLookups.Length; clipIndex++) + { + if (targetListenerIndices[clipIndex] != listenerIndex) + continue; + + ref var clip = ref clipFrameLookups[clipIndex].clip.Value; + int spawnFrame = clipFrameLookups[clipIndex].spawnFrameOrOffset; + var channelWeight = weights[clipIndex].channelWeights[channelIndex]; + var itdWeights = weights[clipIndex].itdWeights; + + double itdMaxOffset = clip.sampleRate * ITD_TIME; + double clipSampleStride = clip.sampleRate / (double)sampleRate; + double clipSamplesPerFrame = clipSampleStride * samplesPerFrame; + + for (int itd = 0; itd < itdWeights.Length; itd++) + { + float weight = itdWeights[itd] * channelWeight; + + double itdOffset = math.lerp(0, -itdMaxOffset, itd / (double)(itdWeights.Length - 1)); + itdOffset = math.select(itdOffset, math.lerp(-itdMaxOffset, 0, itd / (double)(itdWeights.Length - 1)), isRightChannel); + itdOffset = math.select(itdOffset, 0, itdWeights.Length == 1); + if (weight > 0f) + { + int jumpFrames = audioFrame.Value - spawnFrame; + + if (clip.sampleRate == sampleRate) + { + int clipStart = jumpFrames * samplesPerFrame + (int)math.round(itdOffset); + SampleMatchedRate(ref clip, clipStart, isRightChannel, weight, outputSamples); + } + else + { + double clipStart = jumpFrames * clipSamplesPerFrame + itdOffset; + SampleMismatchedRate(ref clip, clipStart, clipSampleStride, isRightChannel, weight, outputSamples); + } + } + } + } + } + + void SampleMatchedRate(ref AudioClipBlob clip, int clipStart, bool isRightChannel, float weight, NativeArray output) + { + int outputStartIndex = math.max(-clipStart, 0); + int remainingClipSamples = clip.samplesLeftOrMono.Length - clipStart; + int remainingSamples = math.min(output.Length, math.select(0, remainingClipSamples, remainingClipSamples > 0)); + + if (isRightChannel && clip.isStereo) + { + for (int i = outputStartIndex; i < remainingSamples; i++) + { + output[i] += clip.samplesRight[clipStart + i] * weight; + } + } + else + { + for (int i = outputStartIndex; i < remainingSamples; i++) + { + output[i] += clip.samplesLeftOrMono[clipStart + i] * weight; + } + } + } + + void SampleMismatchedRate(ref AudioClipBlob clip, double clipStart, double clipSampleStride, bool isRightChannel, float weight, NativeArray output) + { + ref var clipSamples = ref (isRightChannel && clip.isStereo ? ref clip.samplesRight : ref clip.samplesLeftOrMono); + + for (int i = 0; i < output.Length; i++) + { + double pos = clipStart + clipSampleStride * i; + int posLeft = (int)pos; + int posRight = posLeft + 1; + int safeLeft = math.clamp(posLeft, 0, clipSamples.Length - 1); + int safeRight = math.clamp(posRight, 0, clipSamples.Length - 1); + float leftSample = math.select(0f, clipSamples[safeLeft], posLeft == safeLeft); + float rightSample = math.select(0f, clipSamples[safeRight], posRight == safeRight); + output[i] += math.lerp(leftSample, rightSample, (float)math.frac(pos)) * weight; + } + } + } + + //Parallel + [BurstCompile] + public struct SampleLoopedClipsJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray clipFrameLookups; + [ReadOnly] public NativeArray weights; + [ReadOnly] public NativeArray targetListenerIndices; + + [ReadOnly] public NativeArray listenerBufferParameters; + [ReadOnly] public NativeArray forIndexToListenerAndChannelIndices; + + [ReadOnly] public NativeReference audioFrame; + + [NativeDisableParallelForRestriction] public NativeArray outputSamplesMegaBuffer; + + public int sampleRate; + public int samplesPerFrame; + + public void Execute(int forIndex) + { + int listenerIndex = forIndexToListenerAndChannelIndices[forIndex].x; + int channelIndex = forIndexToListenerAndChannelIndices[forIndex].y; + var targetListenerParameters = listenerBufferParameters[listenerIndex]; + bool isRightChannel = targetListenerParameters.leftChannelsCount <= channelIndex; + + var outputSamples = outputSamplesMegaBuffer.GetSubArray(targetListenerParameters.bufferStart + targetListenerParameters.samplesPerChannel * channelIndex, + targetListenerParameters.samplesPerChannel); + + ulong samplesPlayed = (ulong)samplesPerFrame * (ulong)audioFrame.Value; + + for (int clipIndex = 0; clipIndex < clipFrameLookups.Length; clipIndex++) + { + if (targetListenerIndices[clipIndex] != listenerIndex) + continue; + + ref var clip = ref clipFrameLookups[clipIndex].clip.Value; + int loopOffset = clipFrameLookups[clipIndex].spawnFrameOrOffset; + var channelWeight = weights[clipIndex].channelWeights[channelIndex]; + var itdWeights = weights[clipIndex].itdWeights; + + double itdMaxOffset = clip.sampleRate * ITD_TIME; + double clipSampleStride = clip.sampleRate / (double)sampleRate; + + for (int itd = 0; itd < itdWeights.Length; itd++) + { + float weight = itdWeights[itd] * channelWeight; + + double itdOffset = math.lerp(0, -itdMaxOffset, itd / (double)(itdWeights.Length - 1)); + itdOffset = math.select(itdOffset, math.lerp(-itdMaxOffset, 0, itd / (double)(itdWeights.Length - 1)), isRightChannel); + itdOffset = math.select(itdOffset, 0, itdWeights.Length == 1); + + if (weight > 0f) + { + if (clip.sampleRate == sampleRate) + { + int clipStart = (int)((samplesPlayed + (ulong)loopOffset) % (ulong)clip.samplesLeftOrMono.Length) + (int)math.round(itdOffset); + SampleMatchedRate(ref clip, clipStart, isRightChannel, weight, outputSamples); + } + else + { + double samplesPlayedInSourceSamples = samplesPlayed * clipSampleStride; + double clipStart = (samplesPlayedInSourceSamples + loopOffset + itdOffset) % clip.samplesLeftOrMono.Length; + SampleMismatchedRate(ref clip, clipStart, clipSampleStride, isRightChannel, weight, outputSamples); + } + } + } + } + } + + void SampleMatchedRate(ref AudioClipBlob clip, int clipStart, bool isRightChannel, float weight, NativeArray output) + { + while (clipStart < 0) + clipStart += clip.samplesLeftOrMono.Length; + + if (isRightChannel && clip.isStereo) + { + for (int i = 0; i < output.Length; i++) + { + int index = (clipStart + i) % clip.samplesRight.Length; + output[i] += clip.samplesRight[index] * weight; + } + } + else + { + for (int i = 0; i < output.Length; i++) + { + int index = (clipStart + i) % clip.samplesLeftOrMono.Length; + output[i] += clip.samplesLeftOrMono[index] * weight; + } + } + } + + void SampleMismatchedRate(ref AudioClipBlob clip, double clipStart, double clipSampleStride, bool isRightChannel, float weight, NativeArray output) + { + ref var clipSamples = ref (isRightChannel && clip.isStereo ? ref clip.samplesRight : ref clip.samplesLeftOrMono); + + for (int i = 0; i < output.Length; i++) + { + double pos = clipStart + clipSampleStride * i; + int posLeft = (int)pos % clipSamples.Length; + int posRight = (posLeft + 1) % clipSamples.Length; //This handles wraparound between last sample and first sample + float leftSample = clipSamples[posLeft]; + float rightSample = clipSamples[posRight]; + output[i] += math.lerp(leftSample, rightSample, (float)math.frac(pos)) * weight; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Jobs/Sampling.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Sampling.cs.meta new file mode 100644 index 0000000..c2754eb --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Jobs/Sampling.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6771019a13cb11429fd54404c5eb970 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Latios.Myri.asmdef b/Packages/com.latios.latios-framework/MyriAudio/Latios.Myri.asmdef new file mode 100644 index 0000000..17f33f4 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Latios.Myri.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Latios.Myri", + "rootNamespace": "Latios.Myri", + "references": [ + "Latios.Core", + "Unity.Audio.DSPGraph", + "Unity.Burst", + "Unity.Collections", + "Unity.Entities", + "Unity.Jobs", + "Unity.Mathematics", + "Unity.Media.Utilities", + "Unity.Transforms", + "Unity.Entities.Hybrid" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/MyriAudio/Latios.Myri.asmdef.meta b/Packages/com.latios.latios-framework/MyriAudio/Latios.Myri.asmdef.meta new file mode 100644 index 0000000..d4cf4aa --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Latios.Myri.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d1d3d8be6fb0d9248bc0df28e441c521 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Systems.meta b/Packages/com.latios.latios-framework/MyriAudio/Systems.meta new file mode 100644 index 0000000..d16d590 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Systems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3297334d520a892498f30b1ffd7ff9e9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Systems/AudioSystem.cs b/Packages/com.latios.latios-framework/MyriAudio/Systems/AudioSystem.cs new file mode 100644 index 0000000..80d4fca --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Systems/AudioSystem.cs @@ -0,0 +1,423 @@ +using System.Collections.Generic; +using Unity.Audio; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Myri.Systems +{ + [DisableAutoCreation] + [UpdateInGroup(typeof(Latios.Systems.PreSyncPointGroup))] + public partial class AudioSystem : SubSystem + { + private DSPGraph m_graph; + private LatiosDSPGraphDriver m_driver; + private AudioOutputHandle m_outputHandle; + private int m_sampleRate; + private int m_samplesPerFrame; + + private DSPNode m_mixNode; + private DSPConnection m_mixToLimiterMasterConnection; + private NativeList m_mixNodePortFreelist; + private NativeReference m_mixNodePortCount; + + private DSPNode m_limiterMasterNode; + private DSPConnection m_limiterMasterToOutputConnection; + + private DSPNode m_ildNode; + private NativeReference m_ildNodePortCount; + private NativeReference m_packedFrameCounterBufferId; //MSB bufferId, LSB frame + private NativeReference m_audioFrame; + private NativeReference m_lastPlayedAudioFrame; + private NativeReference m_lastReadBufferId; + private int m_currentBufferId; + private List m_buffersInFlight; + private NativeQueue m_audioFrameHistory; + + private JobHandle m_lastUpdateJobHandle; + private EntityQuery m_aliveListenersQuery; + private EntityQuery m_deadListenersQuery; + private EntityQuery m_oneshotsToDestroyWhenFinishedQuery; + private EntityQuery m_oneshotsQuery; + private EntityQuery m_loopedQuery; + + protected override void OnCreate() + { + //Initialize containers first + m_mixNodePortFreelist = new NativeList(Allocator.Persistent); + m_mixNodePortCount = new NativeReference(Allocator.Persistent); + m_ildNodePortCount = new NativeReference(Allocator.Persistent); + m_packedFrameCounterBufferId = new NativeReference(Allocator.Persistent); + m_audioFrame = new NativeReference(Allocator.Persistent); + m_lastPlayedAudioFrame = new NativeReference(Allocator.Persistent); + m_lastReadBufferId = new NativeReference(Allocator.Persistent); + m_buffersInFlight = new List(); + m_audioFrameHistory = new NativeQueue(Allocator.Persistent); + + worldBlackboardEntity.AddComponentDataIfMissing(new AudioSettings + { + safetyAudioFrames = 2, + audioFramesPerUpdate = 1, + lookaheadAudioFrames = 0, + logWarningIfBuffersAreStarved = false + }); + + //Create graph and driver + var format = ChannelEnumConverter.GetSoundFormatFromSpeakerMode(UnityEngine.AudioSettings.speakerMode); + var channels = ChannelEnumConverter.GetChannelCountFromSoundFormat(format); + UnityEngine.AudioSettings.GetDSPBufferSize(out m_samplesPerFrame, out _); + m_sampleRate = UnityEngine.AudioSettings.outputSampleRate; + m_graph = DSPGraph.Create(format, channels, m_samplesPerFrame, m_sampleRate); + m_driver = new LatiosDSPGraphDriver { Graph = m_graph }; + m_outputHandle = m_driver.AttachToDefaultOutput(); + + var commandBlock = m_graph.CreateCommandBlock(); + m_mixNode = commandBlock.CreateDSPNode(); + commandBlock.AddOutletPort(m_mixNode, 2); + m_limiterMasterNode = commandBlock.CreateDSPNode(); + commandBlock.AddInletPort(m_limiterMasterNode, 2); + commandBlock.AddOutletPort(m_limiterMasterNode, 2); + m_mixToLimiterMasterConnection = commandBlock.Connect(m_mixNode, 0, m_limiterMasterNode, 0); + m_limiterMasterToOutputConnection = commandBlock.Connect(m_limiterMasterNode, 0, m_graph.RootDSP, 0); + m_ildNode = commandBlock.CreateDSPNode(); + unsafe + { + commandBlock.UpdateAudioKernel( + new SetReadIldBuffersNodePackedFrameBufferId { ptr = (long*)m_packedFrameCounterBufferId.GetUnsafePtr() }, + m_ildNode); + } + commandBlock.Complete(); + + //Create queries + m_aliveListenersQuery = Fluent.WithAll(true).Build(); + m_deadListenersQuery = Fluent.Without().WithAll().Build(); + m_oneshotsToDestroyWhenFinishedQuery = Fluent.WithAll().WithAll(true).Build(); + m_oneshotsQuery = Fluent.WithAll().Build(); + m_loopedQuery = Fluent.WithAll().Build(); + + //Force initialization of Burst + commandBlock = m_graph.CreateCommandBlock(); + var dummyNode = commandBlock.CreateDSPNode(); + StateVariableFilterNode.Create(commandBlock, StateVariableFilterNode.FilterType.Bandpass, 0f, 0f, 0f, 1); + commandBlock.UpdateAudioKernel( + new MixPortsToStereoNodeUpdate { leftChannelCount = 0 }, + dummyNode); + commandBlock.UpdateAudioKernel(new ReadIldBuffersNodeUpdate + { + ildBuffer = new IldBuffer(), + }, + m_ildNode); + commandBlock.Cancel(); + } + + protected override void OnUpdate() + { + var ecsJH = Dependency; + + //Query arrays + var aliveListenerEntities = m_aliveListenersQuery.ToEntityArrayAsync(Allocator.TempJob, out JobHandle aliveListenerEntitiesJH); + var deadListenerEntities = m_deadListenersQuery.ToEntityArrayAsync(Allocator.TempJob, out JobHandle deadListenerEntitiesJH); + var listenerEntitiesJH = JobHandle.CombineDependencies(aliveListenerEntitiesJH, deadListenerEntitiesJH); + + //Type handles + var entityHandle = GetEntityTypeHandle(); + var listenerHandle = GetComponentTypeHandle(true); + var oneshotHandle = GetComponentTypeHandle(false); + var loopedHandle = GetComponentTypeHandle(false); + var coneHandle = GetComponentTypeHandle(true); + var translationHandle = GetComponentTypeHandle(true); + var rotationHandle = GetComponentTypeHandle(true); + var ltwHandle = GetComponentTypeHandle(true); + var parentHandle = GetComponentTypeHandle(true); + + var audioSettingsCdfe = GetComponentDataFromEntity(true); + var listenerCdfe = GetComponentDataFromEntity(true); + var listenerGraphStateCdfe = GetComponentDataFromEntity(false); + var entityOutputGraphStateCdfe = GetComponentDataFromEntity(false); + + //Buffer + m_currentBufferId++; + var ildBuffer = new ManagedIldBuffer + { + buffer = new NativeList(Allocator.Persistent), + channels = new NativeList(Allocator.Persistent), + bufferId = m_currentBufferId + }; + + //Containers + var destroyCommandBuffer = latiosWorld.syncPoint.CreateDestroyCommandBuffer().AsParallelWriter(); + var entityCommandBuffer = latiosWorld.syncPoint.CreateEntityCommandBuffer(); + var dspCommandBlock = m_graph.CreateCommandBlock(); + var listenersWithTransforms = new NativeList(aliveListenerEntities.Length, Allocator.TempJob); + var listenerBufferParameters = new NativeArray(aliveListenerEntities.Length, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + var forIndexToListenerAndChannelIndices = new NativeList(Allocator.TempJob); + var oneshotEmitters = new NativeArray(m_oneshotsQuery.CalculateEntityCount(), + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + var loopedEmitters = new NativeArray(m_loopedQuery.CalculateEntityCount(), Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var oneshotWeightsStream = new NativeStream(oneshotEmitters.Length / CullingAndWeighting.BATCH_SIZE + 1, Allocator.TempJob); + var loopedWeightsStream = new NativeStream(loopedEmitters.Length / CullingAndWeighting.BATCH_SIZE + 1, Allocator.TempJob); + var oneshotListenerEmitterPairsStream = new NativeStream(oneshotEmitters.Length / CullingAndWeighting.BATCH_SIZE + 1, Allocator.TempJob); + var loopedListenerEmitterPairsStream = new NativeStream(loopedEmitters.Length / CullingAndWeighting.BATCH_SIZE + 1, Allocator.TempJob); + var oneshotClipFrameLookups = new NativeList(Allocator.TempJob); + var loopedClipFrameLookups = new NativeList(Allocator.TempJob); + var oneshotBatchedWeights = new NativeList(Allocator.TempJob); + var loopedBatchedWeights = new NativeList(Allocator.TempJob); + var oneshotTargetListenerIndices = new NativeList(Allocator.TempJob); + var loopedTargetListenerIndices = new NativeList(Allocator.TempJob); + + //Jobs + m_lastUpdateJobHandle.Complete(); + + //This may lag behind what the job threads will see. + //That's fine, as this is only used for disposing memory. + int lastReadIldBufferFromMainThread = m_lastReadBufferId.Value; + + var captureListenersJH = new InitUpdateDestroy.UpdateListenersJob + { + listenerHandle = listenerHandle, + translationHandle = translationHandle, + rotationHandle = rotationHandle, + ltwHandle = ltwHandle, + listenersWithTransforms = listenersWithTransforms + }.Schedule(m_aliveListenersQuery, ecsJH); + + var captureFrameJH = new GraphHandling.CaptureIldFrameJob + { + packedFrameCounterBufferId = m_packedFrameCounterBufferId, + audioFrame = m_audioFrame, + lastPlayedAudioFrame = m_lastPlayedAudioFrame, + lastReadBufferId = m_lastReadBufferId, + audioFrameHistory = m_audioFrameHistory, + audioSettingsCdfe = audioSettingsCdfe, + worldBlackboardEntity = worldBlackboardEntity + }.Schedule(); + + var ecsCaptureFrameJH = JobHandle.CombineDependencies(ecsJH, captureFrameJH); + + var updateListenersGraphJH = new GraphHandling.UpdateListenersGraphJob + { + listenerEntities = aliveListenerEntities, + destroyedListenerEntities = deadListenerEntities, + listenerCdfe = listenerCdfe, + listenerGraphStateCdfe = listenerGraphStateCdfe, + listenerOutputGraphStateCdfe = entityOutputGraphStateCdfe, + ecb = entityCommandBuffer, + audioSettingsCdfe = audioSettingsCdfe, + worldBlackboardEntity = worldBlackboardEntity, + audioFrame = m_audioFrame, + audioFrameHistory = m_audioFrameHistory, + systemMixNodePortFreelist = m_mixNodePortFreelist, + systemMixNodePortCount = m_mixNodePortCount, + systemMixNode = m_mixNode, + systemIldNodePortCount = m_ildNodePortCount, + systemIldNode = m_ildNode, + commandBlock = dspCommandBlock, + listenerBufferParameters = listenerBufferParameters, + forIndexToListenerAndChannelIndices = forIndexToListenerAndChannelIndices, + outputSamplesMegaBuffer = ildBuffer.buffer, + outputSamplesMegaBufferChannels = ildBuffer.channels, + bufferId = m_currentBufferId, + samplesPerFrame = m_samplesPerFrame + }.Schedule(JobHandle.CombineDependencies(captureListenersJH, captureFrameJH, listenerEntitiesJH)); + + var destroyOneshotsJH = new InitUpdateDestroy.DestroyOneshotsWhenFinishedJob + { + dcb = destroyCommandBuffer, + entityHandle = entityHandle, + oneshotHandle = oneshotHandle, + audioFrame = m_audioFrame, + lastPlayedAudioFrame = m_lastPlayedAudioFrame, + sampleRate = m_sampleRate, + settingsCdfe = audioSettingsCdfe, + samplesPerFrame = m_samplesPerFrame, + worldBlackboardEntity = worldBlackboardEntity + }.ScheduleParallel(m_oneshotsToDestroyWhenFinishedQuery, ecsCaptureFrameJH); + + var updateOneshotsJH = new InitUpdateDestroy.UpdateOneshotsJob + { + oneshotHandle = oneshotHandle, + ltwHandle = ltwHandle, + translationHandle = translationHandle, + rotationHandle = rotationHandle, + parentHandle = parentHandle, + coneHandle = coneHandle, + audioFrame = m_audioFrame, + lastPlayedAudioFrame = m_lastPlayedAudioFrame, + lastConsumedBufferId = m_lastReadBufferId, + bufferId = m_currentBufferId, + emitters = oneshotEmitters + }.ScheduleParallel(m_oneshotsQuery, destroyOneshotsJH); + + var updateLoopedJH = new InitUpdateDestroy.UpdateLoopedsJob + { + loopedHandle = loopedHandle, + ltwHandle = ltwHandle, + translationHandle = translationHandle, + rotationHandle = rotationHandle, + parentHandle = parentHandle, + coneHandle = coneHandle, + audioFrame = m_audioFrame, + lastConsumedBufferId = m_lastReadBufferId, + bufferId = m_currentBufferId, + sampleRate = m_sampleRate, + samplesPerFrame = m_samplesPerFrame, + emitters = loopedEmitters + }.ScheduleParallel(m_loopedQuery, ecsCaptureFrameJH); + + //No more ECS + var oneshotsCullingWeightingJH = new CullingAndWeighting.OneshotsJob + { + emitters = oneshotEmitters, + listenersWithTransforms = listenersWithTransforms, + weights = oneshotWeightsStream.AsWriter(), + listenerEmitterPairs = oneshotListenerEmitterPairsStream.AsWriter() + }.ScheduleBatch(oneshotEmitters.Length, CullingAndWeighting.BATCH_SIZE, JobHandle.CombineDependencies(captureListenersJH, updateOneshotsJH)); + + var loopedCullingWeightingJH = new CullingAndWeighting.LoopedJob + { + emitters = loopedEmitters, + listenersWithTransforms = listenersWithTransforms, + weights = loopedWeightsStream.AsWriter(), + listenerEmitterPairs = loopedListenerEmitterPairsStream.AsWriter() + }.ScheduleBatch(loopedEmitters.Length, CullingAndWeighting.BATCH_SIZE, JobHandle.CombineDependencies(captureListenersJH, updateLoopedJH)); + + var oneshotsBatchingJH = new Batching.BatchOneshotsJob + { + emitters = oneshotEmitters, + pairWeights = oneshotWeightsStream.AsReader(), + listenerEmitterPairs = oneshotListenerEmitterPairsStream.AsReader(), + clipFrameLookups = oneshotClipFrameLookups, + batchedWeights = oneshotBatchedWeights, + targetListenerIndices = oneshotTargetListenerIndices + }.Schedule(oneshotsCullingWeightingJH); + + var loopedBatchingJH = new Batching.BatchLoopedJob + { + emitters = loopedEmitters, + pairWeights = loopedWeightsStream.AsReader(), + listenerEmitterPairs = loopedListenerEmitterPairsStream.AsReader(), + clipFrameLookups = loopedClipFrameLookups, + batchedWeights = loopedBatchedWeights, + targetListenerIndices = loopedTargetListenerIndices + }.Schedule(loopedCullingWeightingJH); + + var oneshotSamplingJH = new Sampling.SampleOneshotClipsJob + { + clipFrameLookups = oneshotClipFrameLookups.AsDeferredJobArray(), + weights = oneshotBatchedWeights.AsDeferredJobArray(), + targetListenerIndices = oneshotTargetListenerIndices.AsDeferredJobArray(), + listenerBufferParameters = listenerBufferParameters, + forIndexToListenerAndChannelIndices = forIndexToListenerAndChannelIndices.AsDeferredJobArray(), + outputSamplesMegaBuffer = ildBuffer.buffer.AsDeferredJobArray(), + sampleRate = m_sampleRate, + samplesPerFrame = m_samplesPerFrame, + audioFrame = m_audioFrame + }.Schedule(forIndexToListenerAndChannelIndices, 1, JobHandle.CombineDependencies(updateListenersGraphJH, oneshotsBatchingJH)); + + var loopedSamplingJH = new Sampling.SampleLoopedClipsJob + { + clipFrameLookups = loopedClipFrameLookups.AsDeferredJobArray(), + weights = loopedBatchedWeights.AsDeferredJobArray(), + targetListenerIndices = loopedTargetListenerIndices.AsDeferredJobArray(), + listenerBufferParameters = listenerBufferParameters, + forIndexToListenerAndChannelIndices = forIndexToListenerAndChannelIndices.AsDeferredJobArray(), + outputSamplesMegaBuffer = ildBuffer.buffer.AsDeferredJobArray(), + sampleRate = m_sampleRate, + samplesPerFrame = m_samplesPerFrame, + audioFrame = m_audioFrame + }.Schedule(forIndexToListenerAndChannelIndices, 1, JobHandle.CombineDependencies(oneshotSamplingJH, loopedBatchingJH)); + + var shipItJH = new GraphHandling.SubmitToDspGraphJob + { + commandBlock = dspCommandBlock + }.Schedule(loopedSamplingJH); + + Dependency = JobHandle.CombineDependencies(updateListenersGraphJH, //handles captureListener and captureFrame + updateOneshotsJH, //handles destroyOneshots + updateLoopedJH + ); + + var disposeJobHandles = new NativeList(Allocator.TempJob); + disposeJobHandles.Add(aliveListenerEntities.Dispose(updateListenersGraphJH)); + disposeJobHandles.Add(deadListenerEntities.Dispose(updateListenersGraphJH)); + disposeJobHandles.Add(listenersWithTransforms.Dispose(JobHandle.CombineDependencies(oneshotsCullingWeightingJH, loopedCullingWeightingJH))); + disposeJobHandles.Add(listenerBufferParameters.Dispose(loopedSamplingJH)); + disposeJobHandles.Add(forIndexToListenerAndChannelIndices.Dispose(loopedSamplingJH)); + disposeJobHandles.Add(oneshotEmitters.Dispose(oneshotsBatchingJH)); + disposeJobHandles.Add(loopedEmitters.Dispose(loopedBatchingJH)); + disposeJobHandles.Add(oneshotWeightsStream.Dispose(oneshotsBatchingJH)); + disposeJobHandles.Add(loopedWeightsStream.Dispose(loopedBatchingJH)); + disposeJobHandles.Add(oneshotListenerEmitterPairsStream.Dispose(oneshotsBatchingJH)); + disposeJobHandles.Add(loopedListenerEmitterPairsStream.Dispose(loopedBatchingJH)); + disposeJobHandles.Add(oneshotClipFrameLookups.Dispose(oneshotSamplingJH)); + disposeJobHandles.Add(loopedClipFrameLookups.Dispose(loopedSamplingJH)); + disposeJobHandles.Add(oneshotBatchedWeights.Dispose(oneshotSamplingJH)); + disposeJobHandles.Add(loopedBatchedWeights.Dispose(loopedSamplingJH)); + disposeJobHandles.Add(oneshotTargetListenerIndices.Dispose(oneshotSamplingJH)); + disposeJobHandles.Add(loopedTargetListenerIndices.Dispose(loopedSamplingJH)); + disposeJobHandles.Add(shipItJH); + + for (int i = 0; i < m_buffersInFlight.Count; i++) + { + var buffer = m_buffersInFlight[i]; + if (buffer.bufferId - lastReadIldBufferFromMainThread < 0) + { + disposeJobHandles.Add(buffer.buffer.Dispose(ecsJH)); + disposeJobHandles.Add(buffer.channels.Dispose(ecsJH)); + m_buffersInFlight.RemoveAtSwapBack(i); + i--; + } + } + + m_lastUpdateJobHandle = JobHandle.CombineDependencies(disposeJobHandles); + disposeJobHandles.Dispose(); + + m_buffersInFlight.Add(ildBuffer); + } + + protected override void OnDestroy() + { + //UnityEngine.Debug.Log("AudioSystem.OnDestroy"); + var commandBlock = m_graph.CreateCommandBlock(); + commandBlock.Disconnect(m_mixToLimiterMasterConnection); + commandBlock.Disconnect(m_limiterMasterToOutputConnection); + commandBlock.ReleaseDSPNode(m_ildNode); + commandBlock.ReleaseDSPNode(m_mixNode); + commandBlock.ReleaseDSPNode(m_limiterMasterNode); + commandBlock.Complete(); + AudioOutputExtensions.DisposeOutputHook(ref m_outputHandle); + m_driver.Dispose(); + + m_lastUpdateJobHandle.Complete(); + m_mixNodePortFreelist.Dispose(); + m_mixNodePortCount.Dispose(); + m_ildNodePortCount.Dispose(); + m_packedFrameCounterBufferId.Dispose(); + m_audioFrame.Dispose(); + m_lastPlayedAudioFrame.Dispose(); + m_lastReadBufferId.Dispose(); + m_audioFrameHistory.Dispose(); + + foreach (var buffer in m_buffersInFlight) + { + buffer.buffer.Dispose(); + buffer.channels.Dispose(); + } + } + + private struct ManagedIldBuffer + { + public NativeList buffer; + public NativeList channels; + public int bufferId; + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Systems/AudioSystem.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Systems/AudioSystem.cs.meta new file mode 100644 index 0000000..7937742 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Systems/AudioSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ceaa6fbd02961042bc7c5f3ec18cbe0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Utilities.meta b/Packages/com.latios.latios-framework/MyriAudio/Utilities.meta new file mode 100644 index 0000000..985db61 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 19841463f054abd4192e98399645a8fd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/MyriAudio/Utilities/MyriBootstrap.cs b/Packages/com.latios.latios-framework/MyriAudio/Utilities/MyriBootstrap.cs new file mode 100644 index 0000000..57ad669 --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Utilities/MyriBootstrap.cs @@ -0,0 +1,23 @@ +using Unity.Entities; + +namespace Latios.Myri +{ + /// + /// Static class containing installers for optional runtime features in the Myri module + /// + public static class MyriBootstrap + { + /// + /// Installs Myri into the World at its default location + /// + /// The runtime world in which Myri should be installed + public static void InstallMyri(World world) + { + if (world.Flags.HasFlag(WorldFlags.Conversion)) + throw new System.InvalidOperationException("Cannot install Myri runtime in a conversion world."); + + BootstrapTools.InjectSystem(typeof(Systems.AudioSystem), world); + } + } +} + diff --git a/Packages/com.latios.latios-framework/MyriAudio/Utilities/MyriBootstrap.cs.meta b/Packages/com.latios.latios-framework/MyriAudio/Utilities/MyriBootstrap.cs.meta new file mode 100644 index 0000000..d6237bd --- /dev/null +++ b/Packages/com.latios.latios-framework/MyriAudio/Utilities/MyriBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee9f4c6ccf6cb4442bc2f646c8994190 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics.meta b/Packages/com.latios.latios-framework/PsyshockPhysics.meta new file mode 100644 index 0000000..7891342 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 862bf87f5b5dfd54f82cc706dee86e1a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics.meta new file mode 100644 index 0000000..7cdf254 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff7995d2d71262e45b9c63203a19d69f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring.meta new file mode 100644 index 0000000..d15041c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce152cd6a54e0db48b2d0a0bc52c9013 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderAuthoring.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderAuthoring.cs new file mode 100644 index 0000000..9588860 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderAuthoring.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +using UnityCollider = UnityEngine.Collider; + +namespace Latios.Psyshock.Authoring +{ + public enum AuthoringColliderTypes + { + None, + Compound + } + + [DisallowMultipleComponent] + [AddComponentMenu("Latios/Physics (Psyshock)/Collider")] + public class ColliderAuthoring : MonoBehaviour + { + public AuthoringColliderTypes colliderType = AuthoringColliderTypes.None; + + //Compound Data + public bool generateFromChildren; + + public List colliders; + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderAuthoring.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderAuthoring.cs.meta new file mode 100644 index 0000000..2265ddd --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderAuthoring.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 75a1b87513c8266459a8447e0b51359c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderConversionSystem.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderConversionSystem.cs new file mode 100644 index 0000000..79de4c5 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderConversionSystem.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Core; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine.Profiling; + +using UnityCollider = UnityEngine.Collider; + +namespace Latios.Psyshock.Authoring.Systems +{ + [ConverterVersion("latios", 2)] + public class ColliderConversionSystem : GameObjectConversionSystem + { + protected override void OnUpdate() + { + ConvertCompoundColliders(); + } + + #region CompoundColliders + private struct CompoundColliderComputationData + { + public Hash128 hash; + public int index; + public BlobAssetReference blob; + } + + private struct ColliderTransformHashPair + { + public ulong colliderHash; + public ulong transformHash; + } + + private List m_unityColliders = new List(); + private List m_authorings = new List(); + + private NativeList m_nativeColliders; + private NativeList m_nativeTransforms; + private NativeList m_compoundRanges; + + private void ConvertCompoundColliders() + { + Profiler.BeginSample("ConvertCompoundColliders"); + + m_nativeColliders = new NativeList(128, Allocator.TempJob); + m_nativeTransforms = new NativeList(128, Allocator.TempJob); + m_compoundRanges = new NativeList(128, Allocator.TempJob); + + //Step 1: Find all colliders and construct parallel arrays + m_authorings.Clear(); + Entities.WithNone().ForEach((ColliderAuthoring colliderAuthoring) => + { + //Do stuff + if (colliderAuthoring.colliderType == AuthoringColliderTypes.None) + return; + if (colliderAuthoring.generateFromChildren) + { + m_unityColliders.Clear(); + colliderAuthoring.GetComponentsInChildren(m_unityColliders); + try + { + CreateChildrenColliders(colliderAuthoring, m_unityColliders, m_nativeColliders, m_nativeTransforms, m_compoundRanges); + } + catch (Exception e) + { + DisposeAndThrow(e); + } + } + else + { + try + { + CreateChildrenColliders(colliderAuthoring, colliderAuthoring.colliders, m_nativeColliders, m_nativeTransforms, m_compoundRanges); + } + catch (Exception e) + { + DisposeAndThrow(e); + } + } + m_authorings.Add(colliderAuthoring); + }); + + if (m_authorings.Count > 0) + { + using (var computationContext = new BlobAssetComputationContext(BlobAssetStore, 128, Allocator.Temp)) + { + //Step 2: Compute hashes + var hashes = new NativeArray(m_compoundRanges.Length, Allocator.TempJob); + new ComputeCompoundHashesJob + { + colliders = m_nativeColliders, + transforms = m_nativeTransforms, + ranges = m_compoundRanges, + hashes = hashes + }.ScheduleParallel(m_compoundRanges.Length, 1, default).Complete(); + + //Step 3: Check hashes against computationContext to see if blobs need to be built + for (int i = 0; i < m_authorings.Count; i++) + { + var hash = hashes.ReinterpretLoad(i); + computationContext.AssociateBlobAssetWithUnityObject(hash, m_authorings[i].gameObject); + if (computationContext.NeedToComputeBlobAsset(hash)) + { + computationContext.AddBlobAssetToCompute(hash, new CompoundColliderComputationData + { + hash = hash, + index = i + }); + } + } + + //Step 4: Dispatch builder job + using (var computationData = computationContext.GetSettings(Allocator.TempJob)) + { + new ComputeCompoundBlobs + { + colliders = m_nativeColliders, + transforms = m_nativeTransforms, + ranges = m_compoundRanges, + computationData = computationData + }.ScheduleParallel(computationData.Length, 1, default).Complete(); + + foreach (var data in computationData) + { + computationContext.AddComputedBlobAsset(data.hash, data.blob); + } + } + + //Step 5: Build Collider component + var index = 0; + Entities.ForEach((ColliderAuthoring colliderAuthoring) => + { + computationContext.GetBlobAsset(hashes.ReinterpretLoad(index++), out var blob); + + var targetEntity = GetPrimaryEntity(colliderAuthoring); + + DeclareDependency(colliderAuthoring, colliderAuthoring.transform); + float3 scale = colliderAuthoring.transform.lossyScale; + if (scale.x != scale.y || scale.x != scale.z) + { + throw new InvalidOperationException( + $"GameObject Conversion Error: Failed to convert {colliderAuthoring}. Only uniform scale is permitted on Compound colliders."); + } + + Collider icdCompound = new CompoundCollider + { + compoundColliderBlob = blob, + scale = scale.x + }; + DstEntityManager.AddComponentData(targetEntity, icdCompound); + }); + + hashes.Dispose(); + } + } + + m_nativeColliders.Dispose(); + m_nativeTransforms.Dispose(); + m_compoundRanges.Dispose(); + + Profiler.EndSample(); + } + + private void CreateChildrenColliders(ColliderAuthoring root, + List unityColliders, + NativeList colliders, + NativeList transforms, + NativeList ranges) + { + int2 newRange = new int2(colliders.Length, 0); + foreach (var unityCollider in unityColliders) + { + DeclareDependency(root, unityCollider); + DeclareDependency(root, unityCollider.transform); + if ((unityCollider is UnityEngine.SphereCollider || unityCollider is UnityEngine.CapsuleCollider || unityCollider is UnityEngine.BoxCollider) == false) + { + throw new InvalidOperationException( + $"GameObject Conversion Error: Failed to convert {unityCollider}. Compound Colliders may only be composed of Sphere, Capsule, and Box Colliders. Other collider types may be supported in a future version."); + } + if (!unityCollider.transform.IsChildOf(root.transform)) + { + throw new InvalidOperationException($"GameObject Conversion Error: Failed to convert {root}. Compound Colliders may only reference children colliders."); + } + + Entity entity = TryGetPrimaryEntity(unityCollider); + if (entity == Entity.Null) + { + //The child GameObject must be a child of the StopConversion. Skip + continue; + } + + //Calculate transform + + float3 scale = (float3)unityCollider.transform.lossyScale / root.transform.lossyScale; + if ((unityCollider is UnityEngine.SphereCollider || unityCollider is UnityEngine.CapsuleCollider) && + math.cmax(scale) - math.cmin(scale) > 1.0E-5f) + { + throw new InvalidOperationException( + $"GameObject Conversion Error: Failed to convert {unityCollider}. Only uniform scale is permitted on Sphere and Capsule colliders."); + } + + float4x4 localToRoot = root.transform.localToWorldMatrix.inverse * unityCollider.transform.localToWorldMatrix; + Unity.Transforms.LocalToWorld ltr = new Unity.Transforms.LocalToWorld { Value = localToRoot }; + var rotation = quaternion.LookRotationSafe(ltr.Forward, ltr.Up); + var position = ltr.Position; + transforms.Add(new RigidTransform(rotation, position)); + + //Calculate collider + if (unityCollider is UnityEngine.SphereCollider unitySphere) + { + colliders.Add(new SphereCollider + { + center = unitySphere.center, + radius = unitySphere.radius * scale.x + }); + } + else if (unityCollider is UnityEngine.CapsuleCollider unityCapsule) + { + float3 dir; + if (unityCapsule.direction == 0) + { + dir = new float3(1f, 0f, 0f); + } + else if (unityCapsule.direction == 1) + { + dir = new float3(0f, 1f, 0f); + } + else + { + dir = new float3(0f, 0f, 1f); + } + colliders.Add(new CapsuleCollider + { + pointB = (float3)unityCapsule.center + ((unityCapsule.height / 2f - unityCapsule.radius) * unityCapsule.transform.lossyScale.x * dir), + pointA = (float3)unityCapsule.center - ((unityCapsule.height / 2f - unityCapsule.radius) * unityCapsule.transform.lossyScale.x * dir), + radius = unityCapsule.radius * scale.x + }); + } + else if (unityCollider is UnityEngine.BoxCollider unityBox) + { + colliders.Add(new BoxCollider + { + center = unityBox.center, + halfSize = unityBox.size * scale / 2f + }); + } + newRange.y++; + } + ranges.Add(newRange); + } + + private void DisposeAndThrow(Exception e) + { + m_nativeColliders.Dispose(); + m_nativeTransforms.Dispose(); + m_compoundRanges.Dispose(); + + throw e; + } + + [BurstCompile] + private unsafe struct ComputeCompoundHashesJob : IJobFor + { + [ReadOnly] public NativeArray colliders; + [ReadOnly] public NativeArray transforms; + [ReadOnly] public NativeArray ranges; + public NativeArray hashes; + + public void Execute(int i) + { + int2 range = ranges[i]; + byte* colliderPtr = (byte*)colliders.GetSubArray(range.x, range.y).GetUnsafeReadOnlyPtr(); + ulong colliderHash = XXHash.Hash64(colliderPtr, UnsafeUtility.SizeOf() * range.y); + + byte* transformPtr = (byte*)transforms.GetSubArray(range.x, range.y).GetUnsafeReadOnlyPtr(); + ulong transformHash = XXHash.Hash64(transformPtr, sizeof(RigidTransform) * range.y); + + hashes[i] = new ColliderTransformHashPair { colliderHash = colliderHash, transformHash = transformHash }; + } + } + + [BurstCompile] + private struct ComputeCompoundBlobs : IJobFor + { + [ReadOnly] public NativeArray colliders; + [ReadOnly] public NativeArray transforms; + [ReadOnly] public NativeArray ranges; + public NativeArray computationData; + + public void Execute(int i) + { + var data = computationData[i]; + var range = ranges[data.index]; + + var builder = new BlobBuilder(Allocator.Temp); + ref var root = ref builder.ConstructRoot(); + var blobColliders = builder.Allocate(ref root.colliders, range.y); + var blobTransforms = builder.Allocate(ref root.transforms, range.y); + + Aabb aabb = new Aabb(float.MaxValue, float.MinValue); + for (int j = 0; j < range.y; j++) + { + var c = colliders[j + range.x]; + var t = transforms[j + range.x]; + blobColliders[j] = c; + blobTransforms[j] = t; + var newbox = Physics.AabbFrom(in c, in t); + aabb.min = math.min(aabb.min, newbox.min); + aabb.max = math.max(aabb.max, newbox.max); + } + + root.localAabb = aabb; + data.blob = builder.CreateBlobAssetReference(Allocator.Persistent); + builder.Dispose(); + computationData[i] = data; + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderConversionSystem.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderConversionSystem.cs.meta new file mode 100644 index 0000000..81e3190 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ColliderConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef383084a8399414fbb72dda433011ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ConvexColliderSmartBlobberSystem.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ConvexColliderSmartBlobberSystem.cs new file mode 100644 index 0000000..c09b5ee --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ConvexColliderSmartBlobberSystem.cs @@ -0,0 +1,337 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Latios.Authoring.Systems; +using Latios.Unsafe; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Psyshock.Authoring +{ + public struct ConvexColliderBakeData + { + public Mesh sharedMesh; + } + + public static class ConvexColliderSmartBlobberAPIExtensions + { + public static SmartBlobberHandle CreateBlob(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + ConvexColliderBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvert(gameObject, bakeData); + } + + public static SmartBlobberHandleUntyped CreateBlobUntyped(this GameObjectConversionSystem conversionSystem, + GameObject gameObject, + ConvexColliderBakeData bakeData) + { + return conversionSystem.World.GetExistingSystem().AddToConvertUntyped(gameObject, bakeData); + } + } +} + +namespace Latios.Psyshock.Authoring.Systems +{ + [ConverterVersion("latios", 4)] + public class ConvexColliderSmartBlobberSystem : SmartBlobberConversionSystem + { + protected override void Filter(FilterBlobberData blobberData, ref ConvexContext context, NativeArray inputToFilteredMapping) + { + var hashes = new NativeArray(blobberData.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < blobberData.Count; i++) + { + var input = blobberData.input[i]; + if (input.sharedMesh == null || !input.sharedMesh.isReadable) + { + if (input.sharedMesh != null && !input.sharedMesh.isReadable) + Debug.LogError( + $"Psyshock failed to convert convex mesh {input.sharedMesh.name}. The mesh was not marked as readable. Please correct this in the mesh asset's import settings."); + + hashes[i] = default; + inputToFilteredMapping[i] = -1; + } + else + { + DeclareAssetDependency(blobberData.associatedObject[i], input.sharedMesh); + hashes[i] = input.sharedMesh.GetInstanceID(); + } + } + + new DeduplicateJob { hashes = hashes, inputToFilteredMapping = inputToFilteredMapping }.Run(); + hashes.Dispose(); + } + + protected override void PostFilter(PostFilterBlobberData blobberData, ref ConvexContext context) + { + var meshList = new List(); + + var converters = blobberData.converters; + + for (int i = 0; i < blobberData.Count; i++) + { + var mesh = blobberData.input[i].sharedMesh; + meshList.Add(mesh); + + converters[i] = new ConvexConverter + { + meshName = mesh.name + }; + } + + context.meshes = Mesh.AcquireReadOnlyMeshData(meshList); + } + + [BurstCompile] + struct DeduplicateJob : IJob + { + [ReadOnly] public NativeArray hashes; + public NativeArray inputToFilteredMapping; + + public void Execute() + { + var map = new NativeParallelHashMap(hashes.Length, Allocator.Temp); + for (int i = 0; i < hashes.Length; i++) + { + if (inputToFilteredMapping[i] < 0) + continue; + + if (map.TryGetValue(hashes[i], out int index)) + inputToFilteredMapping[i] = index; + else + map.Add(hashes[i], i); + } + } + } + } + + public struct ConvexConverter : ISmartBlobberContextBuilder + { + public FixedString128Bytes meshName; + + public unsafe BlobAssetReference BuildBlob(int prefilterIndex, int postfilterIndex, ref ConvexContext context) + { + if (!context.vector3Cache.IsCreated) + { + context.vector3Cache = new NativeList(Allocator.Temp); + } + + var builder = new BlobBuilder(Allocator.Temp); + + ref var blobRoot = ref builder.ConstructRoot(); + var mesh = context.meshes[postfilterIndex]; + + blobRoot.meshName = meshName; + + var vector3Cache = context.vector3Cache; + vector3Cache.ResizeUninitialized(mesh.vertexCount); + mesh.GetVertices(vector3Cache); + + // ConvexHullBuilder doesn't seem to properly check duplicated vertices when they are nice numbers. + // So we deduplicate things ourselves. + var hashedMeshVertices = new NativeParallelHashSet(vector3Cache.Length, Allocator.Temp); + for (int i = 0; i < vector3Cache.Length; i++) + hashedMeshVertices.Add(vector3Cache[i]); + var meshVertices = hashedMeshVertices.ToNativeArray(Allocator.Temp); + + // These are the default Unity uses except with 0 bevel radius. + // They don't matter too much since Unity is allowed to violate them anyways to meet storage constraints. + var parameters = new ConvexHullGenerationParameters + { + BevelRadius = 0f, + MinimumAngle = math.radians(2.5f), + SimplificationTolerance = 0.015f + }; + // These are the default storage constraints Unity uses. + // Changing them will likely break EPA. + int maxVertices = 252; + int maxFaces = 252; + int maxFaceVertices = 32; + + var convexHullBuilder = new ConvexHullBuilder(meshVertices, parameters, maxVertices, maxFaces, maxFaceVertices, out float convexRadius); + // Todo: We should handle 2D convex colliders more elegantly. + // Right now our queries don't consider it. + if (convexHullBuilder.dimension != 3) + { + parameters.MinimumAngle = 0f; + parameters.SimplificationTolerance = 0f; + convexHullBuilder = new ConvexHullBuilder(meshVertices, parameters, maxVertices, maxFaces, maxFaceVertices, out convexRadius); + } + UnityEngine.Assertions.Assert.IsTrue(convexRadius < math.EPSILON); + + // Based on Unity.Physics - ConvexCollider.cs + var vertices = new NativeList(convexHullBuilder.vertices.peakCount, Allocator.Temp); + var indexVertexMap = new NativeArray(convexHullBuilder.vertices.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + foreach (int vIndex in convexHullBuilder.vertices.indices) + { + indexVertexMap[vIndex] = (byte)vertices.Length; + vertices.Add(convexHullBuilder.vertices[vIndex].position); + } + + var facePlanes = new NativeList(convexHullBuilder.numFaces, Allocator.Temp); + var edgeIndicesInFaces = new NativeList(convexHullBuilder.numFaceVertices, Allocator.Temp); + var edgeIndicesInFacesStartsAndCounts = new NativeList(convexHullBuilder.numFaces, Allocator.Temp); + var vertexIndicesInEdges = new NativeList(convexHullBuilder.vertices.peakCount, Allocator.Temp); + var edgeHashMap = new NativeParallelHashMap(convexHullBuilder.vertices.peakCount, Allocator.Temp); + var edgeFlippedInFaces = new NativeList(convexHullBuilder.numFaceVertices, Allocator.Temp); + + var tempVerticesInFace = new NativeList(Allocator.Temp); + + for (ConvexHullBuilder.FaceEdge hullFace = convexHullBuilder.GetFirstFace(); hullFace.isValid; hullFace = convexHullBuilder.GetNextFace(hullFace)) + { + ConvexHullBuilder.Edge firstEdge = hullFace; + Plane facePlane = convexHullBuilder.planes[convexHullBuilder.triangles[firstEdge.triangleIndex].faceIndex]; + facePlanes.Add(facePlane); + + // Walk the face's outer vertices & edges + tempVerticesInFace.Clear(); + for (ConvexHullBuilder.FaceEdge edge = hullFace; edge.isValid; edge = convexHullBuilder.GetNextFaceEdge(edge)) + { + tempVerticesInFace.Add(indexVertexMap[convexHullBuilder.StartVertex(edge)]); + } + UnityEngine.Assertions.Assert.IsTrue(tempVerticesInFace.Length >= 3); + + // The rest of this is custom. + int edgeIndicesInFaceStart = edgeIndicesInFaces.Length; + int previousVertexIndex = tempVerticesInFace[tempVerticesInFace.Length - 1]; + for (int i = 0; i < tempVerticesInFace.Length; i++) + { + int2 edge = new int2(previousVertexIndex, tempVerticesInFace[i]); + previousVertexIndex = tempVerticesInFace[i]; + if (edgeHashMap.TryGetValue(edge, out var edgeIndex)) + { + edgeIndicesInFaces.Add(edgeIndex); + edgeFlippedInFaces.Add(false); + } + else if (edgeHashMap.TryGetValue(edge.yx, out edgeIndex)) + { + edgeIndicesInFaces.Add(edgeIndex); + edgeFlippedInFaces.Add(true); + } + else + { + edgeIndex = vertexIndicesInEdges.Length; + vertexIndicesInEdges.Add(edge); + edgeHashMap.Add(edge, edgeIndex); + edgeIndicesInFaces.Add(edgeIndex); + edgeFlippedInFaces.Add(false); + } + } + edgeIndicesInFacesStartsAndCounts.Add(new int2(edgeIndicesInFaceStart, tempVerticesInFace.Length)); + } + + var viie = builder.ConstructFromNativeArray(ref blobRoot.vertexIndicesInEdges, + (int2*)vertexIndicesInEdges.GetUnsafeReadOnlyPtr(), + vertexIndicesInEdges.Length); + var eiif = builder.ConstructFromNativeArray(ref blobRoot.edgeIndicesInFaces, (int*)edgeIndicesInFaces.GetUnsafeReadOnlyPtr(), edgeIndicesInFaces.Length); + var eiifsac = builder.ConstructFromNativeArray(ref blobRoot.edgeIndicesInFacesStartsAndCounts, + (int2*)edgeIndicesInFacesStartsAndCounts.GetUnsafeReadOnlyPtr(), + edgeIndicesInFacesStartsAndCounts.Length); + + var edgeNormals = builder.Allocate(ref blobRoot.edgeNormals, vertexIndicesInEdges.Length); + for (int i = 0; i < edgeNormals.Length; i++) + edgeNormals[i] = float3.zero; + + var facePlaneX = builder.Allocate(ref blobRoot.facePlaneX, edgeIndicesInFacesStartsAndCounts.Length); + var facePlaneY = builder.Allocate(ref blobRoot.facePlaneY, edgeIndicesInFacesStartsAndCounts.Length); + var facePlaneZ = builder.Allocate(ref blobRoot.facePlaneZ, edgeIndicesInFacesStartsAndCounts.Length); + var facePlaneDist = builder.Allocate(ref blobRoot.facePlaneDist, edgeIndicesInFacesStartsAndCounts.Length); + + var faceEdgeOutwardPlanes = builder.Allocate(ref blobRoot.faceEdgeOutwardPlanes, edgeIndicesInFaces.Length); + + for (int faceIndex = 0; faceIndex < edgeIndicesInFacesStartsAndCounts.Length; faceIndex++) + { + var plane = facePlanes[faceIndex]; + facePlaneX[faceIndex] = plane.normal.x; + facePlaneY[faceIndex] = plane.normal.y; + facePlaneZ[faceIndex] = plane.normal.z; + facePlaneDist[faceIndex] = plane.distanceFromOrigin; + + var edgeIndicesStartAndCount = edgeIndicesInFacesStartsAndCounts[faceIndex]; + + bool ccw = true; + { + int2 abIndices = vertexIndicesInEdges[edgeIndicesInFaces[edgeIndicesStartAndCount.x]]; + int2 cIndices = vertexIndicesInEdges[edgeIndicesInFaces[edgeIndicesStartAndCount.x + 1]]; + if (edgeFlippedInFaces[edgeIndicesStartAndCount.x]) + abIndices = abIndices.yx; + if (edgeFlippedInFaces[edgeIndicesStartAndCount.x + 1]) + cIndices = cIndices.yx; + float3 a = vertices[abIndices.x]; + float3 b = vertices[abIndices.y]; + float3 c = vertices[cIndices.y]; + ccw = math.dot(math.cross(plane.normal, b - a), c - a) < 0f; + } + + for (int faceEdgeIndex = edgeIndicesStartAndCount.x; faceEdgeIndex < edgeIndicesStartAndCount.x + edgeIndicesStartAndCount.y; faceEdgeIndex++) + { + int edgeIndex = edgeIndicesInFaces[faceEdgeIndex]; + edgeNormals[edgeIndex] += plane.normal; + + int2 abIndices = vertexIndicesInEdges[edgeIndex]; + if (edgeFlippedInFaces[faceEdgeIndex]) + abIndices = abIndices.yx; + float3 a = vertices[abIndices.x]; + float3 b = vertices[abIndices.y]; + var outwardNormal = math.cross(plane.normal, b - a); + outwardNormal = math.select(-outwardNormal, outwardNormal, ccw); + faceEdgeOutwardPlanes[faceEdgeIndex] = new Plane(outwardNormal, -math.dot(outwardNormal, a)); + } + } + + var vertexNormals = builder.Allocate(ref blobRoot.vertexNormals, vertices.Length); + for (int i = 0; i < vertexNormals.Length; i++) + vertexNormals[i] = float3.zero; + + for (int edgeIndex = 0; edgeIndex < vertexIndicesInEdges.Length; edgeIndex++) + { + edgeNormals[edgeIndex] = math.normalize(edgeNormals[edgeIndex]); + + var abIndices = vertexIndicesInEdges[edgeIndex]; + vertexNormals[abIndices.x] += edgeNormals[edgeIndex]; + vertexNormals[abIndices.y] += edgeNormals[edgeIndex]; + + float3 a = vertices[abIndices.x]; + float3 b = vertices[abIndices.y]; + } + + var verticesX = builder.Allocate(ref blobRoot.verticesX, vertices.Length); + var verticesY = builder.Allocate(ref blobRoot.verticesY, vertices.Length); + var verticesZ = builder.Allocate(ref blobRoot.verticesZ, vertices.Length); + + Aabb aabb = new Aabb(vertices[0], vertices[0]); + + for (int vertexIndex = 0; vertexIndex < vertices.Length; vertexIndex++) + { + var vertex = vertices[vertexIndex]; + + verticesX[vertexIndex] = vertex.x; + verticesY[vertexIndex] = vertex.y; + verticesZ[vertexIndex] = vertex.z; + + aabb = Physics.CombineAabb(vertex, aabb); + + vertexNormals[vertexIndex] = math.normalize(vertexNormals[vertexIndex]); + } + + blobRoot.localAabb = aabb; + + return builder.CreateBlobAssetReference(Allocator.Persistent); + } + } + + public struct ConvexContext : System.IDisposable + { + [ReadOnly] public Mesh.MeshDataArray meshes; + + [NativeDisableContainerSafetyRestriction] public NativeList vector3Cache; + + public void Dispose() => meshes.Dispose(); + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ConvexColliderSmartBlobberSystem.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ConvexColliderSmartBlobberSystem.cs.meta new file mode 100644 index 0000000..39afe17 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/ConvexColliderSmartBlobberSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07e946e9c451be1459565c69118ba048 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/DontConvertColliderTag.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/DontConvertColliderTag.cs new file mode 100644 index 0000000..1402e12 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/DontConvertColliderTag.cs @@ -0,0 +1,10 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock.Authoring +{ + public struct DontConvertColliderTag : IComponentData { } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/DontConvertColliderTag.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/DontConvertColliderTag.cs.meta new file mode 100644 index 0000000..bbf094f --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/DontConvertColliderTag.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca63356c5c5d01042970846bcbe58ca3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyColliderConversionSystem.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyColliderConversionSystem.cs new file mode 100644 index 0000000..49e4873 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyColliderConversionSystem.cs @@ -0,0 +1,104 @@ +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock.Authoring.Systems +{ + [UpdateInGroup(typeof(GameObjectConversionGroup))] + [DisableAutoCreation] + [ConverterVersion("latios", 4)] + public class LegacyColliderConversionSystem : GameObjectConversionSystem + { + protected override void OnUpdate() + { + Entities.WithNone().ForEach((UnityEngine.SphereCollider goSphere) => + { + DeclareDependency(goSphere, + goSphere.transform); + float3 lossyScale = goSphere.transform.lossyScale; + if (math.cmax(lossyScale) - math.cmin(lossyScale) > 1.0E-5f) + { + UnityEngine.Debug.LogWarning( + $"Failed to convert {goSphere}. Only uniform scaling is supported on SphereCollider. Lossy Scale divergence was: {math.cmax(lossyScale) - math.cmin(lossyScale)}"); + return; + } + + Entity entity = GetPrimaryEntity(goSphere); + Collider icdSphere = new SphereCollider + { + center = goSphere.center, + radius = goSphere.radius * goSphere.transform.localScale.x + }; + DstEntityManager.AddComponentData(entity, icdSphere); + }); + + Entities.WithNone().ForEach((UnityEngine.CapsuleCollider goCap) => + { + DeclareDependency(goCap, + goCap.transform); + float3 lossyScale = goCap.transform.lossyScale; + if (math.cmax(lossyScale) - math.cmin(lossyScale) > 1.0E-5f) + { + UnityEngine.Debug.LogWarning( + $"Failed to convert { goCap }. Only uniform scaling is supported on CapsuleCollider. Lossy Scale divergence was: {math.cmax(lossyScale) - math.cmin(lossyScale)}"); + return; + } + + Entity entity = GetPrimaryEntity(goCap); + float3 dir; + if (goCap.direction == 0) + { + dir = new float3(1f, 0f, 0f); + } + else if (goCap.direction == 1) + { + dir = new float3(0f, 1, 0f); + } + else + { + dir = new float3(0f, 0f, 1f); + } + Collider icdCap = new CapsuleCollider + { + pointB = (float3)goCap.center + ((goCap.height / 2f - goCap.radius) * goCap.transform.lossyScale.x * dir), + pointA = (float3)goCap.center - ((goCap.height / 2f - goCap.radius) * goCap.transform.lossyScale.x * dir), + radius = goCap.radius * goCap.transform.lossyScale.x + }; + DstEntityManager.AddComponentData(entity, icdCap); + }); + + Entities.WithNone().ForEach((UnityEngine.BoxCollider goBox) => + { + DeclareDependency(goBox, goBox.transform); + float3 lossyScale = goBox.transform.lossyScale; + + Entity entity = GetPrimaryEntity(goBox); + Collider icdBox = new BoxCollider + { + center = goBox.center, + halfSize = goBox.size * lossyScale / 2f + }; + DstEntityManager.AddComponentData(entity, icdBox); + }); + + Entities.ForEach((ConvexMeshColliderConversionList list) => + { + foreach (var mc in list.meshColliders) + { + var entity = GetPrimaryEntity(mc.mesh); + + var blob = mc.blobHandle.Resolve(); + if (!blob.IsCreated) + continue; + + Collider icdConvex = new ConvexCollider + { + convexColliderBlob = blob, + scale = mc.mesh.transform.lossyScale + }; + DstEntityManager.AddComponentData(entity, icdConvex); + } + }); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyColliderConversionSystem.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyColliderConversionSystem.cs.meta new file mode 100644 index 0000000..e0a1545 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyColliderConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 423babcfc5ce7854b9af3f5978defa90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyConvexColliderPreConversionSystem.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyConvexColliderPreConversionSystem.cs new file mode 100644 index 0000000..cdaaf65 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyConvexColliderPreConversionSystem.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Latios.Authoring; +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Psyshock.Authoring.Systems +{ + internal struct PendingConvexColliderBlob + { + public MeshCollider mesh; + public SmartBlobberHandle blobHandle; + } + + internal class ConvexMeshColliderConversionList : IComponentData + { + public List meshColliders; + } + + [UpdateInGroup(typeof(GameObjectBeforeConversionGroup))] + [DisableAutoCreation] + [ConverterVersion("latios", 4)] + public class LegacyConvexColliderPreConversionSystem : GameObjectConversionSystem + { + protected override void OnUpdate() + { + var convexUnityList = new List(); + + Entities.WithNone().ForEach((MeshCollider goMesh) => + { + DeclareDependency(goMesh, goMesh.transform); + + if (goMesh.convex && goMesh.sharedMesh != null) + { + convexUnityList.Add(new PendingConvexColliderBlob + { + mesh = goMesh, + blobHandle = this.CreateBlob(goMesh.gameObject, new ConvexColliderBakeData { sharedMesh = goMesh.sharedMesh }) + }); + } + }); + + var e = EntityManager.CreateEntity(); + EntityManager.AddComponentObject(e, new ConvexMeshColliderConversionList { meshColliders = convexUnityList }); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyConvexColliderPreConversionSystem.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyConvexColliderPreConversionSystem.cs.meta new file mode 100644 index 0000000..db111fb --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/LegacyConvexColliderPreConversionSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d14f8c0f26e06542802d10c0f18a846 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/PsyshockConversionBootstrap.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/PsyshockConversionBootstrap.cs new file mode 100644 index 0000000..c6b37a2 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/PsyshockConversionBootstrap.cs @@ -0,0 +1,26 @@ +using Latios.Psyshock.Authoring.Systems; +using Unity.Entities; + +namespace Latios.Psyshock.Authoring +{ + /// + /// Static class containing installers for optional authoring time features in the Psyshock module + /// + public static class PsyshockConversionBootstrap + { + /// + /// Enables conversion of Unity's legacy collider components into Psyshock colliders + /// by installing the appropriate conversion systems into the conversion world + /// + /// The conversion world in which to install the legacy conversion systems + public static void InstallLegacyColliderConversion(World world) + { + if (!world.Flags.HasFlag(WorldFlags.Conversion)) + throw new System.InvalidOperationException("Psyshock Legacy Collider Conversion must be installed in a conversion world."); + + BootstrapTools.InjectSystem(typeof(LegacyConvexColliderPreConversionSystem), world); + BootstrapTools.InjectSystem(typeof(LegacyColliderConversionSystem), world); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/PsyshockConversionBootstrap.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/PsyshockConversionBootstrap.cs.meta new file mode 100644 index 0000000..1a4b885 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Authoring/PsyshockConversionBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 624eac5156bf33445977da244afa0621 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character.meta new file mode 100644 index 0000000..52f467e --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c08a477d3009b114c9c71331b2da5c32 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character/Physics.CharacterVelocityUtilities.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character/Physics.CharacterVelocityUtilities.cs new file mode 100644 index 0000000..24fb75c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character/Physics.CharacterVelocityUtilities.cs @@ -0,0 +1,253 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + /// + /// Applies a velocity step for a single axis given an input which drives both the target velocity and acceleration strength + /// + /// A value in the range [-1f, 1f] which represent an input value along an axis + /// The current signed velocity along an axis + /// The maximum acceleration when moving in the positive direction. Must not be negative. + /// The maximum deceleration when moving in the positive direction. Must not be negative. + /// The desired speed when passed in an inputAxis value of 1f. If the currentVelocity is higher than this value, + /// smooth deceleration will be applied. Must not be negative. + /// The maximum acceleration when moving in the negative direction. Must not be negative. + /// The maximum deceleration when moving in the negative direction. Must not be negative. + /// The desired speed when passed in an inputAxis value of -1f. If the -currentVelocity is higher than this value, + /// smooth deceleration will be applied. Must not be negative. + /// The time step over which to apply the velocity update. + /// A new signed velocity value for the axis + /// + /// This algorithm provides a uniquely responsive feel for mapping input to an object's velocity using acceleration constraints. + /// It linearly interpolates the desiredSpeed between zero and the maxSpeed in either the positive or negative domain. + /// It also attenuates the acceleration by the input. This means that with an initial velocity of 0f, applying an input of 0.5f + /// will reach half the max speed in the same amount of time applying an input of 1f would reach max speed. + /// However, deceleration is always clamped to [0.5f, 1f] multiplied by the max deceleration. This gives a snappier release. + /// A neutral input of 0f will always apply 0.5f deceleration. + /// + /// Another feature of this function is that the speed arguments are "soft" meaning that if the currentVelocity exceeds them, + /// the velocity will not be clamped but instead decelerate to that speed value. This allows all speed and acceleration values + /// to be "dynamic", useful for turbo mechanics, control reduction in the air, ect. + /// + public static float StepVelocityWithInput(float inputAxis, + float currentVelocity, + float positiveMaxAcceleration, + float positiveMaxDeceleration, + float positiveSoftMaxSpeed, + float negativeMaxAcceleration, + float negativeMaxDeceleration, + float negativeSoftMaxSpeed, + float deltaTime) + { + var input = math.clamp(inputAxis, -1f, 1f); + var pAccel = positiveMaxAcceleration; + var pDecel = positiveMaxDeceleration; + var pMaxSpeed = positiveSoftMaxSpeed; + var nAccel = negativeMaxAcceleration; + var nDecel = negativeMaxDeceleration; + var nMaxSpeed = negativeSoftMaxSpeed; + + var pInput = math.saturate(input); + var nInput = math.saturate(-input); + + // Solve for positive first + // This is the speed we want based on our input + var desiredSpeed = pMaxSpeed * pInput; + // We also multiply the input to the acceleration to get better easing behavior (this is a "feel" preference) + var accel = pInput * pAccel; + // If our current velocity exceeds our desired speed, apply half the deceleration + accel = math.select(accel, -pDecel * 0.5f, currentVelocity > desiredSpeed); + // If a negative input is applied, apply up to the remainder of the deceleration + var decel = nInput * -pDecel * 0.5f; + // Combine the acceleration and deceleration + var a = accel + decel; + // Step the simulation + var pNewVelocity = currentVelocity + a * deltaTime; + // If we "cross over" the desired speed, then we fix ourselves to the desired speed. + pNewVelocity = math.select(pNewVelocity, math.min(pNewVelocity, desiredSpeed), a >= 0f); + pNewVelocity = math.select(pNewVelocity, math.max(pNewVelocity, desiredSpeed), a < 0f); + + // Now solve for the negative + desiredSpeed = nMaxSpeed * nInput; + accel = nInput * nAccel; + accel = math.select(accel, -nDecel * 0.5f, -currentVelocity > desiredSpeed); + decel = pInput * -nDecel * 0.5f; + a = accel + decel; + var nNewVelocity = currentVelocity - a * deltaTime; + nNewVelocity = math.select(nNewVelocity, math.max(nNewVelocity, -desiredSpeed), a >= 0f); + nNewVelocity = math.select(nNewVelocity, math.min(nNewVelocity, -desiredSpeed), a < 0f); + + // Pick the better option + var useNegative = currentVelocity < 0f; + useNegative |= currentVelocity == 0f & nInput > 0f; + return math.select(pNewVelocity, nNewVelocity, useNegative); + } + + /// + /// Applies a velocity step for two axes given an input which drives both the target velocity and acceleration strength. + /// This is the same as multiple calls to the single axis version for each axis. + /// + /// A value in the range [-1f, 1f] which represent an input value along each axis + /// The current signed velocity along each axis + /// The maximum acceleration when moving in the positive direction. Must not be negative. + /// The maximum deceleration when moving in the positive direction. Must not be negative. + /// The desired speed when passed in an inputAxis value of 1f. If the currentVelocity is higher than this value, + /// smooth deceleration will be applied. Must not be negative. + /// The maximum acceleration when moving in the negative direction. Must not be negative. + /// The maximum deceleration when moving in the negative direction. Must not be negative. + /// The desired speed when passed in an inputAxis value of -1f. If the -currentVelocity is higher than this value, + /// smooth deceleration will be applied. Must not be negative. + /// The time step over which to apply the velocity update. + /// A new signed velocity value for the axis + /// + /// This algorithm provides a uniquely responsive feel for mapping input to an object's velocity using acceleration constraints. + /// It linearly interpolates the desiredSpeed between zero and the maxSpeed in either the positive or negative domain. + /// It also attenuates the acceleration by the input. This means that with an initial velocity of 0f, applying an input of 0.5f + /// will reach half the max speed in the same amount of time applying an input of 1f would reach max speed. + /// However, deceleration is always clamped to [0.5f, 1f] multiplied by the max deceleration. This gives a snappier release. + /// A neutral input of 0f will always apply 0.5f deceleration. + /// + /// Another feature of this function is that the speed arguments are "soft" meaning that if the currentVelocity exceeds them, + /// the velocity will not be clamped but instead decelerate to that speed value. This allows all speed and acceleration values + /// to be "dynamic", useful for turbo mechanics, control reduction in the air, ect. + /// + public static float2 StepVelocityWithInput(float2 inputAxis, + float2 currentVelocity, + float2 positiveMaxAcceleration, + float2 positiveMaxDeceleration, + float2 positiveSoftMaxSpeed, + float2 negativeMaxAcceleration, + float2 negativeMaxDeceleration, + float2 negativeSoftMaxSpeed, + float deltaTime) + { + var input = math.clamp(inputAxis, -1f, 1f); + var pAccel = positiveMaxAcceleration; + var pDecel = positiveMaxDeceleration; + var pMaxSpeed = positiveSoftMaxSpeed; + var nAccel = negativeMaxAcceleration; + var nDecel = negativeMaxDeceleration; + var nMaxSpeed = negativeSoftMaxSpeed; + + var pInput = math.saturate(input); + var nInput = math.saturate(-input); + + // Solve for positive first + // This is the speed we want based on our input + var desiredSpeed = pMaxSpeed * pInput; + // We also multiply the input to the acceleration to get better easing behavior (this is a "feel" preference) + var accel = pInput * pAccel; + // If our current velocity exceeds our desired speed, apply half the deceleration + accel = math.select(accel, -pDecel * 0.5f, currentVelocity > desiredSpeed); + // If a negative input is applied, apply up to the remainder of the deceleration + var decel = nInput * -pDecel * 0.5f; + // Combine the acceleration and deceleration + var a = accel + decel; + // Step the simulation + var pNewVelocity = currentVelocity + a * deltaTime; + // If we "cross over" the desired speed, then we fix ourselves to the desired speed. + pNewVelocity = math.select(pNewVelocity, math.min(pNewVelocity, desiredSpeed), a >= 0f); + pNewVelocity = math.select(pNewVelocity, math.max(pNewVelocity, desiredSpeed), a < 0f); + + // Now solve for the negative + desiredSpeed = nMaxSpeed * nInput; + accel = nInput * nAccel; + accel = math.select(accel, -nDecel * 0.5f, -currentVelocity > desiredSpeed); + decel = pInput * -nDecel * 0.5f; + a = accel + decel; + var nNewVelocity = currentVelocity - a * deltaTime; + nNewVelocity = math.select(nNewVelocity, math.max(nNewVelocity, -desiredSpeed), a >= 0f); + nNewVelocity = math.select(nNewVelocity, math.min(nNewVelocity, -desiredSpeed), a < 0f); + + // Pick the better option + var useNegative = currentVelocity < 0f; + useNegative |= currentVelocity == 0f & nInput > 0f; + return math.select(pNewVelocity, nNewVelocity, useNegative); + } + + /// + /// Applies a velocity step for two axes given an input which drives both the target velocity and acceleration strength. + /// This is the same as multiple calls to the single axis version for each axis. + /// + /// A value in the range [-1f, 1f] which represent an input value along each axis + /// The current signed velocity along each axis + /// The maximum acceleration when moving in the positive direction. Must not be negative. + /// The maximum deceleration when moving in the positive direction. Must not be negative. + /// The desired speed when passed in an inputAxis value of 1f. If the currentVelocity is higher than this value, + /// smooth deceleration will be applied. Must not be negative. + /// The maximum acceleration when moving in the negative direction. Must not be negative. + /// The maximum deceleration when moving in the negative direction. Must not be negative. + /// The desired speed when passed in an inputAxis value of -1f. If the -currentVelocity is higher than this value, + /// smooth deceleration will be applied. Must not be negative. + /// The time step over which to apply the velocity update. + /// A new signed velocity value for the axis + /// + /// This algorithm provides a uniquely responsive feel for mapping input to an object's velocity using acceleration constraints. + /// It linearly interpolates the desiredSpeed between zero and the maxSpeed in either the positive or negative domain. + /// It also attenuates the acceleration by the input. This means that with an initial velocity of 0f, applying an input of 0.5f + /// will reach half the max speed in the same amount of time applying an input of 1f would reach max speed. + /// However, deceleration is always clamped to [0.5f, 1f] multiplied by the max deceleration. This gives a snappier release. + /// A neutral input of 0f will always apply 0.5f deceleration. + /// + /// Another feature of this function is that the speed arguments are "soft" meaning that if the currentVelocity exceeds them, + /// the velocity will not be clamped but instead decelerate to that speed value. This allows all speed and acceleration values + /// to be "dynamic", useful for turbo mechanics, control reduction in the air, ect. + /// + public static float3 StepVelocityWithInput(float3 inputAxis, + float3 currentVelocity, + float3 positiveMaxAcceleration, + float3 positiveMaxDeceleration, + float3 positiveSoftMaxSpeed, + float3 negativeMaxAcceleration, + float3 negativeMaxDeceleration, + float3 negativeSoftMaxSpeed, + float deltaTime) + { + var input = math.clamp(inputAxis, -1f, 1f); + var pAccel = positiveMaxAcceleration; + var pDecel = positiveMaxDeceleration; + var pMaxSpeed = positiveSoftMaxSpeed; + var nAccel = negativeMaxAcceleration; + var nDecel = negativeMaxDeceleration; + var nMaxSpeed = negativeSoftMaxSpeed; + + var pInput = math.saturate(input); + var nInput = math.saturate(-input); + + // Solve for positive first + // This is the speed we want based on our input + var desiredSpeed = pMaxSpeed * pInput; + // We also multiply the input to the acceleration to get better easing behavior (this is a "feel" preference) + var accel = pInput * pAccel; + // If our current velocity exceeds our desired speed, apply half the deceleration + accel = math.select(accel, -pDecel * 0.5f, currentVelocity > desiredSpeed); + // If a negative input is applied, apply up to the remainder of the deceleration + var decel = nInput * -pDecel * 0.5f; + // Combine the acceleration and deceleration + var a = accel + decel; + // Step the simulation + var pNewVelocity = currentVelocity + a * deltaTime; + // If we "cross over" the desired speed, then we fix ourselves to the desired speed. + pNewVelocity = math.select(pNewVelocity, math.min(pNewVelocity, desiredSpeed), a >= 0f); + pNewVelocity = math.select(pNewVelocity, math.max(pNewVelocity, desiredSpeed), a < 0f); + + // Now solve for the negative + desiredSpeed = nMaxSpeed * nInput; + accel = nInput * nAccel; + accel = math.select(accel, -nDecel * 0.5f, -currentVelocity > desiredSpeed); + decel = pInput * -nDecel * 0.5f; + a = accel + decel; + var nNewVelocity = currentVelocity - a * deltaTime; + nNewVelocity = math.select(nNewVelocity, math.max(nNewVelocity, -desiredSpeed), a >= 0f); + nNewVelocity = math.select(nNewVelocity, math.min(nNewVelocity, -desiredSpeed), a < 0f); + + // Pick the better option + var useNegative = currentVelocity < 0f; + useNegative |= currentVelocity == 0f & nInput > 0f; + return math.select(pNewVelocity, nNewVelocity, useNegative); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character/Physics.CharacterVelocityUtilities.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character/Physics.CharacterVelocityUtilities.cs.meta new file mode 100644 index 0000000..ed87e4d --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Character/Physics.CharacterVelocityUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7198bf52a144ffd4eafa3d23e8ac88e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components.meta new file mode 100644 index 0000000..1cb4d59 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37033950c59bfa943af92a0f69eee5d8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.cs new file mode 100644 index 0000000..c851493 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.cs @@ -0,0 +1,81 @@ +using System; +using System.Runtime.InteropServices; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + // The concrete type of a collider + public enum ColliderType : byte + { + //Convex Primitive types + Sphere = 0, + Capsule = 1, + Box = 2, + Triangle = 3, + //Quad = 4, + //Cylinder = 5 + //Cone = 6 + + //Beveled Convex Primitive Types + //BeveledBox = 32 + + //Concave Primitive Types + //Torus = 64 + + //Complex Convex Types + Convex = 128, + //Complex Concave types + //Mesh = 160, + Compound = 161, + //Terrain = 162, + + //192+ ? + } + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public unsafe partial struct Collider : IComponentData + { + [FieldOffset(0)] + Storage m_storage; + + [FieldOffset(0)] + ColliderType m_type; + + [FieldOffset(16)] + internal SphereCollider m_sphere; + + [FieldOffset(16)] + internal CapsuleCollider m_capsule; + + [FieldOffset(16)] + internal BoxCollider m_box; + + [FieldOffset(16)] + internal TriangleCollider m_triangle; + + [FieldOffset(8)] + internal ConvexCollider m_convex; + + [FieldOffset(8)] + internal CompoundCollider m_compound; + + [FieldOffset(8)] + UnsafeUntypedBlobAssetReference m_blobRef; + + public ColliderType type => m_type; + + private struct Storage + { +#pragma warning disable CS0649 //variable never assigned + public float4 a; + public float4 b; + public float4 c; +#pragma warning restore CS0649 + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.cs.meta new file mode 100644 index 0000000..3457f9c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 19dc33d84eda87946a242092cf920ac9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.gen.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.gen.cs new file mode 100644 index 0000000..6070f82 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.gen.cs @@ -0,0 +1,126 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Collider.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Diagnostics; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Entities.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public unsafe partial struct Collider : IComponentData + { + public static implicit operator Collider(SphereCollider sphereCollider) + { + Collider collider = default; + collider.m_type = ColliderType.Sphere; + collider.m_sphere = sphereCollider; + return collider; + } + + public static implicit operator SphereCollider(Collider collider) + { + CheckColliderIsCastTargetType(in collider, ColliderType.Sphere); + return collider.m_sphere; + } + + public static implicit operator Collider(CapsuleCollider capsuleCollider) + { + Collider collider = default; + collider.m_type = ColliderType.Capsule; + collider.m_capsule = capsuleCollider; + return collider; + } + + public static implicit operator CapsuleCollider(Collider collider) + { + CheckColliderIsCastTargetType(in collider, ColliderType.Capsule); + return collider.m_capsule; + } + + public static implicit operator Collider(BoxCollider boxCollider) + { + Collider collider = default; + collider.m_type = ColliderType.Box; + collider.m_box = boxCollider; + return collider; + } + + public static implicit operator BoxCollider(Collider collider) + { + CheckColliderIsCastTargetType(in collider, ColliderType.Box); + return collider.m_box; + } + + public static implicit operator Collider(TriangleCollider triangleCollider) + { + Collider collider = default; + collider.m_type = ColliderType.Box; + collider.m_triangle = triangleCollider; + return collider; + } + + public static implicit operator TriangleCollider(Collider collider) + { + CheckColliderIsCastTargetType(in collider, ColliderType.Triangle); + return collider.m_triangle; + } + + public static implicit operator Collider(ConvexCollider convexCollider) + { + Collider collider = default; + collider.m_type = ColliderType.Convex; + collider.m_convex = convexCollider; + return collider; + } + + public static implicit operator ConvexCollider(Collider collider) + { + CheckColliderIsCastTargetType(in collider, ColliderType.Convex); + return collider.m_convex; + } + + public static implicit operator Collider(CompoundCollider compoundCollider) + { + Collider collider = default; + collider.m_type = ColliderType.Compound; + collider.m_compound = compoundCollider; + return collider; + } + + public static implicit operator CompoundCollider(Collider collider) + { + CheckColliderIsCastTargetType(in collider, ColliderType.Compound); + return collider.m_compound; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + internal static void CheckColliderIsCastTargetType(in Collider c, ColliderType targetType) + { + if (c.m_type != targetType) + { + switch (targetType) + { + case ColliderType.Sphere: throw new InvalidOperationException("Collider is not a SphereCollider but is being casted to one."); + case ColliderType.Capsule: throw new InvalidOperationException("Collider is not a CapsuleCollider but is being casted to one."); + case ColliderType.Box: throw new InvalidOperationException("Collider is not a BoxCollider but is being casted to one."); + case ColliderType.Triangle: throw new InvalidOperationException("Collider is not a TriangleCollider but is being casted to one."); + case ColliderType.Convex: throw new InvalidOperationException( + "Collider is not a ConvexCollider but is being casted to one. Unlike Unity.Physics, ConvexColliders do not aggregate Spheres, Capsules, Boxes, or Triangles."); + case ColliderType.Compound: throw new InvalidOperationException("Collider is not a CompoundCollider but is being casted to one."); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.gen.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.gen.cs.meta new file mode 100644 index 0000000..9a2014c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/ColliderPsyshock.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8c43b51cb243d534f9dc1dc15d56c681 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/PhysicsScale.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/PhysicsScale.cs new file mode 100644 index 0000000..12a4c13 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/PhysicsScale.cs @@ -0,0 +1,38 @@ +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public struct PhysicsScale : IComponentData + { + public float3 scale; + public State state; + public bool ignoreParent; + + public enum State : byte + { + None, + Uniform, + NonUniform, + NonComputable, + } + + public PhysicsScale(float3 scale) + { + if (math.all(scale.x == scale.yz)) + { + if (scale.x == 1f) + state = State.None; + else + state = State.Uniform; + } + else + state = State.NonUniform; + this.scale = scale; + ignoreParent = false; + } + } + + public struct AutoUpdatePhysicsScaleTag : IComponentData { } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/PhysicsScale.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/PhysicsScale.cs.meta new file mode 100644 index 0000000..340cc89 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Components/PhysicsScale.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d52782af852e8747904c0a0003e0d2b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug.meta new file mode 100644 index 0000000..a823158 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 70d9e82762b0ede46978ee1245bfc5e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawCollider.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawCollider.cs new file mode 100644 index 0000000..5a24a64 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawCollider.cs @@ -0,0 +1,269 @@ +using Color = UnityEngine.Color; +using Debug = UnityEngine.Debug; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class PhysicsDebug + { + /// + /// Draws a wireframe of a sphere using UnityEngine.Debug.DrawLine calls + /// + /// The sphere to draw + /// The transform of the sphere in world space + /// The color of the wireframe + /// The number of segments to draw per 180 degree arc + public static void DrawCollider(in SphereCollider sphere, in RigidTransform transform, Color color, int segmentsPerPi = 6) + { + math.sincos(math.PI / segmentsPerPi, out float sin, out float cos); + float2 turnVector = new float2(cos, sin); + float2 previous = new float2(1f, 0f); + + var tf = transform; + tf.pos += sphere.center; + + for (int segment = 0; segment < segmentsPerPi; segment++) + { + float2 current = LatiosMath.ComplexMul(previous, turnVector); + + float2 currentScaled = current * sphere.radius; + float2 previousScaled = previous * sphere.radius; + + for (int i = 0; i < segmentsPerPi; i++) + { + var xTransform = tf; + var zTransform = tf; + xTransform.rot = math.mul(xTransform.rot, quaternion.RotateX(math.PI * i / segmentsPerPi)); + zTransform.rot = math.mul(zTransform.rot, quaternion.RotateZ(math.PI * i / segmentsPerPi)); + + float3 currentX = math.transform(xTransform, new float3(currentScaled.x, 0f, currentScaled.y)); + float3 previousX = math.transform(xTransform, new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousX, currentX, color); + currentX = math.transform(xTransform, -new float3(currentScaled.x, 0f, currentScaled.y)); + previousX = math.transform(xTransform, -new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousX, currentX, color); + if (i != 0) + { + float3 currentZ = math.transform(zTransform, new float3(currentScaled.x, 0f, currentScaled.y)); + float3 previousZ = math.transform(zTransform, new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousZ, currentZ, color); + currentZ = math.transform(zTransform, -new float3(currentScaled.x, 0f, currentScaled.y)); + previousZ = math.transform(zTransform, -new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousZ, currentZ, color); + } + } + + previous = current; + } + } + + /// + /// Draws a wireframe of a capsule using UnityEngine.Debug.DrawLine calls + /// + /// The capsule to draw + /// The transform of the capsule in world space + /// The color of the wireframe + /// The number of segments to draw per 180 degree arc + public static void DrawCollider(in CapsuleCollider capsule, in RigidTransform transform, Color color, int segmentsPerPi = 6) + { + if (math.distance(capsule.pointA, capsule.pointB) < math.EPSILON) + { + SphereCollider sphere = new SphereCollider(capsule.pointA, capsule.radius); + DrawCollider(sphere, transform, color, segmentsPerPi); + return; + } + + math.sincos(math.PI / segmentsPerPi, out float sin, out float cos); + float2 turnVector = new float2(cos, sin); + float2 previous = new float2(1f, 0f); + + float3 capA = float3.zero; + float3 capB = new float3(0f, math.distance(capsule.pointA, capsule.pointB), 0f); + + quaternion rotation; + if (math.all((math.abs(capsule.pointB - capsule.pointA)).xz <= math.EPSILON)) + { + rotation = quaternion.identity; + } + else if (math.all((math.abs(capsule.pointB - capsule.pointA)).xy <= math.EPSILON)) + { + rotation = quaternion.RotateX(math.PI / 2f); + } + else + { + rotation = quaternion.LookRotationSafe(math.forward(), capsule.pointB - capsule.pointA); + } + var localTransform = new RigidTransform(rotation, capsule.pointA); + + for (int segment = 0; segment < segmentsPerPi; segment++) + { + float2 current = LatiosMath.ComplexMul(previous, turnVector); + + float2 currentScaled = current * capsule.radius; + float2 previousScaled = previous * capsule.radius; + + for (int i = 0; i < segmentsPerPi; i++) + { + var axTransform = math.mul(transform, localTransform); + var azTransform = math.mul(transform, localTransform); + axTransform = math.mul(axTransform, new RigidTransform(quaternion.RotateX(math.PI * i / segmentsPerPi), capA)); + azTransform = math.mul(azTransform, new RigidTransform(quaternion.EulerZXY(-math.PI / 2f, 0f, math.PI * i / segmentsPerPi), capA)); + var bxTransform = math.mul(transform, localTransform); + var bzTransform = math.mul(transform, localTransform); + bxTransform = math.mul(bxTransform, new RigidTransform(quaternion.RotateX(math.PI * i / segmentsPerPi), capB)); + bzTransform = math.mul(bzTransform, new RigidTransform(quaternion.EulerZXY(-math.PI / 2f, 0f, math.PI * i / segmentsPerPi), capB)); + + float3 currentX = math.transform(bxTransform, -new float3(currentScaled.x, 0f, currentScaled.y)); + float3 previousX = math.transform(bxTransform, -new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousX, currentX, color); + currentX = math.transform(axTransform, new float3(currentScaled.x, 0f, currentScaled.y)); + previousX = math.transform(axTransform, new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousX, currentX, color); + + float3 currentZ = math.transform(bzTransform, new float3(currentScaled.x, 0f, currentScaled.y)); + float3 previousZ = math.transform(bzTransform, new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousZ, currentZ, color); + currentZ = math.transform(azTransform, -new float3(currentScaled.x, 0f, currentScaled.y)); + previousZ = math.transform(azTransform, -new float3(previousScaled.x, 0f, previousScaled.y)); + Debug.DrawLine(previousZ, currentZ, color); + + if (i == 0) + { + float3 ya = math.transform(axTransform, -new float3(currentScaled.x, 0f, currentScaled.y)); + float3 yb = math.transform(bxTransform, -new float3(currentScaled.x, 0f, currentScaled.y)); + Debug.DrawLine(ya, yb, color); + ya = math.transform(axTransform, new float3(currentScaled.x, 0f, currentScaled.y)); + yb = math.transform(bxTransform, new float3(currentScaled.x, 0f, currentScaled.y)); + Debug.DrawLine(ya, yb, color); + } + } + + previous = current; + } + } + + /// + /// Draws a wireframe of a box using UnityEngine.Debug.DrawLine calls + /// + /// The box to draw + /// The transform of the box in world space + /// The color of the wireframe + public static void DrawCollider(in BoxCollider box, in RigidTransform transform, Color color) + { + var aabb = new Aabb(box.center - box.halfSize, box.center + box.halfSize); + + float3 leftTopFront = math.transform(transform, new float3(aabb.min.x, aabb.max.y, aabb.min.z)); + float3 rightTopFront = math.transform(transform, new float3(aabb.max.x, aabb.max.y, aabb.min.z)); + float3 leftBottomFront = math.transform(transform, new float3(aabb.min.x, aabb.min.y, aabb.min.z)); + float3 rightBottomFront = math.transform(transform, new float3(aabb.max.x, aabb.min.y, aabb.min.z)); + float3 leftTopBack = math.transform(transform, new float3(aabb.min.x, aabb.max.y, aabb.max.z)); + float3 rightTopBack = math.transform(transform, new float3(aabb.max.x, aabb.max.y, aabb.max.z)); + float3 leftBottomBack = math.transform(transform, new float3(aabb.min.x, aabb.min.y, aabb.max.z)); + float3 rightBottomBack = math.transform(transform, new float3(aabb.max.x, aabb.min.y, aabb.max.z)); + + Debug.DrawLine(leftTopFront, rightTopFront, color); + Debug.DrawLine(rightTopFront, rightBottomFront, color); + Debug.DrawLine(rightBottomFront, leftBottomFront, color); + Debug.DrawLine(leftBottomFront, leftTopFront, color); + + Debug.DrawLine(leftTopBack, rightTopBack, color); + Debug.DrawLine(rightTopBack, rightBottomBack, color); + Debug.DrawLine(rightBottomBack, leftBottomBack, color); + Debug.DrawLine(leftBottomBack, leftTopBack, color); + + Debug.DrawLine(leftTopFront, leftTopBack, color); + Debug.DrawLine(rightTopFront, rightTopBack, color); + Debug.DrawLine(leftBottomFront, leftBottomBack, color); + Debug.DrawLine(rightBottomFront, rightBottomBack, color); + } + + /// + /// Draws a wireframe of a triangle using UnityEngine.Debug.DrawLine calls + /// + /// The triangle to draw + /// The transform of the triangle in world space + /// The color of the wireframe + public static void DrawCollider(in TriangleCollider triangle, in RigidTransform transform, Color color) + { + float3 a = math.transform(transform, triangle.pointA); + float3 b = math.transform(transform, triangle.pointB); + float3 c = math.transform(transform, triangle.pointC); + + Debug.DrawLine(a, b, color); + Debug.DrawLine(b, c, color); + Debug.DrawLine(c, a, color); + } + + /// + /// Draws a wireframe of a convex mesh using UnityEngine.Debug.DrawLine calls + /// + /// The convex mesh to draw + /// The transform of the convex mesh in world space + /// The color of the wireframe + public static void DrawCollider(in ConvexCollider convex, in RigidTransform transform, Color color) + { + ref var blob = ref convex.convexColliderBlob.Value; + + for (int i = 0; i < blob.vertexIndicesInEdges.Length; i++) + { + var abIndices = blob.vertexIndicesInEdges[i]; + float3 a = new float3(blob.verticesX[abIndices.x], blob.verticesY[abIndices.x], blob.verticesZ[abIndices.x]) * convex.scale; + float3 b = new float3(blob.verticesX[abIndices.y], blob.verticesY[abIndices.y], blob.verticesZ[abIndices.y]) * convex.scale; + Debug.DrawLine(math.transform(transform, a), math.transform(transform, b), color); + } + } + + /// + /// Draws a wireframe of all subcolliders in a compound using UnityEngine.Debug.DrawLine calls + /// + /// The compound to draw + /// The transform of the compound in world space + /// The color of the wireframe + /// The number of segments to draw per 180 degree arc for any subcolliders which have round features + public static void DrawCollider(in CompoundCollider compound, in RigidTransform transform, Color color, int segmentsPerPi = 6) + { + ref var blob = ref compound.compoundColliderBlob.Value; + var scale = new PhysicsScale(compound.scale); + + for (int i = 0; i < blob.blobColliders.Length; i++) + { + var c = Physics.ScaleCollider(blob.colliders[i], scale); + var t = math.mul(transform, blob.transforms[i]); + DrawCollider(c, t, color, segmentsPerPi); + } + } + + /// + /// Draws a wireframe of a collider using UnityEngine.Debug.DrawLine calls + /// + /// The collider to draw + /// The transform of the collider in world space + /// The color of the wireframe + /// The number of segments to draw per 180 degree arc if the collider has round features + public static void DrawCollider(in Collider collider, in RigidTransform transform, Color color, int segmentsPerPi = 6) + { + switch (collider.type) + { + case ColliderType.Sphere: + DrawCollider(in collider.m_sphere, transform, color, segmentsPerPi); + break; + case ColliderType.Capsule: + DrawCollider(in collider.m_capsule, transform, color, segmentsPerPi); + break; + case ColliderType.Box: + DrawCollider(in collider.m_box, transform, color); + break; + case ColliderType.Triangle: + DrawCollider(in collider.m_triangle, transform, color); + break; + case ColliderType.Convex: + DrawCollider(in collider.m_convex, transform, color); + break; + case ColliderType.Compound: + DrawCollider(in collider.m_compound, transform, color, segmentsPerPi); + break; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawCollider.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawCollider.cs.meta new file mode 100644 index 0000000..1670b62 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawCollider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61b1c5c8616838d4d8cbdaa16e5705aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawLayer.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawLayer.cs new file mode 100644 index 0000000..05c5b64 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawLayer.cs @@ -0,0 +1,471 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace Latios.Psyshock +{ + public static partial class PhysicsDebug + { + /// + /// Draws the bounding boxes of a CollisionLayer using UnityEngine.Debug.DrawLine calls. + /// The boxes are color-coded by the cell they exist in. + /// This is the start of a Fluent chain. + /// + /// The collision layer to draw. + /// A context object from which a scheduler should be invoked. + public static DrawLayerConfig DrawLayer(CollisionLayer layer) + { + var colors = new FixedList512Bytes(); + colors.Length = 7; + colors[0] = Color.red; + colors[1] = Color.green; + colors[2] = Color.blue; + colors[3] = Color.cyan; + colors[4] = Color.yellow; + colors[5] = Color.magenta; + colors[6] = Color.black; + var crossColor = Color.white; + return new DrawLayerConfig { layer = layer, colors = colors, crossColor = crossColor }; + } + + public struct DrawLayerConfig + { + internal CollisionLayer layer; + internal FixedList512Bytes colors; + internal Color crossColor; + + public DrawLayerConfig WithColors(FixedList512Bytes colors, Color crossBucketColor) + { + this.colors = colors; + this.crossColor = crossBucketColor; + return this; + } + + /// + /// Run immediately without using the job system. + /// + public void RunImmediate() + { + var job = new DebugDrawLayerJob + { + layer = layer, + colors = colors, + crossColor = crossColor + }; + for (int i = 0; i < layer.BucketCount; i++) + { + job.Execute(i); + } + } + + /// + /// Run on the same thread using a Burst job. + /// + public void Run() + { + new DebugDrawLayerJob + { + layer = layer, + colors = colors, + crossColor = crossColor + }.Run(layer.BucketCount); + } + + /// + /// Schedule a single-threaded job to perform the drawing operations + /// + /// The JobHandle that this job should wait for before executing + /// A job handle associated with the scheduled job + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + return new DebugDrawLayerJob + { + layer = layer, + colors = colors, + crossColor = crossColor + }.Schedule(layer.BucketCount, inputDeps); + } + + /// + /// Schedule a multi-threaded job to perform the drawing operations + /// + /// The JobHandle that this job should wait for before executing + /// A job handle associated with the scheduled job + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + return new DebugDrawLayerJob + { + layer = layer, + colors = colors, + crossColor = crossColor + }.ScheduleParallel(layer.BucketCount, 1, inputDeps); + } + } + + /// + /// Draws overlapping AABBs within the layer using UnityEngine.Debug.DrawLine calls + /// This is the start of a Fluent chain. + /// + /// The layer to draw + /// A config object from which a scheduler should be called + public static DrawFindPairsConfig DrawFindPairs(CollisionLayer layer) + { + return new DrawFindPairsConfig + { + layerA = layer, + hitColor = Color.red, + missColor = Color.green, + drawMisses = true, + isLayerLayer = false + }; + } + + /// + /// Draws overlapping AABBs between two layers using UnityEngine.Debug.DrawLine calls + /// This is the start of a Fluent chain. + /// + /// The first layer to draw + /// The second layer to draw + /// A config object from which a scheduler should be called + public static DrawFindPairsConfig DrawFindPairs(CollisionLayer layerA, CollisionLayer layerB) + { + return new DrawFindPairsConfig + { + layerA = layerA, + layerB = layerB, + hitColor = Color.red, + missColor = Color.green, + drawMisses = true, + isLayerLayer = true + }; + } + + public struct DrawFindPairsConfig + { + internal CollisionLayer layerA; + internal CollisionLayer layerB; + internal Color hitColor; + internal Color missColor; + internal bool drawMisses; + internal bool isLayerLayer; + + /// + /// Override the default colors drawn and set whether or not to draw non-overlapping + /// + /// The color for when two AABBs overlap + /// The color for when an AABB does not overlap with anything + /// If true, AABBs which do not overlap with anything will be drawn using the nonOverlapColor + /// A config object from which a scheduler should be called + public DrawFindPairsConfig WithColors(Color overlapColor, Color nonOverlapColor, bool drawNonOverlapping = true) + { + hitColor = overlapColor; + missColor = nonOverlapColor; + drawMisses = drawNonOverlapping; + return this; + } + + /// + /// Run immediately without using the job system. + /// + public void RunImmediate() + { + if (isLayerLayer) + { + var hitArrayA = new NativeBitArray(layerA.Count, Allocator.Temp, NativeArrayOptions.ClearMemory); + var hitArrayB = new NativeBitArray(layerB.Count, Allocator.Temp, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerLayerProcessor { hitArrayA = hitArrayA, hitArrayB = hitArrayB }; + Physics.FindPairs(layerA, layerB, processor).RunImmediate(); + var job = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArrayA, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }; + for (int i = 0; i < layerA.Count; i++) + { + job.Execute(i); + } + job.hitArray = hitArrayB; + job.layer = layerB; + for (int i = 0; i < layerB.Count; i++) + { + job.Execute(i); + } + } + else + { + var hitArray = new NativeBitArray(layerA.Count, Allocator.Temp, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerSelfProcessor { hitArray = hitArray }; + Physics.FindPairs(layerA, processor).RunImmediate(); + var job = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArray, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }; + for (int i = 0; i < layerA.Count; i++) + { + job.Execute(i); + } + } + } + + /// + /// Run on the same thread using Burst jobs + /// + public void Run() + { + if (isLayerLayer) + { + var hitArrayA = new NativeBitArray(layerA.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var hitArrayB = new NativeBitArray(layerB.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerLayerProcessor { hitArrayA = hitArrayA, hitArrayB = hitArrayB }; + Physics.FindPairs(layerA, layerB, processor).Run(); + var job = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArrayA, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }; + job.Run(layerA.Count); + job.hitArray = hitArrayB; + job.layer = layerB; + job.Run(layerB.Count); + hitArrayA.Dispose(); + hitArrayB.Dispose(); + } + else + { + var hitArray = new NativeBitArray(layerA.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerSelfProcessor { hitArray = hitArray }; + Physics.FindPairs(layerA, processor).Run(); + new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArray, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }.Run(layerA.Count); + hitArray.Dispose(); + } + } + + /// + /// Schedule single-threadeds job to perform the drawing operations + /// + /// The JobHandle that these jobs should wait for before executing + /// A job handle associated with the scheduled jobs + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + if (isLayerLayer) + { + var hitArrayA = new NativeBitArray(layerA.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var hitArrayB = new NativeBitArray(layerB.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerLayerProcessor { hitArrayA = hitArrayA, hitArrayB = hitArrayB }; + var jh = Physics.FindPairs(layerA, layerB, processor).ScheduleSingle(inputDeps); + var job = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArrayA, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }; + jh = job.Schedule(layerA.Count, jh); + job.hitArray = hitArrayB; + job.layer = layerB; + jh = job.Schedule(layerB.Count, jh); + jh = hitArrayA.Dispose(jh); + return hitArrayB.Dispose(jh); + } + else + { + var hitArray = new NativeBitArray(layerA.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerSelfProcessor { hitArray = hitArray }; + var jh = Physics.FindPairs(layerA, processor).ScheduleSingle(inputDeps); + jh = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArray, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }.Schedule(layerA.Count, jh); + return hitArray.Dispose(jh); + } + } + + /// + /// Schedule multi-threaded jobs to perform the drawing operations + /// + /// The JobHandle that these jobs should wait for before executing + /// A job handle associated with the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + if (isLayerLayer) + { + var hitArrayA = new NativeBitArray(layerA.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var hitArrayB = new NativeBitArray(layerB.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerLayerProcessor { hitArrayA = hitArrayA, hitArrayB = hitArrayB }; + var jh = Physics.FindPairs(layerA, layerB, processor).ScheduleSingle(inputDeps); + var job = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArrayA, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }; + jh = job.ScheduleParallel(layerA.Count, 64, jh); + job.hitArray = hitArrayB; + job.layer = layerB; + jh = job.ScheduleParallel(layerB.Count, 64, jh); + jh = hitArrayA.Dispose(jh); + return hitArrayB.Dispose(jh); + } + else + { + var hitArray = new NativeBitArray(layerA.Count, Allocator.TempJob, NativeArrayOptions.ClearMemory); + var processor = new DebugFindPairsLayerSelfProcessor { hitArray = hitArray }; + var jh = Physics.FindPairs(layerA, processor).ScheduleSingle(inputDeps); + jh = new DebugFindPairsDrawJob + { + layer = layerA, + hitArray = hitArray, + hitColor = hitColor, + missColor = missColor, + drawMisses = drawMisses + }.ScheduleParallel(layerA.Count, 64, jh); + return hitArray.Dispose(jh); + } + } + } + + /// + /// Draw an AABB wireframe using UnityEngine.Debug.DrawLine calls + /// + /// The AABB to draw + /// The color of the wireframe + public static void DrawAabb(Aabb aabb, Color color) + { + float3 leftTopFront = new float3(aabb.min.x, aabb.max.y, aabb.min.z); + float3 rightTopFront = new float3(aabb.max.x, aabb.max.y, aabb.min.z); + float3 leftBottomFront = new float3(aabb.min.x, aabb.min.y, aabb.min.z); + float3 rightBottomFront = new float3(aabb.max.x, aabb.min.y, aabb.min.z); + float3 leftTopBack = new float3(aabb.min.x, aabb.max.y, aabb.max.z); + float3 rightTopBack = new float3(aabb.max.x, aabb.max.y, aabb.max.z); + float3 leftBottomBack = new float3(aabb.min.x, aabb.min.y, aabb.max.z); + float3 rightBottomBack = new float3(aabb.max.x, aabb.min.y, aabb.max.z); + + Debug.DrawLine(leftTopFront, rightTopFront, color); + Debug.DrawLine(rightTopFront, rightBottomFront, color); + Debug.DrawLine(rightBottomFront, leftBottomFront, color); + Debug.DrawLine(leftBottomFront, leftTopFront, color); + + Debug.DrawLine(leftTopBack, rightTopBack, color); + Debug.DrawLine(rightTopBack, rightBottomBack, color); + Debug.DrawLine(rightBottomBack, leftBottomBack, color); + Debug.DrawLine(leftBottomBack, leftTopBack, color); + + Debug.DrawLine(leftTopFront, leftTopBack, color); + Debug.DrawLine(rightTopFront, rightTopBack, color); + Debug.DrawLine(leftBottomFront, leftBottomBack, color); + Debug.DrawLine(rightBottomFront, rightBottomBack, color); + } + + #region DrawLayerUtils + [BurstCompile] + private struct DebugDrawLayerJob : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public FixedList512Bytes colors; + public Color crossColor; + + public void Execute(int index) + { + if (index < layer.BucketCount - 1) + { + Color color = colors[index % colors.Length]; + var slices = layer.GetBucketSlices(index); + for (int i = 0; i < slices.count; i++) + { + Aabb aabb = new Aabb(new float3(slices.xmins[i], slices.yzminmaxs[i].xy), new float3(slices.xmaxs[i], -slices.yzminmaxs[i].zw)); + DrawAabb(aabb, color); + } + } + else + { + Color color = crossColor; + var slices = layer.GetBucketSlices(index); + for (int i = 0; i < slices.count; i++) + { + Aabb aabb = new Aabb(new float3(slices.xmins[i], slices.yzminmaxs[i].xy), new float3(slices.xmaxs[i], -slices.yzminmaxs[i].zw)); + DrawAabb(aabb, color); + } + } + } + } + #endregion + + #region DrawFindPairsUtils + internal struct DebugFindPairsLayerSelfProcessor : IFindPairsProcessor + { + public NativeBitArray hitArray; + + public void Execute(in FindPairsResult result) + { + hitArray.Set(result.indexA, true); + hitArray.Set(result.indexB, true); + } + } + + private struct DebugFindPairsLayerLayerProcessor : IFindPairsProcessor + { + public NativeBitArray hitArrayA; + public NativeBitArray hitArrayB; + + public void Execute(in FindPairsResult result) + { + hitArrayA.Set(result.indexA, true); + hitArrayB.Set(result.indexB, true); + } + } + + [BurstCompile] + private struct DebugFindPairsDrawJob : IJobFor + { + [ReadOnly] public CollisionLayer layer; + [ReadOnly] public NativeBitArray hitArray; + public Color hitColor; + public Color missColor; + public bool drawMisses; + + public void Execute(int i) + { + float3 min = new float3(layer.xmins[i], layer.yzminmaxs[i].xy); + float3 max = new float3(layer.xmaxs[i], -layer.yzminmaxs[i].zw); + var aabb = new Aabb(min, max); + if (hitArray.IsSet(i)) + { + DrawAabb(aabb, hitColor); + } + else if (drawMisses) + { + DrawAabb(aabb, missColor); + } + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawLayer.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawLayer.cs.meta new file mode 100644 index 0000000..a4745ff --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.DrawLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa7548da7c6de9848b670be66e39cb3d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.LogFindPairsStats.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.LogFindPairsStats.cs new file mode 100644 index 0000000..413c66c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.LogFindPairsStats.cs @@ -0,0 +1,189 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class PhysicsDebug + { + /// + /// Prints to console the number of elements in each cell (bucket) of the CollisionLayer. + /// This is the start of a Fluent chain. + /// + /// The layer to extract counts from + /// A name associated with the layer to be printed in the log message + /// A config object from which a scheduler should be called + public static LogBucketCountsForLayerConfig LogBucketCountsForLayer(in CollisionLayer layer, in FixedString128Bytes layerName) + { + return new LogBucketCountsForLayerConfig { layer = layer, layerName = layerName }; + } + + public struct LogBucketCountsForLayerConfig + { + internal CollisionLayer layer; + internal FixedString128Bytes layerName; + + /// + /// Run without using the job system + /// + public void RunImmediate() + { + new LogBucketCountsForLayerJob + { + layer = layer, + layerName = layerName + }.Execute(); + } + + /// + /// Run on the same thread using a Burst job + /// + public void Run() + { + new LogBucketCountsForLayerJob + { + layer = layer, + layerName = layerName + }.Run(); + } + + /// + /// Schedule on a worker thread as part of a job chain + /// + /// The previous job this job should wait upon + /// A handle associated with the scheduled job + public JobHandle Schedule(JobHandle inputDeps) + { + return new LogBucketCountsForLayerJob + { + layer = layer, + layerName = layerName + }.Schedule(inputDeps); + } + } + + internal static JobHandle LogFindPairsStats(CollisionLayer layer, FixedString128Bytes layerName, JobHandle inputDeps) + { + return new FindPairsLayerSelfStatsJob + { + layer = layer, + layerName = layerName + }.ScheduleParallel(layer.BucketCount * 2 - 1, 1, inputDeps); + } + + internal static JobHandle LogFindPairsStats(CollisionLayer layerA, + FixedString128Bytes layerNameA, + CollisionLayer layerB, + FixedString128Bytes layerNameB, + JobHandle inputDeps) + { + return new FindPairsLayerLayerStatsJob + { + layerA = layerA, + layerNameA = layerNameA, + layerB = layerB, + layerNameB = layerNameB + }.ScheduleParallel(3 * layerA.BucketCount - 2, 1, inputDeps); + } + + [BurstCompile] + struct LogBucketCountsForLayerJob : IJob + { + [ReadOnly] public CollisionLayer layer; + public FixedString128Bytes layerName; + + public void Execute() + { + FixedString4096Bytes countsAsString = default; + for (int i = 0; i < layer.bucketStartsAndCounts.Length; i++) + { + if (i == layer.BucketCount - 1) + { + //countsAsString.Append("cross: "); + countsAsString.Append('c'); + countsAsString.Append('r'); + countsAsString.Append('o'); + countsAsString.Append('s'); + countsAsString.Append('s'); + countsAsString.Append(':'); + countsAsString.Append(' '); + } + else if (i == layer.BucketCount) + { + //countsAsString.Append("NaN: "); + countsAsString.Append('N'); + countsAsString.Append('a'); + countsAsString.Append('N'); + countsAsString.Append(':'); + countsAsString.Append(' '); + } + countsAsString.Append(layer.bucketStartsAndCounts[i].y); + countsAsString.Append(','); + countsAsString.Append(' '); + } + UnityEngine.Debug.Log($"Colliders counts per bucket in layer {layerName}:\n{countsAsString}"); + } + } + + [BurstCompile] + struct FindPairsLayerSelfStatsJob : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public FixedString128Bytes layerName; + + public void Execute(int index) + { + if (index < layer.BucketCount) + { + var bucket = layer.GetBucketSlices(index); + FindPairsSweepMethods.SelfSweepStats(bucket, in layerName); + } + else + { + index -= layer.BucketCount; + var bucket = layer.GetBucketSlices(index); + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweepStats(bucket, in layerName, crossBucket, in layerName); + } + } + } + + [BurstCompile] + struct FindPairsLayerLayerStatsJob : IJobFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public FixedString128Bytes layerNameA; + public FixedString128Bytes layerNameB; + + public void Execute(int i) + { + if (i < layerA.BucketCount) + { + var bucketA = layerA.GetBucketSlices(i); + var bucketB = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepStats(bucketA, layerNameA, bucketB, layerNameB); + } + else if (i < 2 * layerB.BucketCount - 1) + { + i -= layerB.BucketCount; + var bucket = layerB.GetBucketSlices(i); + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweepStats(crossBucket, layerNameA, bucket, layerNameB); + } + else + { + var jobIndex = i; + i -= (2 * layerB.BucketCount - 1); + var bucket = layerA.GetBucketSlices(i); + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweepStats(bucket, layerNameA, crossBucket, layerNameB); + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.LogFindPairsStats.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.LogFindPairsStats.cs.meta new file mode 100644 index 0000000..10fbc6a --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Debug/PhysicsDebug.LogFindPairsStats.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eef8709ae28ee7e41ab644a9c02ce68c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics.meta new file mode 100644 index 0000000..21ced2a --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 41d91c54e88411e42979ff8f73614254 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics/Physics.BallisticUtils.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics/Physics.BallisticUtils.cs new file mode 100644 index 0000000..7aabf78 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics/Physics.BallisticUtils.cs @@ -0,0 +1,121 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + /// + /// Computes the acceleration given a force and an inverse mass the force is acting upon + /// + public static float AccelerationFrom(float force, float inverseMass) => force * inverseMass; + /// + /// Computes the acceleration given a force and an inverse mass the force is acting upon + /// + public static float2 AccelerationFrom(float2 force, float2 inverseMass) => force * inverseMass; + /// + /// Computes the acceleration given a force and an inverse mass the force is acting upon + /// + public static float3 AccelerationFrom(float3 force, float3 inverseMass) => force * inverseMass; + + // Simplified model + /// + /// Computes a drag force using simplified drag coefficients + /// + /// The speed and direction of travel + /// The drag coefficient scaled by the velocity's magnitude + /// The drag coefficient scaled by the velocity's magnitude squared + /// A force caused by the drag + public static float DragForceFrom(float velocity, float k1, float k2) => - (k1 * velocity + k2 * velocity * velocity); + /// + /// Computes a drag force using simplified drag coefficients + /// + /// The speed and direction of travel + /// The drag coefficient scaled by the velocity's magnitude + /// The drag coefficient scaled by the velocity's magnitude squared + /// A force caused by the drag + public static float2 DragForceFrom(float2 velocity, float k1, float k2) + { + float speedSq = math.lengthsq(velocity); + float speed = math.sqrt(speedSq); + var unit = velocity / speed; + return -unit * (k1 * speed + k2 * speedSq); + } + /// + /// Computes a drag force using simplified drag coefficients + /// + /// The speed and direction of travel + /// The drag coefficient scaled by the velocity's magnitude + /// The drag coefficient scaled by the velocity's magnitude squared + /// A force caused by the drag + public static float3 DragForceFrom(float3 velocity, float k1, float k2) + { + float speedSq = math.lengthsq(velocity); + float speed = math.sqrt(speedSq); + var unit = velocity / speed; + return -unit * (k1 * speed + k2 * speedSq); + } + + // Source: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4786048/ + /// + /// Computes a drag force using an approximation of a sphere inside a flowing fluid (such as wind or water) + /// + /// The velocity of the spherical object + /// The spherical object's radius + /// The velocity of the flowing fluid + /// The viscosity of the fluid + /// The density of the fluid + /// A drag force to be applied to the spherical object + public static float3 DragForceFrom(float3 velocity, float objectRadius, float3 fluidVelocity, float fluidViscosity, float fluidDensity) + { + float3 combinedVelocity = fluidVelocity - velocity; + float speed = math.length(combinedVelocity); + float re = 2f * objectRadius * fluidDensity * speed / fluidViscosity; // Reynold's Number + float cd = 0.18f; // coefficient of drag + if (re <= 0f) + cd = 0f; + else if (re < 1f) + cd = 24f / re; + else if (re < 400f) + cd = 24 / math.pow(re, 0.646f); + else if (re < 300000f) + cd = 0.5f; + else if (re < 2000000f) + cd = 0.000366f * math.pow(re, 0.4275f); + return 0.5f * fluidDensity * cd * speed * math.PI * objectRadius * objectRadius * combinedVelocity; + } + + /// + /// Computes a bouyancy force of a fluid applied to a fully submersed spherical object + /// + /// The acceleration vector of gravity + /// The spherical object's radius + /// The density of the fluid + /// A bouyancy force to be applied to the spherical object + public static float3 BouyancyForceFrom(float3 gravity, float objectRadius, float fluidDensity) + { + return fluidDensity * 4f / 3f * objectRadius * objectRadius * objectRadius * -gravity; + } + + /// + /// Computes the required mass and gravity multipliers to preserve the same kinetic energy and ballistic trajectory + /// of an object moving significantly slower in simulation relative to real-world velocities + /// + /// The speed the object would move in the real world + /// The speed the object should move in the simulation + /// A multiplier to be applied to the mass + /// A multiplier to be applied to gravity + public static void WarpPropertiesForDesiredSpeed(float referenceSpeed, float desiredSpeed, out float massMultiplier, out float gravityMultiplier) + { + float factor = referenceSpeed / desiredSpeed; + massMultiplier = factor * factor; + gravityMultiplier = 1f / factor; + } + + public static partial class Constants + { + public const float fluidViscosityOfAir = 0.000018f; // Pa / s + public const float densityOfAir = 1.225f; // kg / m^3 + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics/Physics.BallisticUtils.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics/Physics.BallisticUtils.cs.meta new file mode 100644 index 0000000..c84ba29 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Dynamics/Physics.BallisticUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07e335135885dfe4fa4f1696034d1ec3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal.meta new file mode 100644 index 0000000..863db70 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 364802b4c23366d49b1b5d796ce7037a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders.meta new file mode 100644 index 0000000..3627006 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f036eb6572d6b544485e2d24fd3b87f3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/BuildCollisionLayerInternal.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/BuildCollisionLayerInternal.cs new file mode 100644 index 0000000..f3e67ef --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/BuildCollisionLayerInternal.cs @@ -0,0 +1,933 @@ +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Psyshock +{ + internal static class BuildCollisionLayerInternal + { + public struct ColliderAoSData + { + public Collider collider; + public RigidTransform rigidTransform; + public Aabb aabb; + public Entity entity; + } + + #region Jobs + //Parallel + //Calculate RigidTransform, AABB, and target bucket. Write the targetBucket as the layerIndex + [BurstCompile] + public struct Part1FromQueryJob : IJobEntityBatchWithIndex + { + public CollisionLayer layer; + [NoAlias] public NativeArray layerIndices; + [NoAlias] public NativeArray colliderAoS; + [NoAlias] public NativeArray xMinMaxs; + [ReadOnly] public BuildCollisionLayerTypeHandles typeGroup; + public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) + { + bool ltw = chunk.Has(typeGroup.localToWorld); + bool p = chunk.Has(typeGroup.parent); + bool t = chunk.Has(typeGroup.translation); + bool r = chunk.Has(typeGroup.rotation); + bool s = chunk.Has(typeGroup.scale); + + int mask = math.select(0, 0x10, ltw); + mask += math.select(0, 0x8, p); + mask += math.select(0, 0x4, t); + mask += math.select(0, 0x2, r); + mask += math.select(0, 0x1, s); + + switch (mask) + { + case 0x0: ProcessNoTransform(ref chunk, firstEntityIndex); break; + case 0x1: ProcessScale(ref chunk, firstEntityIndex); break; + case 0x2: ProcessRotation(ref chunk, firstEntityIndex); break; + case 0x3: ProcessRotationScale(ref chunk, firstEntityIndex); break; + case 0x4: ProcessTranslation(ref chunk, firstEntityIndex); break; + case 0x5: ProcessTranslationScale(ref chunk, firstEntityIndex); break; + case 0x6: ProcessTranslationRotation(ref chunk, firstEntityIndex); break; + case 0x7: ProcessTranslationRotationScale(ref chunk, firstEntityIndex); break; + + case 0x8: ErrorCase(); break; + case 0x9: ErrorCase(); break; + case 0xa: ErrorCase(); break; + case 0xb: ErrorCase(); break; + case 0xc: ErrorCase(); break; + case 0xd: ErrorCase(); break; + case 0xe: ErrorCase(); break; + case 0xf: ErrorCase(); break; + + case 0x10: ProcessLocalToWorld(ref chunk, firstEntityIndex); break; + case 0x11: ProcessScale(ref chunk, firstEntityIndex); break; + case 0x12: ProcessRotation(ref chunk, firstEntityIndex); break; + case 0x13: ProcessRotationScale(ref chunk, firstEntityIndex); break; + case 0x14: ProcessTranslation(ref chunk, firstEntityIndex); break; + case 0x15: ProcessTranslationScale(ref chunk, firstEntityIndex); break; + case 0x16: ProcessTranslationRotation(ref chunk, firstEntityIndex); break; + case 0x17: ProcessTranslationRotationScale(ref chunk, firstEntityIndex); break; + + case 0x18: ProcessParent(ref chunk, firstEntityIndex); break; + case 0x19: ProcessParentScale(ref chunk, firstEntityIndex); break; + case 0x1a: ProcessParent(ref chunk, firstEntityIndex); break; + case 0x1b: ProcessParentScale(ref chunk, firstEntityIndex); break; + case 0x1c: ProcessParent(ref chunk, firstEntityIndex); break; + case 0x1d: ProcessParentScale(ref chunk, firstEntityIndex); break; + case 0x1e: ProcessParent(ref chunk, firstEntityIndex); break; + case 0x1f: ProcessParentScale(ref chunk, firstEntityIndex); break; + + default: ErrorCase(); break; + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void ErrorCase() + { + throw new System.InvalidOperationException("BuildCollisionLayer.Part1FromQueryJob received an invalid EntityQuery"); + } + + private void ProcessNoTransform(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + for (int i = 0; i < chunk.Count; i++) + { + ProcessEntity(firstEntityIndex + i, chunkEntities[i], chunkColliders[i], RigidTransform.identity); + } + } + + private void ProcessScale(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkScales = chunk.GetNativeArray(typeGroup.scale); + for (int i = 0; i < chunk.Count; i++) + { + var collider = Physics.ScaleCollider(chunkColliders[i], chunkScales[i]); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], in collider, RigidTransform.identity); + } + } + + private void ProcessRotation(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkRotations = chunk.GetNativeArray(typeGroup.rotation); + for (int i = 0; i < chunk.Count; i++) + { + var rigidTransform = new RigidTransform(chunkRotations[i].Value, float3.zero); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], chunkColliders[i], in rigidTransform); + } + } + + private void ProcessRotationScale(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkRotations = chunk.GetNativeArray(typeGroup.rotation); + var chunkScales = chunk.GetNativeArray(typeGroup.scale); + for (int i = 0; i < chunk.Count; i++) + { + var collider = Physics.ScaleCollider(chunkColliders[i], chunkScales[i]); + var rigidTransform = new RigidTransform(chunkRotations[i].Value, float3.zero); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], in collider, in rigidTransform); + } + } + + private void ProcessTranslation(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkTranslations = chunk.GetNativeArray(typeGroup.translation); + for (int i = 0; i < chunk.Count; i++) + { + var rigidTransform = new RigidTransform(quaternion.identity, chunkTranslations[i].Value); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], chunkColliders[i], in rigidTransform); + } + } + + private void ProcessTranslationScale(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkTranslations = chunk.GetNativeArray(typeGroup.translation); + var chunkScales = chunk.GetNativeArray(typeGroup.scale); + for (int i = 0; i < chunk.Count; i++) + { + var collider = Physics.ScaleCollider(chunkColliders[i], chunkScales[i]); + var rigidTransform = new RigidTransform(quaternion.identity, chunkTranslations[i].Value); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], in collider, in rigidTransform); + } + } + + private void ProcessTranslationRotation(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkTranslations = chunk.GetNativeArray(typeGroup.translation); + var chunkRotations = chunk.GetNativeArray(typeGroup.rotation); + for (int i = 0; i < chunk.Count; i++) + { + var rigidTransform = new RigidTransform(chunkRotations[i].Value, chunkTranslations[i].Value); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], chunkColliders[i], in rigidTransform); + } + } + + private void ProcessTranslationRotationScale(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkTranslations = chunk.GetNativeArray(typeGroup.translation); + var chunkRotations = chunk.GetNativeArray(typeGroup.rotation); + var chunkScales = chunk.GetNativeArray(typeGroup.scale); + for (int i = 0; i < chunk.Count; i++) + { + var collider = Physics.ScaleCollider(chunkColliders[i], chunkScales[i]); + var rigidTransform = new RigidTransform(chunkRotations[i].Value, chunkTranslations[i].Value); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], in collider, in rigidTransform); + } + } + + private void ProcessLocalToWorld(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkLocalToWorlds = chunk.GetNativeArray(typeGroup.localToWorld); + for (int i = 0; i < chunk.Count; i++) + { + var localToWorld = chunkLocalToWorlds[i]; + var rotation = quaternion.LookRotationSafe(localToWorld.Forward, localToWorld.Up); + var position = localToWorld.Position; + var rigidTransform = new RigidTransform(rotation, position); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], chunkColliders[i], in rigidTransform); + } + } + + private void ProcessParent(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkLocalToWorlds = chunk.GetNativeArray(typeGroup.localToWorld); + for (int i = 0; i < chunk.Count; i++) + { + var localToWorld = chunkLocalToWorlds[i]; + var rotation = quaternion.LookRotationSafe(localToWorld.Forward, localToWorld.Up); + var position = localToWorld.Position; + var rigidTransform = new RigidTransform(rotation, position); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], chunkColliders[i], in rigidTransform); + } + } + + private void ProcessParentScale(ref ArchetypeChunk chunk, int firstEntityIndex) + { + var chunkEntities = chunk.GetNativeArray(typeGroup.entity); + var chunkColliders = chunk.GetNativeArray(typeGroup.collider); + var chunkLocalToWorlds = chunk.GetNativeArray(typeGroup.localToWorld); + var chunkScales = chunk.GetNativeArray(typeGroup.scale); + for (int i = 0; i < chunk.Count; i++) + { + var localToWorld = chunkLocalToWorlds[i]; + var rotation = quaternion.LookRotationSafe(localToWorld.Forward, localToWorld.Up); + var position = localToWorld.Position; + var rigidTransform = new RigidTransform(rotation, position); + var collider = Physics.ScaleCollider(chunkColliders[i], chunkScales[i]); + ProcessEntity(firstEntityIndex + i, chunkEntities[i], in collider, in rigidTransform); + } + } + + private void ProcessEntity(int index, Entity entity, in Collider collider, in RigidTransform rigidTransform) + { + Aabb aabb = Physics.AabbFrom(in collider, in rigidTransform); + + colliderAoS[index] = new ColliderAoSData + { + collider = collider, + rigidTransform = rigidTransform, + aabb = aabb, + entity = entity + }; + xMinMaxs[index] = new float2(aabb.min.x, aabb.max.x); + + int3 minBucket = math.int3(math.floor((aabb.min - layer.worldMin) / layer.worldAxisStride)); + int3 maxBucket = math.int3(math.floor((aabb.max - layer.worldMin) / layer.worldAxisStride)); + minBucket = math.clamp(minBucket, 0, layer.worldSubdivisionsPerAxis - 1); + maxBucket = math.clamp(maxBucket, 0, layer.worldSubdivisionsPerAxis - 1); + + if (math.any(math.isnan(aabb.min) | math.isnan(aabb.max))) + { + layerIndices[index] = layer.bucketStartsAndCounts.Length - 1; + } + else if (math.all(minBucket == maxBucket)) + { + layerIndices[index] = (minBucket.x * layer.worldSubdivisionsPerAxis.y + minBucket.y) * layer.worldSubdivisionsPerAxis.z + minBucket.z; + } + else + { + layerIndices[index] = layer.bucketStartsAndCounts.Length - 2; + } + } + } + + //Parallel + //Calculated Target Bucket and write as layer index + [BurstCompile] + public struct Part1FromColliderBodyArrayJob : IJobBurstSchedulable, IJobParallelForBurstSchedulable + { + public CollisionLayer layer; + [NoAlias] public NativeArray layerIndices; + [ReadOnly] public NativeArray colliderBodies; + [NoAlias] public NativeArray aabbs; + [NoAlias] public NativeArray xMinMaxs; + + public void Execute() + { + for (int i = 0; i < colliderBodies.Length; i++) + Execute(i); + } + + public void Execute(int i) + { + var aabb = Physics.AabbFrom(colliderBodies[i].collider, colliderBodies[i].transform); + aabbs[i] = aabb; + xMinMaxs[i] = new float2(aabb.min.x, aabb.max.x); + + int3 minBucket = math.int3(math.floor((aabb.min - layer.worldMin) / layer.worldAxisStride)); + int3 maxBucket = math.int3(math.floor((aabb.max - layer.worldMin) / layer.worldAxisStride)); + minBucket = math.clamp(minBucket, 0, layer.worldSubdivisionsPerAxis - 1); + maxBucket = math.clamp(maxBucket, 0, layer.worldSubdivisionsPerAxis - 1); + + if (math.any(math.isnan(aabb.min) | math.isnan(aabb.max))) + { + layerIndices[i] = layer.bucketStartsAndCounts.Length - 1; + } + else if (math.all(minBucket == maxBucket)) + { + layerIndices[i] = (minBucket.x * layer.worldSubdivisionsPerAxis.y + minBucket.y) * layer.worldSubdivisionsPerAxis.z + minBucket.z; + } + else + { + layerIndices[i] = layer.bucketStartsAndCounts.Length - 2; + } + } + } + + //Parallel + //Calculated Target Bucket and write as layer index using the override AABB + [BurstCompile] + public struct Part1FromDualArraysJob : IJobBurstSchedulable, IJobParallelForBurstSchedulable + { + public CollisionLayer layer; + [NoAlias] public NativeArray layerIndices; + [ReadOnly] public NativeArray aabbs; + [NoAlias] public NativeArray xMinMaxs; + + public void Execute() + { + for (int i = 0; i < aabbs.Length; i++) + Execute(i); + } + + public void Execute(int i) + { + var aabb = aabbs[i]; + xMinMaxs[i] = new float2(aabb.min.x, aabb.max.x); + + int3 minBucket = math.int3(math.floor((aabb.min - layer.worldMin) / layer.worldAxisStride)); + int3 maxBucket = math.int3(math.floor((aabb.max - layer.worldMin) / layer.worldAxisStride)); + minBucket = math.clamp(minBucket, 0, layer.worldSubdivisionsPerAxis - 1); + maxBucket = math.clamp(maxBucket, 0, layer.worldSubdivisionsPerAxis - 1); + + if (math.any(math.isnan(aabb.min) | math.isnan(aabb.max))) + { + layerIndices[i] = layer.bucketStartsAndCounts.Length - 1; + } + else if (math.all(minBucket == maxBucket)) + { + layerIndices[i] = (minBucket.x * layer.worldSubdivisionsPerAxis.y + minBucket.y) * layer.worldSubdivisionsPerAxis.z + minBucket.z; + } + else + { + layerIndices[i] = layer.bucketStartsAndCounts.Length - 2; + } + } + } + + //Single + //Count total in each bucket and assign global array position to layerIndex + [BurstCompile] + public struct Part2Job : IJobBurstSchedulable + { + public CollisionLayer layer; + [NoAlias] public NativeArray layerIndices; + + public void Execute() + { + NativeArray countsPerBucket = new NativeArray(layer.bucketStartsAndCounts.Length, Allocator.Temp, NativeArrayOptions.ClearMemory); + for (int i = 0; i < layerIndices.Length; i++) + { + countsPerBucket[layerIndices[i]]++; + } + + int totalProcessed = 0; + for (int i = 0; i < countsPerBucket.Length; i++) + { + layer.bucketStartsAndCounts[i] = new int2(totalProcessed, countsPerBucket[i]); + totalProcessed += countsPerBucket[i]; + countsPerBucket[i] = 0; + } + + for (int i = 0; i < layerIndices.Length; i++) + { + int bucketIndex = layerIndices[i]; + layerIndices[i] = layer.bucketStartsAndCounts[bucketIndex].x + countsPerBucket[bucketIndex]; + countsPerBucket[bucketIndex]++; + } + } + } + + //Parallel + //Reverse array of dst indices to array of src indices + //Todo: Might be faster as an IJob due to potential false sharing + [BurstCompile] + public struct Part3Job : IJobBurstSchedulable, IJobParallelForBurstSchedulable + { + [ReadOnly, DeallocateOnJobCompletion] public NativeArray layerIndices; + [NoAlias, NativeDisableParallelForRestriction] public NativeArray unsortedSrcIndices; + + public void Execute() + { + for (int i = 0; i < layerIndices.Length; i++) + Execute(i); + } + + public void Execute(int i) + { + int spot = layerIndices[i]; + unsortedSrcIndices[spot] = i; + } + } + + //Parallel + //Sort buckets + [BurstCompile] + public struct Part4Job : IJobBurstSchedulable, IJobParallelForBurstSchedulable + { + [NoAlias, NativeDisableParallelForRestriction] public NativeArray unsortedSrcIndices; + [NoAlias, NativeDisableParallelForRestriction] public NativeArray trees; + [ReadOnly, DeallocateOnJobCompletion] public NativeArray xMinMaxs; + [ReadOnly] public NativeArray bucketStartAndCounts; + + public void Execute() + { + for (int i = 0; i < bucketStartAndCounts.Length - 1; i++) + Execute(i); + } + + public void Execute(int i) + { + var startAndCount = bucketStartAndCounts[i]; + + var intSlice = unsortedSrcIndices.GetSubArray(startAndCount.x, startAndCount.y); + RadixSortBucket(intSlice, xMinMaxs); + var tree = trees.GetSubArray(startAndCount.x, startAndCount.y); + BuildEytzingerIntervalTree(tree, intSlice, xMinMaxs); + } + } + + //Parallel + //Sort buckets using Unity's sort (may be better for smaller counts, needs more analysis) + /*[BurstCompile] + public struct Part4UnityJob : IJobFor + { + [NativeDisableParallelForRestriction] public NativeArray unsortedSrcIndices; + [ReadOnly, DeallocateOnJobCompletion] public NativeArray xmins; + [ReadOnly] public NativeArray bucketStartAndCounts; + + public void Execute(int i) + { + var startAndCount = bucketStartAndCounts[i]; + + var intSlice = unsortedSrcIndices.Slice(startAndCount.x, startAndCount.y); + UnitySortBucket(intSlice, xmins); + } + }*/ + + //Parallel + //Copy AoS data to SoA layer + [BurstCompile] + public struct Part5FromQueryJob : IJobBurstSchedulable, IJobParallelForBurstSchedulable + { + [NoAlias, NativeDisableParallelForRestriction] + public CollisionLayer layer; + + [ReadOnly, DeallocateOnJobCompletion] + public NativeArray colliderAoS; + + [ReadOnly] public NativeArray remapSrcIndices; + + public void Execute() + { + for (int i = 0; i < remapSrcIndices.Length; i++) + Execute(i); + } + + public void Execute(int i) + { + var aos = colliderAoS[remapSrcIndices[i]]; + layer.bodies[i] = new ColliderBody + { + collider = aos.collider, + transform = aos.rigidTransform, + entity = aos.entity + }; + layer.xmins[i] = aos.aabb.min.x; + layer.xmaxs[i] = aos.aabb.max.x; + layer.yzminmaxs[i] = new float4(aos.aabb.min.yz, -aos.aabb.max.yz); + } + } + + //Parallel + //Copy array data to layer + [BurstCompile] + public struct Part5FromArraysJob : IJobBurstSchedulable, IJobParallelForBurstSchedulable + { + [NativeDisableParallelForRestriction] + public CollisionLayer layer; + + [ReadOnly] public NativeArray aabbs; + [ReadOnly] public NativeArray bodies; + [ReadOnly] public NativeArray remapSrcIndices; + + public void Execute() + { + for (int i = 0; i < remapSrcIndices.Length; i++) + Execute(i); + } + + public void Execute(int i) + { + int src = remapSrcIndices[i]; + layer.bodies[i] = bodies[src]; + layer.xmins[i] = aabbs[src].min.x; + layer.xmaxs[i] = aabbs[src].max.x; + layer.yzminmaxs[i] = new float4(aabbs[src].min.yz, -aabbs[src].max.yz); + } + } + + //Single + //All five steps for custom arrays + [BurstCompile] + public struct BuildFromColliderArraySingleJob : IJobBurstSchedulable + { + public CollisionLayer layer; + + [ReadOnly] public NativeArray bodies; + + public void Execute() + { + var remapSrcArray = new NativeArray(layer.Count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + BuildImmediate(ref layer, remapSrcArray, bodies); + } + } + + //Single + //All five steps for custom arrays + [BurstCompile] + public struct BuildFromDualArraysSingleJob : IJobBurstSchedulable + { + public CollisionLayer layer; + + [ReadOnly] public NativeArray aabbs; + [ReadOnly] public NativeArray bodies; + + public void Execute() + { + var remapSrcArray = new NativeArray(layer.Count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + BuildImmediate(ref layer, remapSrcArray, bodies, aabbs); + } + } + + //Single + //All five steps for custom arrays with remap + [BurstCompile] + public struct BuildFromColliderArraySingleWithRemapJob : IJobBurstSchedulable + { + public CollisionLayer layer; + + [ReadOnly] public NativeArray bodies; + public NativeArray remapSrcIndices; + + public void Execute() + { + BuildImmediate(ref layer, remapSrcIndices, bodies); + } + } + + //Single + //All five steps for custom arrays with remap + [BurstCompile] + public struct BuildFromDualArraysSingleWithRemapJob : IJobBurstSchedulable + { + public CollisionLayer layer; + + [ReadOnly] public NativeArray aabbs; + [ReadOnly] public NativeArray bodies; + public NativeArray remapSrcIndices; + + public void Execute() + { + BuildImmediate(ref layer, remapSrcIndices, bodies, aabbs); + } + } + + #endregion + + #region Immediate + public static void BuildImmediate(ref CollisionLayer layer, NativeArray remapSrcArray, NativeArray bodies) + { + var aabbs = new NativeArray(remapSrcArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var layerIndices = new NativeArray(remapSrcArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var xMinMaxs = new NativeArray(remapSrcArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + var p1 = new Part1FromColliderBodyArrayJob + { + aabbs = aabbs, + colliderBodies = bodies, + layer = layer, + layerIndices = layerIndices, + xMinMaxs = xMinMaxs + }; + for (int i = 0; i < layer.Count; i++) + { + p1.Execute(i); + } + + new Part2Job + { + layer = layer, + layerIndices = layerIndices + }.Execute(); + + var p3 = new Part3Job + { + layerIndices = layerIndices, + unsortedSrcIndices = remapSrcArray + }; + for (int i = 0; i < layer.Count; i++) + { + p3.Execute(i); + } + + var p4 = new Part4Job + { + bucketStartAndCounts = layer.bucketStartsAndCounts, + unsortedSrcIndices = remapSrcArray, + trees = layer.intervalTrees, + xMinMaxs = xMinMaxs + }; + for (int i = 0; i < layer.BucketCount; i++) + { + p4.Execute(i); + } + + var p5 = new Part5FromArraysJob + { + aabbs = aabbs, + bodies = bodies, + layer = layer, + remapSrcIndices = remapSrcArray + }; + for (int i = 0; i < layer.Count; i++) + { + p5.Execute(i); + } + } + + public static void BuildImmediate(ref CollisionLayer layer, NativeArray remapSrcArray, NativeArray bodies, NativeArray aabbs) + { + var layerIndices = new NativeArray(remapSrcArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var xMinMaxs = new NativeArray(remapSrcArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + var p1 = new Part1FromDualArraysJob + { + aabbs = aabbs, + layer = layer, + layerIndices = layerIndices, + xMinMaxs = xMinMaxs + }; + for (int i = 0; i < layer.Count; i++) + { + p1.Execute(i); + } + + new Part2Job + { + layer = layer, + layerIndices = layerIndices + }.Execute(); + + var p3 = new Part3Job + { + layerIndices = layerIndices, + unsortedSrcIndices = remapSrcArray + }; + for (int i = 0; i < layer.Count; i++) + { + p3.Execute(i); + } + + var p4 = new Part4Job + { + bucketStartAndCounts = layer.bucketStartsAndCounts, + unsortedSrcIndices = remapSrcArray, + xMinMaxs = xMinMaxs + }; + for (int i = 0; i < layer.BucketCount; i++) + { + p4.Execute(i); + } + + var p5 = new Part5FromArraysJob + { + aabbs = aabbs, + bodies = bodies, + layer = layer, + remapSrcIndices = remapSrcArray + }; + for (int i = 0; i < layer.Count; i++) + { + p5.Execute(i); + } + } + #endregion + + #region UnitySortBucket + private struct Ranker : System.IComparable + { + public float key; + public int index; + + public int CompareTo(Ranker other) + { + return key.CompareTo(other.key); + } + } + + private static void UnitySortBucket(NativeSlice unsortedSrcIndices, NativeArray xmins) + { + var count = unsortedSrcIndices.Length; + if (count <= 1) + return; + + var ranks = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < count; i++) + { + ranks[i] = new Ranker + { + index = unsortedSrcIndices[i], + key = xmins[unsortedSrcIndices[i]] + }; + } + + ranks.Sort(); + + for (int i = 0; i < count; i++) + { + unsortedSrcIndices[i] = ranks[i].index; + } + } + #endregion + + #region RadixSortBucket + private struct Indexer + { + public UintAsBytes key; + public int index; + } + + private struct UintAsBytes + { + public byte byte1; + public byte byte2; + public byte byte3; + public byte byte4; + } + + private static UintAsBytes Keys(float val) + { + uint key = math.asuint(val); + uint mask = (key & 0x80000000) > 0 ? 0xffffffff : 0x80000000; + key = mask ^ key; + + UintAsBytes result; + result.byte1 = (byte)(key & 0x000000FF); + key = key >> 8; + result.byte2 = (byte)(key & 0x000000FF); + key = key >> 8; + result.byte3 = (byte)(key & 0x000000FF); + key = key >> 8; + result.byte4 = (byte)(key & 0x000000FF); + return result; + } + + private static void CalculatePrefixSum(NativeArray counts, NativeArray sums) + { + sums[0] = 0; + for (int i = 0; i < counts.Length - 1; i++) + { + sums[i + 1] = sums[i] + counts[i]; + } + } + + private static void RadixSortBucket(NativeArray unsortedSrcIndices, NativeArray xMinMaxs) + { + var count = unsortedSrcIndices.Length; + if (count <= 0) + return; + + NativeArray counts1 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts2 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts3 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray counts4 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray prefixSum1 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum2 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum3 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray prefixSum4 = new NativeArray(256, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + NativeArray frontArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray backArray = new NativeArray(count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + //Counts + for (int i = 0; i < count; i++) + { + var keys = Keys(xMinMaxs[unsortedSrcIndices[i]].x); + counts1[keys.byte1] = counts1[keys.byte1] + 1; + counts2[keys.byte2] = counts2[keys.byte2] + 1; + counts3[keys.byte3] = counts3[keys.byte3] + 1; + counts4[keys.byte4] = counts4[keys.byte4] + 1; + frontArray[i] = new Indexer { key = keys, index = unsortedSrcIndices[i] }; + } + + //Sums + CalculatePrefixSum(counts1, prefixSum1); + CalculatePrefixSum(counts2, prefixSum2); + CalculatePrefixSum(counts3, prefixSum3); + CalculatePrefixSum(counts4, prefixSum4); + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].key.byte1; + int dest = prefixSum1[key]; + backArray[dest] = frontArray[i]; + prefixSum1[key] = prefixSum1[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].key.byte2; + int dest = prefixSum2[key]; + frontArray[dest] = backArray[i]; + prefixSum2[key] = prefixSum2[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = frontArray[i].key.byte3; + int dest = prefixSum3[key]; + backArray[dest] = frontArray[i]; + prefixSum3[key] = prefixSum3[key] + 1; + } + + for (int i = 0; i < count; i++) + { + byte key = backArray[i].key.byte4; + int dest = prefixSum4[key]; + int src = backArray[i].index; + unsortedSrcIndices[dest] = src; + prefixSum4[key] = prefixSum4[key] + 1; + } + } + #endregion + + #region Eytzinger Interval Tree + + // Unless otherwise specified, the following functions are C# adaptations of Paul-Virak Khuong and Pat Morin's + // Eytzinger Array builder: https://github.com/patmorin/arraylayout/blob/master/src/eytzinger_array.h + // This code is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0) + private static void BuildEytzingerIntervalTree(NativeArray tree, NativeArray sortedSrcIndices, NativeArray srcXminMaxs) + { + var builder = new EytzingerIntervalTreeBuilder(tree, sortedSrcIndices, srcXminMaxs); + builder.Build(); + } + + private struct EytzingerIntervalTreeBuilder + { + private NativeArray nodesToPopulate; + private NativeArray sortedSrcIndices; + private NativeArray srcXminMaxs; + + public EytzingerIntervalTreeBuilder(NativeArray tree, NativeArray sortedSrcIndices, NativeArray srcXminMaxs) + { + this.nodesToPopulate = tree; + this.sortedSrcIndices = sortedSrcIndices; + this.srcXminMaxs = srcXminMaxs; + } + + public void Build() + { + BuildEytzingerIntervalTreeRecurse(0, 0); + + PatchSubtreeMaxResurse(0); + } + + private int BuildEytzingerIntervalTreeRecurse(int bucketRelativeIndex, uint treeIndex) + { + // It is for this condition that we need treeIndex to be a uint, which can store 2 * (int.MaxValue - 1) + 2 without overflow. + // If code reaches beyond this point, it is safe to cast treeIndex to an int. + if (treeIndex >= nodesToPopulate.Length) + return bucketRelativeIndex; + + bucketRelativeIndex = BuildEytzingerIntervalTreeRecurse(bucketRelativeIndex, 2 * treeIndex + 1); + + var minmax = srcXminMaxs[sortedSrcIndices[bucketRelativeIndex]]; + nodesToPopulate[(int)treeIndex] = new IntervalTreeNode + { + xmin = minmax.x, + xmax = minmax.y, + subtreeXmax = minmax.y, + bucketRelativeBodyIndex = bucketRelativeIndex + }; + bucketRelativeIndex++; + + bucketRelativeIndex = BuildEytzingerIntervalTreeRecurse(bucketRelativeIndex, 2 * treeIndex + 2); + + return bucketRelativeIndex; + } + + // This function is unique to Latios Framework + // Todo: There is likely a more cache-friendly way to iterate this tree and do this work + private float PatchSubtreeMaxResurse(uint treeIndex) + { + if (treeIndex >= nodesToPopulate.Length) + return 0f; + + float leftTreeMax = PatchSubtreeMaxResurse(2 * treeIndex + 1); + float rightTreeMax = PatchSubtreeMaxResurse(2 * treeIndex + 2); + + var node = nodesToPopulate[(int)treeIndex]; + node.subtreeXmax = math.max(math.max(leftTreeMax, rightTreeMax), node.subtreeXmax); + nodesToPopulate[(int)treeIndex] = node; + + return node.subtreeXmax; + } + } + + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/BuildCollisionLayerInternal.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/BuildCollisionLayerInternal.cs.meta new file mode 100644 index 0000000..44b2b00 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/BuildCollisionLayerInternal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6316619665b4b044a9d67c9fbd8c68dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/ConvexHullBuilder.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/ConvexHullBuilder.cs new file mode 100644 index 0000000..8c807db --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/ConvexHullBuilder.cs @@ -0,0 +1,3071 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Unity.Assertions; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +// The contents of this file mostly come from Unity.Physics. It is primarily used in EPA. +// Only variable names and math utility methods were changed. The math utilities are functionally +// equivalent but Psyshock presents a slightly different API. +// Simplifying the memory model and using hardware 128 bit multiplication are two possible future improvements. + +namespace Latios.Psyshock +{ + /// + /// Convex hull builder. + /// + [NoAlias] + unsafe internal struct ConvexHullBuilder + { + // Mesh representation of the hull + [NoAlias] + private ElementPoolBase m_vertices; + [NoAlias] + private ElementPoolBase m_triangles; + + public unsafe ElementPool vertices + { + get + { + fixed (ElementPoolBase* vertices = &m_vertices) + { + return new ElementPool { elementPoolBase = vertices }; + } + } + } + + public unsafe ElementPool triangles + { + get + { + fixed (ElementPoolBase* triangles = &m_triangles) + { + return new ElementPool { elementPoolBase = triangles }; + } + } + } + + // Number of bits with which vertices are quantized + public enum IntResolution + { + Low, // 16 bit, sufficient for ConvexConvexDistanceQueries + High // 30 bit, required to build hulls larger than ~1m without nearly parallel faces + } + + // Array of faces' planes, length = NumFaces, updated when BuildFaceIndices() is called + public Plane* planes { get; private set; } + + // -1 for empty, 0 for single point, 1 for line segment, 2 for flat convex polygon, 3 for convex polytope + public int dimension { get; private set; } + + // Number of faces, coplanar triangles make a single face, updated when BuildFaceIndices() is called + public int numFaces { get; private set; } + + // Sum of vertex counts of all faces. This is greater than the number of elements in Vertices because + // each vertex is appears on multiple faces. + public int numFaceVertices { get; private set; } + + // Valid only when Dimension == 2, the plane in which the hull lies + public Plane projectionPlane { get; private set; } + + // Valid only after calling UpdateHullMassProperties() + public MassProperties hullMassProperties { get; private set; } + + private long m_intNormalDirectionX; + private long m_intNormalDirectionY; + private long m_intNormalDirectionZ; + private IntResolution m_intResolution; + private IntegerSpace m_integerSpace; + private Aabb m_integerSpaceAabb; + private uint m_nextUid; + + private const float k_planeEps = 1e-4f; // Maximum distance any vertex in a face can be from the plane + + /// + /// Convex hull vertex. + /// + public struct Vertex : IPoolElement + { + public float3 position; + public int3 intPosition; + public int cardinality; + public uint userData; + public int nextFree { get { return (int)userData; } set { userData = (uint)value; } } + + public bool isAllocated => cardinality != -1; + + public Vertex(float3 position, uint userData) + { + this.position = position; + this.userData = userData; + cardinality = 0; + intPosition = new int3(0); + } + + void IPoolElement.MarkFree(int nextFree) + { + cardinality = -1; + this.nextFree = nextFree; + } + } + + /// + /// Convex hull triangle. + /// + public struct Triangle : IPoolElement + { + public int vertex0, vertex1, vertex2; + public Edge link0, link1, link2; + public int faceIndex; + public uint uid { get; private set; } + public int nextFree { get { return (int)uid; } set { uid = (uint)value; } } + + public bool isAllocated => faceIndex != -2; + + public Triangle(int vertex0, int vertex1, int vertex2, uint uid) + { + faceIndex = 0; + this.vertex0 = vertex0; + this.vertex1 = vertex1; + this.vertex2 = vertex2; + link0 = Edge.k_invalid; + link1 = Edge.k_invalid; + link2 = Edge.k_invalid; + this.uid = uid; + } + + public unsafe int GetVertex(int index) + { + fixed (int* p = &vertex0) + { + return p[index]; + } + } + public unsafe void SetVertex(int index, int value) + { + fixed (int* p = &vertex0) + { + p[index] = value; + } + } + + public unsafe Edge GetLink(int index) + { + fixed (Edge* p = &link0) + { + return p[index]; + } + } + public unsafe void SetLink(int index, Edge handle) + { + fixed (Edge* p = &link0) + { + p[index] = handle; + } + } + + void IPoolElement.MarkFree(int nextFree) + { + faceIndex = -2; + this.nextFree = nextFree; + } + } + + /// + /// An edge of a triangle, used internally to traverse hull topology. + /// + public struct Edge : IEquatable + { + public readonly int data; + + public bool isValid => data != k_invalid.data; + public int triangleIndex => data >> 2; + public int edgeIndex => data & 3; + + public static readonly Edge k_invalid = new Edge(0x7fffffff); + + public Edge(int value) + { + data = value; + } + public Edge(int triangleIndex, int edgeIndex) + { + data = triangleIndex << 2 | edgeIndex; + } + + public Edge Next => isValid ? new Edge(triangleIndex, (edgeIndex + 1) % 3) : k_invalid; + public Edge Prev => isValid ? new Edge(triangleIndex, (edgeIndex + 2) % 3) : k_invalid; + + public bool Equals(Edge other) => data == other.data; + } + + /// + /// An edge of a face (possibly made from multiple triangles). + /// + public struct FaceEdge + { + public Edge start; // the first edge of the face + public Edge current; // the current edge of the face + + public bool isValid => current.isValid; + + public static readonly FaceEdge k_invalid = new FaceEdge { start = Edge.k_invalid, current = Edge.k_invalid }; + + public static implicit operator Edge(FaceEdge fe) => fe.current; + } + + /// + /// Convex hull mass properties. + /// + public struct MassProperties + { + public float3 centerOfMass; + public float3x3 inertiaTensor; + public float surfaceArea; + public float volume; + } + + /// + /// A quantized integer space. + /// + private struct IntegerSpace + { + // int * Scale + Offset = float + public readonly float3 offset; + public readonly float scale; + public readonly float invScale; + + public IntegerSpace(Aabb aabb, int resolution) + { + Physics.GetCenterExtents(aabb, out var center, out var extents); + float extent = math.cmax(extents); + scale = extent / resolution; + invScale = math.select(resolution / extent, 0, extent <= 0); + offset = center - (extent / 2); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int3 ToIntegerSpace(float3 x) => new int3((x - offset) * invScale + 0.5f); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float3 ToFloatSpace(int3 x) => x * new float3(scale) + offset; + } + + // Create a hull builder with external storage + // vertices must be at least large enough to hold verticesCapacity elements, triangles and planes must be large enough to hold 2 * verticesCapacity elements + // domain is the AABB of all points that will be added to the hull + // simplificationTolerance is the sum of tolerances that will be passed to SimplifyVertices() and SimplifyFacesAndShrink() + public unsafe ConvexHullBuilder(int verticesCapacity, Vertex* vertices, Triangle* triangles, Plane* planes, + Aabb domain, float simplificationTolerance, IntResolution intResolution) + { + m_vertices = new ElementPoolBase(vertices, verticesCapacity); + m_triangles = new ElementPoolBase(triangles, 2 * verticesCapacity); + this.planes = planes; + dimension = -1; + numFaces = 0; + numFaceVertices = 0; + projectionPlane = new Plane(new float3(0), 0); + hullMassProperties = new MassProperties(); + m_intNormalDirectionX = 0; + m_intNormalDirectionY = 0; + m_intNormalDirectionZ = 0; + m_intResolution = intResolution; + m_nextUid = 1; + + // Add some margin for error to the domain. This loses some quantization resolution and therefore some accuracy, but it's possible that + // SimplifyVertices and SimplifyFacesAndMerge will not stay perfectly within the requested error limits, and expanding the limits avoids + // clipping against the domain AABB in AddPoint + const float constantMargin = 0.01f; + const float linearMargin = 0.1f; + Physics.GetCenterExtents(domain, out var center, out var extents); + extents += math.max(simplificationTolerance * 2f, constantMargin); + extents += math.cmax(extents) * linearMargin; + m_integerSpaceAabb = new Aabb(center - extents, center + extents); + + int quantizationBits = (intResolution == IntResolution.Low ? 16 : 30); + m_integerSpace = new IntegerSpace(m_integerSpaceAabb, (1 << quantizationBits) - 1); + } + + /// + /// Copy the content of another convex hull into this one. + /// + public unsafe ConvexHullBuilder(int verticesCapacity, Vertex* vertices, Triangle* triangles, Plane* planes, + ConvexHullBuilder other) + { + m_vertices = new ElementPoolBase(vertices, verticesCapacity); + m_triangles = new ElementPoolBase(triangles, 2 * verticesCapacity); + this.planes = planes; + dimension = other.dimension; + numFaces = other.numFaces; + numFaceVertices = other.numFaceVertices; + projectionPlane = other.projectionPlane; + hullMassProperties = other.hullMassProperties; + m_intNormalDirectionX = other.m_intNormalDirectionX; + m_intNormalDirectionY = other.m_intNormalDirectionY; + m_intNormalDirectionZ = other.m_intNormalDirectionZ; + m_intResolution = other.m_intResolution; + m_nextUid = other.m_nextUid; + m_integerSpaceAabb = other.m_integerSpaceAabb; + m_integerSpace = other.m_integerSpace; + + this.vertices.CopyFrom(other.vertices); + this.triangles.CopyFrom(other.triangles); + if (other.numFaces > 0) + { + UnsafeUtility.MemCpy(this.planes, other.planes, other.numFaces * sizeof(Plane)); + } + } + + #region Construction + + /// + /// Reset the convex hull. + /// + public void Reset() + { + vertices.Clear(); + triangles.Clear(); + dimension = -1; + numFaces = 0; + numFaceVertices = 0; + projectionPlane = new Plane(new float3(0), 0); + } + + // + public unsafe void Compact() + { + // Compact the vertices array + NativeArray vertexRemap = new NativeArray(vertices.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + if (vertices.Compact((int*)vertexRemap.GetUnsafePtr())) + { + // Remap all of the vertices in triangles, then compact the triangles array + foreach (int t in triangles.indices) + { + Triangle tri = triangles[t]; + tri.vertex0 = vertexRemap[tri.vertex0]; + tri.vertex1 = vertexRemap[tri.vertex1]; + tri.vertex2 = vertexRemap[tri.vertex2]; + triangles.Set(t, tri); + } + } + + triangles.Compact(null); + } + + /// + /// Add a point the the convex hull. + /// + /// Point to insert. + /// User data attached to the new vertex if insertion succeeds. + /// If true, the hull will not grow beyond two dimensions. + /// true if the insertion succeeded, false otherwise. + public unsafe bool AddPoint(float3 point, uint userData = 0, bool force2D = false) + { + // Reset faces. + numFaces = 0; + numFaceVertices = 0; + + // Return false if there is not enough room to allocate a vertex. + if (!vertices.canAllocate) + { + return false; + } + + // Point should be inside the quantization AABB, if not then clip it + if (!math.all(point >= m_integerSpaceAabb.min & point <= m_integerSpaceAabb.max)) + { + point = math.max(math.min(point, m_integerSpaceAabb.max), m_integerSpaceAabb.min); + } + int3 intPoint = m_integerSpace.ToIntegerSpace(point); + + // Insert vertex. + switch (dimension) + { + // Empty hull, just add a vertex. + case -1: + { + AllocateVertex(point, userData); + dimension = 0; + } + break; + + // 0 dimensional hull, make a line. + case 0: + { + const float minDistanceFromPoint = 1e-5f; + if (math.lengthsq(vertices[0].position - point) <= minDistanceFromPoint * minDistanceFromPoint) + return false; + AllocateVertex(point, userData); + dimension = 1; + } + break; + + // 1 dimensional hull, make a triangle. + case 1: + { + IntCross(vertices[0].intPosition - intPoint, vertices[1].intPosition - intPoint, out long normalDirectionX, out long normalDirectionY, + out long normalDirectionZ); + if (normalDirectionX == 0 && normalDirectionY == 0 && normalDirectionZ == 0) + { + // Still 1D, keep whichever two vertices are farthest apart + float3x3 edgesTransposed = + math.transpose(new float3x3(vertices[1].position - vertices[0].position, point - vertices[1].position, vertices[0].position - point)); + float3 edgesLengthSq = edgesTransposed.c0 * edgesTransposed.c0 + edgesTransposed.c1 * edgesTransposed.c1 + edgesTransposed.c2 * edgesTransposed.c2; + bool3 isLongestEdge = edgesLengthSq == math.cmax(edgesLengthSq); + if (isLongestEdge.y) + { + Vertex newVertex = vertices[0]; + newVertex.position = point; + newVertex.intPosition = m_integerSpace.ToIntegerSpace(point); + newVertex.userData = userData; + vertices.Set(0, newVertex); + } + else if (isLongestEdge.z) + { + Vertex newVertex = vertices[1]; + newVertex.position = point; + newVertex.intPosition = m_integerSpace.ToIntegerSpace(point); + newVertex.userData = userData; + vertices.Set(1, newVertex); + } // else, point is on the edge between Vertices[0] and Vertices[1] + } + else + { + // Extend dimension. + AllocateVertex(point, userData); + dimension = 2; + projectionPlane = ComputePlane(0, 1, 2, true); + m_intNormalDirectionX = normalDirectionX; + m_intNormalDirectionY = normalDirectionY; + m_intNormalDirectionZ = normalDirectionZ; + } + } + break; + + // 2 dimensional hull, make a volume or expand face. + case 2: + { + long det = 0; + if (!force2D) + { + // Try to expand to a 3D hull + for (int i = vertices.peakCount - 2, j = vertices.peakCount - 1, k = 0; k < vertices.peakCount - 2; i = j, j = k, k++) + { + det = IntDet(i, j, k, intPoint); + if (det != 0) + { + // Extend dimension. + projectionPlane = new Plane(new float3(0), 0); + + // Orient tetrahedron. + if (det > 0) + { + Vertex t = vertices[k]; + vertices.Set(k, vertices[j]); + vertices.Set(j, t); + } + + // Allocate vertex. + int nv = vertices.peakCount; + int vertexIndex = AllocateVertex(point, userData); + + // Build tetrahedron. + dimension = 3; + Edge nt0 = AllocateTriangle(i, j, k); + Edge nt1 = AllocateTriangle(j, i, vertexIndex); + Edge nt2 = AllocateTriangle(k, j, vertexIndex); + Edge nt3 = AllocateTriangle(i, k, vertexIndex); + BindEdges(nt0, nt1); BindEdges(nt0.Next, nt2); BindEdges(nt0.Prev, nt3); + BindEdges(nt1.Prev, nt2.Next); BindEdges(nt2.Prev, nt3.Next); BindEdges(nt3.Prev, nt1.Next); + + // Re-insert other vertices. + bool success = true; + for (int v = 0; v < nv; v++) + { + if (v == i || v == j || v == k) + { + continue; + } + Vertex vertex = vertices[v]; + vertices.Release(v); + success = success & AddPoint(vertex.position, vertex.userData); + } + return success; + } + } + } + if (det == 0) + { + // Hull is still 2D + bool* isOutside = stackalloc bool[vertices.peakCount]; + bool isOutsideAny = false; + for (int i = vertices.peakCount - 1, j = 0; j < vertices.peakCount; i = j++) + { + // Test if the point is inside the edge + // Note, even with 16 bit quantized coordinates, we cannot fit this calculation in 64 bit integers + int3 edge = vertices[j].intPosition - vertices[i].intPosition; + int3 delta = intPoint - vertices[i].intPosition; + IntCross(edge, delta, out long cx, out long cy, out long cz); + Int128 dot = Int128.Mul(m_intNormalDirectionX, cx) + Int128.Mul(m_intNormalDirectionY, cy) + Int128.Mul(m_intNormalDirectionZ, cz); + isOutside[i] = dot.IsNegative; + isOutsideAny |= isOutside[i]; + } + + // If the point is outside the hull, insert it and remove any points that it was outside of + if (isOutsideAny) + { + Vertex* newVertices = stackalloc Vertex[vertices.peakCount + 1]; + int numNewVertices = 1; + newVertices[0] = new Vertex(point, userData); + newVertices[0].intPosition = intPoint; + for (int i = vertices.peakCount - 1, j = 0; j < vertices.peakCount; i = j++) + { + if (isOutside[i] && isOutside[i] != isOutside[j]) + { + newVertices[numNewVertices++] = vertices[j]; + for (; ; ) + { + if (isOutside[j]) + break; + j = (j + 1) % vertices.peakCount; + newVertices[numNewVertices++] = vertices[j]; + } + break; + } + } + + vertices.CopyFrom(newVertices, numNewVertices); + } + } + } + break; + + // 3 dimensional hull, add vertex. + case 3: + { + int* nextTriangles = stackalloc int[triangles.peakCount]; + for (int i = 0; i < triangles.peakCount; i++) + { + nextTriangles[i] = -1; + } + + Edge* newEdges = stackalloc Edge[vertices.peakCount]; + for (int i = 0; i < vertices.peakCount; i++) + { + newEdges[i] = Edge.k_invalid; + } + + // Classify all triangles as either front(faceIndex = 1) or back(faceIndex = -1). + int firstFrontTriangleIndex = -1, numFrontTriangles = 0, numBackTriangles = 0; + int lastFrontTriangleIndex = -1; + float3 floatPoint = m_integerSpace.ToFloatSpace(intPoint); + float maxDistance = 0.0f; + foreach (int triangleIndex in triangles.indices) + { + Triangle triangle = triangles[triangleIndex]; + long det = IntDet(triangle.vertex0, triangle.vertex1, triangle.vertex2, intPoint); + if (det == 0) + { + // Check for duplicated vertex. + if (math.all(vertices[triangle.vertex0].intPosition == intPoint)) + return false; + if (math.all(vertices[triangle.vertex1].intPosition == intPoint)) + return false; + if (math.all(vertices[triangle.vertex2].intPosition == intPoint)) + return false; + } + if (det > 0) + { + nextTriangles[triangleIndex] = firstFrontTriangleIndex; + firstFrontTriangleIndex = triangleIndex; + if (lastFrontTriangleIndex == -1) + { + lastFrontTriangleIndex = triangleIndex; + } + + triangle.faceIndex = 1; + numFrontTriangles++; + + Plane plane = ComputePlane(triangleIndex, true); + float distance = math.dot(plane.normal, floatPoint) + plane.distanceFromOrigin; + maxDistance = math.max(distance, maxDistance); + } + else + { + triangle.faceIndex = -1; + numBackTriangles++; + } + triangles.Set(triangleIndex, triangle); + } + + // Return false if the vertex is inside the hull + if (numFrontTriangles == 0 || numBackTriangles == 0) + { + return false; + } + + // Link boundary loop. + Edge loopEdge = Edge.k_invalid; + int loopCount = 0; + for (int frontTriangle = firstFrontTriangleIndex; frontTriangle != -1; frontTriangle = nextTriangles[frontTriangle]) + { + for (int j = 0; j < 3; ++j) + { + var edge = new Edge(frontTriangle, j); + Edge linkEdge = GetLinkedEdge(edge); + if (triangles[linkEdge.triangleIndex].faceIndex == -1) + { + int vertexIndex = StartVertex(linkEdge); + + // Vertex already bound. + Assert.IsTrue(newEdges[vertexIndex].Equals(Edge.k_invalid)); + + // Link. + newEdges[vertexIndex] = linkEdge; + loopEdge = linkEdge; + loopCount++; + } + } + } + + // Return false if there is not enough room to allocate new triangles. + if ((triangles.peakCount + loopCount - numFrontTriangles) > triangles.capacity) + { + return false; + } + + // Release front triangles. + do + { + int next = nextTriangles[firstFrontTriangleIndex]; + ReleaseTriangle(firstFrontTriangleIndex); + firstFrontTriangleIndex = next; + } + while (firstFrontTriangleIndex != -1); + + // Add vertex. + int newVertex = AllocateVertex(point, userData); + + // Add fan of triangles. + { + Edge firstFanEdge = Edge.k_invalid, lastFanEdge = Edge.k_invalid; + for (int i = 0; i < loopCount; ++i) + { + int v0 = StartVertex(loopEdge); + int v1 = EndVertex(loopEdge); + Edge t = AllocateTriangle(v1, v0, newVertex); + BindEdges(loopEdge, t); + if (lastFanEdge.isValid) + BindEdges(t.Next, lastFanEdge.Prev); + else + firstFanEdge = t; + + lastFanEdge = t; + loopEdge = newEdges[v1]; + } + BindEdges(lastFanEdge.Prev, firstFanEdge.Next); + } + } + break; + } + return true; + } + + // Flatten the hull to 2D + // This is used to handle edge cases where very thin 3D hulls become 2D or invalid during simplification. + // Extremely thin 3D hulls inevitably have nearly parallel faces, which cause problems in collision detection, + // so the best solution is to flatten them completely. + public unsafe void Rebuild2D() + { + Assert.AreEqual(dimension, 3); + + // Copy the vertices and compute the OLS plane + Plane plane; + float3* tempVertices = stackalloc float3[vertices.peakCount]; + Aabb aabb = new Aabb(float.MaxValue, float.MinValue); + int numVertices = 0; + { + OLSData data = new OLSData(); + data.Init(); + foreach (int v in vertices.indices) + { + float3 position = vertices[v].position; + tempVertices[numVertices++] = position; + data.Include(position, 1.0f); + //aabb.Include(position); + aabb = Physics.CombineAabb(position, aabb); + } + Physics.GetCenterExtents(aabb, out _, out var extents); + float3 direction = 1.0f / math.max(1e-10f, extents); // Use the min aabb extent as regressand + data.Solve(direction, direction); + plane = data.plane; + } + + // Rebuild the hull from the projection of the vertices to the plane + Reset(); + for (int i = 0; i < numVertices; i++) + { + const bool force2D = true; + AddPoint(mathex.ProjectPoint(plane, tempVertices[i]), 0, force2D); + } + + BuildFaceIndices(); + } + + // Helper to sort triangles in BuildFaceIndices + unsafe struct CompareAreaDescending : IComparer + { + public NativeArray areas; + public CompareAreaDescending(NativeArray areas) + { + this.areas = areas; + } + public int Compare(int x, int y) + { + return areas[y].CompareTo(areas[x]); + } + } + + // Set the face index for each triangle. Triangles lying in the same plane will have the same face index. + public void BuildFaceIndices(NativeArray planes = default) + { + const float convexEps = 1e-5f; // Maximum error allowed in face convexity + + numFaces = 0; + numFaceVertices = 0; + + NativeArray planesUsed = new NativeArray(); + if (planes.IsCreated) + { + planesUsed = new NativeArray(planes.Length, Allocator.Temp, NativeArrayOptions.ClearMemory); + } + + switch (dimension) + { + case 2: + numFaces = 2; + numFaceVertices = 2 * vertices.peakCount; + break; + + case 3: + { + // Make a compact list of triangles and their areas + NativeArray triangleIndices = new NativeArray(triangles.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray triangleAreas = new NativeArray(triangles.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + int numTriangles = 0; + foreach (int triangleIndex in triangles.indices) + { + Triangle t = triangles[triangleIndex]; + float3 o = vertices[t.vertex0].position; + float3 a = vertices[t.vertex1].position - o; + float3 b = vertices[t.vertex2].position - o; + triangleAreas[triangleIndex] = math.lengthsq(math.cross(a, b)); + triangleIndices[numTriangles++] = triangleIndex; + } + + // Sort the triangles by descending area. It is best to choose the face plane from the largest triangle + // because 1) it minimizes the distance to other triangles and therefore the plane error, and 2) it avoids numerical + // problems computing degenerate triangle normals + triangleIndices.GetSubArray(0, numTriangles).Sort(new CompareAreaDescending(triangleAreas)); + + // Clear faces + NativeArray boundaryEdges = new NativeArray(triangles.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + for (int iTriangle = 0; iTriangle < numTriangles; iTriangle++) + { + int triangleIndex = triangleIndices[iTriangle]; + Triangle t = triangles[triangleIndex]; t.faceIndex = -1; triangles.Set(triangleIndex, t); + } + + // Merge triangles into faces + for (int iTriangle = 0; iTriangle < numTriangles; iTriangle++) + { + // Check if the triangle is already part of a face + int triangleIndex = triangleIndices[iTriangle]; + if (triangles[triangleIndex].faceIndex != -1) + { + continue; + } + + // Create a new face + int newFaceIndex = numFaces++; + Triangle t = triangles[triangleIndex]; t.faceIndex = newFaceIndex; triangles.Set(triangleIndex, t); + + // Search for the plane that best fits the triangle + int bestPlane = -1; + if (planes != null) + { + float bestError = k_planeEps; + float3 a = vertices[t.vertex0].position; + float3 b = vertices[t.vertex1].position; + float3 c = vertices[t.vertex2].position; + for (int i = 0; i < planes.Length; i++) + { + if (planesUsed[i]) + continue; + Plane currentPlane = planes[i]; + float3 errors = + new float3(mathex.SignedDistance(currentPlane, a), mathex.SignedDistance(currentPlane, b), mathex.SignedDistance(currentPlane, c)); + float error = math.cmax(math.abs(errors)); + if (error < bestError) + { + bestError = error; + bestPlane = i; + } + } + } + + // If a plane that fits the triangle was found, use it. Otherwise compute one from the triangle vertices + Plane plane; + if (bestPlane < 0) + { + plane = ComputePlane(triangleIndex); + } + else + { + planesUsed[bestPlane] = true; + plane = planes[bestPlane]; + } + this.planes[newFaceIndex] = plane; + + // Search for neighboring triangles that can be added to the face + boundaryEdges[0] = new Edge(triangleIndex, 0); + boundaryEdges[1] = new Edge(triangleIndex, 1); + boundaryEdges[2] = new Edge(triangleIndex, 2); + int numBoundaryEdges = 3; + while (true) + { + int openBoundaryEdgeIndex = -1; + float maxArea = -1; + + for (int i = 0; i < numBoundaryEdges; ++i) + { + Edge edge = boundaryEdges[i]; + Edge linkedEdge = GetLinkedEdge(edge); + + int linkedTriangleIndex = linkedEdge.triangleIndex; + + if (triangles[linkedTriangleIndex].faceIndex != -1) + continue; + if (triangleAreas[linkedTriangleIndex] <= maxArea) + continue; + + int apex = ApexVertex(linkedEdge); + float3 newVertex = vertices[apex].position; + if (math.abs(mathex.SignedDistance(plane, newVertex)) > k_planeEps) + { + continue; + } + + float3 linkedNormal = ComputePlane(linkedTriangleIndex).normal; + if (math.dot(plane.normal, linkedNormal) < 0.0f) + { + continue; + } + + float4 p0 = mathex.PlaneFrom(newVertex, newVertex - vertices[StartVertex(edge)].position, plane.normal); + float4 p1 = mathex.PlaneFrom(newVertex, vertices[EndVertex(edge)].position - newVertex, plane.normal); + + var accept = true; + for (int j = 1; accept && j < (numBoundaryEdges - 1); ++j) + { + float3 x = vertices[EndVertex(boundaryEdges[(i + j) % numBoundaryEdges])].position; + float d = math.max(math.dot(p0, x.xyz1()), math.dot(p1, x.xyz1())); + accept &= d < convexEps; + } + + if (accept) + { + openBoundaryEdgeIndex = i; + maxArea = triangleAreas[linkedTriangleIndex]; + } + } + + if (openBoundaryEdgeIndex != -1) + { + Edge linkedEdge = GetLinkedEdge(boundaryEdges[openBoundaryEdgeIndex]); + + // Check if merge has made the shape 2D, if so then quit + if (numBoundaryEdges >= boundaryEdges.Length) + { + numFaces = 3; // force 2D rebuild + break; + } + + // Insert two edges in place of the open boundary edge + for (int i = numBoundaryEdges; i > openBoundaryEdgeIndex; i--) + { + boundaryEdges[i] = boundaryEdges[i - 1]; + } + numBoundaryEdges++; + boundaryEdges[openBoundaryEdgeIndex] = linkedEdge.Next; + boundaryEdges[openBoundaryEdgeIndex + 1] = linkedEdge.Prev; + + Triangle tri = triangles[linkedEdge.triangleIndex]; + tri.faceIndex = newFaceIndex; + triangles.Set(linkedEdge.triangleIndex, tri); + } + else + { + break; + } + } + numFaceVertices += numBoundaryEdges; + } + + // Triangle merging may turn 3D shapes into 2D, check for that case and reduce the dimension + if (numFaces < 4) + { + Rebuild2D(); + } + } + break; + } + } + + private int AllocateVertex(float3 point, uint userData) + { + Assert.IsTrue(math.all(point >= m_integerSpaceAabb.min & point <= m_integerSpaceAabb.max)); + var vertex = new Vertex(point, userData) + { + intPosition = m_integerSpace.ToIntegerSpace(point) + }; + return vertices.Allocate(vertex); + } + + private Edge AllocateTriangle(int vertex0, int vertex1, int vertex2) + { + Triangle triangle = new Triangle(vertex0, vertex1, vertex2, m_nextUid++); + int triangleIndex = triangles.Allocate(triangle); + + Vertex v; + v = vertices[vertex0]; v.cardinality++; vertices.Set(vertex0, v); + v = vertices[vertex1]; v.cardinality++; vertices.Set(vertex1, v); + v = vertices[vertex2]; v.cardinality++; vertices.Set(vertex2, v); + + return new Edge(triangleIndex, 0); + } + + private void ReleaseTriangle(int triangle, bool releaseOrphanVertices = true) + { + for (int i = 0; i < 3; ++i) + { + int j = triangles[triangle].GetVertex(i); + Vertex v = vertices[j]; + v.cardinality--; + vertices.Set(j, v); + if (v.cardinality == 0 && releaseOrphanVertices) + { + vertices.Release(j); + } + } + + triangles.Release(triangle); + } + + private void BindEdges(Edge lhs, Edge rhs) + { + // Incompatible edges. + Assert.IsTrue(EndVertex(lhs) == StartVertex(rhs) && StartVertex(lhs) == EndVertex(rhs)); + + Triangle lf = triangles[lhs.triangleIndex]; + Triangle rf = triangles[rhs.triangleIndex]; + lf.SetLink(lhs.edgeIndex, rhs); + rf.SetLink(rhs.edgeIndex, lhs); + triangles.Set(lhs.triangleIndex, lf); + triangles.Set(rhs.triangleIndex, rf); + } + + #endregion + + #region Simplification + + // Removes vertices that are colinear with two neighbors or coplanar with all neighbors. + public unsafe void RemoveRedundantVertices() + { + const float toleranceSq = 1e-10f; + + NativeArray newVertices = new NativeArray(vertices.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray removed = new NativeArray(vertices.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + while (true) + { + bool remove = false; + if (dimension != 3) + break; + + for (int i = 0; i < vertices.peakCount; i++) + { + removed[i] = false; + } + + int numNewVertices = 0; + foreach (int v in vertices.indices) + { + float3 x = vertices[v].position; + bool keep = true; + bool coplanar = true; + bool anyRemoved = false; + + // For each pair of edges incident to v + Edge firstEdge = GetVertexEdge(v); + Edge edge0 = firstEdge; + do + { + Triangle triangle0 = triangles[edge0.triangleIndex]; + int index0 = triangle0.GetVertex((edge0.edgeIndex + 1) % 3); + anyRemoved |= removed[index0]; + if (!removed[index0]) // Ignore already removed vertices + { + // Double precision is necessary because the calculation involves comparing a difference of squares, which loses a lot of accuracy for long + // edges, against a very small fixed tolerance + double3 v0 = vertices[index0].position; + double3 edge0Vec = x - v0; + double edge0LengthSq = math.lengthsq(edge0Vec); + Edge edge1 = GetLinkedEdge(edge0.Prev); + + // Test if the triangle normals face the same direction. This is necessary for nearly flat hulls, where a vertex can be on triangles that + // have nearly opposite face normals, so all of the points may be nearly coplanar but the vertex is not safe to remove + { + Triangle triangle1 = triangles[edge1.triangleIndex]; + float3 v00 = vertices[triangle0.vertex0].position, v01 = vertices[triangle0.vertex1].position, v02 = vertices[triangle0.vertex2].position; + float3 v10 = vertices[triangle1.vertex0].position, v11 = vertices[triangle1.vertex1].position, v12 = vertices[triangle1.vertex2].position; + coplanar &= (math.dot(math.cross(v01 - v00, v02 - v00), math.cross(v11 - v10, v12 - v10)) >= 0); + } + + while (edge1.data != firstEdge.data && keep) + { + // Test if the distance from x to the line through v1 and v0 is less than tolerance. If not, then the three vertices + // are colinear and x is unnecessary. + // The math below is derived from the fact that the distance is the length of the rejection of (x - v0) from (v1 - v0), and + // lengthSq(rejection) + lengthSq(x - v0) = lengthSq(projection) + int index1 = triangles[edge1.triangleIndex].GetVertex((edge1.edgeIndex + 1) % 3); + double3 v1 = vertices[index1].position; + if (!removed[index1]) // Ignore already removed vertices + { + double3 lineVec = v1 - v0; + double lineVecLengthSq = math.lengthsq(lineVec); + double dot = math.dot(edge0Vec, lineVec); + double diffSq = edge0LengthSq * lineVecLengthSq - dot * dot; + double scaledTolSq = toleranceSq * lineVecLengthSq; + keep &= (dot < 0 || dot > lineVecLengthSq || diffSq > scaledTolSq); + + Edge edge2 = GetLinkedEdge(edge1.Prev); + if (edge2.data != firstEdge.data) + { + int index2 = triangles[edge2.triangleIndex].GetVertex((edge2.edgeIndex + 1) % 3); + if (!removed[index2]) + { + double3 v2 = vertices[index2].position; + double3 n = math.cross(v2 - v0, v1 - v0); + double det = math.dot(n, edge0Vec); + coplanar &= (det * det < math.lengthsq(n) * toleranceSq); + } + } + } + edge1 = GetLinkedEdge(edge1.Prev); + } + } + edge0 = GetLinkedEdge(edge0.Prev); + } + while (edge0.data != firstEdge.data && keep); + keep &= (!coplanar || anyRemoved); + + removed[v] = !keep; + if (keep) + { + newVertices[numNewVertices++] = vertices[v]; + } + else + { + remove = true; + } + } + + if (!remove) + { + break; // nothing to remove + } + + if (numNewVertices < 4) + { + // This can happen for nearly-flat hulls + Rebuild2D(); + break; + } + + Reset(); + for (int i = 0; i < numNewVertices; i++) + { + Vertex vertex = newVertices[i]; + AddPoint(vertex.position, vertex.userData); + } + } + } + + // Simplification of two vertices into one new vertex + struct Collapse + { + public int vertexA; // Source vertex, index into Vertices + public int vertexB; // Source vertex + public float3 target; // Position to replace the original vertices with + public float cost; // Sum of squared distances from Target to the planes incident to the original vertices + } + + // Orders Collapses by Cost + struct CollapseComparer : IComparer + { + public int Compare(Collapse x, Collapse y) + { + return x.cost.CompareTo(y.cost); + } + } + + void SetUserData(int v, uint data) + { + Vertex vertex = vertices[v]; vertex.userData = data; vertices.Set(v, vertex); + } + + // Returns a plane containing the edge through vertexIndex0 and vertexIndex1, with normal at equal angles + // to normal0 and normal1 (those of the triangles that share the edge) + Plane GetEdgePlane(int vertexIndex0, int vertexIndex1, float3 normal0, float3 normal1) + { + float3 vertex0 = vertices[vertexIndex0].position; + float3 vertex1 = vertices[vertexIndex1].position; + float3 edgeVec = vertex1 - vertex0; + float3 edgeNormal0 = math.normalize(math.cross(edgeVec, normal0)); + float3 edgeNormal1 = math.normalize(math.cross(normal1, edgeVec)); + float3 edgeNormal = math.normalize(edgeNormal0 + edgeNormal1); + return new Plane(edgeNormal, -math.dot(edgeNormal, vertex0)); + } + + // Returns a matrix M so that (x, y, z, 1) * M * (x, y, z, 1)^T = square of the distance from (x, y, z) to plane + double4x4 GetPlaneDistSqMatrix(double4 plane) + { + return new double4x4(plane * plane.x, plane * plane.y, plane * plane.z, plane * plane.w); + } + + // Finds the minimum cost Collapse for a, b using previously computed error matrices. Returns false if preservFaces = true and there is no collapse that would not + // violate a face, true otherwise. + // faceIndex: if >=0, index of the one multi-triangle face on the collapsing edge. -1 if there are two multi-tri faces, -2 if there are none. + Collapse GetCollapse(int a, int b, ref NativeArray matrices) + { + double4x4 matrix = matrices[(int)vertices[a].userData] + matrices[(int)vertices[b].userData]; + + // error = x^T * matrix * x, its only extreme point is a minimum and its gradient is linear + // the value of x that minimizes error is the root of the gradient + float4 x = float4.zero; + float cost = float.MaxValue; + switch (dimension) + { + case 2: + { + // In 2D force vertices to collapse on their original edge (could potentially get lower error by restricting + // to the plane, but this is simpler and good enough) + float3 u = vertices[a].position; + float3 v = vertices[b].position; + float3 edge = v - u; + double3x3 solveMatrix = new double3x3(matrix.c0.xyz, matrix.c1.xyz, matrix.c2.xyz); + double denom = math.dot(math.mul(solveMatrix, edge), edge); + if (denom == 0) // unexpected, just take the midpoint to avoid divide by zero + { + x = new float4(u + edge * 0.5f, 1); + cost = (float)math.dot(math.mul(matrix, x), x); + } + else + { + // Find the extreme point on the line through u and v + double3 solveOffset = matrix.c3.xyz; + float extremum = (float)(-math.dot(math.mul(solveMatrix, u) + solveOffset, edge) / denom); + x = new float4(u + edge * math.clamp(extremum, 0.0f, 1.0f), 1); + + // Minimum error is at the extremum or one of the two boundaries, test all three and choose the least + float uError = (float)math.dot(math.mul(matrix, new double4(u, 1)), u.xyz1()); + float vError = (float)math.dot(math.mul(matrix, new double4(v, 1)), v.xyz1()); + float xError = (float)math.dot(math.mul(matrix, x), x); + cost = math.min(math.min(uError, vError), xError); + float3 point = math.select(u.xyz, v.xyz, cost == vError); + point = math.select(point, x.xyz, cost == xError); + } + break; + } + + case 3: + { + // 3D, collapse point does not have to be on the edge between u and v + double4x4 solveMatrix = new double4x4( + new double4(matrix.c0.xyz, 0), + new double4(matrix.c1.xyz, 0), + new double4(matrix.c2.xyz, 0), + new double4(matrix.c3.xyz, 1)); + double det = math.determinant(solveMatrix); + if (det < 1e-6f) // determinant should be positive, small values indicate fewer than three planes that are significantly distinct from each other + { + goto case 2; + } + + x = (float4)math.mul(math.inverse(solveMatrix), new double4(0, 0, 0, 1)); + cost = (float)math.dot(math.mul(matrix, x), x); + + break; + } + } + + return new Collapse + { + vertexA = a, + vertexB = b, + target = x.xyz, + cost = cost + }; + } + + // Returns the index of pair i,j in an array of all unique unordered pairs of nonnegative ints less than n, sorted (0,0),(0,1),...(0,n-1),(1,1),(1,2),...,(1,n-1),...,(n-1,n-1) + int Index2d(uint i, uint j, uint n) + { + return (int)(i * (n + n - i - 1) / 2 + j); + } + + // Simplifies the hull by collapsing pairs of vertices until the number of vertices is no more than maxVertices and no further pairs can be collapsed without + // introducing error in excess of maxError. + // Based on QEM, but with contractions only allowed for vertices connected by a triangle edge, and only to be replaced by vertices on the same edge + // Note, calling this function destroys vertex user data + public unsafe void SimplifyVertices(float maxError, int maxVertices) + { + // Simplification is only possible in 2D / 3D + if (dimension < 2) + { + return; + } + + // Must build faces before calling + if (numFaces == 0) + { + Assert.IsTrue(false); + return; + } + + // Calculate initial error matrices + NativeArray collapses = new NativeArray(); + NativeArray matrices = new NativeArray(vertices.peakCount, Allocator.Temp, NativeArrayOptions.ClearMemory); + int numVertices = 0; + if (dimension == 3) + { + // Get an edge from each face + NativeArray firstEdges = new NativeArray(numFaces, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + for (FaceEdge faceEdge = GetFirstFace(); faceEdge.isValid; faceEdge = GetNextFace(faceEdge)) + { + int triangleIndex = faceEdge.current.triangleIndex; + int faceIndex = triangles[triangleIndex].faceIndex; + firstEdges[faceIndex] = faceEdge.start; + } + + // Build error matrices + for (int i = 0; i < numFaces; i++) + { + // Calculate the error matrix for this face + float4 plane = planes[i]; + double4x4 faceMatrix = GetPlaneDistSqMatrix(plane); + + // Add it to the error matrix of each vertex on the face + for (FaceEdge edge = new FaceEdge { start = firstEdges[i], current = firstEdges[i] }; edge.isValid; edge = GetNextFaceEdge(edge)) // For each edge of the current face + { + // Add the error matrix + int vertex0 = triangles[edge.current.triangleIndex].GetVertex(edge.current.edgeIndex); + matrices[vertex0] += faceMatrix; + + // Check if the edge is acute + Edge opposite = GetLinkedEdge(edge); + Triangle oppositeTriangle = triangles[opposite.triangleIndex]; + int vertex1 = oppositeTriangle.GetVertex(opposite.edgeIndex); + if (vertex0 < vertex1) // Count each edge only once + { + float3 oppositeNormal = planes[oppositeTriangle.faceIndex].normal; + if (math.dot(plane.xyz, oppositeNormal) < -0.017452f) // 91 degrees -- right angles are common in input data, avoid creating edge planes that distort the original faces + { + // Add an edge plane to the cost metric for each vertex on the edge to preserve sharp features + float4 edgePlane = GetEdgePlane(vertex0, vertex1, plane.xyz, oppositeNormal); + double4x4 edgeMatrix = GetPlaneDistSqMatrix(edgePlane); + matrices[vertex0] += edgeMatrix; + matrices[vertex1] += edgeMatrix; + } + } + } + } + + // Allocate space for QEM + collapses = new NativeArray(triangles.peakCount * 3 / 2, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + } + else + { + // In 2D, the vertices are sorted and each one has two edge planes + for (int i = vertices.peakCount - 1, j = 0; j < vertices.peakCount; i = j, j++) + { + //float4 edgePlane = PlaneFromTwoEdges(Vertices[i].Position, Vertices[j].Position - Vertices[i].Position, ProjectionPlane.Normal); + float4 edgePlane = mathex.PlaneFrom(vertices[i].position, vertices[j].position - vertices[i].position, projectionPlane.normal); + double4x4 edgeMatrix = GetPlaneDistSqMatrix(edgePlane); + matrices[i] += edgeMatrix; + matrices[j] += edgeMatrix; + } + + numVertices = vertices.peakCount; + collapses = new NativeArray(vertices.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + } + + // Write the original index of each vertex to its user data so that we can maintain its error matrix across rebuilds + foreach (int v in vertices.indices) + { + SetUserData(v, (uint)v); + numVertices++; + } + + // Repeatedly simplify the hull until the count is less than maxVertices and there are no further changes that satisfy maxError + NativeArray newVertices = new NativeArray(numVertices, Allocator.Temp, NativeArrayOptions.UninitializedMemory); // Note, only Position and UserData are used + bool enforceMaxVertices = false; + float maxCost = maxError * maxError; + while (true) + { + // Build a list of potential edge collapses + int numCollapses = 0; + bool force2D = false; + if (dimension == 3) + { + // Build collapses for each edge + foreach (int t in triangles.indices) + { + Triangle triangle = triangles[t]; + for (int i = 0; i < 3; i++) + { + int opposite = triangle.GetLink(i).triangleIndex; + if (t < opposite) // Count each edge only once + { + // Calculate the error matrix for the two vertex edges combined + int a = triangle.GetVertex(i); + int b = triangle.GetVertex((i + 1) % 3); + + Collapse collapse = GetCollapse(a, b, ref matrices); + if (collapse.cost <= maxCost || enforceMaxVertices) + { + collapses[numCollapses++] = collapse; + } + } + } + } + } + else + { + force2D = true; // if hull is ever 2D, it must remain 2D through simplification + for (int i = vertices.peakCount - 1, j = 0; j < vertices.peakCount; i = j, j++) + { + Collapse collapse = GetCollapse(i, j, ref matrices); + if (collapse.cost <= maxCost || enforceMaxVertices) + { + collapses[numCollapses++] = collapse; + } + } + } + + // Collapse vertices in order of increasing cost + collapses.GetSubArray(0, numCollapses).Sort(new CollapseComparer()); + int numNewVertices = 0; + for (int i = 0; i < numCollapses; i++) + { + // If this pass is just to enforce the vertex count, then stop collapsing as soon as it's satisfied + Collapse collapse = collapses[i]; + if (enforceMaxVertices && numVertices <= maxVertices) + { + break; + } + + // If one of the vertices has been collapsed already, it can't collapse again until the next pass + uint matrixA = vertices[collapse.vertexA].userData; + uint matrixB = vertices[collapse.vertexB].userData; + if (matrixA == uint.MaxValue || matrixB == uint.MaxValue) + { + continue; + } + + // Mark the vertices removed + SetUserData(collapse.vertexA, uint.MaxValue); + SetUserData(collapse.vertexB, uint.MaxValue); + numVertices--; + + // Combine error matrices for use in the next pass + double4x4 combined = matrices[(int)matrixA] + matrices[(int)matrixB]; + matrices[(int)matrixA] = combined; + + newVertices[numNewVertices++] = new Vertex + { + position = collapse.target, + userData = matrixA + }; + } + + // If nothing was collapsed, we're done + if (numNewVertices == 0) + { + if (numVertices > maxVertices) + { + // Now it's necessary to exceed maxError in order to get under the vertex limit + Assert.IsFalse(enforceMaxVertices); + enforceMaxVertices = true; + continue; + } + break; + } + + // Add all of the original vertices that weren't removed to the list + foreach (int v in vertices.indices) + { + if (vertices[v].userData != uint.MaxValue) + { + newVertices[numNewVertices++] = vertices[v]; + } + } + + // Rebuild + Reset(); + for (int i = 0; i < numNewVertices; ++i) + { + AddPoint(newVertices[i].position, newVertices[i].userData, force2D); + } + RemoveRedundantVertices(); + + // If this was a max vertices pass and we are now under the limit, we're done + if (enforceMaxVertices && numVertices <= maxVertices) + { + break; + } + + // Further simplification is only possible in 2D / 3D + if (dimension < 2) + { + break; + } + + // Count the vertices + numVertices = 0; + foreach (int v in vertices.indices) + { + numVertices++; + } + } + } + + // Simplification of two planes into a single new plane + struct FaceMerge + { + public int face0; // Face index + public int face1; // Face index + public Plane plane; // Plane to replace the two faces + public float cost; // Sum of squared distances from original vertices to Plane + public bool smallAngle; // True if the angle between the source faces is below the minimum angle threshold + } + + // Data required to calculate the OLS of a set of points, without needing to store the points themselves. + // OLSData for the union of point sets can be computed from those sets' OLSDatas without needing the original points. + struct OLSData + { + // Inputs + private float m_weight; // Cost multiplier + private int m_count; // Number of points in the set + private float3 m_sums; // Sum of x, y, z + private float3 m_squareSums; // Sum of x^2, y^2, z^2 + private float3 m_productSums; // Sum of xy, yz, zx + + // Outputs, assigned when Solve() is called + public Plane plane; // OLS plane of the points in the set + public float cost; // m_Weight * sum of squared distances from points in the set to Plane + + // Empty the set + public void Init() + { + m_weight = 0; + m_count = 0; + m_sums = float3.zero; + m_squareSums = float3.zero; + m_productSums = float3.zero; + } + + // Add a single point to the set + public void Include(float3 v, float weight) + { + m_weight = math.max(m_weight, weight); + m_count++; + m_sums += v; + m_squareSums += v * v; + m_productSums += v * v.yzx; + } + + // Add all points from the + public void Include(OLSData source, float weight) + { + m_weight = math.max(math.max(m_weight, source.m_weight), weight); + m_count += source.m_count; + m_sums += source.m_sums; + m_squareSums += source.m_squareSums; + m_productSums += source.m_productSums; + } + + // Calculate OLS of all included points and store the results in Plane and Cost. + // Returned plane has normal dot direction >= 0. + public void Solve(float3 normal0, float3 normal1) + { + float3 averageDirection = normal0 + normal1; + float3 absAverageDirection = math.abs(averageDirection); + bool3 maxAxis = math.cmax(absAverageDirection) == absAverageDirection; + + // Solve using the axis closest to the average normal for the regressand + bool planeOk; + if (maxAxis.x) + { + planeOk = Solve(m_count, m_sums.yzx, m_squareSums.yzx, m_productSums.yzx, out Plane plane); + this.plane = new Plane(plane.normal.zxy, plane.distanceFromOrigin); + } + else if (maxAxis.y) + { + planeOk = Solve(m_count, m_sums.zxy, m_squareSums.zxy, m_productSums.zxy, out Plane plane); + this.plane = new Plane(plane.normal.yzx, plane.distanceFromOrigin); + } + else + { + planeOk = Solve(m_count, m_sums, m_squareSums, m_productSums, out plane); + } + + // Calculate the error + if (!planeOk) + { + cost = float.MaxValue; + } + else + { + float4x4 errorMatrix = new float4x4( + m_squareSums.x, m_productSums.x, m_productSums.z, m_sums.x, + m_productSums.x, m_squareSums.y, m_productSums.y, m_sums.y, + m_productSums.z, m_productSums.y, m_squareSums.z, m_sums.z, + m_sums.x, m_sums.y, m_sums.z, m_count); + cost = math.dot(math.mul(errorMatrix, plane), plane) * m_weight; + } + + // Flip the plane if it's pointing the wrong way + if (math.dot(plane.normal, averageDirection) < 0) + { + plane = mathex.Flip(plane); + } + } + + // Solve implementation, uses regressor xy regressand z + // Returns false if the problem is singular + private static bool Solve(int count, float3 sums, float3 squareSums, float3 productSums, out Plane plane) + { + // Calculate the plane with minimum sum of squares of distances to points in the set + double3x3 gram = new double3x3( + count, sums.x, sums.y, + sums.x, squareSums.x, productSums.x, + sums.y, productSums.x, squareSums.y); + if (math.determinant(gram) == 0) // check for singular gram matrix (unexpected, points should be from nondegenerate tris and so span at least 2 dimensions) + { + plane = new Plane(new float3(1, 0, 0), 0); + return false; + } + double3x3 gramInv = math.inverse(gram); + double3 momentSum = new double3(sums.z, productSums.zy); + float3 coeff = (float3)math.mul(gramInv, momentSum); + float3 normal = new float3(coeff.yz, -1); + float invLength = math.rsqrt(math.lengthsq(normal)); + plane = new Plane(normal * invLength, coeff.x * invLength); + return true; + } + } + + // Helper for calculating edge weights, returns the squared sin of the angle between normal0 and normal1 if the angle is > 90 degrees, otherwise returns 1. + float SinAngleSq(float3 normal0, float3 normal1) + { + float cosAngle = math.dot(normal0, normal1); + return math.select(1.0f, 1.0f - cosAngle * cosAngle, cosAngle < 0); + } + + // 1) Simplifies the hull by combining pairs of neighboring faces until the estimated face count is below maxFaces, there are no faces left + // with an angle below minAngleBetweenFaces, and no combinations that can be made without increasing the error above simplificationTolerance. + // 2) Shrinks the hull by pushing its planes in as much as possible without moving a vertex further than shrinkDistance or zeroing the volume. + // 3) Reduces the vertex count below a fixed maximum, this is necessary in case face simplification increased the count above the limit + // Returns - the distance that the faces were moved in by shrinking + // Merging and shrinking are combined into a single operation because both work on the planes of the hull and require vertices to be rebuilt + // afterwards. Rebuilding vertices is the slowest part of hull generation, so best to do it only once. + public unsafe float SimplifyFacesAndShrink(float simplificationTolerance, float minAngleBetweenFaces, float shrinkDistance, int maxFaces, int maxVertices) + { + // Return if merging is not allowed and shrinking is off + if (simplificationTolerance <= 0.0f && minAngleBetweenFaces <= 0.0f && shrinkDistance <= 0.0f) + { + return 0.0f; + } + + // Only 3D shapes can shrink + if (dimension < 3) + { + return 0.0f; + } + + float cosMinAngleBetweenFaces = math.cos(minAngleBetweenFaces); + float simplificationToleranceSq = simplificationTolerance * simplificationTolerance; + const float k_cosMaxMergeAngle = 0.707107f; // Don't merge planes at >45 degrees + + // Make a copy of the planes that we can edit + int numPlanes = numFaces; + int maxNumPlanes = numPlanes + triangles.peakCount; + NativeArray planes = new NativeArray(maxNumPlanes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); // +Triangles.PeakCount to make room for edge planes + for (int i = 0; i < numPlanes; i++) + { + planes[i] = this.planes[i]; + } + + // Find the boundary edges of each face + NativeArray firstFaceEdgeIndex = new NativeArray(numPlanes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray numFaceEdges = new NativeArray(numPlanes, Allocator.Temp, NativeArrayOptions.ClearMemory); + NativeArray faceEdges = new NativeArray(triangles.peakCount * 3, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + int totalNumEdges = 0; + foreach (int t in triangles.indices) + { + // Search each triangle to find one on each face + Triangle triangle = triangles[t]; + int faceIndex = triangle.faceIndex; + if (numFaceEdges[faceIndex] > 0) + { + continue; + } + + // Find a boundary edge on the triangle + firstFaceEdgeIndex[faceIndex] = totalNumEdges; + for (int i = 0; i < 3; i++) + { + int linkedTriangle = triangle.GetLink(i).triangleIndex; + if (triangles[linkedTriangle].faceIndex != faceIndex) + { + // Save all edges of the face + Edge edge = new Edge(t, i); + for (FaceEdge faceEdge = new FaceEdge { start = edge, current = edge }; faceEdge.isValid; faceEdge = GetNextFaceEdge(faceEdge)) + { + faceEdges[totalNumEdges++] = faceEdge.current; + } + numFaceEdges[faceIndex] = totalNumEdges - firstFaceEdgeIndex[faceIndex]; + break; + } + } + } + + // Build OLS data for each face, and calculate the minimum span of the hull among its plane normal directions + NativeArray olsData = new NativeArray(maxNumPlanes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + float minSpan = float.MaxValue; + for (int i = 0; i < numPlanes; i++) + { + Plane plane = planes[i]; + OLSData ols = new OLSData(); ols.Init(); + + float lastSinAngleSq; + { + Edge lastEdge = faceEdges[firstFaceEdgeIndex[i] + numFaceEdges[i] - 1]; + Plane lastPlane = planes[triangles[GetLinkedEdge(lastEdge).triangleIndex].faceIndex]; + lastSinAngleSq = SinAngleSq(plane.normal, lastPlane.normal); + } + + for (int j = 0; j < numFaceEdges[i]; j++) + { + // Use the minimum angle of the two edges incident to the vertex to weight it + Edge nextEdge = faceEdges[firstFaceEdgeIndex[i] + j]; + Plane nextPlane = planes[triangles[GetLinkedEdge(nextEdge).triangleIndex].faceIndex]; + float nextSinAngleSq = SinAngleSq(plane.normal, nextPlane.normal); + float weight = 1.0f / math.max(float.Epsilon, math.min(lastSinAngleSq, nextSinAngleSq)); + lastSinAngleSq = nextSinAngleSq; + + // Include the weighted vertex in OLS data + float3 vertex = vertices[triangles[nextEdge.triangleIndex].GetVertex(nextEdge.edgeIndex)].position; + ols.Include(vertex, weight); + } + + olsData[i] = ols; + + // Calculate the span in the plane normal direction + float span = 0.0f; + foreach (Vertex vertex in vertices.elements) + { + span = math.max(span, -mathex.SignedDistance(plane, vertex.position)); + } + minSpan = math.min(span, minSpan); + } + + // If the minimum span is below the simplification tolerance then we can build a 2D hull without exceeding the tolerance. + // This often gives a more accurate result, since nearly-flat hulls will get rebuilt from edge plane collisions. + // Reserve it for extreme cases where the error from flattening is far less than the edge plane error. + if (minSpan < simplificationTolerance * 0.1f) + { + Rebuild2D(); + return 0.0f; + } + + // Build a list of potential merges and calculate their costs + // Also add edge planes at any sharp edges, because small changes in angle at those edges can introduce significant error. (Consider for example + // a thin wedge, if one of the planes at the sharp end rotates so that the edge angle decreases further then the intersection of those planes + // could move a long distance). + // Note -- no merges are built for edge planes, which means that they could introduce faces with an angle below minAngleBetweenFaces. + // This should be rare and edge faces should be extremely thin, which makes it very unlikely for a body to come to rest on one and jitter. + NativeArray merges = new NativeArray(numPlanes * (numPlanes - 1), Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray edgePlanes = new NativeArray(triangles.peakCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + int numMerges = 0; + int numEdgePlanes = 0; + for (int i = 0; i < numPlanes; i++) + { + for (int j = 0; j < numFaceEdges[i]; j++) + { + Edge edge = faceEdges[firstFaceEdgeIndex[i] + j]; + + // Get the neighboring face + Edge neighborEdge = GetLinkedEdge(edge); + Triangle neighborTriangle = triangles[neighborEdge.triangleIndex]; + int neighborFaceIndex = neighborTriangle.faceIndex; + if (neighborFaceIndex < i) + { + continue; // One merge entry per pair + } + + // Check for sharp angles + const float k_cosSharpAngle = -0.866025f; // 150deg + float dot = math.dot(planes[i].normal, planes[neighborFaceIndex].normal); + if (dot < k_cosMaxMergeAngle) + { + if (dot < k_cosSharpAngle) + { + int edgeIndex = numEdgePlanes++; + edgePlanes[edgeIndex] = edge; + + // Create an edge plane + float3 normal0 = planes[i].normal; + float3 normal1 = planes[neighborFaceIndex].normal; + int vertexIndex0 = triangles[edge.triangleIndex].GetVertex(edge.edgeIndex); + int vertexIndex1 = neighborTriangle.GetVertex(neighborEdge.edgeIndex); + int edgePlaneIndex = numPlanes + edgeIndex; + Plane edgePlane = GetEdgePlane(vertexIndex0, vertexIndex1, normal0, normal1); + edgePlane.distanceFromOrigin -= simplificationTolerance / 2.0f; // push the edge plane out slightly so it only becomes active if the face planes change significiantly + planes[edgePlaneIndex] = edgePlane; + + // Build its OLS data + OLSData ols = new OLSData(); ols.Init(); + ols.Include(vertices[vertexIndex0].position, 1.0f); + ols.Include(vertices[vertexIndex1].position, 1.0f); + olsData[edgePlaneIndex] = ols; + } + + // Don't merge faces with >90 degree angle + continue; + } + + // Calculate the cost to merge the faces + OLSData combined = olsData[i]; + combined.Include(olsData[neighborFaceIndex], 0.0f); + combined.Solve(planes[i].normal, planes[neighborFaceIndex].normal); + bool smallAngle = (dot > cosMinAngleBetweenFaces); + if (combined.cost <= simplificationToleranceSq || smallAngle) + { + merges[numMerges++] = new FaceMerge + { + face0 = i, + face1 = neighborFaceIndex, + cost = combined.cost, + plane = combined.plane, + smallAngle = smallAngle + }; + } + } + } + + // Calculate the plane offset for shrinking + // shrinkDistance is the maximum distance that we may move a vertex. Find the largest plane offset that respects that limit. + float offset = shrinkDistance; + { + // Find an edge incident to each vertex (doesn't matter which one) + Edge* vertexEdges = stackalloc Edge[vertices.peakCount]; + foreach (int triangleIndex in triangles.indices) + { + Triangle triangle = triangles[triangleIndex]; + vertexEdges[triangle.vertex0] = new Edge(triangleIndex, 0); + vertexEdges[triangle.vertex1] = new Edge(triangleIndex, 1); + vertexEdges[triangle.vertex2] = new Edge(triangleIndex, 2); + } + + // Calculates the square of the distance that each vertex moves if all of its incident planes' are moved unit distance along their normals + float maxShiftSq = 1.0f; + NativeArray planeIndices = new NativeArray(numPlanes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + foreach (int iVertex in vertices.indices) + { + Edge vertexEdge = vertexEdges[iVertex]; + + // Build a list of planes of faces incident to the vertex + int numPlaneIndices = 0; + Edge edge = vertexEdge; + int lastFaceIndex = -1; + do + { + int faceIndex = triangles[edge.triangleIndex].faceIndex; + if (faceIndex != lastFaceIndex) // there could be multiple incident triangles on the same face, only need to add it once + { + planeIndices[numPlaneIndices++] = faceIndex; + lastFaceIndex = faceIndex; + } + edge = GetLinkedEdge(edge).Next; + } + while (edge.data != vertexEdge.data); + while (planeIndices[numPlaneIndices - 1] == planeIndices[0]) + { + numPlaneIndices--; // first and last edge could be on different triangles on the same face + } + + // Iterate over all triplets of planes + const float k_cosWideAngle = 0.866025f; // Only limit movement of vertices at corners sharper than 30 degrees + for (int i = 0; i < numPlaneIndices - 2; i++) + { + float3 iNormal = planes[planeIndices[i]].normal; + for (int j = i + 1; j < numPlaneIndices - 1; j++) + { + float3 jNormal = planes[planeIndices[j]].normal; + float3 ijCross = math.cross(iNormal, jNormal); + + for (int k = j + 1; k < numPlaneIndices; k++) + { + float3 kNormal = planes[planeIndices[k]].normal; + + // Skip corners with wide angles + float3 dots = new float3( + math.dot(iNormal, jNormal), + math.dot(jNormal, kNormal), + math.dot(kNormal, iNormal)); + if (math.any(dots < k_cosWideAngle)) + { + // Calculate the movement of the planes' intersection with respect to the planes' shift + float det = math.dot(ijCross, kNormal); + float invDet = math.rcp(det); + float3 jkCross = math.cross(jNormal, kNormal); + float3 kiCross = math.cross(kNormal, iNormal); + float shiftSq = math.lengthsq(ijCross + jkCross + kiCross) * invDet * invDet; + shiftSq = math.select(shiftSq, 1e10f, invDet == 0.0f); // avoid nan/inf in unexpected case of zero or extremely small det + Assert.IsTrue(shiftSq >= 1.0f); + maxShiftSq = math.max(maxShiftSq, shiftSq); + } + } + } + } + } + + // Calculate how far we can move the planes without moving vertices more than the limit + offset *= math.rsqrt(maxShiftSq); + + // Can't shrink more than the inner sphere radius, minSpan / 4 is a lower bound on that radius so use it to clamp the offset + offset = math.min(offset, minSpan / 4.0f); + } + + // Merge faces + int numMerged = 0; + int numOriginalPlanes = numPlanes; + numPlanes += numEdgePlanes; + NativeArray removed = new NativeArray(numPlanes, Allocator.Temp, NativeArrayOptions.ClearMemory); + while (true) + { + while (numMerges > 0 && numPlanes > 4) + { + // Find the cheapest merge + int mergeIndex = 0; + int smallAngleMergeIndex = -1; + float smallAngleMergeCost = float.MaxValue; + for (int i = 0; i < numMerges; i++) + { + if (merges[i].cost < merges[mergeIndex].cost) + { + mergeIndex = i; + } + + if (merges[i].smallAngle && merges[i].cost < smallAngleMergeCost) + { + smallAngleMergeIndex = i; + smallAngleMergeCost = merges[i].cost; + } + } + + // If the cheapest merge is above the cost threshold, take the cheapest merge between a pair of planes that are below the angle + // threshold and therefore must be merged regardless of cost. If there are none, then quit if the estimated face count is below + // the limit, otherwise stick with the cheapest merge + if (merges[mergeIndex].cost > simplificationToleranceSq) + { + if (smallAngleMergeIndex < 0) + { + // We can't know the exact face count, because until we build the shape we don't know which planes will have intersections + // on the hull. Eg. edge planes may or may not be used, or planes may be removed due to shrinking. Make a rough guess. + int estimatedFaceCount = numPlanes - numEdgePlanes - numMerged; + if (estimatedFaceCount <= maxFaces) + { + break; + } + } + else + { + mergeIndex = smallAngleMergeIndex; + } + } + + // Remove the selected merge from the list + FaceMerge merge = merges[mergeIndex]; + merges[mergeIndex] = merges[--numMerges]; + numMerged++; + + // Replace plane 0 with the merged plane, and remove plane 1 + planes[merge.face0] = merge.plane; + removed[merge.face1] = true; + + // Combine plane 1's OLS data into plane 0's + { + OLSData combined = olsData[merge.face0]; + combined.Include(olsData[merge.face1], 0.0f); + olsData[merge.face0] = combined; + } + + // Update any other potential merges involving either of the original planes to point to the new merged planes + for (int i = numMerges - 1; i >= 0; i--) + { + // Test if the merge includes one of the planes that was just updated + // If it references the plane that was removed, update it to point to the new combined plane + FaceMerge updateMerge = merges[i]; + if (updateMerge.face0 == merge.face1) + { + updateMerge.face0 = merge.face0; + } + else if (updateMerge.face1 == merge.face1) + { + updateMerge.face1 = merge.face0; + } + else if (updateMerge.face0 != merge.face0 && updateMerge.face1 != merge.face0) + { + continue; + } + + // Can't merge a plane with itself, this happens if there is eg. a trifan that gets merged together + if (updateMerge.face0 == updateMerge.face1) + { + merges[i] = merges[--numMerges]; + continue; + } + + // Limit the maximum merge angle + float dot = math.dot(planes[updateMerge.face0].normal, planes[updateMerge.face1].normal); + if (dot < k_cosMaxMergeAngle) + { + merges[i] = merges[--numMerges]; + continue; + } + + // Calculate the new plane and cost + float weight = 1.0f / math.max(float.Epsilon, SinAngleSq(planes[updateMerge.face0].normal, planes[updateMerge.face1].normal)); + OLSData combined = olsData[updateMerge.face0]; + combined.Include(olsData[updateMerge.face1], weight); + combined.Solve(planes[updateMerge.face0].normal, planes[updateMerge.face1].normal); + bool smallAngle = (dot > cosMinAngleBetweenFaces); + if (updateMerge.cost <= simplificationToleranceSq || smallAngle) + { + // Write back + updateMerge.cost = combined.cost; + updateMerge.plane = combined.plane; + updateMerge.smallAngle = smallAngle; + merges[i] = updateMerge; + } + else + { + // Remove the merge + merges[i] = merges[--numMerges]; + } + } + } + + if (numMerged == 0) + { + break; // Nothing merged, quit + } + + // Check for any planes with small angles. It is somewhat uncommon, but sometimes planes that either were not neighbors, or whose merge was dropped, later become nearly + // parallel to each other as a result of another merge, and therefore need to be merged to each other + numMerges = 0; + for (int i = 0; i < numOriginalPlanes - 1; i++) + { + if (removed[i]) + continue; + for (int j = i + 1; j < numOriginalPlanes; j++) + { + if (removed[j]) + continue; + if (math.dot(planes[i].normal, planes[j].normal) > cosMinAngleBetweenFaces) + { + OLSData combined = olsData[i]; + combined.Include(olsData[j], 0.0f); + combined.Solve(planes[i].normal, planes[j].normal); + merges[numMerges++] = new FaceMerge + { + face0 = i, + face1 = j, + cost = combined.cost, + plane = combined.plane, + smallAngle = true + }; + } + } + } + if (numMerges == 0) + { + break; // No new merges found, quit + } + } + + // Compact the planes and push them in + for (int i = numPlanes - 1; i >= 0; i--) + { + if (removed[i]) + { + planes[i] = planes[--numPlanes]; + } + else + { + planes[i] = new Plane(planes[i].normal, planes[i].distanceFromOrigin + offset); + } + } + + // Calculate cross products of all face pairs + NativeArray crosses = new NativeArray(numPlanes * (numPlanes - 1) / 2, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + int crossIndex = 0; + for (int i = 0; i < numPlanes - 1; i++) + { + Plane plane0 = planes[i]; + float3 point0 = -plane0.normal * plane0.distanceFromOrigin; // A point on plane0 + for (int j = i + 1; j < numPlanes; j++) + { + Plane plane1 = planes[j]; + float3 cross = math.cross(plane0.normal, plane1.normal); + + // Get the line through the two planes and check if it intersects the domain. + // If not, then it has no intersections that will be kept and we can skip it in the n^4 loop. + float3 tangent0 = math.cross(plane0.normal, cross); + float3 point01 = point0 - tangent0 * mathex.SignedDistance(plane1, point0) / math.dot(plane1.normal, tangent0); //point on both plane0 and plane1 + float3 invCross = math.select(math.rcp(cross), math.sqrt(float.MaxValue), cross == float3.zero); + float3 tMin = (m_integerSpaceAabb.min - point01) * invCross; + float3 tMax = (m_integerSpaceAabb.max - point01) * invCross; + float3 tEnter = math.min(tMin, tMax); + float3 tExit = math.max(tMin, tMax); + bool hit = (math.cmax(tEnter) <= math.cmin(tExit)); + if (hit) + { + crosses[crossIndex] = cross; + } + else + { + crosses[crossIndex] = float3.zero; + } + crossIndex++; + } + } + + // Find all intersections of three planes. Note, this is a very slow O(numPlanes^4) operation. + // Intersections are calculated with double precision, otherwise points more than a couple meters from the origin can have error + // above the tolerance for the inner loop. + NativeArray newVertices = new NativeArray(vertices.peakCount * 100, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + int numNewVertices = 0; + int indexMultiplier = 2 * numPlanes - 3; + for (int i = 0; i < numPlanes - 2; i++) + { + int iBase = i * (indexMultiplier - i) / 2 - 1; + for (int j = i + 1; j < numPlanes - 1; j++) + { + // Test if discs i and j intersect + double3 ijCross = crosses[iBase + j]; + if (math.all(ijCross == 0.0f)) // broadphase test + { + continue; + } + + int jBase = j * (indexMultiplier - j) / 2 - 1; + for (int k = j + 1; k < numPlanes; k++) + { + // Test if all discs intersect pairwise + double3 ikCross = crosses[iBase + k]; + double3 jkCross = crosses[jBase + k]; + if (math.all(ikCross == 0.0f) || math.all(jkCross == 0.0f)) // broadphase test + { + continue; + } + + // Find the planes' point of intersection + float3 x; + { + double det = math.dot(planes[i].normal, jkCross); + if (math.abs(det) < 1e-8f) + { + continue; + } + double invDet = 1.0f / det; + x = + (float3)((planes[i].distanceFromOrigin * jkCross - planes[j].distanceFromOrigin * ikCross + planes[k].distanceFromOrigin * ijCross) * -invDet); + } + + // Test if the point is inside of all of the other planes + { + bool inside = true; + for (int l = 0; l < numPlanes; l++) + { + const float tolerance = 1e-5f; + if (math.dot(planes[l].normal, x) > tolerance - planes[l].distanceFromOrigin) + { + inside = false; + break; + } + } + + if (!inside) + { + continue; + } + } + + // Check if we already found an intersection that is almost exactly the same as x + float minDistanceSq = 1e-10f; + bool keep = true; + for (int l = 0; l < numNewVertices; l++) + { + if (math.distancesq(newVertices[l], x) < minDistanceSq) + { + keep = false; + break; + } + } + + if (keep) + { + newVertices[numNewVertices++] = x; + } + } + crossIndex++; + } + } + + // Check if there are enough vertices to form a 3D shape + if (numNewVertices < 4) + { + // This can happen if the hull was nearly flat + Rebuild2D(); + return 0.0f; + } + + // Rebuild faces using the plane intersection vertices + if (numNewVertices >= 4) + { + Reset(); + for (int i = 0; i < numNewVertices; i++) + { + AddPoint(newVertices[i]); + } + } + + // When more than three planes meet at one point, the intersections computed from each subset of three planes can be slightly different + // due to float rounding. This creates unnecessary extra points in the hull and sometimes also numerical problems for BuildFaceIndices + // from degenerate triangles. This is fixed by another round of simplification with the error tolerance set low enough that the vertices + // cannot move far enough to introduce new unintended faces. + RemoveRedundantVertices(); + BuildFaceIndices(planes.GetSubArray(0, numPlanes)); + SimplifyVertices(k_planeEps, maxVertices); + + // Snap coords to their quantized values for the last build + foreach (int v in vertices.indices) + { + Vertex vertex = vertices[v]; + vertex.position = m_integerSpace.ToFloatSpace(vertex.intPosition); + vertices.Set(v, vertex); + } + + BuildFaceIndices(planes.GetSubArray(0, numPlanes)); + + return offset; + } + + #endregion + + #region Edge methods + + /// + /// Returns one of the triangle edges starting from a given vertex. + /// Note: May be one of the inner edges of a face. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Edge GetVertexEdge(int vertexIndex) + { + Assert.IsTrue(dimension == 3); + foreach (int triangleIndex in triangles.indices) + { + Triangle triangle = triangles[triangleIndex]; + if (triangle.vertex0 == vertexIndex) + return new Edge(triangleIndex, 0); + if (triangle.vertex1 == vertexIndex) + return new Edge(triangleIndex, 1); + if (triangle.vertex2 == vertexIndex) + return new Edge(triangleIndex, 2); + } + return Edge.k_invalid; + } + + /// + /// Returns an edge's linked edge on the neighboring triangle. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Edge GetLinkedEdge(Edge edge) => edge.isValid ? triangles[edge.triangleIndex].GetLink(edge.edgeIndex) : edge; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int StartVertex(Edge edge) => triangles[edge.triangleIndex].GetVertex(edge.edgeIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int EndVertex(Edge edge) => StartVertex(edge.Next); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ApexVertex(Edge edge) => StartVertex(edge.Prev); + + /// + /// Returns (the first edge of) the first face. + /// + public FaceEdge GetFirstFace() + { + return numFaces > 0 ? GetFirstFace(0) : FaceEdge.k_invalid; + } + + /// + /// Returns the first face edge from a given face index. + /// + public FaceEdge GetFirstFace(int faceIndex) + { + foreach (int triangleIndex in triangles.indices) + { + if (triangles[triangleIndex].faceIndex != faceIndex) + { + continue; + } + for (int i = 0; i < 3; ++i) + { + var edge = new Edge(triangleIndex, i); + if (triangles[GetLinkedEdge(edge).triangleIndex].faceIndex != faceIndex) + { + return new FaceEdge { start = edge, current = edge }; + } + } + } + return FaceEdge.k_invalid; + } + + /// + /// Returns (the first edge of) the next face. + /// + public FaceEdge GetNextFace(FaceEdge fe) + { + int faceIndex = fe.isValid ? triangles[fe.start.triangleIndex].faceIndex + 1 : 0; + if (faceIndex < numFaces) + return GetFirstFace(faceIndex); + return FaceEdge.k_invalid; + } + + /// + /// Returns the next edge within a face. + /// + public FaceEdge GetNextFaceEdge(FaceEdge fe) + { + int faceIndex = triangles[fe.start.triangleIndex].faceIndex; + bool found = false; + fe.current = fe.current.Next; + for (int n = vertices[StartVertex(fe.current)].cardinality; n > 0; --n) + { + if (triangles[GetLinkedEdge(fe.current).triangleIndex].faceIndex == faceIndex) + { + fe.current = GetLinkedEdge(fe.current).Next; + } + else + { + found = true; + break; + } + } + + if (!found || fe.current.Equals(fe.start)) + return FaceEdge.k_invalid; + return fe; + } + + #endregion + + #region Queries + + /// + /// Returns the centroid of the convex hull. + /// + public float3 ComputeCentroid() + { + float4 sum = new float4(0); + foreach (Vertex vertex in vertices.elements) + { + sum += new float4(vertex.position, 1); + } + + if (sum.w > 0) + return sum.xyz / sum.w; + return new float3(0); + } + + /// + /// Compute the mass properties of the convex hull. + /// Note: Inertia computation adapted from S. Melax, http://www.melax.com/volint. + /// + public unsafe void UpdateHullMassProperties() + { + var mp = new MassProperties(); + switch (dimension) + { + case 0: + mp.centerOfMass = vertices[0].position; + break; + case 1: + mp.centerOfMass = (vertices[0].position + vertices[1].position) * 0.5f; + break; + case 2: + { + float3 offset = ComputeCentroid(); + for (int n = vertices.peakCount, i = n - 1, j = 0; j < n; i = j++) + { + float w = math.length(math.cross(vertices[i].position - offset, vertices[j].position - offset)); + mp.centerOfMass += (vertices[i].position + vertices[j].position + offset) * w; + mp.surfaceArea += w; + } + mp.centerOfMass /= mp.surfaceArea * 3; + mp.inertiaTensor = float3x3.identity; // + mp.surfaceArea *= 0.5f; + } + break; + case 3: + { + float3 offset = ComputeCentroid(); + int numTriangles = 0; + float* dets = stackalloc float[triangles.capacity]; + foreach (int i in triangles.indices) + { + float3 v0 = vertices[triangles[i].vertex0].position - offset; + float3 v1 = vertices[triangles[i].vertex1].position - offset; + float3 v2 = vertices[triangles[i].vertex2].position - offset; + float w = math.determinant(new float3x3(v0, v1, v2)); + mp.centerOfMass += (v0 + v1 + v2) * w; + mp.volume += w; + mp.surfaceArea += math.length(math.cross(v1 - v0, v2 - v0)); + dets[i] = w; + numTriangles++; + } + + mp.centerOfMass = mp.centerOfMass / (mp.volume * 4) + offset; + + var diag = new float3(0); + var offd = new float3(0); + + foreach (int i in triangles.indices) + { + float3 v0 = vertices[triangles[i].vertex0].position - mp.centerOfMass; + float3 v1 = vertices[triangles[i].vertex1].position - mp.centerOfMass; + float3 v2 = vertices[triangles[i].vertex2].position - mp.centerOfMass; + diag += (v0 * v1 + v1 * v2 + v2 * v0 + v0 * v0 + v1 * v1 + v2 * v2) * dets[i]; + offd += (v0.yzx * v1.zxy + v1.yzx * v2.zxy + v2.yzx * v0.zxy + + v0.yzx * v2.zxy + v1.yzx * v0.zxy + v2.yzx * v1.zxy + + (v0.yzx * v0.zxy + v1.yzx * v1.zxy + v2.yzx * v2.zxy) * 2) * dets[i]; + numTriangles++; + } + + diag /= mp.volume * (60 / 6); + offd /= mp.volume * (120 / 6); + + mp.inertiaTensor.c0 = new float3(diag.y + diag.z, -offd.z, -offd.y); + mp.inertiaTensor.c1 = new float3(-offd.z, diag.x + diag.z, -offd.x); + mp.inertiaTensor.c2 = new float3(-offd.y, -offd.x, diag.x + diag.y); + + mp.surfaceArea /= 2; + mp.volume /= 6; + } + break; + } + + hullMassProperties = mp; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Plane ComputePlane(int vertex0, int vertex1, int vertex2, bool fromIntCoordinates) + { + float3 cross; // non-normalized plane direction + float3 point; // point on the plane + if (fromIntCoordinates) + { + int3 o = vertices[vertex0].intPosition; + int3 a = vertices[vertex1].intPosition - o; + int3 b = vertices[vertex2].intPosition - o; + IntCross(a, b, out long cx, out long cy, out long cz); + float scaleSq = m_integerSpace.scale * m_integerSpace.scale; // scale down to avoid overflow normalizing + cross = new float3(cx * scaleSq, cy * scaleSq, cz * scaleSq); + point = m_integerSpace.ToFloatSpace(o); + } + else + { + point = vertices[vertex0].position; + float3 a = vertices[vertex1].position - point; + float3 b = vertices[vertex2].position - point; + cross = math.cross(a, b); + } + float3 n = math.normalize(cross); + return new Plane(n, -math.dot(n, point)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Plane ComputePlane(int triangleIndex, bool fromIntCoordinates = true) + { + return ComputePlane(triangles[triangleIndex].vertex0, triangles[triangleIndex].vertex1, triangles[triangleIndex].vertex2, fromIntCoordinates); + } + + #endregion + + #region int math + + // Sets cx, cy, cz = a x b, note that all components of a and b must be 31 bits or fewer + private static void IntCross(int3 a, int3 b, out long cx, out long cy, out long cz) + { + cx = (long)a.y * b.z - (long)a.z * b.y; + cy = (long)a.z * b.x - (long)a.x * b.z; + cz = (long)a.x * b.y - (long)a.y * b.x; + } + + // Computes det (b-a, c-a, d-a) and returns an integer that is positive when det is positive, negative when det is negative, and zero when det is zero. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private long IntDet(int3 a, int3 b, int3 c, int3 d) + { + int3 ab = b - a, ac = c - a, ad = d - a; + IntCross(ab, ac, out long kx, out long ky, out long kz); + if (m_intResolution == IntResolution.Low) + { + // abcd coords are 16 bit, k are 35 bit, dot product is 54 bit and fits in long + return kx * ad.x + ky * ad.y + kz * ad.z; + } + else + { + // abcd coords are 30 bit, k are 63 bit, dot product is 96 bit and won't fit in long so use int128 + Int128 det = Int128.Mul(kx, ad.x) + Int128.Mul(ky, ad.y) + Int128.Mul(kz, ad.z); + return (long)(det.high | (det.low & 1) | (det.low >> 1)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private long IntDet(int a, int b, int c, int d) + { + return IntDet(vertices[a].intPosition, vertices[b].intPosition, vertices[c].intPosition, vertices[d].intPosition); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private long IntDet(int a, int b, int c, int3 d) + { + return IntDet(vertices[a].intPosition, vertices[b].intPosition, vertices[c].intPosition, d); + } + + #endregion + + public ConvexHullBuilder( + NativeArray points, + ConvexHullGenerationParameters generationParameters, + int maxVertices, int maxFaces, int maxFaceVertices, + out float convexRadius + ) + { + int verticesCapacity = math.max(maxVertices, points.Length); + var vertices = new NativeArray(verticesCapacity, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var triangles = new NativeArray(verticesCapacity * 2, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var planes = new NativeArray(verticesCapacity * 2, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + var simplificationTolerance = generationParameters.SimplificationTolerance; + var shrinkDistance = generationParameters.BevelRadius; + var minAngle = generationParameters.MinimumAngle; + + // Build the points' AABB + Aabb domain = new Aabb(float.MaxValue, float.MinValue); + for (int iPoint = 0; iPoint < points.Length; iPoint++) + { + domain = Physics.CombineAabb(points[iPoint], domain); + } + + // Build the initial hull + ConvexHullBuilder builder = new ConvexHullBuilder(vertices.Length, (Vertex*)vertices.GetUnsafePtr(), + (Triangle*)triangles.GetUnsafePtr(), (Plane*)planes.GetUnsafePtr(), + domain, simplificationTolerance, IntResolution.High); + for (int iPoint = 0; iPoint < points.Length; iPoint++) + { + builder.AddPoint(points[iPoint]); + } + + builder.RemoveRedundantVertices(); + + // Simplify the vertices using half of the tolerance + builder.BuildFaceIndices(); + builder.SimplifyVertices(simplificationTolerance / 2, maxVertices); + builder.BuildFaceIndices(); + + // Build mass properties before shrinking + builder.UpdateHullMassProperties(); + + // SimplifyFacesAndShrink() can increase the vertex count, potentially above the size of the input vertices. Check if there is enough space in the + // buffers, and if not then allocate temporary storage + NativeArray tempVertices = new NativeArray(); + NativeArray tempTriangles = new NativeArray(); + NativeArray tempPlanes = new NativeArray(); + bool allocateTempBuilder = false; + if (builder.dimension == 3) + { + int maxNumVertices = 0; + foreach (int v in builder.vertices.indices) + { + maxNumVertices += builder.vertices[v].cardinality - 1; + } + + allocateTempBuilder = true; // TEMP TESTING maxNumVertices > Vertices.Length; + if (allocateTempBuilder) + { + tempVertices = new NativeArray(maxNumVertices, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + tempTriangles = new NativeArray(maxNumVertices * 2, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + tempPlanes = new NativeArray(maxNumVertices * 2, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + ConvexHullBuilder tempBuilder = new ConvexHullBuilder(maxNumVertices, + (Vertex*)tempVertices.GetUnsafePtr(), + (Triangle*)tempTriangles.GetUnsafePtr(), (Plane*)tempPlanes.GetUnsafePtr(), builder); + builder = tempBuilder; + } + + // Merge faces + convexRadius = builder.SimplifyFacesAndShrink(simplificationTolerance / 2, minAngle, shrinkDistance, maxFaces, maxVertices); + } + else + { + convexRadius = 0f; + } + + // Simplifier cannot directly enforce k_MaxFaceVertices or k_MaxFaces. It can also fail to satisfy k_MaxFaces due to numerical error. + // In these cases the hull is simplified by collapsing vertices until the counts are low enough, at the cost of possibly violating + // simplificationTolerance or minAngle. + maxVertices = builder.vertices.peakCount; + while (true) + { + // Check if the face count is low enough, and in the 2D case, if the vertices per face is low enough + if (builder.numFaces <= maxFaces && (builder.dimension == 3 || builder.vertices.peakCount < maxFaceVertices)) + { + // Iterate over all faces to check if any have too many vertices + bool simplify = false; + for (FaceEdge hullFace = builder.GetFirstFace(); hullFace.isValid; hullFace = builder.GetNextFace(hullFace)) + { + int numFaceVertices = 0; + for (FaceEdge edge = hullFace; edge.isValid; edge = builder.GetNextFaceEdge(edge)) + { + numFaceVertices++; + } + + if (numFaceVertices > maxFaceVertices) + { + simplify = true; + break; + } + } + + if (!simplify) + { + break; + } + } + + // Reduce the vertex count 20%, but no need to go below the highest vertex count that always satisfies k_MaxFaces and k_MaxFaceVertices + int limit = math.min(maxFaces / 2, maxFaceVertices); + maxVertices = math.max((int)(maxVertices * 0.8f), limit); + builder.SimplifyVertices(simplificationTolerance, maxVertices); + builder.BuildFaceIndices(); + if (maxVertices == limit) + { + break; // We should now be within the limits, and if not then something has gone wrong and it's better not to loop forever + } + } + + if (allocateTempBuilder) + { + // The vertex, triangle and face counts should now be within limits, so we can copy back to the original storage + builder.Compact(); + ConvexHullBuilder tempBuilder = new ConvexHullBuilder(vertices.Length, (Vertex*)vertices.GetUnsafePtr(), + (Triangle*)triangles.GetUnsafePtr(), (Plane*)planes.GetUnsafePtr(), builder); + builder = tempBuilder; + } + + // Write back + this = builder; + } + } + + // ConvexHullBuilder combined with NativeArrays to store its data + // Keeping NativeArray out of the ConvexHullBuilder itself allows ConvexHullBuilder to be passed to Burst jobs + internal struct ConvexHullBuilderStorage : IDisposable + { + private NativeArray m_vertices; + private NativeArray m_triangles; + private NativeArray m_planes; + public ConvexHullBuilder builder; + + public unsafe ConvexHullBuilderStorage(int verticesCapacity, Allocator allocator, Aabb domain, float simplificationTolerance, ConvexHullBuilder.IntResolution resolution) + { + int trianglesCapacity = 2 * verticesCapacity; + m_vertices = new NativeArray(verticesCapacity, allocator); + m_triangles = new NativeArray(trianglesCapacity, allocator); + m_planes = new NativeArray(trianglesCapacity, allocator); + builder = new ConvexHullBuilder(verticesCapacity, (ConvexHullBuilder.Vertex*)NativeArrayUnsafeUtility.GetUnsafePtr(m_vertices), + (ConvexHullBuilder.Triangle*)NativeArrayUnsafeUtility.GetUnsafePtr(m_triangles), + (Plane*)NativeArrayUnsafeUtility.GetUnsafePtr(m_planes), + domain, simplificationTolerance, resolution); + } + + public unsafe ConvexHullBuilderStorage(int verticesCapacity, Allocator allocator, ref ConvexHullBuilder builder) + { + m_vertices = new NativeArray(verticesCapacity, allocator); + m_triangles = new NativeArray(verticesCapacity * 2, allocator); + m_planes = new NativeArray(verticesCapacity * 2, allocator); + this.builder = new ConvexHullBuilder(verticesCapacity, (ConvexHullBuilder.Vertex*)NativeArrayUnsafeUtility.GetUnsafePtr(m_vertices), + (ConvexHullBuilder.Triangle*)NativeArrayUnsafeUtility.GetUnsafePtr(m_triangles), + (Plane*)NativeArrayUnsafeUtility.GetUnsafePtr(m_planes), builder); + } + + public void Dispose() + { + if (m_vertices.IsCreated) + { + m_vertices.Dispose(); + } + if (m_triangles.IsCreated) + { + m_triangles.Dispose(); + } + if (m_planes.IsCreated) + { + m_planes.Dispose(); + } + } + } + + // Basic 128 bit signed integer arithmetic + internal struct Int128 + { + public ulong low; + public ulong high; + + const ulong k_Low32 = 0xffffffffUL; + + public bool IsNegative => (high & 0x8000000000000000UL) != 0; + public bool IsNonNegative => (high & 0x8000000000000000UL) == 0; + public bool IsZero => (high | low) == 0; + public bool IsPositive => IsNonNegative && !IsZero; + + public static Int128 Zero => new Int128 { high = 0, low = 0 }; + + public static Int128 operator +(Int128 a, Int128 b) + { + ulong low = a.low + b.low; + ulong high = a.high + b.high; + if (low < a.low) + high++; + return new Int128 + { + low = low, + high = high + }; + } + + public static Int128 operator -(Int128 a, Int128 b) + { + return a + (-b); + } + + public static Int128 operator -(Int128 a) + { + ulong low = ~a.low + 1; + ulong high = ~a.high; + if (a.low == 0) + high++; + return new Int128 + { + low = low, + high = high + }; + } + + public static Int128 Mul(long x, int y) + { + ulong absX = (ulong)math.abs(x); + ulong absY = (ulong)math.abs(y); + ulong lowProduct = (absX & k_Low32) * absY; + ulong highProduct = (absX >> 32) * absY; + ulong low = (highProduct << 32) + lowProduct; + ulong carry = ((highProduct & k_Low32) + (lowProduct >> 32)) >> 32; + ulong high = ((highProduct >> 32) & k_Low32) + carry; + Int128 product = new Int128 + { + low = low, + high = high + }; + if (x < 0 ^ y < 0) + { + product = -product; + } + return product; + } + + public static Int128 Mul(long x, long y) + { + ulong absX = (ulong)math.abs(x); + ulong absY = (ulong)math.abs(y); + + ulong loX = absX & k_Low32; + ulong hiX = absX >> 32; + ulong loY = absY & k_Low32; + ulong hiY = absY >> 32; + + ulong lolo = loX * loY; + ulong lohi = loX * hiY; + ulong hilo = hiX * loY; + ulong hihi = hiX * hiY; + + ulong low = lolo + (lohi << 32) + (hilo << 32); + ulong carry = ((lolo >> 32) + (lohi & k_Low32) + (hilo & k_Low32)) >> 32; + ulong high = hihi + (lohi >> 32) + (hilo >> 32) + carry; + + Int128 product = new Int128 + { + low = low, + high = high + }; + if (x < 0 ^ y < 0) + { + product = -product; + } + return product; + } + } + + [Serializable] + internal struct ConvexHullGenerationParameters : IEquatable + { + internal const string k_BevelRadiusTooltip = + "Determines how rounded the edges of the convex shape will be. A value greater than 0 results in more optimized collision, at the expense of some shape detail."; + + const float k_DefaultSimplificationTolerance = 0.015f; + const float k_DefaultBevelRadius = 0.05f; + const float k_DefaultMinAngle = 2.5f * math.PI / 180f; // 2.5 degrees + + public static readonly ConvexHullGenerationParameters Default = new ConvexHullGenerationParameters + { + SimplificationTolerance = k_DefaultSimplificationTolerance, + BevelRadius = k_DefaultBevelRadius, + MinimumAngle = k_DefaultMinAngle + }; + + public float SimplificationTolerance { get => m_SimplificationTolerance; set => m_SimplificationTolerance = value; } + [UnityEngine.Tooltip("Specifies maximum distance that any input point may be moved when simplifying convex hull.")] + [UnityEngine.SerializeField] + float m_SimplificationTolerance; + + public float BevelRadius { get => m_BevelRadius; set => m_BevelRadius = value; } + [UnityEngine.Tooltip(k_BevelRadiusTooltip)] + [UnityEngine.SerializeField] + float m_BevelRadius; + + public float MinimumAngle { get => m_MinimumAngle; set => m_MinimumAngle = value; } + [UnityEngine.Tooltip("Specifies the angle between adjacent faces below which they should be made coplanar.")] + [UnityEngine.SerializeField] + float m_MinimumAngle; + + public bool Equals(ConvexHullGenerationParameters other) => + m_SimplificationTolerance == other.m_SimplificationTolerance && + m_BevelRadius == other.m_BevelRadius && + m_MinimumAngle == other.m_MinimumAngle; + + public override int GetHashCode() => + unchecked ((int)math.hash(new float3(m_SimplificationTolerance, m_BevelRadius, m_MinimumAngle))); + } + + interface IPoolElement + { + bool isAllocated { get; } + void MarkFree(int nextFree); + int nextFree { get; } + } + + // Underlying implementation of ElementPool + // This is split into a different structure so that it can be unmanaged (since templated structures are always managed) + [NoAlias] + unsafe internal struct ElementPoolBase + { + [NativeDisableContainerSafetyRestriction] + [NoAlias] + private void* m_elements; // storage for all elements (allocated and free) + private readonly int m_capacity; // number of elements + private int m_firstFreeIndex; // the index of the first free element (or -1 if none free) + + public int capacity => m_capacity; // the maximum number of elements that can be allocated + public int peakCount { get; private set; } // the maximum number of elements allocated so far + public bool canAllocate => m_firstFreeIndex >= 0 || peakCount < capacity; + + public unsafe ElementPoolBase(void* userBuffer, int capacity) + { + m_elements = userBuffer; + m_capacity = capacity; + m_firstFreeIndex = -1; + peakCount = 0; + } + + // Add an element to the pool + public int Allocate(T element) where T : unmanaged, IPoolElement + { + T* elements = ((T*)m_elements); + + Assert.IsTrue(element.isAllocated); + if (m_firstFreeIndex != -1) + { + int index = m_firstFreeIndex; + T* freeElement = (T*)m_elements + index; + m_firstFreeIndex = freeElement->nextFree; + *freeElement = element; + return index; + } + + Assert.IsTrue(peakCount < capacity); + elements[peakCount++] = element; + return peakCount - 1; + } + + // Remove an element from the pool + public void Release(int index) where T : unmanaged, IPoolElement + { + T* elementsTyped = (T*)m_elements; + elementsTyped[index].MarkFree(m_firstFreeIndex); + m_firstFreeIndex = index; + } + + // Empty the pool + public void Clear() + { + peakCount = 0; + m_firstFreeIndex = -1; + } + + public bool IsAllocated(int index) where T : unmanaged, IPoolElement + { + T element = ((T*)m_elements)[index]; + return element.isAllocated; + } + + public T Get(int index) where T : unmanaged, IPoolElement + { + Assert.IsTrue(index < capacity); + T element = ((T*)m_elements)[index]; + Assert.IsTrue(element.isAllocated); + return element; + } + + public void Set(int index, T value) where T : unmanaged, IPoolElement + { + Assert.IsTrue(index < capacity); + ((T*)m_elements)[index] = value; + } + + public unsafe void CopyFrom(ElementPoolBase other) where T : unmanaged, IPoolElement + { + Assert.IsTrue(other.peakCount <= capacity); + peakCount = other.peakCount; + m_firstFreeIndex = other.m_firstFreeIndex; + UnsafeUtility.MemCpy(m_elements, other.m_elements, peakCount * UnsafeUtility.SizeOf()); + } + + public unsafe void CopyFrom(void* buffer, int length) where T : unmanaged, IPoolElement + { + Assert.IsTrue(length <= capacity); + peakCount = length; + m_firstFreeIndex = -1; + UnsafeUtility.MemCpy(m_elements, buffer, peakCount * UnsafeUtility.SizeOf()); + } + + // Compacts the pool so that all of the allocated elements are contiguous, and resets PeakCount to the current allocated count. + // remap may be null or an array of size at least PeakCount, if not null and the return value is true then Compact() sets remap[oldIndex] = newIndex for all allocated elements. + // Returns true if compact occurred, false if the pool was already compact. + public unsafe bool Compact(int* remap) where T : unmanaged, IPoolElement + { + if (m_firstFreeIndex == -1) + { + return false; + } + int numElements = 0; + for (int i = 0; i < peakCount; i++) + { + T element = ((T*)m_elements)[i]; + if (element.isAllocated) + { + if (remap != null) + remap[i] = numElements; + ((T*)m_elements)[numElements++] = element; + } + } + peakCount = numElements; + m_firstFreeIndex = -1; + return true; + } + + #region Enumerables + + public IndexEnumerable GetIndices() where T : unmanaged, IPoolElement + { + NativeArray slice = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_elements, peakCount, Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref slice, AtomicSafetyHandle.GetTempUnsafePtrSliceHandle()); +#endif + return new IndexEnumerable { slice = slice }; + } + + public ElementEnumerable GetElements() where T : unmanaged, IPoolElement + { + NativeArray slice = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_elements, peakCount, Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref slice, AtomicSafetyHandle.GetTempUnsafePtrSliceHandle()); +#endif + return new ElementEnumerable { slice = slice }; + } + + public struct IndexEnumerable where T : unmanaged, IPoolElement + { + internal NativeArray slice; + + public IndexEnumerator GetEnumerator() => new IndexEnumerator(ref slice); + } + + public struct ElementEnumerable where T : unmanaged, IPoolElement + { + internal NativeArray slice; + + public ElementEnumerator GetEnumerator() => new ElementEnumerator(ref slice); + } + + // An enumerator for iterating over the indices + public struct IndexEnumerator where T : unmanaged, IPoolElement + { + internal NativeArray slice; + internal int index; + + public int Current => index; + + internal IndexEnumerator(ref NativeArray slice) + { + this.slice = slice; + index = -1; + } + + public bool MoveNext() + { + while (true) + { + if (++index >= slice.Length) + { + return false; + } + if (slice[index].isAllocated) + { + return true; + } + } + } + } + + // An enumerator for iterating over the allocated elements + public struct ElementEnumerator where T : unmanaged, IPoolElement + { + internal NativeArray slice; + internal IndexEnumerator indexEnumerator; + + public T Current => slice[indexEnumerator.Current]; + + internal ElementEnumerator(ref NativeArray slice) + { + this.slice = slice; + indexEnumerator = new IndexEnumerator(ref slice); + } + + public bool MoveNext() => indexEnumerator.MoveNext(); + } + + #endregion + } + + // A fixed capacity array acting as a pool of allocated/free structs referenced by indices + unsafe internal struct ElementPool where T : unmanaged, IPoolElement + { + public ElementPoolBase* elementPoolBase; + + public int capacity => elementPoolBase->capacity; // the maximum number of elements that can be allocated + public int peakCount => elementPoolBase->peakCount; // the maximum number of elements allocated so far + public bool canAllocate => elementPoolBase->canAllocate; + + // Add an element to the pool + public int Allocate(T element) + { + return elementPoolBase->Allocate(element); + } + + // Remove an element from the pool + public void Release(int index) + { + elementPoolBase->Release(index); + } + + // Empty the pool + public void Clear() + { + elementPoolBase->Clear(); + } + + public bool IsAllocated(int index) + { + return elementPoolBase->IsAllocated(index); + } + + // Get/set an element + public T this[int index] + { + get { return elementPoolBase->Get(index); } + set { elementPoolBase->Set(index, value); } + } + + public void Set(int index, T value) + { + elementPoolBase->Set(index, value); + } + + public unsafe void CopyFrom(ElementPool other) + { + elementPoolBase->CopyFrom(*other.elementPoolBase); + } + + public unsafe void CopyFrom(void* buffer, int length) + { + elementPoolBase->CopyFrom(buffer, length); + } + + public unsafe bool Compact(int* remap) + { + return elementPoolBase->Compact(remap); + } + + public ElementPoolBase.IndexEnumerable indices => elementPoolBase->GetIndices(); + public ElementPoolBase.ElementEnumerable elements => elementPoolBase->GetElements(); + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/ConvexHullBuilder.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/ConvexHullBuilder.cs.meta new file mode 100644 index 0000000..3fef8b5 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Builders/ConvexHullBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16b8f3a37e10c3c46a2c7bf9fc89d67f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math.meta new file mode 100644 index 0000000..fbe53a7 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b19d126b7e600a74f9f110e2d5ebdf1f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/Plane.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/Plane.cs new file mode 100644 index 0000000..5e30ed4 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/Plane.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal struct Plane + { + private float4 m_normalAndDistance; + + public float3 normal + { + get => m_normalAndDistance.xyz; + set => m_normalAndDistance.xyz = value; + } + + public float distanceFromOrigin + { + get => m_normalAndDistance.w; + set => m_normalAndDistance.w = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Plane(float3 normal, float distance) + { + m_normalAndDistance = new float4(normal, distance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator float4(Plane plane) => plane.m_normalAndDistance; + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/Plane.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/Plane.cs.meta new file mode 100644 index 0000000..8f9e4d9 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/Plane.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4992f3d812fd9b94d8cd37d3a8bdad81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.collision.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.collision.cs new file mode 100644 index 0000000..4181648 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.collision.cs @@ -0,0 +1,47 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class mathex + { + //Formerly getFactoredLength + public static float GetLengthAndNormal(float3 v, out float3 normal) + { + float lengthSq = math.lengthsq(v); + float invLength = math.rsqrt(lengthSq); + normal = v * invLength; + return lengthSq * invLength; + } + + public static float4 GetLengthAndNormal(in simdFloat3 v, out simdFloat3 normal) + { + float4 lengthSq = simd.lengthsq(v); + float4 invLength = math.rsqrt(lengthSq); + normal = v * invLength; + return lengthSq * invLength; + } + + // From Unity's CalculatePerpendicularNormalized. + // Todo: If input is unscaledNormal, which is tangent and which is bitangent? + public static void GetDualPerpendicularNormalized(float3 unsacledInput, out float3 perpendicularA, out float3 perpendicularB) + { + float3 v = unsacledInput; + float3 vSquared = v * v; + float3 lengthsSquared = vSquared + vSquared.xxx; // y = ||j x v||^2, z = ||k x v||^2 + float3 invLengths = math.rsqrt(lengthsSquared); + + // select first direction, j x v or k x v, whichever has greater magnitude + float3 dir0 = new float3(-v.y, v.x, 0.0f); + float3 dir1 = new float3(-v.z, 0.0f, v.x); + bool cmp = (lengthsSquared.y > lengthsSquared.z); + float3 dir = math.select(dir1, dir0, cmp); + + // normalize and get the other direction + float invLength = math.select(invLengths.z, invLengths.y, cmp); + perpendicularA = dir * invLength; + float3 cross = math.cross(v, dir); + perpendicularB = cross * invLength; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.collision.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.collision.cs.meta new file mode 100644 index 0000000..b08c74a --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.collision.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0741d2f054713f640939148487a0bb0f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.plane.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.plane.cs new file mode 100644 index 0000000..9131cdc --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.plane.cs @@ -0,0 +1,33 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class mathex + { + // Returns the distance from the point to the plane, positive if the point is on the side of + // the plane on which the plane normal points, zero if the point is on the plane, negative otherwise. + public static float SignedDistance(Plane plane, float3 point) + { + return math.dot(plane, point.xyz1()); + } + + public static float3 ProjectPoint(Plane plane, float3 point) + { + return point - plane.normal * SignedDistance(plane, point); + } + + public static Plane PlaneFrom(float3 relativeOrigin, float3 edgeA, float3 edgeB) + { + var normal = math.normalize(math.cross(edgeA, edgeB)); + return new Plane(normal, -math.dot(normal, relativeOrigin)); + } + + public static Plane Flip(Plane plane) + { + float4 v = plane; + v = -v; + return new Plane(v.xyz, v.w); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.plane.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.plane.cs.meta new file mode 100644 index 0000000..2cbb5ba --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Math/mathex.plane.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 480a73230ee7511498281e506ecaf864 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries.meta new file mode 100644 index 0000000..56a2e87 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 529492f8c38e465489bf7d926e92bcd0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderCast.LatiosMpr.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderCast.LatiosMpr.cs new file mode 100644 index 0000000..a2d1271 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderCast.LatiosMpr.cs @@ -0,0 +1,1000 @@ +using Unity.Mathematics; + +// This file contains Minkowski Portal Refinement algorithms primarily purposed for shapecasting. +// The implementations here were not derived from any particular reference code. + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + internal static bool MprCastNoRoundness(in Collider caster, + in Collider target, + in RigidTransform targetInCasterSpace, + float3 normalizedCastDirectionInCasterSpace, + float maxCastDistance, + out float distanceOfImpact, + out bool somethingWentWrong) + { + somethingWentWrong = false; + + // First, test if the caster can even reach the target + SupportPoint portalA = GetSupport(in caster, in target, normalizedCastDirectionInCasterSpace, targetInCasterSpace); + float minDistance = + math.dot(GetSupport(in caster, in target, -normalizedCastDirectionInCasterSpace, targetInCasterSpace).pos, normalizedCastDirectionInCasterSpace); + float maxDistance = math.dot(portalA.pos, normalizedCastDirectionInCasterSpace) + maxCastDistance; + if (minDistance > 0f || maxDistance < 0f) + { + distanceOfImpact = Mpr.k_missDistance; + return false; + } + + // Second, check if the caster's AABB reaches the target's AABB laterally + var initialCsoAabb = GetCsoAabb(in caster, in target, targetInCasterSpace); + if (math.all(initialCsoAabb.max < 0f | initialCsoAabb.min > 0f)) + { + // The origin is not in the Aabb. Cast the ray in both directions + float castMagnitude = math.max(maxDistance, maxCastDistance) - minDistance; + bool hitForward = SpatialInternal.RaycastAabb(new Ray(0f, normalizedCastDirectionInCasterSpace * castMagnitude), initialCsoAabb, out _); + bool hitBackward = SpatialInternal.RaycastAabb(new Ray(0f, -normalizedCastDirectionInCasterSpace * castMagnitude), initialCsoAabb, out _); + if (!(hitForward | hitBackward)) + { + distanceOfImpact = Mpr.k_missDistance; + return false; + } + } + + // Third, check if the caster reaches the target laterally using planar MPR + if (!Mpr.DoPlanarMpr(in caster, in target, in targetInCasterSpace, normalizedCastDirectionInCasterSpace, portalA.pos, out var portalB, out var portalC, + ref somethingWentWrong)) + { + distanceOfImpact = Mpr.k_missDistance; + return false; + } + + // At this point, wen know that the extents results in a collision. So it is safe to do the full 3D MPR. + // But first, we need to slide the caster so that the origin is behind the CSO. + float slideDistance = -minDistance; + RigidTransform slidTargetInCasterSpace = targetInCasterSpace; + slidTargetInCasterSpace.pos -= slideDistance * normalizedCastDirectionInCasterSpace; + // Because our space has been slid, we need to recover our supports using the IDs and the new space + + portalA = Get3DSupportFromPlanar(in caster, in target, slidTargetInCasterSpace, portalA); + portalB = Get3DSupportFromPlanar(in caster, in target, slidTargetInCasterSpace, portalB); + portalC = Get3DSupportFromPlanar(in caster, in target, slidTargetInCasterSpace, portalC); + + // Our planar supports might be interior supports due to the axis reduction. + //portalA = GetSupport(caster, target, portalA.pos, slidTargetInCasterSpace); + //portalB = GetSupport(caster, target, portalB.pos, slidTargetInCasterSpace); + //portalC = GetSupport(caster, target, portalC.pos, slidTargetInCasterSpace); + + // Catch the case where the ray misses the portal triangle. It is frustrating that it happens, but this should catch it. + Mpr.DoMprPortalSearch(in caster, in target, in slidTargetInCasterSpace, normalizedCastDirectionInCasterSpace, ref portalA, ref portalB, ref portalC, + ref somethingWentWrong); + + float mprDistance = Mpr.DoMprRefine3D(in caster, + in target, + in slidTargetInCasterSpace, + normalizedCastDirectionInCasterSpace, + portalA, + portalB, + portalC, + ref somethingWentWrong); + + distanceOfImpact = slideDistance - mprDistance; + return distanceOfImpact <= maxCastDistance; + } + + // If distanceOfImpact is negative, the caller should test if the two objects overlap at the initial position. The two objects projected OBBs intersect. + internal static bool MprCastNoRoundnessDebug(Collider caster, + Collider target, + RigidTransform targetInCasterSpace, + float3 normalizedCastDirectionInCasterSpace, + float maxCastDistance, + out float distanceOfImpact, + out bool somethingWentWrong) + { + somethingWentWrong = false; + + // First, test if the caster can even reach the target + SupportPoint portalA = GetSupport(caster, target, normalizedCastDirectionInCasterSpace, targetInCasterSpace); + float minDistance = + math.dot(GetSupport(caster, target, -normalizedCastDirectionInCasterSpace, targetInCasterSpace).pos, normalizedCastDirectionInCasterSpace); + float maxDistance = math.dot(portalA.pos, normalizedCastDirectionInCasterSpace) + maxCastDistance; + if (minDistance > 0f || maxDistance < 0f) + { + distanceOfImpact = Mpr.k_missDistance; + return false; + } + + // Second, check if the caster's AABB reaches the target's AABB laterally + var initialCsoAabb = GetCsoAabb(caster, target, targetInCasterSpace); + if (math.all(initialCsoAabb.max < 0f | initialCsoAabb.min > 0f)) + { + // The origin is not in the Aabb. Cast the ray in both directions + float castMagnitude = math.max(maxDistance, maxCastDistance) - minDistance; + bool hitForward = SpatialInternal.RaycastAabb(new Ray(0f, normalizedCastDirectionInCasterSpace * castMagnitude), initialCsoAabb, out _); + bool hitBackward = SpatialInternal.RaycastAabb(new Ray(0f, -normalizedCastDirectionInCasterSpace * castMagnitude), initialCsoAabb, out _); + if (!(hitForward | hitBackward)) + { + distanceOfImpact = Mpr.k_missDistance; + return false; + } + } + + // Third, check if the caster reaches the target laterally using planar MPR + if (!Mpr.DoPlanarMprDebug(caster, target, targetInCasterSpace, normalizedCastDirectionInCasterSpace, portalA.pos, out var portalB, out var portalC, + ref somethingWentWrong)) + { + distanceOfImpact = Mpr.k_missDistance; + return false; + } + + // At this point, wen know that the extents results in a collision. So it is safe to do the full 3D MPR. + // But first, we need to slide the caster so that the origin is behind the CSO. + float slideDistance = -minDistance; + UnityEngine.Debug.Log($"Running Mpr3D, sliding: {slideDistance}"); + RigidTransform slidTargetInCasterSpace = targetInCasterSpace; + slidTargetInCasterSpace.pos -= slideDistance * normalizedCastDirectionInCasterSpace; + // Because our space has been slid, we need to recover our supports using the IDs and the new space + + UnityEngine.Debug.Log($"Post-planar portal: A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + + portalA = Get3DSupportFromPlanar(caster, target, slidTargetInCasterSpace, portalA); + portalB = Get3DSupportFromPlanar(caster, target, slidTargetInCasterSpace, portalB); + portalC = Get3DSupportFromPlanar(caster, target, slidTargetInCasterSpace, portalC); + + UnityEngine.Debug.Log($"3D portal: A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + + // Our planar supports might be interior supports due to the axis reduction. + //portalA = GetSupport(caster, target, portalA.pos, slidTargetInCasterSpace); + //portalB = GetSupport(caster, target, portalB.pos, slidTargetInCasterSpace); + //portalC = GetSupport(caster, target, portalC.pos, slidTargetInCasterSpace); + + //UnityEngine.Debug.Log($"Corrected portal: A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + + // Catch the case where the ray misses the portal triangle. It is frustrating that it happens, but this should catch it. + Mpr.DoMprPortalSearchDebug(caster, target, slidTargetInCasterSpace, normalizedCastDirectionInCasterSpace, ref portalA, ref portalB, ref portalC, + ref somethingWentWrong); + + UnityEngine.Debug.Log($"Post-search portal: A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + + float mprDistance = Mpr.DoMprRefine3DDebug(caster, + target, + slidTargetInCasterSpace, + normalizedCastDirectionInCasterSpace, + portalA, + portalB, + portalC, + ref somethingWentWrong); + + UnityEngine.Debug.Log($"mprDistance: {mprDistance}"); + + distanceOfImpact = slideDistance - mprDistance; + return distanceOfImpact <= maxCastDistance; + } + + private static class Mpr + { + internal const float k_missDistance = 2f; + + internal static void DoMprPortalSearch(in Collider colliderA, + in Collider colliderB, + in RigidTransform bInASpace, + float3 normalizedSearchDirectionInASpace, + ref SupportPoint portalA, + ref SupportPoint portalB, + ref SupportPoint portalC, + ref bool somethingWentWrong) + { + int iters = 100; + while (iters > 0) + { + iters--; + + simdFloat3 portalVerts = new simdFloat3(portalA.pos, portalB.pos, portalC.pos, portalA.pos); + simdFloat3 normals = simd.cross(portalVerts, portalVerts.bcad); + normals = simd.select(normals, -normals, simd.dot(normals, portalVerts.cabd) < 0f); + bool4 isWrongSide = simd.dot(normals, normalizedSearchDirectionInASpace) < 0f; + if (isWrongSide.x) + { + portalC = GetSupport(in colliderA, in colliderB, -normals.a, bInASpace); + } + else if (isWrongSide.y) + { + portalA = GetSupport(in colliderA, in colliderB, -normals.b, bInASpace); + } + else if (isWrongSide.z) + { + portalB = GetSupport(in colliderA, in colliderB, -normals.c, bInASpace); + } + else + { + return; + } + if (portalA.id == portalB.id || portalA.id == portalC.id || portalB.id == portalC.id) + { + break; + } + } + + somethingWentWrong |= true; + } + + internal static float DoMprRefine3D(in Collider colliderA, + in Collider colliderB, + in RigidTransform bInASpace, + float3 normalizedSearchDirectionInASpace, + SupportPoint portalA, + SupportPoint portalB, + SupportPoint portalC, + ref bool somethingWentWrong) + { + const float k_smallNormal = 1e-4f; + const float k_normalScaler = 1000f; + + // Triangles OAB, OBC, and OCA should surround the ray. + // Triangle ABC is a portal that the ray passes through. + // The triangle ABC normal should point aligned with the ray, facing the opposite of the origin + float3 portalUnscaledNormal = math.cross(portalB.pos - portalA.pos, portalC.pos - portalB.pos); + if (math.all(portalUnscaledNormal == 0f)) + portalUnscaledNormal = normalizedSearchDirectionInASpace; + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, normalizedSearchDirectionInASpace) < 0f); + // If our normal gets small, scale it up. + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + + var replacedPoint = portalA; + int iters = 100; + while (iters > 0) + { + iters--; + + // Find a new support out through the portal. + SupportPoint newSupport = GetSupport(in colliderA, in colliderB, portalUnscaledNormal, bInASpace); + + // If the new support is actually one of our portal supports, then terminate. + uint3 ids = new uint3(portalA.id, portalB.id, portalC.id); + if (math.any(newSupport.id == ids)) + { + break; + } + + // This new support creates three new triangles connecting to the origin and each of the portal vertices. + simdFloat3 portalVerts = new simdFloat3(portalA.pos, portalB.pos, portalC.pos, portalA.pos); + simdFloat3 newPlanes = simd.cross(portalVerts, newSupport.pos); + newPlanes = simd.select(newPlanes, newPlanes * k_normalScaler, simd.cmaxxyz(simd.abs(newPlanes)) < k_smallNormal); + // Align the new planes to point towards the next portal vertex + newPlanes = simd.select(newPlanes, -newPlanes, simd.dot(newPlanes, portalVerts.bcaa) < 0f); + // Find which side of each plane the ray is on + bool4 pointsTowardsRay = simd.dot(newPlanes, normalizedSearchDirectionInASpace) >= 0f; + // It is possible that our new support could be outside the portal. So do the backward case as well. + newPlanes = simd.select(newPlanes, -newPlanes, simd.dot(newPlanes, portalVerts.caba) < 0f); + bool4 pointsTowardsRayBackwards = simd.dot(newPlanes, normalizedSearchDirectionInASpace) >= 0f; + // Split up exclusion zones such that those in front of A and behind B exclude C, and likewise forward + bool4 isInZone = (pointsTowardsRay & !pointsTowardsRay.yzxw) | (pointsTowardsRayBackwards.yzxw & !pointsTowardsRayBackwards); + + // Prevent ping-ponging due to the ray being on the border of the portal. + bool terminateAfterAssign = newSupport.id == replacedPoint.id; + // Update our portal triangle + if (isInZone.x) + { + replacedPoint = portalC; + portalC = newSupport; + } + else if (isInZone.y) + { + replacedPoint = portalA; + portalA = newSupport; + } + else if (isInZone.z) + { + replacedPoint = portalB; + portalB = newSupport; + } + else + { + // Our portal has degenerated or we have a new support really close to the ray + // We either got three planes pointing towards the ray or three planes pointing against it. + // In either case, replace with the point least aligned to the ray + float3 alignment = simd.dot(portalVerts, normalizedSearchDirectionInASpace).xyz; + float min = math.cmin(alignment); + if (alignment.x == min) + { + replacedPoint = portalA; + portalA = newSupport; + } + else if (alignment.y == min) + { + replacedPoint = portalB; + portalB = newSupport; + } + else + { + replacedPoint = portalC; + portalC = newSupport; + } + } + + if (terminateAfterAssign) + { + break; + } + + // Check that we didn't make backward progress + /*if (math.dot(newSupport.pos, normalizedSearchDirectionInASpace) < math.dot(replacedPoint.pos, normalizedSearchDirectionInASpace)) + { + float oldTrianglePerimeter = math.csum(simd.length(portalVerts - portalVerts.bcad)); + float newTrianglePerimeter = math.distance(portalA.pos, portalB.pos) + math.distance(portalB.pos, portalC.pos) + math.distance(portalC.pos, portalA.pos); + if (newTrianglePerimeter > oldTrianglePerimeter) + { + // The simplex went the wrong way. Undo and exit + if (isInZone.x) + { + portalC = replacedPoint; + } + else if (isInZone.y) + { + portalA = replacedPoint; + } + else if (isInZone.z) + { + portalB = replacedPoint; + } + else + { + if (degenerateRestoreTarget == 0) + portalA = replacedPoint; + else if (degenerateRestoreTarget == 1) + portalB = replacedPoint; + else + portalC = replacedPoint; + } + break; + } + }*/ + + // Update the portal's normal + var pendingNormal = math.cross(portalB.pos - portalA.pos, portalC.pos - portalB.pos); + portalUnscaledNormal = math.select(pendingNormal, portalUnscaledNormal, math.all(pendingNormal == 0f)); + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, normalizedSearchDirectionInASpace) < 0f); + // If our normal gets small, scale it up. + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + } + if (iters <= 0) + { + somethingWentWrong |= true; + } + + // Our portal is now the surface triangle of the CSO our ray passes through. + // Find the distance to the plane of the portal and return. + // We don't use triangle raycast here because precision issues could cause our ray to miss. + Plane plane = mathex.PlaneFrom(portalA.pos, portalB.pos - portalA.pos, portalC.pos - portalA.pos); + float denom = math.dot(plane.normal, normalizedSearchDirectionInASpace); + if (math.abs(denom) < math.EPSILON) + { + // The triangle is coplanar with the ray. + + if (math.all(plane.normal == 0f)) + { + // Our triangle is a line segment. Find the extreme points. + float distAB = math.distancesq(portalA.pos, portalB.pos); + float distBC = math.distancesq(portalB.pos, portalC.pos); + float distCA = math.distancesq(portalC.pos, portalA.pos); + int winner = math.select(0, 1, distBC > distAB); + float bestDist = math.max(distAB, distBC); + winner = math.select(winner, 2, distCA > bestDist); + float3 targetA = default; + float3 targetB = default; + switch (winner) + { + case 0: + targetA = portalA.pos; + targetB = portalB.pos; + break; + case 1: + targetA = portalB.pos; + targetB = portalC.pos; + break; + case 2: + targetA = portalC.pos; + targetB = portalA.pos; + break; + } + float3 farthestAway = math.select(targetA, targetB, math.lengthsq(targetB) > math.lengthsq(targetA)); + float3 rayExtents = normalizedSearchDirectionInASpace * math.dot(farthestAway, normalizedSearchDirectionInASpace) * 2f; + QueriesLowLevelUtils.SegmentSegment(0f, rayExtents, targetA, targetB - targetA, out var closestA, out _); + return math.length(closestA); + } + // We have a real triangle with a real normal + simdFloat3 triangle = new simdFloat3(portalA.pos, portalB.pos, portalC.pos, portalC.pos); + simdFloat3 posPoints = triangle + plane.normal; + simdFloat3 negPoints = triangle - plane.normal; + float rayLength = math.cmax(simd.length(triangle)) * 2f; + float3 rayStart = rayLength * normalizedSearchDirectionInASpace; + Ray ray = new Ray(rayStart, 0f); + simdFloat3 quadAB = simd.shuffle(negPoints, + posPoints, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY, + math.ShuffleComponent.LeftY); + simdFloat3 quadBC = simd.shuffle(negPoints, + posPoints, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightY, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.LeftZ); + simdFloat3 quadCA = simd.shuffle(negPoints, + posPoints, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.RightX); + bool3 hit = default; + float3 fractions = default; + hit.x = SpatialInternal.RaycastQuad(ray, quadAB, out fractions.x); + hit.y = SpatialInternal.RaycastQuad(ray, quadBC, out fractions.y); + hit.z = SpatialInternal.RaycastQuad(ray, quadCA, out fractions.z); + fractions = math.select(float.MaxValue, fractions, hit); + return (1f - math.cmin(fractions)) * rayLength; + } + return math.abs(plane.distanceFromOrigin / denom); + } + + internal static bool DoPlanarMpr(in Collider colliderA, + in Collider colliderB, + in RigidTransform bInASpace, + float3 planeNormal, + float3 searchStart, + out SupportPoint planarSupportIdA, + out SupportPoint planarSupportIdB, + ref bool somethingWentWrong) + { + const float k_smallNormal = 1e-4f; + const float k_normalScaler = 1000f; + + float3 center = searchStart - math.project(searchStart, planeNormal); + float3 ray = -center; + + SupportPoint portalA = GetPlanarSupport(in colliderA, in colliderB, -center, bInASpace, planeNormal); + // We are sliding everything such that (0, 0, 0) is the ray start and the ray is the original origin + portalA.pos += ray; + planarSupportIdA = portalA; + float3 normalizedPortalA = math.normalizesafe(portalA.pos); + float3 normalizedRay = math.normalize(ray); + float3 projectionPoint = math.dot(normalizedPortalA, normalizedRay) * normalizedRay; + float3 searchDirection = projectionPoint - normalizedPortalA; + // Our initial support could actually be aligned to the ray, in which case our ray is the zero vector, which gets normalized to NaN + // We catch that here and force the next condition to be true to handle it. + if (math.all(ray == 0f)) + searchDirection = 0f; + + // If we don't have a search direction, then our first support is the vertex aligned along the ray (rare). + // But more common is that precision issues cause the search direction to be an imperfect scale of the ray. + // That's the same issue but a lot harder to catch. So we compare it to the orthogonal to catch it, since + // the searchDirection should always be orthogonal to the ray anyways. + if (math.all(searchDirection == 0f) || math.abs(math.dot(searchDirection, ray)) >= math.abs(math.dot(searchDirection, math.cross(ray, planeNormal)))) + { + if (math.lengthsq(portalA.pos) >= math.lengthsq(ray)) + { + if (math.all(searchDirection == 0f)) + { + mathex.GetDualPerpendicularNormalized(planeNormal, out var dirA, out var dirB); + planarSupportIdA = GetPlanarSupport(in colliderA, in colliderB, dirA, bInASpace, planeNormal); + planarSupportIdB = GetPlanarSupport(in colliderA, in colliderB, dirB, bInASpace, planeNormal); + } + else + { + // We still need a second planar support, so just find a cross product and test both directions + searchDirection = math.cross(searchDirection, planeNormal); + planarSupportIdB = GetPlanarSupport(in colliderA, in colliderB, searchDirection, bInASpace, planeNormal); + if (planarSupportIdB.id == planarSupportIdA.id) + { + planarSupportIdB = GetPlanarSupport(in colliderA, in colliderB, -searchDirection, bInASpace, planeNormal); + if (planarSupportIdB.id == planarSupportIdA.id) + { + somethingWentWrong |= true; + } + } + } + return true; + } + else + { + planarSupportIdB = default; + return false; + } + } + // If our search direction is really small, scale it up. + searchDirection = math.select(searchDirection, searchDirection * k_normalScaler, math.all(math.abs(searchDirection) < k_smallNormal)); + // Find a new support point orthogonal to our ray away from the first support point + SupportPoint portalB = GetPlanarSupport(in colliderA, in colliderB, searchDirection, bInASpace, planeNormal); + portalB.pos += ray; + planarSupportIdB = portalB; + // Get the portal normal facing away from the center + float3 portalUnscaledNormal = math.cross(portalB.pos - portalA.pos, planeNormal); + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, ray) < 0f); + + // If the segment from the ray endpoint to a portal endpoint is aligned with the portal normal, then the portal is beyond the ray endpoint. + if (math.dot(portalA.pos - ray, portalUnscaledNormal) >= 0f) + return true; + + // Todo: Set a max iterations and assertion to prevent freezes if NaNs happen. + int iters = 100; + while (iters > 0) + { + iters--; + + // Find a new support out through the portal. + SupportPoint newSupport = GetSupport(in colliderA, in colliderB, portalUnscaledNormal, bInASpace); + newSupport.pos += ray; + // If the new support is actually one of our portal supports, then terminate. + if (newSupport.id == portalA.id || newSupport.id == portalB.id) + break; + // Create the split plane + float3 newPlane = math.cross(newSupport.pos, planeNormal); + newPlane = math.select(newPlane, newPlane * k_normalScaler, math.all(math.abs(newPlane) < k_smallNormal)); + // Point the plane towards B + newPlane = math.select(newPlane, -newPlane, math.dot(newPlane, portalB.pos) < 0f); + + // Find which side of the plane the ray is on and replace the opposite portal point + if (math.dot(ray, newPlane) > 0f) + portalA = newSupport; + else + portalB = newSupport; + + // Update the portal's normal + portalUnscaledNormal = math.cross(portalB.pos - portalA.pos, planeNormal); + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, ray) < 0f); + // If the segment from the ray endpoint to a portal endpoint is aligned with the portal normal, then the portal is beyond the ray endpoint. + if (math.dot(portalA.pos - ray, portalUnscaledNormal) >= 0f) + { + planarSupportIdA = portalA; + planarSupportIdB = portalB; + return true; + } + } + if (iters <= 0) + { + somethingWentWrong |= true; + } + + // By this point, we found the portal and our ray endpoint is outside it, which means no collision. + return false; + } + + #region DebugCast + + internal static void DoMprPortalSearchDebug(Collider colliderA, + Collider colliderB, + RigidTransform bInASpace, + float3 normalizedSearchDirectionInASpace, + ref SupportPoint portalA, + ref SupportPoint portalB, + ref SupportPoint portalC, + ref bool somethingWentWrong) + { + UnityEngine.Debug.Log( + $"Enter DoMprPortalSearch, normalizedSearchDirectionInASpace: {normalizedSearchDirectionInASpace} A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + int iters = 100; + while (iters > 0) + { + iters--; + + simdFloat3 portalVerts = new simdFloat3(portalA.pos, portalB.pos, portalC.pos, portalA.pos); + simdFloat3 normals = simd.cross(portalVerts, portalVerts.bcad); + normals = simd.select(normals, -normals, simd.dot(normals, portalVerts.cabd) < 0f); + bool4 isWrongSide = simd.dot(normals, normalizedSearchDirectionInASpace) < 0f; + if (isWrongSide.x) + { + portalC = GetSupport(colliderA, colliderB, -normals.a, bInASpace); + } + else if (isWrongSide.y) + { + portalA = GetSupport(colliderA, colliderB, -normals.b, bInASpace); + } + else if (isWrongSide.z) + { + portalB = GetSupport(colliderA, colliderB, -normals.c, bInASpace); + } + else + { + UnityEngine.Debug.Log($"normals: {normals.a}, {normals.b}, {normals.c}, dots: {simd.dot(normals, normalizedSearchDirectionInASpace)}"); + UnityEngine.Debug.Log("DoMprPortalSearch exited normally."); + return; + } + UnityEngine.Debug.Log( + $"iters: {iters}, isWrongSide: {isWrongSide}, A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + if (portalA.id == portalB.id || portalA.id == portalC.id || portalB.id == portalC.id) + { + UnityEngine.Debug.Log("Portal degenerated."); + break; + } + } + UnityEngine.Debug.Log("DoMprPortalSearch exited from too many iterations or portal degeneration."); + somethingWentWrong |= true; + } + + internal static float DoMprRefine3DDebug(Collider colliderA, + Collider colliderB, + RigidTransform bInASpace, + float3 normalizedSearchDirectionInASpace, + SupportPoint portalA, + SupportPoint portalB, + SupportPoint portalC, + ref bool somethingWentWrong) + { + const float k_smallNormal = 1e-4f; + const float k_normalScaler = 1000f; + + UnityEngine.Debug.Log( + $"Entering DoMprRefine3D. normalizedSearchDirectionInASpace: {normalizedSearchDirectionInASpace}, somethingWentWrong: {somethingWentWrong}"); + + // Triangles OAB, OBC, and OCA should surround the ray. + // Triangle ABC is a portal that the ray passes through. + // The triangle ABC normal should point aligned with the ray, facing the opposite of the origin + float3 portalUnscaledNormal = math.cross(portalB.pos - portalA.pos, portalC.pos - portalB.pos); + if (math.all(portalUnscaledNormal == 0f)) + portalUnscaledNormal = normalizedSearchDirectionInASpace; + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, normalizedSearchDirectionInASpace) < 0f); + // If our normal gets small, scale it up. + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + + var replacedPoint = portalA; + int iters = 100; + while (iters > 0) + { + iters--; + + // Find a new support out through the portal. + SupportPoint newSupport = GetSupport(colliderA, colliderB, portalUnscaledNormal, bInASpace); + + UnityEngine.Debug.Log($"iter: {iters}, portalUnscaledNormal: {portalUnscaledNormal}"); + UnityEngine.Debug.Log( + $"newSupport: {newSupport.pos} - {newSupport.id}, A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + + // If the new support is actually one of our portal supports, then terminate. + uint3 ids = new uint3(portalA.id, portalB.id, portalC.id); + if (math.any(newSupport.id == ids)) + { + UnityEngine.Debug.Log("New support matches portal support. Exiting loop."); + break; + } + + // This new support creates three new triangles connecting to the origin and each of the portal vertices. + simdFloat3 portalVerts = new simdFloat3(portalA.pos, portalB.pos, portalC.pos, portalA.pos); + simdFloat3 newPlanes = simd.cross(portalVerts, newSupport.pos); + newPlanes = simd.select(newPlanes, newPlanes * k_normalScaler, simd.cmaxxyz(simd.abs(newPlanes)) < k_smallNormal); + // Align the new planes to point towards the next portal vertex + newPlanes = simd.select(newPlanes, -newPlanes, simd.dot(newPlanes, portalVerts.bcaa) < 0f); + // Find which side of each plane the ray is on + bool4 pointsTowardsRay = simd.dot(newPlanes, normalizedSearchDirectionInASpace) >= 0f; + // It is possible that our new support could be outside the portal. So do the backward case as well. + newPlanes = simd.select(newPlanes, -newPlanes, simd.dot(newPlanes, portalVerts.caba) < 0f); + bool4 pointsTowardsRayBackwards = simd.dot(newPlanes, normalizedSearchDirectionInASpace) >= 0f; + // Split up exclusion zones such that those in front of A and behind B exclude C, and likewise forward + bool4 isInZone = (pointsTowardsRay & !pointsTowardsRay.yzxw) | (pointsTowardsRayBackwards.yzxw & !pointsTowardsRayBackwards); + + // Prevent ping-ponging due to the ray being on the border of the portal. + bool terminateAfterAssign = newSupport.id == replacedPoint.id; + // Update our portal triangle + if (isInZone.x) + { + replacedPoint = portalC; + portalC = newSupport; + } + else if (isInZone.y) + { + replacedPoint = portalA; + portalA = newSupport; + } + else if (isInZone.z) + { + replacedPoint = portalB; + portalB = newSupport; + } + else + { + // Our portal has degenerated or we have a new support really close to the ray + // We either got three planes pointing towards the ray or three planes pointing against it. + // In either case, just pick an arbitrary point to replace. + UnityEngine.Debug.Log($"Degenerated portal. pointsTowardsRay: {pointsTowardsRay}, isInZone: {isInZone}"); + + // Replace with the point least aligned to the ray + float3 alignment = simd.dot(portalVerts, normalizedSearchDirectionInASpace).xyz; + float min = math.cmin(alignment); + if (alignment.x == min) + { + replacedPoint = portalA; + portalA = newSupport; + } + else if (alignment.y == min) + { + replacedPoint = portalB; + portalB = newSupport; + } + else + { + replacedPoint = portalC; + portalC = newSupport; + } + } + + if (terminateAfterAssign) + { + UnityEngine.Debug.Log("Exiting loop due to ping-pong"); + break; + } + + // Check that we didn't make backward progress + /*if (math.dot(newSupport.pos, normalizedSearchDirectionInASpace) < math.dot(replacedPoint.pos, normalizedSearchDirectionInASpace)) + { + float oldTrianglePerimeter = math.csum(simd.length(portalVerts - portalVerts.bcad)); + float newTrianglePerimeter = math.distance(portalA.pos, portalB.pos) + math.distance(portalB.pos, portalC.pos) + math.distance(portalC.pos, portalA.pos); + if (newTrianglePerimeter > oldTrianglePerimeter) + { + // The simplex went the wrong way. Undo and exit + UnityEngine.Debug.Log("Inverted simplex. Restoring point and exiting loop."); + UnityEngine.Debug.Log($"isInZone: {isInZone}, oldTrianglePerimeter: {oldTrianglePerimeter}, newTrianglePerimeter: {newTrianglePerimeter}"); + if (isInZone.x) + { + portalC = replacedPoint; + } + else if (isInZone.y) + { + portalA = replacedPoint; + } + else if (isInZone.z) + { + portalB = replacedPoint; + } + else + { + if (degenerateRestoreTarget == 0) + portalA = replacedPoint; + else if (degenerateRestoreTarget == 1) + portalB = replacedPoint; + else + portalC = replacedPoint; + } + break; + } + }*/ + + // Update the portal's normal + var pendingNormal = math.cross(portalB.pos - portalA.pos, portalC.pos - portalB.pos); + portalUnscaledNormal = math.select(pendingNormal, portalUnscaledNormal, math.all(pendingNormal == 0f)); + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, normalizedSearchDirectionInASpace) < 0f); + // If our normal gets small, scale it up. + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + } + if (iters <= 0) + { + UnityEngine.Debug.Log("Exhausted iterations in 3D refining portal"); + somethingWentWrong |= true; + } + + UnityEngine.Debug.Log($"Portal after loop. A: {portalA.pos}, {portalA.id}, B: {portalB.pos}, {portalB.id}, C: {portalC.pos}, {portalC.id}"); + + // Our portal is now the surface triangle of the CSO our ray passes through. + // Find the distance to the plane of the portal and return. + // We don't use triangle raycast here because precision issues could cause our ray to miss. + Plane plane = mathex.PlaneFrom(portalA.pos, portalB.pos - portalA.pos, portalC.pos - portalA.pos); + float denom = math.dot(plane.normal, normalizedSearchDirectionInASpace); + UnityEngine.Debug.Log($"plane: {plane.normal}, {plane.distanceFromOrigin}, denom: {denom}"); + if (math.abs(denom) < math.EPSILON) + { + // The triangle is coplanar with the ray. + + if (math.all(plane.normal == 0f)) + { + // Our triangle is a line segment. Find the extreme points. + float distAB = math.distancesq(portalA.pos, portalB.pos); + float distBC = math.distancesq(portalB.pos, portalC.pos); + float distCA = math.distancesq(portalC.pos, portalA.pos); + int winner = math.select(0, 1, distBC > distAB); + float bestDist = math.max(distAB, distBC); + winner = math.select(winner, 2, distCA > bestDist); + float3 targetA = default; + float3 targetB = default; + switch (winner) + { + case 0: + targetA = portalA.pos; + targetB = portalB.pos; + break; + case 1: + targetA = portalB.pos; + targetB = portalC.pos; + break; + case 2: + targetA = portalC.pos; + targetB = portalA.pos; + break; + } + float3 farthestAway = math.select(targetA, targetB, math.lengthsq(targetB) > math.lengthsq(targetA)); + float3 rayExtents = normalizedSearchDirectionInASpace * math.dot(farthestAway, normalizedSearchDirectionInASpace) * 2f; + QueriesLowLevelUtils.SegmentSegment(0f, rayExtents, targetA, targetB - targetA, out var closestA, out _); + UnityEngine.Debug.Log($"Coplanar portal is line. winner: {winner}, closestA: {closestA}"); + return math.length(closestA); + } + // We have a real triangle with a real normal + simdFloat3 triangle = new simdFloat3(portalA.pos, portalB.pos, portalC.pos, portalC.pos); + simdFloat3 posPoints = triangle + plane.normal; + simdFloat3 negPoints = triangle - plane.normal; + float rayLength = math.cmax(simd.length(triangle)) * 2f; + float3 rayStart = rayLength * normalizedSearchDirectionInASpace; + Ray ray = new Ray(rayStart, 0f); + simdFloat3 quadAB = simd.shuffle(negPoints, + posPoints, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY, + math.ShuffleComponent.LeftY); + simdFloat3 quadBC = simd.shuffle(negPoints, + posPoints, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightY, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.LeftZ); + simdFloat3 quadCA = simd.shuffle(negPoints, + posPoints, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.RightX); + bool3 hit = default; + float3 fractions = default; + hit.x = SpatialInternal.RaycastQuad(ray, quadAB, out fractions.x); + hit.y = SpatialInternal.RaycastQuad(ray, quadBC, out fractions.y); + hit.z = SpatialInternal.RaycastQuad(ray, quadCA, out fractions.z); + fractions = math.select(float.MaxValue, fractions, hit); + UnityEngine.Debug.Log("Coplanar portal is triangle. hit: {hit}"); + return (1f - math.cmin(fractions)) * rayLength; + } + return math.abs(plane.distanceFromOrigin / denom); + } + + internal static bool DoPlanarMprDebug(Collider colliderA, + Collider colliderB, + RigidTransform bInASpace, + float3 planeNormal, + float3 searchStart, + out SupportPoint planarSupportIdA, + out SupportPoint planarSupportIdB, + ref bool somethingWentWrong) + { + const float k_smallNormal = 1e-4f; + const float k_normalScaler = 1000f; + + float3 center = searchStart - math.project(searchStart, planeNormal); + float3 ray = -center; + + SupportPoint portalA = GetPlanarSupport(colliderA, colliderB, -center, bInASpace, planeNormal); + // We are sliding everything such that (0, 0, 0) is the ray start and the ray is the original origin + portalA.pos += ray; + planarSupportIdA = portalA; + float3 normalizedPortalA = math.normalizesafe(portalA.pos); + float3 normalizedRay = math.normalize(ray); + float3 projectionPoint = math.dot(normalizedPortalA, normalizedRay) * normalizedRay; + float3 searchDirection = projectionPoint - normalizedPortalA; + // Our initial support could actually be aligned to the ray, in which case our ray is the zero vector, which gets normalized to NaN + // We catch that here and force the next condition to be true to handle it. + if (math.all(ray == 0f)) + searchDirection = 0f; + + UnityEngine.Debug.Log( + $"Entering DoPlanarMpr. planeNormal: {planeNormal}, searchStart: {searchStart}, ray: {ray}, normalizedRay: {normalizedRay}, portalA: {portalA.pos}, {portalA.id}, searchDirection: {searchDirection}"); + // If we don't have a search direction, then our first support is the vertex aligned along the ray (rare). + // But more common is that precision issues cause the search direction to be an imperfect scale of the ray. + // That's the same issue but a lot harder to catch. So we compare it to the orthogonal to catch it, since + // the searchDirection should always be orthogonal to the ray anyways. + if (math.all(searchDirection == 0f) || math.abs(math.dot(searchDirection, ray)) >= math.abs(math.dot(searchDirection, math.cross(ray, planeNormal)))) + { + if (math.lengthsq(portalA.pos) >= math.lengthsq(ray)) + { + if (math.all(searchDirection == 0f)) + { + UnityEngine.Debug.Log("Search direction is 0. Getting both planar supports and exiting."); + mathex.GetDualPerpendicularNormalized(planeNormal, out var dirA, out var dirB); + planarSupportIdA = GetPlanarSupport(colliderA, colliderB, dirA, bInASpace, planeNormal); + planarSupportIdB = GetPlanarSupport(colliderA, colliderB, dirB, bInASpace, planeNormal); + } + else + { + UnityEngine.Debug.Log("Support aligned with ray and is beyond the ray. Getting second planar support and exiting."); + // We still need a second planar support, so just find a cross product and test both directions + searchDirection = math.cross(searchDirection, planeNormal); + planarSupportIdB = GetPlanarSupport(colliderA, colliderB, searchDirection, bInASpace, planeNormal); + if (planarSupportIdB.id == planarSupportIdA.id) + { + planarSupportIdB = GetPlanarSupport(colliderA, colliderB, -searchDirection, bInASpace, planeNormal); + if (planarSupportIdB.id == planarSupportIdA.id) + { + UnityEngine.Debug.Log("Can't find orthogonal support."); + somethingWentWrong |= true; + } + } + } + return true; + } + else + { + planarSupportIdB = default; + UnityEngine.Debug.Log("Support aligned with ray and ray extends beyond it. Returning a miss."); + return false; + } + } + // If our search direction is really small, scale it up. + searchDirection = math.select(searchDirection, searchDirection * k_normalScaler, math.all(math.abs(searchDirection) < k_smallNormal)); + // Find a new support point orthogonal to our ray away from the first support point + SupportPoint portalB = GetPlanarSupport(colliderA, colliderB, searchDirection, bInASpace, planeNormal); + portalB.pos += ray; + planarSupportIdB = portalB; + // Get the portal normal facing away from the center + float3 portalUnscaledNormal = math.cross(portalB.pos - portalA.pos, planeNormal); + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, ray) < 0f); + + UnityEngine.Debug.Log($"ray: {ray}, searchDirection: {searchDirection}, planarA: {portalA.pos}, {portalA.id}, planarB: {portalB.pos}, {portalB.id}"); + + // If the segment from the ray endpoint to a portal endpoint is aligned with the portal normal, then the portal is beyond the ray endpoint. + if (math.dot(portalA.pos - ray, portalUnscaledNormal) >= 0f) + return true; + + // Todo: Set a max iterations and assertion to prevent freezes if NaNs happen. + int iters = 100; + while (iters > 0) + { + iters--; + + // Find a new support out through the portal. + SupportPoint newSupport = GetSupport(colliderA, colliderB, portalUnscaledNormal, bInASpace); + newSupport.pos += ray; + // If the new support is actually one of our portal supports, then terminate. + if (newSupport.id == portalA.id || newSupport.id == portalB.id) + break; + // Create the split plane + float3 newPlane = math.cross(newSupport.pos, planeNormal); + newPlane = math.select(newPlane, newPlane * k_normalScaler, math.all(math.abs(newPlane) < k_smallNormal)); + // Point the plane towards B + newPlane = math.select(newPlane, -newPlane, math.dot(newPlane, portalB.pos) < 0f); + + UnityEngine.Debug.Log( + $"iter: {iters}, newSupport: {newSupport.pos}, {newSupport.id}, planarA: {portalA.pos}, {portalA.id}, planarB: {portalB.pos}, {portalB.id}, newPlane: {newPlane}"); + + // Find which side of the plane the ray is on and replace the opposite portal point + if (math.dot(ray, newPlane) > 0f) + portalA = newSupport; + else + portalB = newSupport; + + // Update the portal's normal + portalUnscaledNormal = math.cross(portalB.pos - portalA.pos, planeNormal); + portalUnscaledNormal = math.select(portalUnscaledNormal, portalUnscaledNormal * k_normalScaler, math.all(math.abs(portalUnscaledNormal) < k_smallNormal)); + portalUnscaledNormal = math.select(portalUnscaledNormal, -portalUnscaledNormal, math.dot(portalUnscaledNormal, ray) < 0f); + // If the segment from the ray endpoint to a portal endpoint is aligned with the portal normal, then the portal is beyond the ray endpoint. + if (math.dot(portalA.pos - ray, portalUnscaledNormal) >= 0f) + { + planarSupportIdA = portalA; + planarSupportIdB = portalB; + UnityEngine.Debug.Log("Found hit. Returning."); + return true; + } + } + if (iters <= 0) + { + UnityEngine.Debug.Log("Exhausted iterations in 2D"); + somethingWentWrong |= true; + } + + // By this point, we found the portal and our ray endpoint is outside it, which means no collision. + UnityEngine.Debug.Log("No hit found in 2D"); + return false; + } + #endregion + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderCast.LatiosMpr.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderCast.LatiosMpr.cs.meta new file mode 100644 index 0000000..24a1c6d --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderCast.LatiosMpr.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd305a739ee006f43afe8f35b35ccac8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxBox.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxBox.cs new file mode 100644 index 0000000..88a7aff --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxBox.cs @@ -0,0 +1,1131 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + private const float k_boxBoxEpsilon = math.EPSILON; + + public const float k_boxBoxAccuracy = 2e-5f; // Record: 1.120567e-5f + + public static bool BoxBoxDistance(BoxCollider boxA, + BoxCollider boxB, + RigidTransform bInASpace, + RigidTransform aInBSpace, + float maxDistance, + out ColliderDistanceResultInternal result) + { + //Step 1: Points vs faces + simdFloat3 bTopPoints = default; + simdFloat3 bBottomPoints = default; + bTopPoints.x = math.select(-boxB.halfSize.x, boxB.halfSize.x, new bool4(true, true, false, false)); + bBottomPoints.x = bTopPoints.x; + bBottomPoints.y = -boxB.halfSize.y; + bTopPoints.y = boxB.halfSize.y; + bTopPoints.z = math.select(-boxB.halfSize.z, boxB.halfSize.z, new bool4(true, false, true, false)); + bBottomPoints.z = bTopPoints.z; + bTopPoints += boxB.center; + bBottomPoints += boxB.center; + var bTopPointsInAOS = simd.transform(bInASpace, bTopPoints) - boxA.center; //OS = origin space + var bBottomPointsInAOS = simd.transform(bInASpace, bBottomPoints) - boxA.center; + + QueriesLowLevelUtils.OriginAabb8PointsWithEspilonFudge(boxA.halfSize, + bTopPointsInAOS, + bBottomPointsInAOS, + out float3 pointsClosestAInA, + out float3 pointsClosestBInA, + out float pointsAxisDistanceInA); + bool4 bTopMatch = bTopPointsInAOS == pointsClosestBInA; + bool4 bBottomMatch = bBottomPointsInAOS == pointsClosestBInA; + int bInABIndex = math.tzcnt((math.bitmask(bBottomMatch) << 4) | math.bitmask(bTopMatch)); + float pointsDistanceSqInA = math.distancesq(pointsClosestAInA, pointsClosestBInA); + bool isInvalidInA = (pointsAxisDistanceInA < 0f) & math.distance(pointsAxisDistanceInA * pointsAxisDistanceInA, pointsDistanceSqInA) > k_boxBoxEpsilon; + pointsAxisDistanceInA = math.select(pointsAxisDistanceInA, float.MinValue, isInvalidInA); + pointsDistanceSqInA = math.select( pointsDistanceSqInA, float.MaxValue, isInvalidInA); + + simdFloat3 aTopPoints = default; + simdFloat3 aBottomPoints = default; + aTopPoints.x = math.select(-boxA.halfSize.x, boxA.halfSize.x, new bool4(true, true, false, false)); + aBottomPoints.x = aTopPoints.x; + aBottomPoints.y = -boxA.halfSize.y; + aTopPoints.y = boxA.halfSize.y; + aTopPoints.z = math.select(-boxA.halfSize.z, boxA.halfSize.z, new bool4(true, false, true, false)); + aBottomPoints.z = aTopPoints.z; + aTopPoints += boxA.center; + aBottomPoints += boxA.center; + var aTopPointsInBOS = simd.transform(aInBSpace, aTopPoints) - boxB.center; + var aBottomPointsInBOS = simd.transform(aInBSpace, aBottomPoints) - boxB.center; + + QueriesLowLevelUtils.OriginAabb8PointsWithEspilonFudge(boxB.halfSize, + aTopPointsInBOS, + aBottomPointsInBOS, + out float3 pointsClosestBInB, + out float3 pointsClosestAInB, + out float pointsAxisDistanceInB); + bool4 aTopMatch = aTopPointsInBOS == pointsClosestAInB; + bool4 aBottomMatch = aBottomPointsInBOS == pointsClosestAInB; + int aInBAIndex = math.tzcnt((math.bitmask(aBottomMatch) << 4) | math.bitmask(aTopMatch)); + float pointsDistanceSqInB = math.distancesq(pointsClosestAInB, pointsClosestBInB); + bool isInvalidInB = (pointsAxisDistanceInB < 0f) & math.distance(pointsAxisDistanceInB * pointsAxisDistanceInB, pointsDistanceSqInB) > k_boxBoxEpsilon; + pointsAxisDistanceInB = math.select(pointsAxisDistanceInB, float.MinValue, isInvalidInB); + pointsDistanceSqInB = math.select(pointsDistanceSqInB, float.MaxValue, isInvalidInB); + + //Step 2: Edges vs edges + + //For any pair of normals, if the normals are colinear, then there must also exist a point-face pair that is equidistant. + //However, for a pair of normals, up to two edges from each box can be valid. + //For box A, assemble the points and edges procedurally using the box dimensions. + //For box B, use a simd dot product and mask it against the best result. The first 1 index and last 1 index are taken. In most cases, these are the same, which is fine. + //It is also worth noting that unlike a true SAT, directionality matters here, so we want to find the separating axis directionally oriented from a to b to get the correct closest features. + //That's the max dot for a and the min dot for b. + float3 bCenterInASpace = math.transform(bInASpace, boxB.center) - boxA.center; + simdFloat3 faceNormalsBoxA = new simdFloat3(new float3(1f, 0f, 0f), new float3(0f, 1f, 0f), new float3(0f, 0f, 1f), new float3(1f, 0f, 0f)); + simdFloat3 faceNormalsBoxB = simd.mul(bInASpace.rot, faceNormalsBoxA); + simdFloat3 edgeAxes03 = simd.cross(faceNormalsBoxA.aaab, faceNormalsBoxB); //normalsB is already .abca + simdFloat3 edgeAxes47 = simd.cross(faceNormalsBoxA.bbcc, faceNormalsBoxB.bcab); + float3 edgeAxes8 = math.cross(faceNormalsBoxA.c, faceNormalsBoxB.c); + edgeAxes03 = simd.select(-edgeAxes03, edgeAxes03, simd.dot(edgeAxes03, bCenterInASpace) >= 0f); + edgeAxes47 = simd.select(-edgeAxes47, edgeAxes47, simd.dot(edgeAxes47, bCenterInASpace) >= 0f); + edgeAxes8 = math.select(-edgeAxes8, edgeAxes8, math.dot(edgeAxes8, bCenterInASpace) >= 0f); + bool4 edgeInvalids03 = edgeAxes03 == 0f; + bool4 edgeInvalids47 = edgeAxes47 == 0f; + bool edgeInvalids8 = edgeAxes8.Equals(float3.zero); + simdFloat3 bLeftPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW); + simdFloat3 bRightPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY); + simdFloat3 bFrontPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.LeftW, + math.ShuffleComponent.RightY, + math.ShuffleComponent.RightW); + simdFloat3 bBackPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightZ); + simdFloat3 bNormalsX = new simdFloat3(new float3(0f, math.SQRT2 / 2f, math.SQRT2 / 2f), + new float3(0f, math.SQRT2 / 2f, -math.SQRT2 / 2f), + new float3(0f, -math.SQRT2 / 2f, math.SQRT2 / 2f), + new float3(0f, -math.SQRT2 / 2f, -math.SQRT2 / 2f)); + simdFloat3 bNormalsY = new simdFloat3(new float3(math.SQRT2 / 2f, 0f, math.SQRT2 / 2f), + new float3(math.SQRT2 / 2f, 0f, -math.SQRT2 / 2f), + new float3(-math.SQRT2 / 2f, 0f, math.SQRT2 / 2f), + new float3(-math.SQRT2 / 2f, 0f, -math.SQRT2 / 2f)); + simdFloat3 bNormalsZ = new simdFloat3(new float3(math.SQRT2 / 2f, math.SQRT2 / 2f, 0f), + new float3(-math.SQRT2 / 2f, math.SQRT2 / 2f, 0f), + new float3(math.SQRT2 / 2f, -math.SQRT2 / 2f, 0f), + new float3(-math.SQRT2 / 2f, -math.SQRT2 / 2f, 0f)); + float3 bCenterPlaneDistancesInA = simd.dot(bCenterInASpace, faceNormalsBoxB).xyz; + + //x vs x + float3 axisXX = edgeAxes03.a; + float3 aXX = math.select(-boxA.halfSize, boxA.halfSize, axisXX > 0f); + float3 aExtraXX = math.select(aXX, boxA.halfSize, axisXX == 0f); + aExtraXX.x = -boxA.halfSize.x; + simdFloat3 aPointsXX = new simdFloat3(aXX, aXX, aExtraXX, aExtraXX); + simdFloat3 aEdgesXX = new simdFloat3(new float3(2f * boxA.halfSize.x, 0f, 0f)); + + var dotsXX = simd.dot(bLeftPointsInAOS, axisXX); + float bestDotXX = math.cmin(dotsXX); + int dotsMaskXX = math.bitmask(dotsXX == bestDotXX); + math.ShuffleComponent bIndexXX = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskXX), 0, 3); + math.ShuffleComponent bExtraIndexXX = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskXX) - 28), 0, 3); + simdFloat3 bPointsXX = simd.shuffle(bLeftPointsInAOS, bLeftPointsInAOS, bIndexXX, bExtraIndexXX, bIndexXX, bExtraIndexXX); + simdFloat3 bEdgesXX = simd.shuffle(bRightPointsInAOS, bRightPointsInAOS, bIndexXX, bExtraIndexXX, bIndexXX, bExtraIndexXX) - bPointsXX; + simdFloat3 bNormalsXX = simd.shuffle(bNormalsX, bNormalsX, bIndexXX, bExtraIndexXX, bIndexXX, bExtraIndexXX); + var isInvalidXX = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsXX, + aEdgesXX, + bPointsXX, + bEdgesXX, + out simdFloat3 closestAsXX, + out simdFloat3 closestBsXX); + var projectionXX = simd.project(closestBsXX - closestAsXX, axisXX); + isInvalidXX |= edgeInvalids03.x | !simd.isfiniteallxyz(projectionXX); + var signedAxisDistancesSqXX = simd.lengthsq(projectionXX) * math.sign(simd.dot(projectionXX, axisXX)); + var distancesSqXX = simd.distancesq(closestAsXX, closestBsXX); + isInvalidXX |= (signedAxisDistancesSqXX < 0f) & math.distance(math.abs(signedAxisDistancesSqXX), distancesSqXX) > k_boxBoxEpsilon; + signedAxisDistancesSqXX = math.select(signedAxisDistancesSqXX, float.MinValue, isInvalidXX); + distancesSqXX = math.select(distancesSqXX, float.MaxValue, isInvalidXX); + + //x vs y + float3 axisXY = edgeAxes03.b; + float3 aXY = math.select(-boxA.halfSize, boxA.halfSize, axisXY > 0f); + float3 aExtraXY = math.select(aXY, boxA.halfSize, axisXY == 0f); + aExtraXY.x = -boxA.halfSize.x; + simdFloat3 aPointsXY = new simdFloat3(aXY, aXY, aExtraXY, aExtraXY); + simdFloat3 aEdgesXY = new simdFloat3(new float3(2f * boxA.halfSize.x, 0f, 0f)); + + var dotsXY = simd.dot(bBottomPointsInAOS, axisXY); + float bestDotXY = math.cmin(dotsXY); + int dotsMaskXY = math.bitmask(dotsXY == bestDotXY); + math.ShuffleComponent bIndexXY = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskXY), 0, 3); + math.ShuffleComponent bExtraIndexXY = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskXY) - 28), 0, 3); + simdFloat3 bPointsXY = simd.shuffle(bBottomPointsInAOS, bBottomPointsInAOS, bIndexXY, bExtraIndexXY, bIndexXY, bExtraIndexXY); + simdFloat3 bEdgesXY = simd.shuffle(bTopPointsInAOS, bTopPointsInAOS, bIndexXY, bExtraIndexXY, bIndexXY, bExtraIndexXY) - bPointsXY; + simdFloat3 bNormalsXY = simd.shuffle(bNormalsY, bNormalsY, bIndexXY, bExtraIndexXY, bIndexXY, bExtraIndexXY); + var isInvalidXY = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsXY, + aEdgesXY, + bPointsXY, + bEdgesXY, + out simdFloat3 closestAsXY, + out simdFloat3 closestBsXY); + var projectionXY = simd.project(closestBsXY - closestAsXY, axisXY); + isInvalidXY |= edgeInvalids03.y | !simd.isfiniteallxyz(projectionXY); + var signedAxisDistancesSqXY = simd.lengthsq(projectionXY) * math.sign(simd.dot(projectionXY, axisXY)); + var distancesSqXY = simd.distancesq(closestAsXY, closestBsXY); + isInvalidXY |= (signedAxisDistancesSqXY < 0f) & math.distance(math.abs(signedAxisDistancesSqXY), distancesSqXY) > k_boxBoxEpsilon; + signedAxisDistancesSqXY = math.select(signedAxisDistancesSqXY, float.MinValue, isInvalidXY); + distancesSqXY = math.select(distancesSqXY, float.MaxValue, isInvalidXY); + + //x vs z + float3 axisXZ = edgeAxes03.c; + float3 aXZ = math.select(-boxA.halfSize, boxA.halfSize, axisXZ > 0f); + float3 aExtraXZ = math.select(aXZ, boxA.halfSize, axisXZ == 0f); + aExtraXZ.x = -boxA.halfSize.x; + simdFloat3 aPointsXZ = new simdFloat3(aXZ, aXZ, aExtraXZ, aExtraXZ); + simdFloat3 aEdgesXZ = new simdFloat3(new float3(2f * boxA.halfSize.x, 0f, 0f)); + + var dotsXZ = simd.dot(bFrontPointsInAOS, axisXZ); + float bestDotXZ = math.cmin(dotsXZ); + int dotsMaskXZ = math.bitmask(dotsXZ == bestDotXZ); + math.ShuffleComponent bIndexXZ = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskXZ), 0, 3); + math.ShuffleComponent bExtraIndexXZ = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskXZ) - 28), 0, 3); + simdFloat3 bPointsXZ = simd.shuffle(bFrontPointsInAOS, bFrontPointsInAOS, bIndexXZ, bExtraIndexXZ, bIndexXZ, bExtraIndexXZ); + simdFloat3 bEdgesXZ = simd.shuffle(bBackPointsInAOS, bBackPointsInAOS, bIndexXZ, bExtraIndexXZ, bIndexXZ, bExtraIndexXZ) - bPointsXZ; + simdFloat3 bNormalsXZ = simd.shuffle(bNormalsZ, bNormalsZ, bIndexXZ, bExtraIndexXZ, bIndexXZ, bExtraIndexXZ); + var isInvalidXZ = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsXZ, + aEdgesXZ, + bPointsXZ, + bEdgesXZ, + out simdFloat3 closestAsXZ, + out simdFloat3 closestBsXZ); + var projectionXZ = simd.project(closestBsXZ - closestAsXZ, axisXZ); + isInvalidXZ |= edgeInvalids03.z | !simd.isfiniteallxyz(projectionXZ); + var signedAxisDistancesSqXZ = simd.lengthsq(projectionXZ) * math.sign(simd.dot(projectionXZ, axisXZ)); + var distancesSqXZ = simd.distancesq(closestAsXZ, closestBsXZ); + isInvalidXZ |= (signedAxisDistancesSqXZ < 0f) & math.distance(math.abs(signedAxisDistancesSqXZ), distancesSqXZ) > k_boxBoxEpsilon; + signedAxisDistancesSqXZ = math.select(signedAxisDistancesSqXZ, float.MinValue, isInvalidXZ); + distancesSqXZ = math.select(distancesSqXZ, float.MaxValue, isInvalidXZ); + + //y + //y vs x + float3 axisYX = edgeAxes03.d; + float3 aYX = math.select(-boxA.halfSize, boxA.halfSize, axisYX > 0f); + float3 aExtraYX = math.select(aYX, boxA.halfSize, axisYX == 0f); + aExtraYX.y = -boxA.halfSize.y; + simdFloat3 aPointsYX = new simdFloat3(aYX, aYX, aExtraYX, aExtraYX); + simdFloat3 aEdgesYX = new simdFloat3(new float3(0f, 2f * boxA.halfSize.y, 0f)); + + var dotsYX = simd.dot(bLeftPointsInAOS, axisYX); + float bestDotYX = math.cmin(dotsYX); + int dotsMaskYX = math.bitmask(dotsYX == bestDotYX); + math.ShuffleComponent bIndexYX = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskYX), 0, 3); + math.ShuffleComponent bExtraIndexYX = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskYX) - 28), 0, 3); + simdFloat3 bPointsYX = simd.shuffle(bLeftPointsInAOS, bLeftPointsInAOS, bIndexYX, bExtraIndexYX, bIndexYX, bExtraIndexYX); + simdFloat3 bEdgesYX = simd.shuffle(bRightPointsInAOS, bRightPointsInAOS, bIndexYX, bExtraIndexYX, bIndexYX, bExtraIndexYX) - bPointsYX; + simdFloat3 bNormalsYX = simd.shuffle(bNormalsX, bNormalsX, bIndexYX, bExtraIndexYX, bIndexYX, bExtraIndexYX); + var isInvalidYX = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsYX, + aEdgesYX, + bPointsYX, + bEdgesYX, + out simdFloat3 closestAsYX, + out simdFloat3 closestBsYX); + var projectionYX = simd.project(closestBsYX - closestAsYX, axisYX); + isInvalidYX |= edgeInvalids03.w | !simd.isfiniteallxyz(projectionYX); + var signedAxisDistancesSqYX = simd.lengthsq(projectionYX) * math.sign(simd.dot(projectionYX, axisYX)); + var distancesSqYX = simd.distancesq(closestAsYX, closestBsYX); + isInvalidYX |= (signedAxisDistancesSqYX < 0f) & math.distance(math.abs(signedAxisDistancesSqYX), distancesSqYX) > k_boxBoxEpsilon; + signedAxisDistancesSqYX = math.select(signedAxisDistancesSqYX, float.MinValue, isInvalidYX); + distancesSqYX = math.select(distancesSqYX, float.MaxValue, isInvalidYX); + + //y vs y + float3 axisYY = edgeAxes47.a; + float3 aYY = math.select(-boxA.halfSize, boxA.halfSize, axisYY > 0f); + float3 aExtraYY = math.select(aYY, boxA.halfSize, axisYY == 0f); + aExtraYY.y = -boxA.halfSize.y; + simdFloat3 aPointsYY = new simdFloat3(aYY, aYY, aExtraYY, aExtraYY); + simdFloat3 aEdgesYY = new simdFloat3(new float3(0f, 2f * boxA.halfSize.y, 0f)); + + var dotsYY = simd.dot(bBottomPointsInAOS, axisYY); + float bestDotYY = math.cmin(dotsYY); + int dotsMaskYY = math.bitmask(dotsYY == bestDotYY); + math.ShuffleComponent bIndexYY = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskYY), 0, 3); + math.ShuffleComponent bExtraIndexYY = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskYY) - 28), 0, 3); + simdFloat3 bPointsYY = simd.shuffle(bBottomPointsInAOS, bBottomPointsInAOS, bIndexYY, bExtraIndexYY, bIndexYY, bExtraIndexYY); + simdFloat3 bEdgesYY = simd.shuffle(bTopPointsInAOS, bTopPointsInAOS, bIndexYY, bExtraIndexYY, bIndexYY, bExtraIndexYY) - bPointsYY; + simdFloat3 bNormalsYY = simd.shuffle(bNormalsY, bNormalsY, bIndexYY, bExtraIndexYY, bIndexYY, bExtraIndexYY); + var isInvalidYY = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsYY, + aEdgesYY, + bPointsYY, + bEdgesYY, + out simdFloat3 closestAsYY, + out simdFloat3 closestBsYY); + var projectionYY = simd.project(closestBsYY - closestAsYY, axisYY); + isInvalidYY |= edgeInvalids47.x | !simd.isfiniteallxyz(projectionYY); + var signedAxisDistancesSqYY = simd.lengthsq(projectionYY) * math.sign(simd.dot(projectionYY, axisYY)); + var distancesSqYY = simd.distancesq(closestAsYY, closestBsYY); + isInvalidYY |= (signedAxisDistancesSqYY < 0f) & math.distance(math.abs(signedAxisDistancesSqYY), distancesSqYY) > k_boxBoxEpsilon; + signedAxisDistancesSqYY = math.select(signedAxisDistancesSqYY, float.MinValue, isInvalidYY); + distancesSqYY = math.select(distancesSqYY, float.MaxValue, isInvalidYY); + + //y vs z + float3 axisYZ = edgeAxes47.b; + float3 aYZ = math.select(-boxA.halfSize, boxA.halfSize, axisYZ > 0f); + float3 aExtraYZ = math.select(aYZ, boxA.halfSize, axisYZ == 0f); + aExtraYZ.y = -boxA.halfSize.y; + simdFloat3 aPointsYZ = new simdFloat3(aYZ, aYZ, aExtraYZ, aExtraYZ); + simdFloat3 aEdgesYZ = new simdFloat3(new float3(0f, 2f * boxA.halfSize.y, 0f)); + + var dotsYZ = simd.dot(bFrontPointsInAOS, axisYZ); + float bestDotYZ = math.cmin(dotsYZ); + int dotsMaskYZ = math.bitmask(dotsYZ == bestDotYZ); + math.ShuffleComponent bIndexYZ = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskYZ), 0, 3); + math.ShuffleComponent bExtraIndexYZ = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskYZ) - 28), 0, 3); + simdFloat3 bPointsYZ = simd.shuffle(bFrontPointsInAOS, bFrontPointsInAOS, bIndexYZ, bExtraIndexYZ, bIndexYZ, bExtraIndexYZ); + simdFloat3 bEdgesYZ = simd.shuffle(bBackPointsInAOS, bBackPointsInAOS, bIndexYZ, bExtraIndexYZ, bIndexYZ, bExtraIndexYZ) - bPointsYZ; + simdFloat3 bNormalsYZ = simd.shuffle(bNormalsZ, bNormalsZ, bIndexYZ, bExtraIndexYZ, bIndexYZ, bExtraIndexYZ); + var isInvalidYZ = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsYZ, + aEdgesYZ, + bPointsYZ, + bEdgesYZ, + out simdFloat3 closestAsYZ, + out simdFloat3 closestBsYZ); + var projectionYZ = simd.project(closestBsYZ - closestAsYZ, axisYZ); + isInvalidYZ |= edgeInvalids47.y | !simd.isfiniteallxyz(projectionYZ); + var signedAxisDistancesSqYZ = simd.lengthsq(projectionYZ) * math.sign(simd.dot(projectionYZ, axisYZ)); + var distancesSqYZ = simd.distancesq(closestAsYZ, closestBsYZ); + isInvalidYZ |= (signedAxisDistancesSqYZ < 0f) & math.distance(math.abs(signedAxisDistancesSqYZ), distancesSqYZ) > k_boxBoxEpsilon; + signedAxisDistancesSqYZ = math.select(signedAxisDistancesSqYZ, float.MinValue, isInvalidYZ); + distancesSqYZ = math.select(distancesSqYZ, float.MaxValue, isInvalidYZ); + + //z + //z vs x + float3 axisZX = edgeAxes47.c; + float3 aZX = math.select(-boxA.halfSize, boxA.halfSize, axisZX > 0f); + float3 aExtraZX = math.select(aZX, boxA.halfSize, axisZX == 0f); + aExtraZX.z = -boxA.halfSize.z; + simdFloat3 aPointsZX = new simdFloat3(aZX, aZX, aExtraZX, aExtraZX); + simdFloat3 aEdgesZX = new simdFloat3(new float3(0f, 0f, 2f * boxA.halfSize.z)); + + var dotsZX = simd.dot(bLeftPointsInAOS, axisZX); + float bestDotZX = math.cmin(dotsZX); + int dotsMaskZX = math.bitmask(dotsZX == bestDotZX); + math.ShuffleComponent bIndexZX = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskZX), 0, 3); + math.ShuffleComponent bExtraIndexZX = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskZX) - 28), 0, 3); + simdFloat3 bPointsZX = simd.shuffle(bLeftPointsInAOS, bLeftPointsInAOS, bIndexZX, bExtraIndexZX, bIndexZX, bExtraIndexZX); + simdFloat3 bEdgesZX = simd.shuffle(bRightPointsInAOS, bRightPointsInAOS, bIndexZX, bExtraIndexZX, bIndexZX, bExtraIndexZX) - bPointsZX; + simdFloat3 bNormalsZX = simd.shuffle(bNormalsX, bNormalsX, bIndexZX, bExtraIndexZX, bIndexZX, bExtraIndexZX); + var isInvalidZX = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsZX, + aEdgesZX, + bPointsZX, + bEdgesZX, + out simdFloat3 closestAsZX, + out simdFloat3 closestBsZX); + var projectionZX = simd.project(closestBsZX - closestAsZX, axisZX); + isInvalidZX |= edgeInvalids47.z | !simd.isfiniteallxyz(projectionZX); + var signedAxisDistancesSqZX = simd.lengthsq(projectionZX) * math.sign(simd.dot(projectionZX, axisZX)); + var distancesSqZX = simd.distancesq(closestAsZX, closestBsZX); + isInvalidZX |= (signedAxisDistancesSqZX < 0f) & math.distance(math.abs(signedAxisDistancesSqZX), distancesSqZX) > k_boxBoxEpsilon; + signedAxisDistancesSqZX = math.select(signedAxisDistancesSqZX, float.MinValue, isInvalidZX); + distancesSqZX = math.select(distancesSqZX, float.MaxValue, isInvalidZX); + + //z vs y + float3 axisZY = edgeAxes47.d; + float3 aZY = math.select(-boxA.halfSize, boxA.halfSize, axisZY > 0f); + float3 aExtraZY = math.select(aZY, boxA.halfSize, axisZY == 0f); + aExtraZY.z = -boxA.halfSize.z; + simdFloat3 aPointsZY = new simdFloat3(aZY, aZY, aExtraZY, aExtraZY); + simdFloat3 aEdgesZY = new simdFloat3(new float3(0f, 0f, 2f * boxA.halfSize.z)); + + var dotsZY = simd.dot(bBottomPointsInAOS, axisZY); + float bestDotZY = math.cmin(dotsZY); + int dotsMaskZY = math.bitmask(dotsZY == bestDotZY); + math.ShuffleComponent bIndexZY = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskZY), 0, 3); + math.ShuffleComponent bExtraIndexZY = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskZY) - 28), 0, 3); + simdFloat3 bPointsZY = simd.shuffle(bBottomPointsInAOS, bBottomPointsInAOS, bIndexZY, bExtraIndexZY, bIndexZY, bExtraIndexZY); + simdFloat3 bEdgesZY = simd.shuffle(bTopPointsInAOS, bTopPointsInAOS, bIndexZY, bExtraIndexZY, bIndexZY, bExtraIndexZY) - bPointsZY; + simdFloat3 bNormalsZY = simd.shuffle(bNormalsY, bNormalsY, bIndexZY, bExtraIndexZY, bIndexZY, bExtraIndexZY); + var isInvalidZY = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsZY, + aEdgesZY, + bPointsZY, + bEdgesZY, + out simdFloat3 closestAsZY, + out simdFloat3 closestBsZY); + var projectionZY = simd.project(closestBsZY - closestAsZY, axisZY); + isInvalidZY |= edgeInvalids47.w | !simd.isfiniteallxyz(projectionZY); + var signedAxisDistancesSqZY = simd.lengthsq(projectionZY) * math.sign(simd.dot(projectionZY, axisZY)); + var distancesSqZY = simd.distancesq(closestAsZY, closestBsZY); + isInvalidZY |= (signedAxisDistancesSqZY < 0f) & math.distance(math.abs(signedAxisDistancesSqZY), distancesSqZY) > k_boxBoxEpsilon; + signedAxisDistancesSqZY = math.select(signedAxisDistancesSqZY, float.MinValue, isInvalidZY); + distancesSqZY = math.select(distancesSqZY, float.MaxValue, isInvalidZY); + + //z vs z + float3 axisZZ = edgeAxes8; + float3 aZZ = math.select(-boxA.halfSize, boxA.halfSize, axisZZ > 0f); + float3 aExtraZZ = math.select(aZZ, boxA.halfSize, axisZZ == 0f); + aExtraZZ.z = -boxA.halfSize.z; + simdFloat3 aPointsZZ = new simdFloat3(aZZ, aZZ, aExtraZZ, aExtraZZ); + simdFloat3 aEdgesZZ = new simdFloat3(new float3(0f, 0f, 2f * boxA.halfSize.z)); + + var dotsZZ = simd.dot(bFrontPointsInAOS, axisZZ); + float bestDotZZ = math.cmin(dotsZZ); + int dotsMaskZZ = math.bitmask(dotsZZ == bestDotZZ); + math.ShuffleComponent bIndexZZ = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskZZ), 0, 3); + math.ShuffleComponent bExtraIndexZZ = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskZZ) - 28), 0, 3); + simdFloat3 bPointsZZ = simd.shuffle(bFrontPointsInAOS, bFrontPointsInAOS, bIndexZZ, bExtraIndexZZ, bIndexZZ, bExtraIndexZZ); + simdFloat3 bEdgesZZ = simd.shuffle(bBackPointsInAOS, bBackPointsInAOS, bIndexZZ, bExtraIndexZZ, bIndexZZ, bExtraIndexZZ) - bPointsZZ; + simdFloat3 bNormalsZZ = simd.shuffle(bNormalsZ, bNormalsZ, bIndexZZ, bExtraIndexZZ, bIndexZZ, bExtraIndexZZ); + var isInvalidZZ = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsZZ, + aEdgesZZ, + bPointsZZ, + bEdgesZZ, + out simdFloat3 closestAsZZ, + out simdFloat3 closestBsZZ); + var projectionZZ = simd.project(closestBsZZ - closestAsZZ, axisZZ); + isInvalidZZ |= edgeInvalids8 | !simd.isfiniteallxyz(projectionZZ); + var signedAxisDistancesSqZZ = simd.lengthsq(projectionZZ) * math.sign(simd.dot(projectionZZ, axisZZ)); + var distancesSqZZ = simd.distancesq(closestAsZZ, closestBsZZ); + isInvalidZZ |= (signedAxisDistancesSqZZ < 0f) & math.distance(math.abs(signedAxisDistancesSqZZ), distancesSqZZ) > k_boxBoxEpsilon; + signedAxisDistancesSqZZ = math.select(signedAxisDistancesSqZZ, float.MinValue, isInvalidZZ); + distancesSqZZ = math.select(distancesSqZZ, float.MaxValue, isInvalidZZ); + + //Step 3: Find the best result. + float4 bestEdgeSignedAxisDistancesSq = signedAxisDistancesSqXX; + float4 bestEdgeDistancesSq = distancesSqXX; + simdFloat3 bestEdgeClosestAs = closestAsXX; + simdFloat3 bestEdgeClosestBs = closestBsXX; + simdFloat3 bestNormalBs = bNormalsXX; + + bool4 newEdgeIsBetters = math.select(signedAxisDistancesSqXY > bestEdgeSignedAxisDistancesSq, + distancesSqXY < bestEdgeDistancesSq, + signedAxisDistancesSqXY >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqXY, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqXY, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsXY, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsXY, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsXY, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqXZ > bestEdgeSignedAxisDistancesSq, + distancesSqXZ < bestEdgeDistancesSq, + signedAxisDistancesSqXZ >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqXZ, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqXZ, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsXZ, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsXZ, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsXZ, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqYX > bestEdgeSignedAxisDistancesSq, + distancesSqYX < bestEdgeDistancesSq, + signedAxisDistancesSqYX >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqYX, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqYX, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsYX, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsYX, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsYX, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqYY > bestEdgeSignedAxisDistancesSq, + distancesSqYY < bestEdgeDistancesSq, + signedAxisDistancesSqYY >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqYY, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqYY, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsYY, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsYY, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsYY, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqYZ > bestEdgeSignedAxisDistancesSq, + distancesSqYZ < bestEdgeDistancesSq, + signedAxisDistancesSqYZ >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqYZ, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqYZ, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsYZ, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsYZ, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsYZ, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqZX > bestEdgeSignedAxisDistancesSq, + distancesSqZX < bestEdgeDistancesSq, + signedAxisDistancesSqZX >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqZX, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqZX, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsZX, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsZX, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsZX, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqZY > bestEdgeSignedAxisDistancesSq, + distancesSqZY < bestEdgeDistancesSq, + signedAxisDistancesSqZY >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqZY, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqZY, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsZY, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsZY, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsZY, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqZZ > bestEdgeSignedAxisDistancesSq, + distancesSqZZ < bestEdgeDistancesSq, + signedAxisDistancesSqZZ >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqZZ, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqZZ, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsZZ, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsZZ, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsZZ, newEdgeIsBetters); + + var absoluteDistanceIsValid = bestEdgeSignedAxisDistancesSq >= 0f; + bool anyAbsoluteDistanceIsValid = math.any(absoluteDistanceIsValid); + float4 maskedAbsoluteDistances = math.select(float.MaxValue, bestEdgeSignedAxisDistancesSq, absoluteDistanceIsValid); + bool4 isTheBestEdge = + math.select(bestEdgeSignedAxisDistancesSq == math.cmax(bestEdgeSignedAxisDistancesSq), + maskedAbsoluteDistances == math.cmin(maskedAbsoluteDistances), + anyAbsoluteDistanceIsValid); + int bestEdgeIndex = math.tzcnt(math.bitmask(isTheBestEdge)); + float bestEdgeSignedAxisDistanceSq = bestEdgeSignedAxisDistancesSq[bestEdgeIndex]; + float bestEdgeDistanceSq = bestEdgeDistancesSq[bestEdgeIndex]; + float3 bestEdgeClosestA = bestEdgeClosestAs[bestEdgeIndex]; + float3 bestEdgeClosestB = bestEdgeClosestBs[bestEdgeIndex]; + float3 bestEdgeNormalB = bestNormalBs[bestEdgeIndex]; + + simdFloat3 topUnnormals = default; + simdFloat3 bottomUnnormals = default; + topUnnormals.x = math.select(-1f, 1f, new bool4(true, true, false, false)); + bottomUnnormals.x = topUnnormals.x; + bottomUnnormals.y = -1f; + topUnnormals.y = 1f; + topUnnormals.z = math.select(-1f, 1f, new bool4(true, false, true, false)); + bottomUnnormals.z = topUnnormals.z; + + float3 pointsNormalBFromBInA = simd.shuffle(topUnnormals, bottomUnnormals, (math.ShuffleComponent)bInABIndex); + float3 pointsNormalAFromAInB = simd.shuffle(topUnnormals, bottomUnnormals, (math.ShuffleComponent)aInBAIndex); + pointsNormalBFromBInA = math.normalize(math.rotate(bInASpace, pointsNormalBFromBInA)); + pointsNormalAFromAInB = math.normalize(pointsNormalAFromAInB); + float3 pointsNormalBFromAInB = math.select(0f, 1f, pointsClosestBInB == boxB.halfSize) + math.select(0f, -1f, pointsClosestBInB == -boxB.halfSize); + pointsNormalBFromAInB = math.normalize(math.rotate(bInASpace, pointsNormalBFromAInB)); + float3 pointsNormalAFromBInA = + math.normalize(math.select(0f, 1f, pointsClosestAInA == boxA.halfSize) + math.select(0f, -1f, pointsClosestAInA == -boxA.halfSize)); + float3 bestEdgeNormalA = math.normalize(math.select(0f, 1f, bestEdgeClosestA == boxA.halfSize) + math.select(0f, -1f, bestEdgeClosestA == -boxA.halfSize)); + int matchedBIndexFromEdgeTop = math.tzcnt(math.bitmask(bestEdgeClosestB == bTopPointsInAOS)); + int matchedBIndexFromEdgeBottom = math.tzcnt(math.bitmask((bestEdgeClosestB == bBottomPointsInAOS))) + 4; + int matchedIndexBFromEdge = math.select(matchedBIndexFromEdgeTop, matchedBIndexFromEdgeBottom, matchedBIndexFromEdgeBottom < 8); + float3 edgeNormalBAsCorner = simd.shuffle(topUnnormals, bottomUnnormals, (math.ShuffleComponent)math.clamp(matchedIndexBFromEdge, 0, 7)); + edgeNormalBAsCorner = math.normalize(edgeNormalBAsCorner); + bestEdgeNormalB = math.select(bestEdgeNormalB, edgeNormalBAsCorner, matchedIndexBFromEdge < 8); + + bool bInAIsBetter = math.select(pointsAxisDistanceInA >= pointsAxisDistanceInB, + pointsDistanceSqInA <= pointsDistanceSqInB, + pointsAxisDistanceInA >= 0f && pointsAxisDistanceInB >= 0f); + float pointsAxisDistance = math.select(pointsAxisDistanceInB, pointsAxisDistanceInA, bInAIsBetter); + float pointsDistanceSq = math.select(pointsDistanceSqInB, pointsDistanceSqInA, bInAIsBetter); + float3 pointsClosestA = math.select(math.transform(bInASpace, pointsClosestAInB + boxB.center), pointsClosestAInA + boxA.center, bInAIsBetter); + float3 pointsClosestB = math.select(math.transform(bInASpace, pointsClosestBInB + boxB.center), pointsClosestBInA + boxA.center, bInAIsBetter); + float3 pointsNormalA = math.select(pointsNormalAFromAInB, pointsNormalAFromBInA, bInAIsBetter); + float3 pointsNormalB = math.select(pointsNormalBFromAInB, pointsNormalBFromBInA, bInAIsBetter); + + bool pointsIsBetter = math.select(pointsAxisDistance * pointsAxisDistance * math.sign(pointsAxisDistance) >= bestEdgeSignedAxisDistanceSq, + pointsDistanceSq <= bestEdgeDistanceSq, + pointsAxisDistance >= 0f && bestEdgeSignedAxisDistanceSq >= 0f); + float bestAxisDistance = math.select(math.sign(bestEdgeSignedAxisDistanceSq) * math.sqrt(math.abs(bestEdgeSignedAxisDistanceSq)), pointsAxisDistance, pointsIsBetter); + float bestDistanceSq = math.select(bestEdgeDistanceSq, pointsDistanceSq, pointsIsBetter); + float3 bestClosestA = math.select(bestEdgeClosestA + boxA.center, pointsClosestA, pointsIsBetter); + float3 bestClosestB = math.select(bestEdgeClosestB + boxA.center, pointsClosestB, pointsIsBetter); + float3 bestNormalA = math.select(bestEdgeNormalA, pointsNormalA, pointsIsBetter); + float3 bestNormalB = math.select(math.rotate(bInASpace, bestEdgeNormalB), pointsNormalB, pointsIsBetter); + + //Step 4: Build result + result = new ColliderDistanceResultInternal + { + hitpointA = bestClosestA, + hitpointB = bestClosestB, + normalA = bestNormalA, + normalB = bestNormalB, + distance = math.sign(bestAxisDistance) * math.sqrt(math.abs(bestDistanceSq)) + }; + return result.distance <= maxDistance; + } + + public static bool BoxBoxDistanceDebug(BoxCollider boxA, + BoxCollider boxB, + RigidTransform bInASpace, + RigidTransform aInBSpace, + float maxDistance, + out ColliderDistanceResultInternal result) + { + //Step 1: Points vs faces + simdFloat3 bTopPoints = default; + simdFloat3 bBottomPoints = default; + bTopPoints.x = math.select(-boxB.halfSize.x, boxB.halfSize.x, new bool4(true, true, false, false)); + bBottomPoints.x = bTopPoints.x; + bBottomPoints.y = -boxB.halfSize.y; + bTopPoints.y = boxB.halfSize.y; + bTopPoints.z = math.select(-boxB.halfSize.z, boxB.halfSize.z, new bool4(true, false, true, false)); + bBottomPoints.z = bTopPoints.z; + bTopPoints += boxB.center; + bBottomPoints += boxB.center; + var bTopPointsInAOS = simd.transform(bInASpace, bTopPoints) - boxA.center; //OS = origin space + var bBottomPointsInAOS = simd.transform(bInASpace, bBottomPoints) - boxA.center; + + QueriesLowLevelUtils.OriginAabb8PointsWithEspilonFudgeDebug(boxA.halfSize, + bTopPointsInAOS, + bBottomPointsInAOS, + out float3 pointsClosestAInA, + out float3 pointsClosestBInA, + out float pointsAxisDistanceInA); + bool4 bTopMatch = bTopPointsInAOS == pointsClosestBInA; + bool4 bBottomMatch = bBottomPointsInAOS == pointsClosestBInA; + int bInABIndex = math.tzcnt((math.bitmask(bBottomMatch) << 4) | math.bitmask(bTopMatch)); + float pointsDistanceSqInA = math.distancesq(pointsClosestAInA, pointsClosestBInA); + UnityEngine.Debug.Log($"Points B in A: axisDistance: {pointsAxisDistanceInA}, distanceSq: {pointsDistanceSqInA}, bInABIndex: {bInABIndex}"); + + simdFloat3 aTopPoints = default; + simdFloat3 aBottomPoints = default; + aTopPoints.x = math.select(-boxA.halfSize.x, boxA.halfSize.x, new bool4(true, true, false, false)); + aBottomPoints.x = aTopPoints.x; + aBottomPoints.y = -boxA.halfSize.y; + aTopPoints.y = boxA.halfSize.y; + aTopPoints.z = math.select(-boxA.halfSize.z, boxA.halfSize.z, new bool4(true, false, true, false)); + aBottomPoints.z = aTopPoints.z; + aTopPoints += boxA.center; + aBottomPoints += boxA.center; + var aTopPointsInBOS = simd.transform(aInBSpace, aTopPoints) - boxB.center; + var aBottomPointsInBOS = simd.transform(aInBSpace, aBottomPoints) - boxB.center; + if (math.any(math.isnan(aTopPointsInBOS.x))) + { + UnityEngine.Debug.Log( + $"Something went wrong transforming to B-space. aTopPoints.a: {aTopPoints.a}, boxB.center: {boxB.center}, aInBSpace: {aInBSpace.rot}, {aInBSpace.pos}"); + } + + QueriesLowLevelUtils.OriginAabb8PointsWithEspilonFudgeDebug(boxB.halfSize, + aTopPointsInBOS, + aBottomPointsInBOS, + out float3 pointsClosestBInB, + out float3 pointsClosestAInB, + out float pointsAxisDistanceInB); + bool4 aTopMatch = aTopPointsInBOS == pointsClosestAInB; + bool4 aBottomMatch = aBottomPointsInBOS == pointsClosestAInB; + int aInBAIndex = math.tzcnt((math.bitmask(aBottomMatch) << 4) | math.bitmask(aTopMatch)); + float pointsDistanceSqInB = math.distancesq(pointsClosestAInB, pointsClosestBInB); + + UnityEngine.Debug.Log($"Points A in B: axisDistance: {pointsAxisDistanceInB}, distanceSq: {pointsDistanceSqInB}, aInBAIndex: {aInBAIndex}"); + + //Step 2: Edges vs edges + + //For any pair of normals, if the normals are colinear, then there must also exist a point-face pair that is equidistant. + //However, for a pair of normals, up to two edges from each box can be valid. + //For box A, assemble the points and edges procedurally using the box dimensions. + //For box B, use a simd dot product and mask it against the best result. The first 1 index and last 1 index are taken. In most cases, these are the same, which is fine. + //It is also worth noting that unlike a true SAT, directionality matters here, so we want to find the separating axis directionally oriented from a to b to get the correct closest features. + //That's the max dot for a and the min dot for b. + float3 bCenterInASpace = math.transform(bInASpace, boxB.center) - boxA.center; + simdFloat3 faceNormalsBoxA = new simdFloat3(new float3(1f, 0f, 0f), new float3(0f, 1f, 0f), new float3(0f, 0f, 1f), new float3(1f, 0f, 0f)); + simdFloat3 faceNormalsBoxB = simd.mul(bInASpace.rot, faceNormalsBoxA); + simdFloat3 edgeAxes03 = simd.cross(faceNormalsBoxA.aaab, faceNormalsBoxB); //normalsB is already .abca + simdFloat3 edgeAxes47 = simd.cross(faceNormalsBoxA.bbcc, faceNormalsBoxB.bcab); + float3 edgeAxes8 = math.cross(faceNormalsBoxA.c, faceNormalsBoxB.c); + edgeAxes03 = simd.select(-edgeAxes03, edgeAxes03, simd.dot(edgeAxes03, bCenterInASpace) >= 0f); + edgeAxes47 = simd.select(-edgeAxes47, edgeAxes47, simd.dot(edgeAxes47, bCenterInASpace) >= 0f); + edgeAxes8 = math.select(-edgeAxes8, edgeAxes8, math.dot(edgeAxes8, bCenterInASpace) >= 0f); + bool4 edgeInvalids03 = edgeAxes03 == 0f; + bool4 edgeInvalids47 = edgeAxes47 == 0f; + bool edgeInvalids8 = edgeAxes8.Equals(float3.zero); + simdFloat3 bLeftPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW); + simdFloat3 bRightPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY); + simdFloat3 bFrontPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.LeftW, + math.ShuffleComponent.RightY, + math.ShuffleComponent.RightW); + simdFloat3 bBackPointsInAOS = simd.shuffle(bTopPointsInAOS, + bBottomPointsInAOS, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightZ); + simdFloat3 bNormalsX = new simdFloat3(new float3(0f, math.SQRT2 / 2f, math.SQRT2 / 2f), + new float3(0f, math.SQRT2 / 2f, -math.SQRT2 / 2f), + new float3(0f, -math.SQRT2 / 2f, math.SQRT2 / 2f), + new float3(0f, -math.SQRT2 / 2f, -math.SQRT2 / 2f)); + simdFloat3 bNormalsY = new simdFloat3(new float3(math.SQRT2 / 2f, 0f, math.SQRT2 / 2f), + new float3(math.SQRT2 / 2f, 0f, -math.SQRT2 / 2f), + new float3(-math.SQRT2 / 2f, 0f, math.SQRT2 / 2f), + new float3(-math.SQRT2 / 2f, 0f, -math.SQRT2 / 2f)); + simdFloat3 bNormalsZ = new simdFloat3(new float3(math.SQRT2 / 2f, math.SQRT2 / 2f, 0f), + new float3(-math.SQRT2 / 2f, math.SQRT2 / 2f, 0f), + new float3(math.SQRT2 / 2f, -math.SQRT2 / 2f, 0f), + new float3(-math.SQRT2 / 2f, -math.SQRT2 / 2f, 0f)); + float3 bCenterPlaneDistancesInA = simd.dot(bCenterInASpace, faceNormalsBoxB).xyz; + + //x vs x + float3 axisXX = edgeAxes03.a; + float3 aXX = math.select(-boxA.halfSize, boxA.halfSize, axisXX > 0f); + float3 aExtraXX = math.select(aXX, boxA.halfSize, axisXX == 0f); + aExtraXX.x = -boxA.halfSize.x; + simdFloat3 aPointsXX = new simdFloat3(aXX, aXX, aExtraXX, aExtraXX); + simdFloat3 aEdgesXX = new simdFloat3(new float3(2f * boxA.halfSize.x, 0f, 0f)); + + var dotsXX = simd.dot(bLeftPointsInAOS, axisXX); + float bestDotXX = math.cmin(dotsXX); + int dotsMaskXX = math.bitmask(dotsXX == bestDotXX); + math.ShuffleComponent bIndexXX = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskXX), 0, 3); + math.ShuffleComponent bExtraIndexXX = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskXX) - 28), 0, 3); + simdFloat3 bPointsXX = simd.shuffle(bLeftPointsInAOS, bLeftPointsInAOS, bIndexXX, bExtraIndexXX, bIndexXX, bExtraIndexXX); + simdFloat3 bEdgesXX = simd.shuffle(bRightPointsInAOS, bRightPointsInAOS, bIndexXX, bExtraIndexXX, bIndexXX, bExtraIndexXX) - bPointsXX; + simdFloat3 bNormalsXX = simd.shuffle(bNormalsX, bNormalsX, bIndexXX, bExtraIndexXX, bIndexXX, bExtraIndexXX); + var isInvalidXX = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsXX, + aEdgesXX, + bPointsXX, + bEdgesXX, + out simdFloat3 closestAsXX, + out simdFloat3 closestBsXX); + var projectionXX = simd.project(closestBsXX - closestAsXX, axisXX); + isInvalidXX |= edgeInvalids03.x | !simd.isfiniteallxyz(projectionXX); + var signedAxisDistancesSqXX = simd.lengthsq(projectionXX) * math.sign(simd.dot(projectionXX, axisXX)); + var distancesSqXX = simd.distancesq(closestAsXX, closestBsXX); + isInvalidXX |= (signedAxisDistancesSqXX < 0f) & math.distance(math.abs(signedAxisDistancesSqXX), distancesSqXX) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"XX: axis: {axisXX}, signedAxisDistancesSq: {signedAxisDistancesSqXX}, distancesSq: {distancesSqXX}, isInvalid: {isInvalidXX}"); + signedAxisDistancesSqXX = math.select(signedAxisDistancesSqXX, float.MinValue, isInvalidXX); + distancesSqXX = math.select(distancesSqXX, float.MaxValue, isInvalidXX); + UnityEngine.Debug.Log($"edgeInvalids03: {edgeInvalids03}, eps: {k_boxBoxEpsilon}"); + + //x vs y + float3 axisXY = edgeAxes03.b; + float3 aXY = math.select(-boxA.halfSize, boxA.halfSize, axisXY > 0f); + float3 aExtraXY = math.select(aXY, boxA.halfSize, axisXY == 0f); + aExtraXY.x = -boxA.halfSize.x; + simdFloat3 aPointsXY = new simdFloat3(aXY, aXY, aExtraXY, aExtraXY); + simdFloat3 aEdgesXY = new simdFloat3(new float3(2f * boxA.halfSize.x, 0f, 0f)); + + var dotsXY = simd.dot(bBottomPointsInAOS, axisXY); + float bestDotXY = math.cmin(dotsXY); + int dotsMaskXY = math.bitmask(dotsXY == bestDotXY); + math.ShuffleComponent bIndexXY = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskXY), 0, 3); + math.ShuffleComponent bExtraIndexXY = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskXY) - 28), 0, 3); + simdFloat3 bPointsXY = simd.shuffle(bBottomPointsInAOS, bBottomPointsInAOS, bIndexXY, bExtraIndexXY, bIndexXY, bExtraIndexXY); + simdFloat3 bEdgesXY = simd.shuffle(bTopPointsInAOS, bTopPointsInAOS, bIndexXY, bExtraIndexXY, bIndexXY, bExtraIndexXY) - bPointsXY; + simdFloat3 bNormalsXY = simd.shuffle(bNormalsY, bNormalsY, bIndexXY, bExtraIndexXY, bIndexXY, bExtraIndexXY); + var isInvalidXY = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsXY, + aEdgesXY, + bPointsXY, + bEdgesXY, + out simdFloat3 closestAsXY, + out simdFloat3 closestBsXY); + var projectionXY = simd.project(closestBsXY - closestAsXY, axisXY); + isInvalidXY |= edgeInvalids03.y | !simd.isfiniteallxyz(projectionXY); + var signedAxisDistancesSqXY = simd.lengthsq(projectionXY) * math.sign(simd.dot(projectionXY, axisXY)); + var distancesSqXY = simd.distancesq(closestAsXY, closestBsXY); + isInvalidXY |= (signedAxisDistancesSqXY < 0f) & math.distance(math.abs(signedAxisDistancesSqXY), distancesSqXY) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"XY: axis: {axisXY}, signedAxisDistancesSq: {signedAxisDistancesSqXY}, distancesSq: {distancesSqXY}, isInvalid: {isInvalidXY}"); + signedAxisDistancesSqXY = math.select(signedAxisDistancesSqXY, float.MinValue, isInvalidXY); + distancesSqXY = math.select(distancesSqXY, float.MaxValue, isInvalidXY); + + //x vs z + float3 axisXZ = edgeAxes03.c; + float3 aXZ = math.select(-boxA.halfSize, boxA.halfSize, axisXZ > 0f); + float3 aExtraXZ = math.select(aXZ, boxA.halfSize, axisXZ == 0f); + aExtraXZ.x = -boxA.halfSize.x; + simdFloat3 aPointsXZ = new simdFloat3(aXZ, aXZ, aExtraXZ, aExtraXZ); + simdFloat3 aEdgesXZ = new simdFloat3(new float3(2f * boxA.halfSize.x, 0f, 0f)); + + var dotsXZ = simd.dot(bFrontPointsInAOS, axisXZ); + float bestDotXZ = math.cmin(dotsXZ); + int dotsMaskXZ = math.bitmask(dotsXZ == bestDotXZ); + math.ShuffleComponent bIndexXZ = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskXZ), 0, 3); + math.ShuffleComponent bExtraIndexXZ = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskXZ) - 28), 0, 3); + simdFloat3 bPointsXZ = simd.shuffle(bFrontPointsInAOS, bFrontPointsInAOS, bIndexXZ, bExtraIndexXZ, bIndexXZ, bExtraIndexXZ); + simdFloat3 bEdgesXZ = simd.shuffle(bBackPointsInAOS, bBackPointsInAOS, bIndexXZ, bExtraIndexXZ, bIndexXZ, bExtraIndexXZ) - bPointsXZ; + simdFloat3 bNormalsXZ = simd.shuffle(bNormalsZ, bNormalsZ, bIndexXZ, bExtraIndexXZ, bIndexXZ, bExtraIndexXZ); + var isInvalidXZ = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsXZ, + aEdgesXZ, + bPointsXZ, + bEdgesXZ, + out simdFloat3 closestAsXZ, + out simdFloat3 closestBsXZ); + var projectionXZ = simd.project(closestBsXZ - closestAsXZ, axisXZ); + isInvalidXZ |= edgeInvalids03.z | !simd.isfiniteallxyz(projectionXZ); + var signedAxisDistancesSqXZ = simd.lengthsq(projectionXZ) * math.sign(simd.dot(projectionXZ, axisXZ)); + var distancesSqXZ = simd.distancesq(closestAsXZ, closestBsXZ); + isInvalidXZ |= (signedAxisDistancesSqXZ < 0f) & math.distance(math.abs(signedAxisDistancesSqXZ), distancesSqXZ) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"XZ: axis: {axisXZ}, signedAxisDistancesSq: {signedAxisDistancesSqXZ}, distancesSq: {distancesSqXZ}, isInvalid: {isInvalidXZ}"); + signedAxisDistancesSqXZ = math.select(signedAxisDistancesSqXZ, float.MinValue, isInvalidXZ); + distancesSqXZ = math.select(distancesSqXZ, float.MaxValue, isInvalidXZ); + + //y + //y vs x + float3 axisYX = edgeAxes03.d; + float3 aYX = math.select(-boxA.halfSize, boxA.halfSize, axisYX > 0f); + float3 aExtraYX = math.select(aYX, boxA.halfSize, axisYX == 0f); + aExtraYX.y = -boxA.halfSize.y; + simdFloat3 aPointsYX = new simdFloat3(aYX, aYX, aExtraYX, aExtraYX); + simdFloat3 aEdgesYX = new simdFloat3(new float3(0f, 2f * boxA.halfSize.y, 0f)); + + var dotsYX = simd.dot(bLeftPointsInAOS, axisYX); + float bestDotYX = math.cmin(dotsYX); + int dotsMaskYX = math.bitmask(dotsYX == bestDotYX); + math.ShuffleComponent bIndexYX = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskYX), 0, 3); + math.ShuffleComponent bExtraIndexYX = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskYX) - 28), 0, 3); + simdFloat3 bPointsYX = simd.shuffle(bLeftPointsInAOS, bLeftPointsInAOS, bIndexYX, bExtraIndexYX, bIndexYX, bExtraIndexYX); + simdFloat3 bEdgesYX = simd.shuffle(bRightPointsInAOS, bRightPointsInAOS, bIndexYX, bExtraIndexYX, bIndexYX, bExtraIndexYX) - bPointsYX; + simdFloat3 bNormalsYX = simd.shuffle(bNormalsX, bNormalsX, bIndexYX, bExtraIndexYX, bIndexYX, bExtraIndexYX); + var isInvalidYX = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsYX, + aEdgesYX, + bPointsYX, + bEdgesYX, + out simdFloat3 closestAsYX, + out simdFloat3 closestBsYX); + var projectionYX = simd.project(closestBsYX - closestAsYX, axisYX); + isInvalidYX |= edgeInvalids03.w | !simd.isfiniteallxyz(projectionYX); + var signedAxisDistancesSqYX = simd.lengthsq(projectionYX) * math.sign(simd.dot(projectionYX, axisYX)); + var distancesSqYX = simd.distancesq(closestAsYX, closestBsYX); + isInvalidYX |= (signedAxisDistancesSqYX < 0f) & math.distance(math.abs(signedAxisDistancesSqYX), distancesSqYX) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"YX: axis: {axisYX}, signedAxisDistancesSq: {signedAxisDistancesSqYX}, distancesSq: {distancesSqYX}, isInvalid: {isInvalidYX}"); + signedAxisDistancesSqYX = math.select(signedAxisDistancesSqYX, float.MinValue, isInvalidYX); + distancesSqYX = math.select(distancesSqYX, float.MaxValue, isInvalidYX); + + //y vs y + float3 axisYY = edgeAxes47.a; + float3 aYY = math.select(-boxA.halfSize, boxA.halfSize, axisYY > 0f); + float3 aExtraYY = math.select(aYY, boxA.halfSize, axisYY == 0f); + aExtraYY.y = -boxA.halfSize.y; + simdFloat3 aPointsYY = new simdFloat3(aYY, aYY, aExtraYY, aExtraYY); + simdFloat3 aEdgesYY = new simdFloat3(new float3(0f, 2f * boxA.halfSize.y, 0f)); + + var dotsYY = simd.dot(bBottomPointsInAOS, axisYY); + float bestDotYY = math.cmin(dotsYY); + int dotsMaskYY = math.bitmask(dotsYY == bestDotYY); + math.ShuffleComponent bIndexYY = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskYY), 0, 3); + math.ShuffleComponent bExtraIndexYY = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskYY) - 28), 0, 3); + simdFloat3 bPointsYY = simd.shuffle(bBottomPointsInAOS, bBottomPointsInAOS, bIndexYY, bExtraIndexYY, bIndexYY, bExtraIndexYY); + simdFloat3 bEdgesYY = simd.shuffle(bTopPointsInAOS, bTopPointsInAOS, bIndexYY, bExtraIndexYY, bIndexYY, bExtraIndexYY) - bPointsYY; + simdFloat3 bNormalsYY = simd.shuffle(bNormalsY, bNormalsY, bIndexYY, bExtraIndexYY, bIndexYY, bExtraIndexYY); + var isInvalidYY = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsYY, + aEdgesYY, + bPointsYY, + bEdgesYY, + out simdFloat3 closestAsYY, + out simdFloat3 closestBsYY); + var projectionYY = simd.project(closestBsYY - closestAsYY, axisYY); + isInvalidYY |= edgeInvalids47.x | !simd.isfiniteallxyz(projectionYY); + var signedAxisDistancesSqYY = simd.lengthsq(projectionYY) * math.sign(simd.dot(projectionYY, axisYY)); + var distancesSqYY = simd.distancesq(closestAsYY, closestBsYY); + isInvalidYY |= (signedAxisDistancesSqYY < 0f) & math.distance(math.abs(signedAxisDistancesSqYY), distancesSqYY) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"YY: axis: {axisYY}, signedAxisDistancesSq: {signedAxisDistancesSqYY}, distancesSq: {distancesSqYY}, isInvalid: {isInvalidYY}"); + signedAxisDistancesSqYY = math.select(signedAxisDistancesSqYY, float.MinValue, isInvalidYY); + distancesSqYY = math.select(distancesSqYY, float.MaxValue, isInvalidYY); + + //y vs z + float3 axisYZ = edgeAxes47.b; + float3 aYZ = math.select(-boxA.halfSize, boxA.halfSize, axisYZ > 0f); + float3 aExtraYZ = math.select(aYZ, boxA.halfSize, axisYZ == 0f); + aExtraYZ.y = -boxA.halfSize.y; + simdFloat3 aPointsYZ = new simdFloat3(aYZ, aYZ, aExtraYZ, aExtraYZ); + simdFloat3 aEdgesYZ = new simdFloat3(new float3(0f, 2f * boxA.halfSize.y, 0f)); + + var dotsYZ = simd.dot(bFrontPointsInAOS, axisYZ); + float bestDotYZ = math.cmin(dotsYZ); + int dotsMaskYZ = math.bitmask(dotsYZ == bestDotYZ); + math.ShuffleComponent bIndexYZ = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskYZ), 0, 3); + math.ShuffleComponent bExtraIndexYZ = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskYZ) - 28), 0, 3); + simdFloat3 bPointsYZ = simd.shuffle(bFrontPointsInAOS, bFrontPointsInAOS, bIndexYZ, bExtraIndexYZ, bIndexYZ, bExtraIndexYZ); + simdFloat3 bEdgesYZ = simd.shuffle(bBackPointsInAOS, bBackPointsInAOS, bIndexYZ, bExtraIndexYZ, bIndexYZ, bExtraIndexYZ) - bPointsYZ; + simdFloat3 bNormalsYZ = simd.shuffle(bNormalsZ, bNormalsZ, bIndexYZ, bExtraIndexYZ, bIndexYZ, bExtraIndexYZ); + var isInvalidYZ = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsYZ, + aEdgesYZ, + bPointsYZ, + bEdgesYZ, + out simdFloat3 closestAsYZ, + out simdFloat3 closestBsYZ); + var projectionYZ = simd.project(closestBsYZ - closestAsYZ, axisYZ); + isInvalidYZ |= edgeInvalids47.y | !simd.isfiniteallxyz(projectionYZ); + var signedAxisDistancesSqYZ = simd.lengthsq(projectionYZ) * math.sign(simd.dot(projectionYZ, axisYZ)); + var distancesSqYZ = simd.distancesq(closestAsYZ, closestBsYZ); + isInvalidYZ |= (signedAxisDistancesSqYZ < 0f) & math.distance(math.abs(signedAxisDistancesSqYZ), distancesSqYZ) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"YZ: axis: {axisYZ}, signedAxisDistancesSq: {signedAxisDistancesSqYZ}, distancesSq: {distancesSqYZ}, isInvalid: {isInvalidYZ}"); + signedAxisDistancesSqYZ = math.select(signedAxisDistancesSqYZ, float.MinValue, isInvalidYZ); + distancesSqYZ = math.select(distancesSqYZ, float.MaxValue, isInvalidYZ); + + //z + //z vs x + float3 axisZX = edgeAxes47.c; + float3 aZX = math.select(-boxA.halfSize, boxA.halfSize, axisZX > 0f); + float3 aExtraZX = math.select(aZX, boxA.halfSize, axisZX == 0f); + aExtraZX.z = -boxA.halfSize.z; + simdFloat3 aPointsZX = new simdFloat3(aZX, aZX, aExtraZX, aExtraZX); + simdFloat3 aEdgesZX = new simdFloat3(new float3(0f, 0f, 2f * boxA.halfSize.z)); + + var dotsZX = simd.dot(bLeftPointsInAOS, axisZX); + float bestDotZX = math.cmin(dotsZX); + int dotsMaskZX = math.bitmask(dotsZX == bestDotZX); + math.ShuffleComponent bIndexZX = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskZX), 0, 3); + math.ShuffleComponent bExtraIndexZX = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskZX) - 28), 0, 3); + simdFloat3 bPointsZX = simd.shuffle(bLeftPointsInAOS, bLeftPointsInAOS, bIndexZX, bExtraIndexZX, bIndexZX, bExtraIndexZX); + simdFloat3 bEdgesZX = simd.shuffle(bRightPointsInAOS, bRightPointsInAOS, bIndexZX, bExtraIndexZX, bIndexZX, bExtraIndexZX) - bPointsZX; + simdFloat3 bNormalsZX = simd.shuffle(bNormalsX, bNormalsX, bIndexZX, bExtraIndexZX, bIndexZX, bExtraIndexZX); + var isInvalidZX = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsZX, + aEdgesZX, + bPointsZX, + bEdgesZX, + out simdFloat3 closestAsZX, + out simdFloat3 closestBsZX); + var projectionZX = simd.project(closestBsZX - closestAsZX, axisZX); + isInvalidZX |= edgeInvalids47.z | !simd.isfiniteallxyz(projectionZX); + var signedAxisDistancesSqZX = simd.lengthsq(projectionZX) * math.sign(simd.dot(projectionZX, axisZX)); + var distancesSqZX = simd.distancesq(closestAsZX, closestBsZX); + isInvalidZX |= (signedAxisDistancesSqZX < 0f) & math.distance(math.abs(signedAxisDistancesSqZX), distancesSqZX) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"ZX: axis: {axisZX}, signedAxisDistancesSq: {signedAxisDistancesSqZX}, distancesSq: {distancesSqZX}, isInvalid: {isInvalidZX}"); + signedAxisDistancesSqZX = math.select(signedAxisDistancesSqZX, float.MinValue, isInvalidZX); + distancesSqZX = math.select(distancesSqZX, float.MaxValue, isInvalidZX); + + //z vs y + float3 axisZY = edgeAxes47.d; + float3 aZY = math.select(-boxA.halfSize, boxA.halfSize, axisZY > 0f); + float3 aExtraZY = math.select(aZY, boxA.halfSize, axisZY == 0f); + aExtraZY.z = -boxA.halfSize.z; + simdFloat3 aPointsZY = new simdFloat3(aZY, aZY, aExtraZY, aExtraZY); + simdFloat3 aEdgesZY = new simdFloat3(new float3(0f, 0f, 2f * boxA.halfSize.z)); + + var dotsZY = simd.dot(bBottomPointsInAOS, axisZY); + float bestDotZY = math.cmin(dotsZY); + int dotsMaskZY = math.bitmask(dotsZY == bestDotZY); + math.ShuffleComponent bIndexZY = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskZY), 0, 3); + math.ShuffleComponent bExtraIndexZY = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskZY) - 28), 0, 3); + simdFloat3 bPointsZY = simd.shuffle(bBottomPointsInAOS, bBottomPointsInAOS, bIndexZY, bExtraIndexZY, bIndexZY, bExtraIndexZY); + simdFloat3 bEdgesZY = simd.shuffle(bTopPointsInAOS, bTopPointsInAOS, bIndexZY, bExtraIndexZY, bIndexZY, bExtraIndexZY) - bPointsZY; + simdFloat3 bNormalsZY = simd.shuffle(bNormalsY, bNormalsY, bIndexZY, bExtraIndexZY, bIndexZY, bExtraIndexZY); + var isInvalidZY = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsZY, + aEdgesZY, + bPointsZY, + bEdgesZY, + out simdFloat3 closestAsZY, + out simdFloat3 closestBsZY); + var projectionZY = simd.project(closestBsZY - closestAsZY, axisZY); + isInvalidZY |= edgeInvalids47.w | !simd.isfiniteallxyz(projectionZY); + var signedAxisDistancesSqZY = simd.lengthsq(projectionZY) * math.sign(simd.dot(projectionZY, axisZY)); + var distancesSqZY = simd.distancesq(closestAsZY, closestBsZY); + isInvalidZY |= (signedAxisDistancesSqZY < 0f) & math.distance(math.abs(signedAxisDistancesSqZY), distancesSqZY) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"ZY: axis: {axisZY}, signedAxisDistancesSq: {signedAxisDistancesSqZY}, distancesSq: {distancesSqZY}, isInvalid: {isInvalidZY}"); + signedAxisDistancesSqZY = math.select(signedAxisDistancesSqZY, float.MinValue, isInvalidZY); + distancesSqZY = math.select(distancesSqZY, float.MaxValue, isInvalidZY); + + //z vs z + float3 axisZZ = edgeAxes8; + float3 aZZ = math.select(-boxA.halfSize, boxA.halfSize, axisZZ > 0f); + float3 aExtraZZ = math.select(aZZ, boxA.halfSize, axisZZ == 0f); + aExtraZZ.z = -boxA.halfSize.z; + simdFloat3 aPointsZZ = new simdFloat3(aZZ, aZZ, aExtraZZ, aExtraZZ); + simdFloat3 aEdgesZZ = new simdFloat3(new float3(0f, 0f, 2f * boxA.halfSize.z)); + + var dotsZZ = simd.dot(bFrontPointsInAOS, axisZZ); + float bestDotZZ = math.cmin(dotsZZ); + int dotsMaskZZ = math.bitmask(dotsZZ == bestDotZZ); + math.ShuffleComponent bIndexZZ = (math.ShuffleComponent)math.clamp(math.tzcnt(dotsMaskZZ), 0, 3); + math.ShuffleComponent bExtraIndexZZ = (math.ShuffleComponent)math.clamp(3 - (math.lzcnt(dotsMaskZZ) - 28), 0, 3); + simdFloat3 bPointsZZ = simd.shuffle(bFrontPointsInAOS, bFrontPointsInAOS, bIndexZZ, bExtraIndexZZ, bIndexZZ, bExtraIndexZZ); + simdFloat3 bEdgesZZ = simd.shuffle(bBackPointsInAOS, bBackPointsInAOS, bIndexZZ, bExtraIndexZZ, bIndexZZ, bExtraIndexZZ) - bPointsZZ; + simdFloat3 bNormalsZZ = simd.shuffle(bNormalsZ, bNormalsZ, bIndexZZ, bExtraIndexZZ, bIndexZZ, bExtraIndexZZ); + var isInvalidZZ = !QueriesLowLevelUtils.SegmentSegmentInvalidateEndpoints(aPointsZZ, + aEdgesZZ, + bPointsZZ, + bEdgesZZ, + out simdFloat3 closestAsZZ, + out simdFloat3 closestBsZZ); + var projectionZZ = simd.project(closestBsZZ - closestAsZZ, axisZZ); + isInvalidZZ |= edgeInvalids8 | !simd.isfiniteallxyz(projectionZZ); + var signedAxisDistancesSqZZ = simd.lengthsq(projectionZZ) * math.sign(simd.dot(projectionZZ, axisZZ)); + var distancesSqZZ = simd.distancesq(closestAsZZ, closestBsZZ); + isInvalidZZ |= (signedAxisDistancesSqZZ < 0f) & math.distance(math.abs(signedAxisDistancesSqZZ), distancesSqZZ) > k_boxBoxEpsilon; + UnityEngine.Debug.Log($"ZZ: axis: {axisZZ}, signedAxisDistancesSq: {signedAxisDistancesSqZZ}, distancesSq: {distancesSqZZ}, isInvalid: {isInvalidZZ}"); + UnityEngine.Debug.Log($"projectionZZ: {projectionZZ.a}, {projectionZZ.b}, {projectionZZ.c}, {projectionZZ.d}"); + signedAxisDistancesSqZZ = math.select(signedAxisDistancesSqZZ, float.MinValue, isInvalidZZ); + distancesSqZZ = math.select(distancesSqZZ, float.MaxValue, isInvalidZZ); + + //Step 3: Find the best result. + float4 bestEdgeSignedAxisDistancesSq = signedAxisDistancesSqXX; + float4 bestEdgeDistancesSq = distancesSqXX; + simdFloat3 bestEdgeClosestAs = closestAsXX; + simdFloat3 bestEdgeClosestBs = closestBsXX; + simdFloat3 bestNormalBs = bNormalsXX; + int4 bestEdgeIds = 0; + + bool4 newEdgeIsBetters = math.select(signedAxisDistancesSqXY > bestEdgeSignedAxisDistancesSq, + distancesSqXY < bestEdgeDistancesSq, + signedAxisDistancesSqXY >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqXY, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqXY, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsXY, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsXY, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsXY, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 1, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqXZ > bestEdgeSignedAxisDistancesSq, + distancesSqXZ < bestEdgeDistancesSq, + signedAxisDistancesSqXZ >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqXZ, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqXZ, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsXZ, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsXZ, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsXZ, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 2, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqYX > bestEdgeSignedAxisDistancesSq, + distancesSqYX < bestEdgeDistancesSq, + signedAxisDistancesSqYX >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqYX, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqYX, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsYX, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsYX, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsYX, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 3, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqYY > bestEdgeSignedAxisDistancesSq, + distancesSqYY < bestEdgeDistancesSq, + signedAxisDistancesSqYY >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqYY, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqYY, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsYY, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsYY, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsYY, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 4, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqYZ > bestEdgeSignedAxisDistancesSq, + distancesSqYZ < bestEdgeDistancesSq, + signedAxisDistancesSqYZ >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqYZ, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqYZ, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsYZ, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsYZ, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsYZ, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 5, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqZX > bestEdgeSignedAxisDistancesSq, + distancesSqZX < bestEdgeDistancesSq, + signedAxisDistancesSqZX >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqZX, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqZX, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsZX, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsZX, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsZX, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 6, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqZY > bestEdgeSignedAxisDistancesSq, + distancesSqZY < bestEdgeDistancesSq, + signedAxisDistancesSqZY >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqZY, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqZY, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsZY, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsZY, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsZY, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 7, newEdgeIsBetters); + + newEdgeIsBetters = math.select(signedAxisDistancesSqZZ > bestEdgeSignedAxisDistancesSq, + distancesSqZZ < bestEdgeDistancesSq, + signedAxisDistancesSqZZ >= 0f & bestEdgeDistancesSq >= 0f); + bestEdgeSignedAxisDistancesSq = math.select(bestEdgeSignedAxisDistancesSq, signedAxisDistancesSqZZ, newEdgeIsBetters); + bestEdgeDistancesSq = math.select(bestEdgeDistancesSq, distancesSqZZ, newEdgeIsBetters); + bestEdgeClosestAs = simd.select(bestEdgeClosestAs, closestAsZZ, newEdgeIsBetters); + bestEdgeClosestBs = simd.select(bestEdgeClosestBs, closestBsZZ, newEdgeIsBetters); + bestNormalBs = simd.select(bestNormalBs, bNormalsZZ, newEdgeIsBetters); + bestEdgeIds = math.select(bestEdgeIds, 8, newEdgeIsBetters); + UnityEngine.Debug.Log($"Best edge ids so far: {bestEdgeIds}"); + + //float bestEdgeSignedDistanceSq = math.cmin(bestEdgeSignedDistancesSq); + var absoluteDistanceIsValid = bestEdgeSignedAxisDistancesSq >= 0f; + bool anyAbsoluteDistanceIsValid = math.any(absoluteDistanceIsValid); + float4 maskedAbsoluteDistances = math.select(float.MaxValue, bestEdgeSignedAxisDistancesSq, absoluteDistanceIsValid); + bool4 isTheBestEdge = + math.select(bestEdgeSignedAxisDistancesSq == math.cmax(bestEdgeSignedAxisDistancesSq), + maskedAbsoluteDistances == math.cmin(maskedAbsoluteDistances), + anyAbsoluteDistanceIsValid); + int bestEdgeIndex = math.tzcnt(math.bitmask(isTheBestEdge)); + if (bestEdgeIndex > 3) + { + UnityEngine.Debug.LogError( + $"WTF? best: {bestEdgeSignedAxisDistancesSq}, masked: {maskedAbsoluteDistances}, any: {anyAbsoluteDistanceIsValid}, isTheBestEdge: {isTheBestEdge}"); + } + float bestEdgeSignedAxisDistanceSq = bestEdgeSignedAxisDistancesSq[bestEdgeIndex]; + float bestEdgeDistanceSq = bestEdgeDistancesSq[bestEdgeIndex]; + float3 bestEdgeClosestA = bestEdgeClosestAs[bestEdgeIndex]; + float3 bestEdgeClosestB = bestEdgeClosestBs[bestEdgeIndex]; + float3 bestEdgeNormalB = bestNormalBs[bestEdgeIndex]; + int bestId = bestEdgeIds[bestEdgeIndex]; + UnityEngine.Debug.Log($"Best Edge Index: {bestEdgeIndex}"); + + simdFloat3 topUnnormals = default; + simdFloat3 bottomUnnormals = default; + topUnnormals.x = math.select(-1f, 1f, new bool4(true, true, false, false)); + bottomUnnormals.x = topUnnormals.x; + bottomUnnormals.y = -1f; + topUnnormals.y = 1f; + topUnnormals.z = math.select(-1f, 1f, new bool4(true, false, true, false)); + bottomUnnormals.z = topUnnormals.z; + + float3 pointsNormalBFromBInA = simd.shuffle(topUnnormals, bottomUnnormals, (math.ShuffleComponent)bInABIndex); + float3 pointsNormalAFromAInB = simd.shuffle(topUnnormals, bottomUnnormals, (math.ShuffleComponent)aInBAIndex); + pointsNormalBFromBInA = math.normalize(math.rotate(bInASpace, pointsNormalBFromBInA)); + pointsNormalAFromAInB = math.normalize(pointsNormalAFromAInB); + float3 pointsNormalBFromAInB = math.select(0f, 1f, pointsClosestBInB == boxB.halfSize) + math.select(0f, -1f, pointsClosestBInB == -boxB.halfSize); + pointsNormalBFromAInB = math.normalize(math.rotate(bInASpace, pointsNormalBFromAInB)); + float3 pointsNormalAFromBInA = + math.normalize(math.select(0f, 1f, pointsClosestAInA == boxA.halfSize) + math.select(0f, -1f, pointsClosestAInA == -boxA.halfSize)); + float3 bestEdgeNormalA = math.normalize(math.select(0f, 1f, bestEdgeClosestA == boxA.halfSize) + math.select(0f, -1f, bestEdgeClosestA == -boxA.halfSize)); + int matchedBIndexFromEdgeTop = math.tzcnt(math.bitmask(bestEdgeClosestB == bTopPointsInAOS)); + int matchedBIndexFromEdgeBottom = math.tzcnt(math.bitmask((bestEdgeClosestB == bBottomPointsInAOS))) + 4; + int matchedIndexBFromEdge = math.select(matchedBIndexFromEdgeTop, matchedBIndexFromEdgeBottom, matchedBIndexFromEdgeBottom < 8); + float3 edgeNormalBAsCorner = simd.shuffle(topUnnormals, bottomUnnormals, (math.ShuffleComponent)math.clamp(matchedIndexBFromEdge, 0, 7)); + edgeNormalBAsCorner = math.normalize(edgeNormalBAsCorner); + bestEdgeNormalB = math.select(bestEdgeNormalB, edgeNormalBAsCorner, matchedIndexBFromEdge < 8); + + bool bInAIsBetter = math.select(pointsAxisDistanceInA >= pointsAxisDistanceInB, + pointsDistanceSqInA <= pointsDistanceSqInB, + pointsAxisDistanceInA >= 0f && pointsAxisDistanceInB >= 0f); + float pointsAxisDistance = math.select(pointsAxisDistanceInB, pointsAxisDistanceInA, bInAIsBetter); + float pointsDistanceSq = math.select(pointsDistanceSqInB, pointsDistanceSqInA, bInAIsBetter); + float3 pointsClosestA = math.select(math.transform(bInASpace, pointsClosestAInB + boxB.center), pointsClosestAInA + boxA.center, bInAIsBetter); + float3 pointsClosestB = math.select(math.transform(bInASpace, pointsClosestBInB + boxB.center), pointsClosestBInA + boxA.center, bInAIsBetter); + float3 pointsNormalA = math.select(pointsNormalAFromAInB, pointsNormalAFromBInA, bInAIsBetter); + float3 pointsNormalB = math.select(pointsNormalBFromAInB, pointsNormalBFromBInA, bInAIsBetter); + int pointsBestId = math.select(10, 9, bInAIsBetter); + + bool pointsIsBetter = math.select(pointsAxisDistance * pointsAxisDistance * math.sign(pointsAxisDistance) >= bestEdgeSignedAxisDistanceSq, + pointsDistanceSq <= bestEdgeDistanceSq, + pointsAxisDistance >= 0f && bestEdgeSignedAxisDistanceSq >= 0f); + float bestAxisDistance = math.select(math.sign(bestEdgeSignedAxisDistanceSq) * math.sqrt(math.abs(bestEdgeSignedAxisDistanceSq)), pointsAxisDistance, pointsIsBetter); + float bestDistanceSq = math.select(bestEdgeDistanceSq, pointsDistanceSq, pointsIsBetter); + float3 bestClosestA = math.select(bestEdgeClosestA + boxA.center, pointsClosestA, pointsIsBetter); + float3 bestClosestB = math.select(bestEdgeClosestB + boxA.center, pointsClosestB, pointsIsBetter); + float3 bestNormalA = math.select(bestEdgeNormalA, pointsNormalA, pointsIsBetter); + float3 bestNormalB = math.select(math.rotate(bInASpace, bestEdgeNormalB), pointsNormalB, pointsIsBetter); + bestId = math.select(bestId, pointsBestId, pointsIsBetter); + UnityEngine.Debug.Log( + $"bestId: {bestId}, bestAxisDistance: {bestAxisDistance}, bestDistanceSq: {bestDistanceSq}, bestClosestA: {bestClosestA}, bestClosestB: {bestClosestB}"); + + //Step 4: Build result + result = new ColliderDistanceResultInternal + { + hitpointA = bestClosestA, + hitpointB = bestClosestB, + normalA = bestNormalA, + normalB = bestNormalB, + distance = math.sign(bestAxisDistance) * math.sqrt(math.abs(bestDistanceSq)) + }; + return result.distance <= maxDistance; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxBox.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxBox.cs.meta new file mode 100644 index 0000000..ca1ac98 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxBox.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0d0478d020c327488882f7258fd8e46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxCapsule.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxCapsule.cs new file mode 100644 index 0000000..7f8d8a6 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxCapsule.cs @@ -0,0 +1,154 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + public static bool BoxCapsuleDistance(BoxCollider box, CapsuleCollider capsule, float maxDistance, out ColliderDistanceResultInternal result) + { + float3 osPointA = capsule.pointA - box.center; //os = origin space + float3 osPointB = capsule.pointB - box.center; + float3 pointsPointOnBox; + float3 pointsPointOnSegment; + float axisDistance; + //Step 1: Points vs Planes + { + float3 distancesToMin = math.max(osPointA, osPointB) + box.halfSize; + float3 distancesToMax = box.halfSize - math.min(osPointA, osPointB); + float3 bestDistances = math.min(distancesToMin, distancesToMax); + float bestDistance = math.cmin(bestDistances); + bool3 bestAxisMask = bestDistance == bestDistances; + //Prioritize y first, then z, then x if multiple distances perfectly match. + //Todo: Should this be configurabe? + bestAxisMask.xz &= !bestAxisMask.y; + bestAxisMask.x &= !bestAxisMask.z; + float3 zeroMask = math.select(0f, 1f, bestAxisMask); + bool useMin = (bestDistances * zeroMask).Equals(distancesToMin * zeroMask); + float aOnAxis = math.dot(osPointA, zeroMask); + float bOnAxis = math.dot(osPointB, zeroMask); + bool aIsGreater = aOnAxis > bOnAxis; + pointsPointOnSegment = math.select(osPointA, osPointB, useMin ^ aIsGreater); + pointsPointOnBox = math.select(pointsPointOnSegment, math.select(box.halfSize, -box.halfSize, useMin), bestAxisMask); + pointsPointOnBox = math.clamp(pointsPointOnBox, -box.halfSize, box.halfSize); + axisDistance = -bestDistance; + } + float signedDistanceSq = math.distancesq(pointsPointOnSegment, pointsPointOnBox); + signedDistanceSq = math.select(signedDistanceSq, -signedDistanceSq, axisDistance <= 0f); + + //Step 2: Edge vs Edges + //Todo: We could inline the SegmentSegment invocations to simplify the initial dot products. + float3 capsuleEdge = osPointB - osPointA; + simdFloat3 simdCapsuleEdge = new simdFloat3(capsuleEdge); + simdFloat3 simdOsPointA = new simdFloat3(osPointA); + //x-axes + simdFloat3 boxPointsX = new simdFloat3(new float3(-box.halfSize.x, box.halfSize.y, box.halfSize.z), + new float3(-box.halfSize.x, box.halfSize.y, -box.halfSize.z), + new float3(-box.halfSize.x, -box.halfSize.y, box.halfSize.z), + new float3(-box.halfSize.x, -box.halfSize.y, -box.halfSize.z)); + simdFloat3 boxEdgesX = new simdFloat3(new float3(2f * box.halfSize.x, 0f, 0f)); + QueriesLowLevelUtils.SegmentSegment(simdOsPointA, simdCapsuleEdge, boxPointsX, boxEdgesX, out simdFloat3 edgesClosestAsX, out simdFloat3 edgesClosestBsX); + simdFloat3 boxNormalsX = new simdFloat3(new float3(0f, math.SQRT2 / 2f, math.SQRT2 / 2f), + new float3(0f, math.SQRT2 / 2f, -math.SQRT2 / 2f), + new float3(0f, -math.SQRT2 / 2f, math.SQRT2 / 2f), + new float3(0f, -math.SQRT2 / 2f, -math.SQRT2 / 2f)); + //Imagine a line that goes perpendicular through a box's edge at the midpoint. + //All orientations of that line which do not penetrate the box (tangency is not considered penetrating in this case) are validly resolved collisions. + //Orientations of the line which do penetrate are not valid. + //If we constrain the capsule edge to be perpendicular, normalize it, and then compute the dot product, we can compare that to the necessary 45 degree angle + //where penetration occurs. Parallel lines are excluded because either we want to record a capsule point (step 1) or a perpendicular edge on the box. + bool notParallelX = !capsuleEdge.yz.Equals(float2.zero); + float4 alignmentsX = simd.dot(math.normalize(new float3(0f, capsuleEdge.yz)), boxNormalsX); + bool4 validsX = (math.abs(alignmentsX) <= math.SQRT2 / 2f) & notParallelX; + float4 signedDistancesSqX = simd.distancesq(edgesClosestAsX, edgesClosestBsX); + bool4 insidesX = + (math.abs(edgesClosestAsX.x) <= box.halfSize.x) & (math.abs(edgesClosestAsX.y) <= box.halfSize.y) & (math.abs(edgesClosestAsX.z) <= box.halfSize.z); + signedDistancesSqX = math.select(signedDistancesSqX, -signedDistancesSqX, insidesX); + signedDistancesSqX = math.select(float.MaxValue, signedDistancesSqX, validsX); + + //y-axis + simdFloat3 boxPointsY = new simdFloat3(new float3(box.halfSize.x, -box.halfSize.y, box.halfSize.z), + new float3(box.halfSize.x, -box.halfSize.y, -box.halfSize.z), + new float3(-box.halfSize.x, -box.halfSize.y, box.halfSize.z), + new float3(-box.halfSize.x, -box.halfSize.y, -box.halfSize.z)); + simdFloat3 boxEdgesY = new simdFloat3(new float3(0f, 2f * box.halfSize.y, 0f)); + QueriesLowLevelUtils.SegmentSegment(simdOsPointA, simdCapsuleEdge, boxPointsY, boxEdgesY, out simdFloat3 edgesClosestAsY, out simdFloat3 edgesClosestBsY); + simdFloat3 boxNormalsY = new simdFloat3(new float3(math.SQRT2 / 2f, 0f, math.SQRT2 / 2f), + new float3(math.SQRT2 / 2f, 0f, -math.SQRT2 / 2f), + new float3(-math.SQRT2 / 2f, 0f, math.SQRT2 / 2f), + new float3(-math.SQRT2 / 2f, 0f, -math.SQRT2 / 2f)); + bool notParallelY = !capsuleEdge.xz.Equals(float2.zero); + float4 alignmentsY = simd.dot(math.normalize(new float3(capsuleEdge.x, 0f, capsuleEdge.z)), boxNormalsY); + bool4 validsY = (math.abs(alignmentsY) <= math.SQRT2 / 2f) & notParallelY; + float4 signedDistancesSqY = simd.distancesq(edgesClosestAsY, edgesClosestBsY); + bool4 insidesY = + (math.abs(edgesClosestAsY.x) <= box.halfSize.x) & (math.abs(edgesClosestAsY.y) <= box.halfSize.y) & (math.abs(edgesClosestAsY.z) <= box.halfSize.z); + signedDistancesSqY = math.select(signedDistancesSqY, -signedDistancesSqY, insidesY); + signedDistancesSqY = math.select(float.MaxValue, signedDistancesSqY, validsY); + + //z-axis + simdFloat3 boxPointsZ = new simdFloat3(new float3(box.halfSize.x, box.halfSize.y, -box.halfSize.z), + new float3(box.halfSize.x, -box.halfSize.y, -box.halfSize.z), + new float3(-box.halfSize.x, box.halfSize.y, -box.halfSize.z), + new float3(-box.halfSize.x, -box.halfSize.y, -box.halfSize.z)); + simdFloat3 boxEdgesZ = new simdFloat3(new float3(0f, 0f, 2f * box.halfSize.z)); + QueriesLowLevelUtils.SegmentSegment(simdOsPointA, simdCapsuleEdge, boxPointsZ, boxEdgesZ, out simdFloat3 edgesClosestAsZ, out simdFloat3 edgesClosestBsZ); + simdFloat3 boxNormalsZ = new simdFloat3(new float3(math.SQRT2 / 2f, math.SQRT2 / 2f, 0f), + new float3(math.SQRT2 / 2f, -math.SQRT2 / 2f, 0f), + new float3(-math.SQRT2 / 2f, math.SQRT2 / 2f, 0f), + new float3(-math.SQRT2 / 2f, -math.SQRT2 / 2f, 0f)); + bool notParallelZ = !capsuleEdge.xy.Equals(float2.zero); + float4 alignmentsZ = simd.dot(math.normalize(new float3(capsuleEdge.xy, 0f)), boxNormalsZ); + bool4 validsZ = (math.abs(alignmentsZ) <= math.SQRT2 / 2f) & notParallelZ; + float4 signedDistancesSqZ = simd.distancesq(edgesClosestAsZ, edgesClosestBsZ); + bool4 insidesZ = + (math.abs(edgesClosestAsZ.x) <= box.halfSize.x) & (math.abs(edgesClosestAsZ.y) <= box.halfSize.y) & (math.abs(edgesClosestAsZ.z) <= box.halfSize.z); + signedDistancesSqZ = math.select(signedDistancesSqZ, -signedDistancesSqZ, insidesZ); + signedDistancesSqZ = math.select(float.MaxValue, signedDistancesSqZ, validsZ); + + //Step 3: Find best result + float4 bestAxisSignedDistancesSq = signedDistancesSqX; + simdFloat3 bestAxisPointsOnSegment = edgesClosestAsX; + simdFloat3 bestAxisPointsOnBox = edgesClosestBsX; + bool4 yWins = (signedDistancesSqY < bestAxisSignedDistancesSq) ^ ((bestAxisSignedDistancesSq < 0f) & (signedDistancesSqY < 0f)); + bestAxisSignedDistancesSq = math.select(bestAxisSignedDistancesSq, signedDistancesSqY, yWins); + bestAxisPointsOnSegment = simd.select(bestAxisPointsOnSegment, edgesClosestAsY, yWins); + bestAxisPointsOnBox = simd.select(bestAxisPointsOnBox, edgesClosestBsY, yWins); + bool4 zWins = (signedDistancesSqZ < bestAxisSignedDistancesSq) ^ ((bestAxisSignedDistancesSq < 0f) & (signedDistancesSqZ < 0f)); + bestAxisSignedDistancesSq = math.select(bestAxisSignedDistancesSq, signedDistancesSqZ, zWins); + bestAxisPointsOnSegment = simd.select(bestAxisPointsOnSegment, edgesClosestAsZ, zWins); + bestAxisPointsOnBox = simd.select(bestAxisPointsOnBox, edgesClosestBsZ, zWins); + bool bBeatsA = (bestAxisSignedDistancesSq.y < bestAxisSignedDistancesSq.x) ^ (math.all(bestAxisSignedDistancesSq.xy < 0f)); + bool dBeatsC = (bestAxisSignedDistancesSq.w < bestAxisSignedDistancesSq.z) ^ (math.all(bestAxisSignedDistancesSq.zw < 0f)); + float bestAbSignedDistanceSq = math.select(bestAxisSignedDistancesSq.x, bestAxisSignedDistancesSq.y, bBeatsA); + float bestCdSignedDistanceSq = math.select(bestAxisSignedDistancesSq.z, bestAxisSignedDistancesSq.w, dBeatsC); + float3 bestAbPointOnSegment = math.select(bestAxisPointsOnSegment.a, bestAxisPointsOnSegment.b, bBeatsA); + float3 bestCdPointOnSegment = math.select(bestAxisPointsOnSegment.c, bestAxisPointsOnSegment.d, dBeatsC); + float3 bestAbPointOnBox = math.select(bestAxisPointsOnBox.a, bestAxisPointsOnBox.b, bBeatsA); + float3 bestCdPointOnBox = math.select(bestAxisPointsOnBox.c, bestAxisPointsOnBox.d, dBeatsC); + bool cdBeatsAb = (bestCdSignedDistanceSq < bestAbSignedDistanceSq) ^ ((bestCdSignedDistanceSq < 0f) & (bestAbSignedDistanceSq < 0f)); + float bestSignedDistanceSq = math.select(bestAbSignedDistanceSq, bestCdSignedDistanceSq, cdBeatsAb); + float3 bestPointOnSegment = math.select(bestAbPointOnSegment, bestCdPointOnSegment, cdBeatsAb); + float3 bestPointOnBox = math.select(bestAbPointOnBox, bestCdPointOnBox, cdBeatsAb); + bool pointsBeatEdges = (signedDistanceSq <= bestSignedDistanceSq) ^ ((signedDistanceSq < 0f) & (bestSignedDistanceSq < 0f)); + bestSignedDistanceSq = math.select(bestSignedDistanceSq, signedDistanceSq, pointsBeatEdges); + bestPointOnSegment = math.select(bestPointOnSegment, pointsPointOnSegment, pointsBeatEdges); + bestPointOnBox = math.select(bestPointOnBox, pointsPointOnBox, pointsBeatEdges); + + //Step 4: Build result + float3 boxNormal = math.normalize(math.select(0f, 1f, bestPointOnBox == box.halfSize) + math.select(0f, -1f, bestPointOnBox == -box.halfSize)); + float3 capsuleNormal = math.normalizesafe(bestPointOnBox - bestPointOnSegment, -boxNormal); + capsuleNormal = math.select(capsuleNormal, -capsuleNormal, bestSignedDistanceSq < 0f); + result = new ColliderDistanceResultInternal + { + hitpointA = bestPointOnBox + box.center, + hitpointB = bestPointOnSegment + box.center + capsuleNormal * capsule.radius, + normalA = boxNormal, + normalB = capsuleNormal, + distance = math.sign(bestSignedDistanceSq) * math.sqrt(math.abs(bestSignedDistanceSq)) - capsule.radius + }; + + return result.distance <= maxDistance; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxCapsule.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxCapsule.cs.meta new file mode 100644 index 0000000..60fd282 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.BoxCapsule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b97f19c00dd8e84da03f8a1cecc11d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.UnityGjkEpa.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.UnityGjkEpa.cs new file mode 100644 index 0000000..4037c47 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.UnityGjkEpa.cs @@ -0,0 +1,572 @@ +using Unity.Mathematics; + +// This file contains several heavy general-purpose collision and spatial algorithms for convex shapes borrowed +// from Unity. The GJK + EPA implementation comes from Unity.Physics (ConvexConvexDistance.cs). It has been +// adapted to the Psyshock ecosystem. The support functions were replaced entirely. Other mechanisms like +// FourTransformedPoints were replaced with Psyshock equivalents. EPA relies heavily on ConvexHullBuilder +// and has been transferred mostly untouched. +// Overall, these two algorithms behave correctly in Psyshock, but are a little noisy, generating errors up to +// 5.5e-4 or worse. The SAT-based algorithms should be preferred where available. + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + // Todo: It would be nice if we could get the real normals out of Gjk, + // but due to precision issues, we could slide off a key feature during barycentric sampling. + // Fortunately, with the hit points, we can use point queries to extract the normals. + // But this means we can't use Gjk for point vs convex mesh distance queries + internal struct GjkResult + { + public float distance; + public float3 hitpointOnAInASpace; + public float3 hitpointOnBInASpace; + public float3 normalizedOriginToClosestCsoPoint; + } + + // From Unity.Physics ConvexConvexDistance.cs + // Mostly unmodified other than naming and interface + unsafe internal static GjkResult DoGjkEpa(Collider colliderA, Collider colliderB, RigidTransform bInASpace) + { + const float epsTerminationSq = 1e-8f; // Main loop quits when it cannot find a point that improves the simplex by at least this much + const float epsPenetrationSq = 1e-9f; // Epsilon used to check for penetration. + + // Initialize simplex with an arbitrary CSO support point + Simplex simplex = default; + simplex.numVertices = 1; + simplex.a = GetSupport(colliderA, colliderB, new float3(1f, 0f, 0f), bInASpace); + simplex.originToClosestPointUnscaledDirection = simplex.a.pos; + simplex.scaledDistance = math.lengthsq(simplex.a.pos); + float scaleSq = simplex.scaledDistance; + + // Iterate. + int iteration = 0; + bool penetration = false; + const int maxIterations = 64; // Todo: This seems arbitrary + for (; iteration < maxIterations; ++iteration) + { + // Find a new support vertex + SupportPoint newSupportPoint = GetSupport(colliderA, colliderB, -simplex.originToClosestPointUnscaledDirection, bInASpace); + + // If the new vertex is not significantly closer to the origin, quit + float scaledImprovement = math.dot(simplex.a.pos - newSupportPoint.pos, simplex.originToClosestPointUnscaledDirection); + if (scaledImprovement * math.abs(scaledImprovement) < epsTerminationSq * scaleSq) + { + break; + } + + // Add the new vertex and reduce the simplex + switch (simplex.numVertices++) + { + case 1: simplex.b = newSupportPoint; break; + case 2: simplex.c = newSupportPoint; break; + default: simplex.d = newSupportPoint; break; + } + simplex.SolveClosestToOriginAndReduce(); + + // Check for penetration + scaleSq = math.lengthsq(simplex.originToClosestPointUnscaledDirection); + float scaledDistanceSq = simplex.scaledDistance * simplex.scaledDistance; + // Todo: the numVertices == 4 makes sense as that only happens if the simplex contains the origin. + // However, I still don't understand the second half of this expression. These scale factors don't make much sense. + if (simplex.numVertices == 4 || scaledDistanceSq <= epsPenetrationSq * scaleSq) + { + penetration = true; + break; + } + } + + UnityEngine.Assertions.Assert.IsTrue(iteration < maxIterations); + + // Finalize result. + GjkResult result = default; + float3 normalizedOriginToClosestCsoPoint = default; + + // Handle penetration. + if (penetration) + { + // Allocate a hull for EPA + int verticesCapacity = 64; + int triangleCapacity = 2 * verticesCapacity; + ConvexHullBuilder.Vertex* vertices = stackalloc ConvexHullBuilder.Vertex[verticesCapacity]; + ConvexHullBuilder.Triangle* triangles = stackalloc ConvexHullBuilder.Triangle[triangleCapacity]; + Aabb domain = GetCsoAabb(colliderA, colliderB, bInASpace); + const float simplificationTolerance = 0.0f; + var hull = new ConvexHullBuilder(verticesCapacity, vertices, triangles, null, + domain, simplificationTolerance, ConvexHullBuilder.IntResolution.Low); + const float k_epaEpsilon = 1e-4f; + + // Add simplex vertices to the hull, remove any vertices from the simplex that do not increase the hull dimension + hull.AddPoint(simplex.a.pos, simplex.a.id); + if (simplex.numVertices > 1) + { + hull.AddPoint(simplex.b.pos, simplex.b.id); + if (simplex.numVertices > 2) + { + int dimension = hull.dimension; + hull.AddPoint(simplex.c.pos, simplex.c.id); + if (dimension == 0 && hull.dimension == 1) + { + simplex.b = simplex.c; + } + if (simplex.numVertices > 3) + { + dimension = hull.dimension; + hull.AddPoint(simplex.d.pos, simplex.d.id); + if (dimension > hull.dimension) + { + if (dimension == 0) + { + simplex.b = simplex.d; + } + else if (dimension == 1) + { + simplex.c = simplex.d; + } + } + } + } + } + simplex.numVertices = (hull.dimension + 1); + + // If the simplex is not 3D, try expanding the hull in all directions + while (hull.dimension < 3) + { + // Choose expansion directions + float3 support0, support1, support2; + switch (simplex.numVertices) + { + case 1: + support0 = new float3(1, 0, 0); + support1 = new float3(0, 1, 0); + support2 = new float3(0, 0, 1); + break; + case 2: + mathex.GetDualPerpendicularNormalized(math.normalize(simplex.b.pos - simplex.a.pos), out support0, out support1); + support2 = float3.zero; + break; + default: + UnityEngine.Assertions.Assert.IsTrue(simplex.numVertices == 3); + support0 = math.cross(simplex.b.pos - simplex.a.pos, simplex.c.pos - simplex.a.pos); + support1 = float3.zero; + support2 = float3.zero; + break; + } + + // Try each one + int numSupports = 4 - simplex.numVertices; + bool success = false; + for (int i = 0; i < numSupports; i++) + { + for (int j = 0; j < 2; j++) // +/- each direction + { + SupportPoint vertex = GetSupport(colliderA, colliderB, support0, bInASpace); + hull.AddPoint(vertex.pos, vertex.id); + if (hull.dimension == simplex.numVertices) + { + switch (simplex.numVertices) + { + case 1: simplex.b = vertex; break; + case 2: simplex.c = vertex; break; + default: simplex.d = vertex; break; + } + + // Next dimension + success = true; + simplex.numVertices++; + i = numSupports; + break; + } + support0 = -support0; + } + support0 = support1; + support1 = support2; + } + + if (!success) + { + break; + } + } + + // We can still fail to build a tetrahedron if the minkowski difference is really flat. + // In those cases just find the closest point to the origin on the infinite extension of the simplex (point / line / plane) + if (hull.dimension != 3) + { + switch (simplex.numVertices) + { + case 1: + { + result.distance = math.length(simplex.a.pos); + normalizedOriginToClosestCsoPoint = -math.normalizesafe(simplex.a.pos, new float3(1f, 0f, 0f)); + break; + } + case 2: + { + float3 edge = math.normalize(simplex.b.pos - simplex.a.pos); + float3 direction = math.cross(math.cross(edge, simplex.a.pos), edge); + mathex.GetDualPerpendicularNormalized(edge, out float3 safeNormal, out _); // backup, take any direction perpendicular to the edge + float3 normal = math.normalizesafe(direction, safeNormal); + result.distance = math.dot(normal, simplex.a.pos); + normalizedOriginToClosestCsoPoint = -normal; + break; + } + default: + { + UnityEngine.Assertions.Assert.IsTrue(simplex.numVertices == 3); + float3 cross = math.cross(simplex.b.pos - simplex.a.pos, simplex.c.pos - simplex.a.pos); + float crossLengthSq = math.lengthsq(cross); + if (crossLengthSq < 1e-8f) // hull builder can accept extremely thin triangles for which we cannot compute an accurate normal + { + simplex.numVertices = 2; + goto case 2; + } + float3 normal = cross * math.rsqrt(crossLengthSq); + float dot = math.dot(normal, simplex.a.pos); + result.distance = math.abs(dot); + normalizedOriginToClosestCsoPoint = math.select(-normal, normal, dot < 0f); + break; + } + } + } + else + { + int closestTriangleIndex; + Plane closestPlane = new Plane(); + float stopThreshold = k_epaEpsilon; + uint* uidsCache = stackalloc uint[triangleCapacity]; + for (int i = 0; i < triangleCapacity; i++) + { + uidsCache[i] = 0; + } + float* distancesCache = stackalloc float[triangleCapacity]; + do + { + // Select closest triangle. + closestTriangleIndex = -1; + foreach (int triangleIndex in hull.triangles.indices) + { + if (hull.triangles[triangleIndex].uid != uidsCache[triangleIndex]) + { + uidsCache[triangleIndex] = hull.triangles[triangleIndex].uid; + distancesCache[triangleIndex] = hull.ComputePlane(triangleIndex).distanceFromOrigin; + } + if (closestTriangleIndex == -1 || distancesCache[closestTriangleIndex] < distancesCache[triangleIndex]) + { + closestTriangleIndex = triangleIndex; + } + } + closestPlane = hull.ComputePlane(closestTriangleIndex); + + // Add supporting vertex or exit. + SupportPoint sv = GetSupport(colliderA, colliderB, closestPlane.normal, bInASpace); + float d2P = math.dot(closestPlane.normal, sv.pos) + closestPlane.distanceFromOrigin; + if (math.abs(d2P) > stopThreshold && hull.AddPoint(sv.pos, sv.id)) + stopThreshold *= 1.3f; + else + break; + } + while (++iteration < maxIterations); + + // There could be multiple triangles in the closest plane, pick the one that has the closest point to the origin on its face + foreach (int triangleIndex in hull.triangles.indices) + { + if (distancesCache[triangleIndex] >= closestPlane.distanceFromOrigin - k_epaEpsilon) + { + ConvexHullBuilder.Triangle triangle = hull.triangles[triangleIndex]; + float3 a = hull.vertices[triangle.vertex0].position; + float3 b = hull.vertices[triangle.vertex1].position; + float3 c = hull.vertices[triangle.vertex2].position; + float3 cross = math.cross(b - a, c - a); + float3 dets = new float3( + math.dot(math.cross(a - c, cross), a), + math.dot(math.cross(b - a, cross), b), + math.dot(math.cross(c - b, cross), c)); + if (math.all(dets >= 0)) + { + Plane plane = hull.ComputePlane(triangleIndex); + if (math.dot(plane.normal, closestPlane.normal) > (1f - k_epaEpsilon)) + { + closestTriangleIndex = triangleIndex; + closestPlane = hull.ComputePlane(triangleIndex); + } + break; + } + } + } + + // Generate simplex. + { + ConvexHullBuilder.Triangle triangle = hull.triangles[closestTriangleIndex]; + simplex.numVertices = 3; + simplex.a.pos = hull.vertices[triangle.vertex0].position; simplex.a.id = hull.vertices[triangle.vertex0].userData; + simplex.b.pos = hull.vertices[triangle.vertex1].position; simplex.b.id = hull.vertices[triangle.vertex1].userData; + simplex.c.pos = hull.vertices[triangle.vertex2].position; simplex.c.id = hull.vertices[triangle.vertex2].userData; + simplex.originToClosestPointUnscaledDirection = -closestPlane.normal; + simplex.scaledDistance = closestPlane.distanceFromOrigin; + + // Set normal and distance. + normalizedOriginToClosestCsoPoint = -closestPlane.normal; + result.distance = closestPlane.distanceFromOrigin; + } + } + } + else + { + // Compute distance and normal. + float lengthSq = math.lengthsq(simplex.originToClosestPointUnscaledDirection); + float invLength = math.rsqrt(lengthSq); + bool smallLength = lengthSq == 0; + //ret.ClosestPoints.Distance = math.select(simplex.scaledDistance * invLength, 0.0f, smallLength); + result.distance = math.select(simplex.scaledDistance * invLength, 0f, smallLength); + // If the distance is 0, then the direction shouldn't matter, so pick something safe. + normalizedOriginToClosestCsoPoint = math.select(simplex.originToClosestPointUnscaledDirection * invLength, new float3(1f, 0f, 0f), smallLength); + + // Todo: I don't understand how the commented out check would fail without result.distance being broken as well. So I added this Assert instead. + UnityEngine.Assertions.Assert.IsTrue(math.all(math.isfinite(normalizedOriginToClosestCsoPoint))); + + // Make sure the normal is always valid. + //if (!math.all(math.isfinite(normalizedHitpointBToHitpointA))) + //{ + // ret.ClosestPoints.NormalInA = new float3(1, 0, 0); + //} + } + + // Compute position. + float3 closestPoint = normalizedOriginToClosestCsoPoint * result.distance; + float4 coordinates = simplex.ComputeBarycentricCoordinates(closestPoint); + result.hitpointOnAInASpace = GetSupport(colliderA, simplex.a.idA) * coordinates.x + + GetSupport(colliderA, simplex.b.idA) * coordinates.y + + GetSupport(colliderA, simplex.c.idA) * coordinates.z + + GetSupport(colliderA, simplex.d.idA) * coordinates.w; + + // Done. + UnityEngine.Assertions.Assert.IsTrue(math.isfinite(result.distance)); + UnityEngine.Assertions.Assert.IsTrue(math.abs(math.lengthsq(normalizedOriginToClosestCsoPoint) - 1.0f) < 1e-5f); + + // Patch distance with radius + float radialA = GetRadialPadding(colliderA); + float radialB = GetRadialPadding(colliderB); + result.hitpointOnAInASpace += normalizedOriginToClosestCsoPoint * radialA; + result.distance -= radialA; + result.distance -= radialB; + result.hitpointOnBInASpace = result.hitpointOnAInASpace - normalizedOriginToClosestCsoPoint * result.distance; + result.normalizedOriginToClosestCsoPoint = normalizedOriginToClosestCsoPoint; + return result; + } + + private struct Simplex + { + public SupportPoint a, b, c, d; + public float3 originToClosestPointUnscaledDirection; // Points from the origin towards the closest point on the simplex + public float scaledDistance; // closestPoint = originToClosestPointUnscaledDirection * scaledDistance / lengthSq(originToClosestPointUnscaledDirection) + public int numVertices; + + /// + /// Compute the closest point on the simplex, returns true if the simplex contains a duplicate vertex + /// + public void SolveClosestToOriginAndReduce() + { + switch (numVertices) + { + // Point. + case 1: + // closestPoint = a.pos * scaledDistance / scaled Distance = a.pos * 1f = a.pos + originToClosestPointUnscaledDirection = a.pos; + scaledDistance = math.lengthsq(originToClosestPointUnscaledDirection); + break; + + // Line. + case 2: + { + float3 ab = b.pos - a.pos; + float den = math.dot(ab, ab); + float num = math.dot(-a.pos, ab); //math.dot(aToOrigin, ab) + + // Reduce if closest point does not project on the line segment. + if (num >= den) + { + numVertices = 1; + a = b; + goto case 1; + } + + // Get the unscaled direction from the origin to the closest point on the line by finding the perpendicular vector from oa to ab, + // then finding the perpendicular vector from that and ab. + originToClosestPointUnscaledDirection = math.cross(math.cross(ab, a.pos), ab); + // Todo: Understand chained cross product magnitude so that this scaling makes sense. + scaledDistance = math.dot(originToClosestPointUnscaledDirection, a.pos); + } + break; + + // Triangle. + case 3: + { + //Todo: Need to break this down further. Still don't fully understand this. + float3 ca = a.pos - c.pos; + float3 cb = b.pos - c.pos; + float3 n = math.cross(cb, ca); + + // Reduce if the closest point does not project in the triangle. + float3 unscaledCbNormal = math.cross(cb, n); + float detCbnOB = math.dot(unscaledCbNormal, b.pos); + float detNcaOC = math.determinant(new float3x3(n, ca, c.pos)); + if (detCbnOB < 0) // if origin to b points opposite to unscaledCbNormal + { + // if origin to c points aligned to unscaledCaNormal or origin to c points opposite to unscaledCbNormal + if (detNcaOC >= 0 || math.determinant(new float3x3(n, unscaledCbNormal, c.pos)) < 0) + { + a = b; + } + } + else if (detNcaOC >= 0) + { + float dot = math.dot(c.pos, n); + if (dot < 0) + { + // Reorder vertices so that n points away from the origin + SupportPoint temp = a; + a = b; + b = temp; + n = -n; + dot = -dot; + } + originToClosestPointUnscaledDirection = n; + scaledDistance = dot; + break; + } + + b = c; + numVertices = 2; + goto case 2; + } + + // Tetrahedra. + case 4: + { + // Todo: I don't fully understand this case either. Need to draw it out. + simdFloat3 tetra = new simdFloat3(a.pos, b.pos, c.pos, d.pos); + + // This routine finds the closest feature to the origin on the tetra by testing the origin against the planes of the + // voronoi diagram. If the origin is near the border of two regions in the diagram, then the plane tests might exclude + // it from both because of float rounding. To avoid this problem we use some tolerance testing the face planes and let + // EPA handle those border cases. 1e-5 is a somewhat arbitrary value and the actual distance scales with the tetra, so + // this might need to be tuned later! + float3 faceTest = simd.dot(simd.cross(tetra, tetra.bcad), d.pos).xyz; + if (math.all(faceTest >= -1e-5f)) + { + // Origin is inside the tetra + originToClosestPointUnscaledDirection = float3.zero; + break; + } + + // Check if the closest point is on a face + bool3 insideFace = (faceTest >= 0).xyz; + simdFloat3 edges = d.pos - tetra; + simdFloat3 normals = simd.cross(edges, edges.bcad); + bool3 insideEdge0 = (simd.dot(simd.cross(normals, edges), d.pos) >= 0f).xyz; + bool3 insideEdge1 = (simd.dot(simd.cross(edges.bcad, normals), d.pos) >= 0f).xyz; + bool3 onFace = insideEdge0 & insideEdge1 & !insideFace; + if (math.any(onFace)) + { + if (onFace.y) + { + a = b; b = c; + } + else if (onFace.z) + { + b = c; + } + } + else + { + // Check if the closest point is on an edge + // TODO maybe we can safely drop two vertices in this case + bool3 insideVertex = (simd.dot(edges, d.pos) >= 0f).xyz; + bool3 onEdge = (!insideEdge0 & !insideEdge1.zxy & insideVertex); + if (math.any(onEdge.yz)) + { + a = b; b = c; + } + } + + c = d; + numVertices = 3; + goto case 3; + } + } + } + + // Compute the barycentric coordinates of the closest point. + public float4 ComputeBarycentricCoordinates(float3 closestPoint) + { + // Todo: Understand the inner workings of this function. Doesn't stop me from sneaking in some simdFloat3 though ;) + float4 coordinates = float4.zero; + switch (numVertices) + { + case 1: + coordinates.x = 1; + break; + case 2: + float distance = math.distance(a.pos, b.pos); + UnityEngine.Assertions.Assert.AreNotEqual(distance, 0.0f); // TODO just checking if this happens in my tests + if (distance == 0.0f) // Very rare case, simplex is really 1D. + { + goto case 1; + } + coordinates.x = math.distance(b.pos, closestPoint) / distance; + coordinates.y = 1 - coordinates.x; + break; + case 3: + { + simdFloat3 abc = new simdFloat3(a.pos, b.pos, c.pos, d.pos); + + //coordinates.x = math.length(math.cross(b.pos - closestPoint, c.pos - closestPoint)); + //coordinates.y = math.length(math.cross(c.pos - closestPoint, a.pos - closestPoint)); + //coordinates.z = math.length(math.cross(a.pos - closestPoint, b.pos - closestPoint)); + coordinates.xyz = simd.length(simd.cross(abc.bcad - closestPoint, abc.cabd - closestPoint)).xyz; + float sum = math.csum(coordinates.xyz); + if (sum == 0.0f) // Very rare case, simplex is really 2D. Happens because of int->float conversion from the hull builder. + { + // Choose the two farthest apart vertices to keep + float3 lengthsSq = simd.distancesq(abc, abc.bcad).xyz; + bool3 longest = math.cmin(lengthsSq) == lengthsSq; + if (longest.y) + { + a.pos = c.pos; + } + else if (longest.z) + { + a.pos = b.pos; + b.pos = c.pos; + } + coordinates.z = 0.0f; + numVertices = 2; + goto case 2; + } + coordinates /= sum; + break; + } + case 4: + { + simdFloat3 abcd = new simdFloat3(a.pos, b.pos, c.pos, d.pos); + coordinates = simd.dot(simd.cross(abcd.ddda, abcd.cabb), abcd.bcac); // four determinants + float sum = math.csum(coordinates); + UnityEngine.Assertions.Assert.AreNotEqual(sum, 0.0f); // TODO just checking that this doesn't happen in my tests + if (sum == 0.0f) // Unexpected case, may introduce significant error by dropping a vertex but it's better than nan + { + coordinates.zw = 0.0f; + numVertices = 3; + goto case 3; + } + coordinates /= sum; + break; + } + } + + return coordinates; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.UnityGjkEpa.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.UnityGjkEpa.cs.meta new file mode 100644 index 0000000..20263f8 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.UnityGjkEpa.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9ab9dcc4ddca17448f7b4b82beb1469 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.cs new file mode 100644 index 0000000..927883c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.cs @@ -0,0 +1,204 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + public struct ColliderDistanceResultInternal + { + public float3 hitpointA; + public float3 hitpointB; + public float3 normalA; + public float3 normalB; + public float distance; + } + + #region Sphere + public static bool SphereSphereDistance(SphereCollider sphereA, SphereCollider sphereB, float maxDistance, out ColliderDistanceResultInternal result) + { + float3 delta = sphereB.center - sphereA.center; + float ccDistanceSq = math.lengthsq(delta); //center center distance + bool distanceIsZero = ccDistanceSq == 0.0f; + float invCCDistance = math.select(math.rsqrt(ccDistanceSq), 0.0f, distanceIsZero); + float3 normalA = math.select(delta * invCCDistance, new float3(0, 1, 0), distanceIsZero); // choose an arbitrary normal when the distance is zero + float distance = ccDistanceSq * invCCDistance - sphereA.radius - sphereB.radius; + result = new ColliderDistanceResultInternal + { + hitpointA = sphereA.center + normalA * sphereA.radius, + hitpointB = sphereA.center + normalA * (sphereA.radius + distance), //hitpoint A + A's normal * distance [expand distributive property] + normalA = normalA, + normalB = -normalA, + distance = distance, + }; + return distance <= maxDistance; + } + #endregion Sphere + + #region Capsule + public static bool CapsuleSphereDistance(CapsuleCollider capsule, SphereCollider sphere, float maxDistance, out ColliderDistanceResultInternal result) + { + //Strategy: Project p onto the capsule's line clamped to the segment. Then inflate point on line as sphere + float3 edge = capsule.pointB - capsule.pointA; + float3 ap = sphere.center - capsule.pointA; + float dot = math.dot(ap, edge); + float edgeLengthSq = math.lengthsq(edge); + dot = math.clamp(dot, 0f, edgeLengthSq); + float3 pointOnSegment = capsule.pointA + edge * dot / edgeLengthSq; + SphereCollider sphereA = new SphereCollider(pointOnSegment, capsule.radius); + return SphereSphereDistance(sphereA, sphere, maxDistance, out result); + } + + public static bool CapsuleCapsuleDistance(CapsuleCollider capsuleA, CapsuleCollider capsuleB, float maxDistance, out ColliderDistanceResultInternal result) + { + float3 edgeA = capsuleA.pointB - capsuleA.pointA; + float3 edgeB = capsuleB.pointB - capsuleB.pointA; + + QueriesLowLevelUtils.SegmentSegment(capsuleA.pointA, edgeA, capsuleB.pointA, edgeB, out float3 closestA, out float3 closestB); + //Todo: There may be some precision issues at close distances. Figure this out later. + SphereCollider sphereA = new SphereCollider(closestA, capsuleA.radius); + SphereCollider sphereB = new SphereCollider(closestB, capsuleB.radius); + return SphereSphereDistance(sphereA, sphereB, maxDistance, out result); + } + #endregion Capsule + + #region Box + public static bool BoxSphereDistance(BoxCollider box, SphereCollider sphere, float maxDistance, out ColliderDistanceResultInternal result) + { + bool hit = PointBoxDistance(sphere.center, box, maxDistance + sphere.radius, out PointDistanceResultInternal pointDistanceResult); + float3 normalB = math.normalizesafe(pointDistanceResult.hitpoint - sphere.center, -pointDistanceResult.normal); + result = new ColliderDistanceResultInternal + { + distance = pointDistanceResult.distance - sphere.radius, + hitpointA = pointDistanceResult.hitpoint, + hitpointB = sphere.center + normalB * sphere.radius, + normalA = pointDistanceResult.normal, + normalB = normalB, + }; + return hit; + } + + //The following is defined in DistanceQueries.Collider.BoxCapsule + //public static bool DistanceBetween(BoxCollider box, CapsuleCollider capsule, float maxDistance, out ColliderDistanceResultInternal result) + + //The following is defined in DistanceQueries.Collider.BoxBox + //public static bool DistanceBetween(BoxCollider boxA, + // BoxCollider boxB, + // RigidTransform bInASpace, + // RigidTransform aInBSpace, + // float maxDistance, + // out ColliderDistanceResultInternal result) + #endregion Box + + #region Triangle + public static bool TriangleSphereDistance(TriangleCollider triangle, SphereCollider sphere, float maxDistance, out ColliderDistanceResultInternal result) + { + bool hit = PointTriangleDistance(sphere.center, triangle, maxDistance + sphere.radius, out PointDistanceResultInternal pointDistanceResult); + float3 normalB = math.normalizesafe(pointDistanceResult.hitpoint - sphere.center, -pointDistanceResult.normal); + result = new ColliderDistanceResultInternal + { + distance = pointDistanceResult.distance - sphere.radius, + hitpointA = pointDistanceResult.hitpoint, + hitpointB = sphere.center + normalB * sphere.radius, + normalA = pointDistanceResult.normal, + normalB = normalB, + }; + return hit; + } + + public static bool TriangleCapsuleDistance(TriangleCollider triangle, CapsuleCollider capsule, float maxDistance, out ColliderDistanceResultInternal result) + { + // The strategy for this is different from Unity Physics, but is inspired by the capsule-capsule algorithm + // and this blog: https://wickedengine.net/2020/04/26/capsule-collision-detection/ + // The idea is to reorder the checks so that the axis intersection branch culls some more math. + simdFloat3 triPoints = new simdFloat3(triangle.pointA, triangle.pointB, triangle.pointC, triangle.pointA); + simdFloat3 triEdges = triPoints.bcaa - triPoints; + + float3 capEdge = capsule.pointB - capsule.pointA; + QueriesLowLevelUtils.SegmentSegment(triPoints, triEdges, new simdFloat3(capsule.pointA), new simdFloat3(capEdge), out var closestTriEdges, out var closestCapsuleAxis); + float3 segSegDists = simd.distancesq(closestTriEdges, closestCapsuleAxis).xyz; + bool bIsBetter = segSegDists.y < segSegDists.x; + float3 closestEdgePoint = math.select(closestTriEdges.a, closestTriEdges.b, bIsBetter); + float3 closestAxisPoint = math.select(closestCapsuleAxis.a, closestCapsuleAxis.b, bIsBetter); + bool cIsBetter = segSegDists.z < math.cmin(segSegDists.xy); + closestEdgePoint = math.select(closestEdgePoint, closestTriEdges.c, cIsBetter); + closestAxisPoint = math.select(closestAxisPoint, closestCapsuleAxis.c, cIsBetter); + + if (RaycastTriangle(new Ray(capsule.pointA, capsule.pointB), triPoints, out float fraction, out _)) + { + float3 triNormal = math.normalizesafe(math.cross(triEdges.a, triEdges.b), math.normalizesafe(capEdge, 0f)); + float minFractionOffset = math.min(fraction, 1f - fraction); + // This is how much we have to move the axis along the triangle through the axis to achieve separation. + float dot = math.dot(triNormal, capEdge); + + float offsetDistance = minFractionOffset * math.abs(dot); + + if (offsetDistance * offsetDistance <= math.distancesq(closestEdgePoint, closestAxisPoint)) + { + bool useCapB = 1f - fraction < fraction; + triNormal = math.select(triNormal, -triNormal, (dot < 0f) ^ useCapB); + float3 capsuleOffset = triNormal * (offsetDistance + capsule.radius); + SphereCollider sphere = new SphereCollider(math.select(capsule.pointA, capsule.pointB, useCapB) + capsuleOffset, capsule.radius); + TriangleSphereDistance(triangle, sphere, maxDistance, out result); + result.distance = -offsetDistance; + result.hitpointB -= capsuleOffset; + return true; + } + else + { + SphereCollider axisSphere = new SphereCollider(closestAxisPoint, capsule.radius); + SphereCollider edgeSphere = new SphereCollider(closestEdgePoint, 0f); + // This gives us the positive distance from the capsule to edge. + // The penetration point is the opposite side of the capsule. + SphereSphereDistance(edgeSphere, axisSphere, float.MaxValue, out result); + result.distance = -result.distance - capsule.radius; + result.normalB = -result.normalB; + result.hitpointB += result.normalB * 2f * capsule.radius; + return true; + } + } + else + { + SphereCollider axisSphere = new SphereCollider(closestAxisPoint, capsule.radius); + bool hitAxis = TriangleSphereDistance(triangle, axisSphere, maxDistance, out var axisResult); + SphereCollider aSphere = new SphereCollider(capsule.pointA, capsule.radius); + bool hitA = TriangleSphereDistance(triangle, aSphere, maxDistance, out var aResult); + SphereCollider bSphere = new SphereCollider(capsule.pointB, capsule.radius); + bool hitB = TriangleSphereDistance(triangle, bSphere, maxDistance, out var bResult); + if (!hitAxis && !hitA && !hitB) + { + result = axisResult; + return false; + } + + result = default; + result.distance = float.MaxValue; + if (hitAxis) + result = axisResult; + if (hitA && aResult.distance < result.distance) + result = aResult; + if (hitB && bResult.distance < result.distance) + result = bResult; + return true; + } + } + #endregion + + #region Convex + public static bool ConvexSphereDistance(ConvexCollider convex, SphereCollider sphere, float maxDistance, out ColliderDistanceResultInternal result) + { + bool hit = PointConvexDistance(sphere.center, convex, maxDistance + sphere.radius, out PointDistanceResultInternal pointDistanceResult); + float3 normalB = math.normalizesafe(pointDistanceResult.hitpoint - sphere.center, -pointDistanceResult.normal); + result = new ColliderDistanceResultInternal + { + distance = pointDistanceResult.distance - sphere.radius, + hitpointA = pointDistanceResult.hitpoint, + hitpointB = sphere.center + normalB * sphere.radius, + normalA = pointDistanceResult.normal, + normalB = normalB, + }; + return hit; + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.cs.meta new file mode 100644 index 0000000..2bdf9f6 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/ColliderDistance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: deb6e27570d34984fa97a7b7afce0df1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindObjectsInternal.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindObjectsInternal.cs new file mode 100644 index 0000000..fd681dc --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindObjectsInternal.cs @@ -0,0 +1,41 @@ +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +//Todo: Stream types, single schedulers, scratchlists, and inflations +namespace Latios.Psyshock +{ + public partial struct FindObjectsConfig where T : struct, IFindObjectsProcessor + { + internal static class FindObjectsInternal + { + #region Jobs + [BurstCompile] + public struct Single : IJob + { + [ReadOnly] public CollisionLayer layer; + public T processor; + public Aabb aabb; + + public void Execute() + { + LayerQuerySweepMethods.AabbSweep(aabb, layer, ref processor); + } + } + #endregion + + #region ImmediateMethods + public static void RunImmediate(Aabb aabb, CollisionLayer layer, T processor) + { + LayerQuerySweepMethods.AabbSweep(aabb, layer, ref processor); + } + #endregion + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindObjectsInternal.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindObjectsInternal.cs.meta new file mode 100644 index 0000000..cd29160 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindObjectsInternal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01fb7f25d0e3ef34da0596d2e2f923ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsInternal.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsInternal.cs new file mode 100644 index 0000000..60586e8 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsInternal.cs @@ -0,0 +1,1045 @@ +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +//Todo: Stream types, single schedulers, scratchlists, and inflations +namespace Latios.Psyshock +{ + public partial struct FindPairsLayerSelfConfig where T : struct, IFindPairsProcessor + { + internal static class FindPairsInternal + { + #region Jobs + [BurstCompile] + public struct LayerSelfSingle : IJob + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute() + { + RunImmediate(layer, processor); + } + } + + [BurstCompile] + public struct LayerSelfPart1 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute(int index) + { + var bucket = layer.GetBucketSlices(index); + FindPairsSweepMethods.SelfSweep(layer, bucket, index, ref processor); + } + } + + [BurstCompile] + public struct LayerSelfPart2 : IJob + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute() + { + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layer, layer, bucket, crossBucket, layer.BucketCount + i, ref processor); + } + } + } + + [BurstCompile] + public struct LayerSelfParallelUnsafe : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute(int i) + { + if (i < layer.BucketCount) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.SelfSweep(layer, bucket, i, ref processor); + } + else + { + i -= layer.BucketCount; + var bucket = layer.GetBucketSlices(i); + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweep(layer, layer, bucket, crossBucket, i + layer.BucketCount, ref processor); + } + } + } + #endregion + + #region ImmediateMethods + public static void RunImmediate(CollisionLayer layer, T processor) + { + int jobIndex = 0; + for (int i = 0; i < layer.BucketCount; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.SelfSweep(layer, bucket, jobIndex++, ref processor, false); + } + + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layer, layer, bucket, crossBucket, jobIndex++, ref processor, false); + } + } + #endregion + + #region SafeChecks + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Scheudle for 2 iterations + [BurstCompile] + public struct LayerSelfPart2_WithSafety : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute(int index) + { + if (index == 0) + { + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layer, layer, bucket, crossBucket, layer.BucketCount + i, ref processor); + } + } + else + { + EntityAliasCheck(layer); + } + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void EntityAliasCheck(CollisionLayer layer) + { + var hashSet = new NativeParallelHashSet(layer.Count, Allocator.Temp); + for (int i = 0; i < layer.Count; i++) + { + if (!hashSet.Add(layer.bodies[i].entity)) + { + var entity = layer.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using a layer containing more than one instance of Entity {entity}"); + } + } + } +#endif + + #endregion + } + } + + public partial struct FindPairsLayerSelfWithCrossCacheConfig where T : struct, IFindPairsProcessor + { + internal static class FindPairsInternal + { + #region Jobs + + // Schedule for (2 * layer.BucketCount - 1) iterations + [BurstCompile] + public struct LayerSelfPart1 : IJobParallelFor, IFindPairsProcessor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + [NativeDisableParallelForRestriction] public NativeStream.Writer cache; + + public void Execute(int index) + { + if (index < layer.BucketCount) + { + var bucket = layer.GetBucketSlices(index); + FindPairsSweepMethods.SelfSweep(layer, bucket, index, ref processor); + } + else + { + var bucket = layer.GetBucketSlices(index - layer.BucketCount); + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + + cache.BeginForEachIndex(index - layer.BucketCount); + FindPairsSweepMethods.BipartiteSweep(layer, layer, bucket, crossBucket, index, ref this); + cache.EndForEachIndex(); + } + } + + public void Execute(in FindPairsResult result) + { + int2 pair = new int2(result.indexA, result.indexB); + cache.Write(pair); + } + } + + [BurstCompile] + public struct LayerSelfPart2 : IJob + { + [ReadOnly] public CollisionLayer layer; + public T processor; + [ReadOnly] public NativeStream.Reader cache; + + public void Execute() + { + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var result = FindPairsResult.CreateGlobalResult(layer, layer, layer.BucketCount + i, true); + + var count = cache.BeginForEachIndex(i); + for (; count > 0; count--) + { + var pair = cache.Read(); + result.SetBucketRelativePairIndices(pair.x, pair.y); + processor.Execute(in result); + } + cache.EndForEachIndex(); + } + } + } + + #endregion + + #region SafeChecks + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + // Schedule for 2 iterations + [BurstCompile] + public struct LayerSelfPart2_WithSafety : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + [ReadOnly] public NativeStream.Reader cache; + + public void Execute(int index) + { + if (index == 0) + { + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var result = FindPairsResult.CreateGlobalResult(layer, layer, layer.BucketCount + i, true); + + var count = cache.BeginForEachIndex(i); + for (; count > 0; count--) + { + var pair = cache.Read(); + result.SetBucketRelativePairIndices(pair.x, pair.y); + processor.Execute(in result); + } + cache.EndForEachIndex(); + } + } + else + { + EntityAliasCheck(layer); + } + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void EntityAliasCheck(CollisionLayer layer) + { + var hashSet = new NativeParallelHashSet(layer.Count, Allocator.Temp); + for (int i = 0; i < layer.Count; i++) + { + if (!hashSet.Add(layer.bodies[i].entity)) + { + var entity = layer.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using a layer containing more than one instance of Entity {entity}"); + } + } + } +#endif + + #endregion + } + } + + public partial struct FindPairsLayerLayerConfig where T : struct, IFindPairsProcessor + { + internal static class FindPairsInternal + { + #region Jobs + [BurstCompile] + public struct LayerLayerSingle : IJob + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute() + { + RunImmediate(layerA, layerB, processor); + } + } + + [BurstCompile] + public struct LayerLayerPart1 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int index) + { + var bucketA = layerA.GetBucketSlices(index); + var bucketB = layerB.GetBucketSlices(index); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucketA, bucketB, index, ref processor); + } + } + + // Schedule for 2 iterations + [BurstCompile] + public struct LayerLayerPart2 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int index) + { + if (index == 0) + { + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + for (int i = 0; i < layerB.BucketCount - 1; i++) + { + var bucket = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, crossBucket, bucket, layerA.BucketCount + i, ref processor); + } + } + else if (index == 1) + { + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerA.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucket, crossBucket, layerA.BucketCount + layerB.BucketCount - 1 + i, ref processor); + } + } + } + } + + // Schedule for (3 * layer.BucketCount - 2) iterations + [BurstCompile] + public struct LayerLayerParallelUnsafe : IJobFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int i) + { + if (i < layerA.BucketCount) + { + var bucketA = layerA.GetBucketSlices(i); + var bucketB = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucketA, bucketB, i, ref processor); + } + else if (i < 2 * layerB.BucketCount - 1) + { + i -= layerB.BucketCount; + var bucket = layerB.GetBucketSlices(i); + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, crossBucket, bucket, i + layerB.BucketCount, ref processor); + } + else + { + var jobIndex = i; + i -= (2 * layerB.BucketCount - 1); + var bucket = layerA.GetBucketSlices(i); + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucket, crossBucket, jobIndex, ref processor); + } + } + } + #endregion + + #region ImmediateMethods + + public static void RunImmediate(CollisionLayer layerA, CollisionLayer layerB, T processor) + { + int jobIndex = 0; + for (int i = 0; i < layerA.BucketCount; i++) + { + var bucketA = layerA.GetBucketSlices(i); + var bucketB = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucketA, bucketB, jobIndex++, ref processor, false); + } + + var crossBucketA = layerA.GetBucketSlices(layerA.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, crossBucketA, bucket, jobIndex++, ref processor, false); + } + + var crossBucketB = layerB.GetBucketSlices(layerB.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerA.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucket, crossBucketB, jobIndex++, ref processor, false); + } + } + #endregion + + #region SafeChecks + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + // Schedule for 3 iterations + [BurstCompile] + public struct LayerLayerPart2_WithSafety : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int index) + { + if (index == 0) + { + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + for (int i = 0; i < layerB.BucketCount - 1; i++) + { + var bucket = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, crossBucket, bucket, layerA.BucketCount + i, ref processor); + } + } + else if (index == 1) + { + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerA.GetBucketSlices(i); + //var marker = new Unity.Profiling.ProfilerMarker($"Cross B {crossBucket.count} - {bucket.count}"); + //marker.Begin(); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucket, crossBucket, layerA.BucketCount + layerB.BucketCount - 1 + i, ref processor); + //marker.End(); + } + } + else + { + EntityAliasCheck(layerA, layerB); + } + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void EntityAliasCheck(CollisionLayer layerA, CollisionLayer layerB) + { + var hashSet = new NativeParallelHashSet(layerA.Count + layerB.Count, Allocator.Temp); + for (int i = 0; i < layerA.Count; i++) + { + if (!hashSet.Add(layerA.bodies[i].entity)) + { + //Note: At this point, we know the issue lies exclusively in layerA. + var entity = layerA.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using a layer containing more than one instance of Entity {entity}"); + } + } + for (int i = 0; i < layerB.Count; i++) + { + if (!hashSet.Add(layerB.bodies[i].entity)) + { + //Note: At this point, it is unknown whether the repeating entity first showed up in layerA or layerB. + var entity = layerB.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using two layers combined containing more than one instance of Entity {entity}"); + } + } + } +#endif + + #endregion + } + } + + public partial struct FindPairsLayerLayerWithCrossCacheConfig where T : struct, IFindPairsProcessor + { + internal static class FindPairsInternal + { + #region Jobs + + // Schedule for (3 * layer.BucketCount - 2) iterations + [BurstCompile] + public struct LayerLayerPart1 : IJobParallelFor, IFindPairsProcessor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + [NativeDisableParallelForRestriction] public NativeStream.Writer cache; + + public void Execute(int i) + { + if (i < layerA.BucketCount) + { + var bucketA = layerA.GetBucketSlices(i); + var bucketB = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucketA, bucketB, i, ref processor); + } + else if (i < 2 * layerB.BucketCount - 1) + { + i -= layerB.BucketCount; + var bucket = layerB.GetBucketSlices(i); + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + cache.BeginForEachIndex(i); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, crossBucket, bucket, i + layerB.BucketCount, ref this); + cache.EndForEachIndex(); + } + else + { + var jobIndex = i; + i -= (2 * layerB.BucketCount - 1); + var bucket = layerA.GetBucketSlices(i); + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + cache.BeginForEachIndex(i + layerB.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweep(layerA, layerB, bucket, crossBucket, jobIndex, ref this); + cache.EndForEachIndex(); + } + } + + public void Execute(in FindPairsResult result) + { + int2 pair = new int2(result.indexA, result.indexB); + cache.Write(pair); + } + } + + // Schedule for 2 iterations + [BurstCompile] + public struct LayerLayerPart2 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + public NativeStream.Reader cache; + + public void Execute(int index) + { + if (index == 0) + { + for (int i = 0; i < layerB.BucketCount - 1; i++) + { + var result = FindPairsResult.CreateGlobalResult(layerA, layerB, layerA.BucketCount + i, true); + + var count = cache.BeginForEachIndex(i); + for (; count > 0; count--) + { + var pair = cache.Read(); + result.SetBucketRelativePairIndices(pair.x, pair.y); + processor.Execute(in result); + } + cache.EndForEachIndex(); + } + } + else if (index == 1) + { + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var result = FindPairsResult.CreateGlobalResult(layerA, layerB, layerA.BucketCount + layerB.BucketCount - 1 + i, true); + + var count = cache.BeginForEachIndex(i + layerA.BucketCount - 1); + for (; count > 0; count--) + { + var pair = cache.Read(); + result.SetBucketRelativePairIndices(pair.x, pair.y); + processor.Execute(in result); + } + cache.EndForEachIndex(); + } + } + } + } + + #endregion + + #region SafeChecks + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + // Schedule for 3 iterations + [BurstCompile] + public struct LayerLayerPart2_WithSafety : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + public NativeStream.Reader cache; + + public void Execute(int index) + { + if (index == 0) + { + for (int i = 0; i < layerB.BucketCount - 1; i++) + { + var result = FindPairsResult.CreateGlobalResult(layerA, layerB, layerA.BucketCount + i, true); + + var count = cache.BeginForEachIndex(i); + for (; count > 0; count--) + { + var pair = cache.Read(); + result.SetBucketRelativePairIndices(pair.x, pair.y); + processor.Execute(in result); + } + cache.EndForEachIndex(); + } + } + else if (index == 1) + { + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var result = FindPairsResult.CreateGlobalResult(layerA, layerB, layerA.BucketCount + layerB.BucketCount - 1 + i, true); + + var count = cache.BeginForEachIndex(i + layerA.BucketCount - 1); + for (; count > 0; count--) + { + var pair = cache.Read(); + result.SetBucketRelativePairIndices(pair.x, pair.y); + processor.Execute(in result); + } + cache.EndForEachIndex(); + } + } + else + { + EntityAliasCheck(layerA, layerB); + } + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void EntityAliasCheck(CollisionLayer layerA, CollisionLayer layerB) + { + var hashSet = new NativeParallelHashSet(layerA.Count + layerB.Count, Allocator.Temp); + for (int i = 0; i < layerA.Count; i++) + { + if (!hashSet.Add(layerA.bodies[i].entity)) + { + //Note: At this point, we know the issue lies exclusively in layerA. + var entity = layerA.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using a layer containing more than one instance of Entity {entity}"); + } + } + for (int i = 0; i < layerB.Count; i++) + { + if (!hashSet.Add(layerB.bodies[i].entity)) + { + //Note: At this point, it is unknown whether the repeating entity first showed up in layerA or layerB. + var entity = layerB.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using two layers combined containing more than one instance of Entity {entity}"); + } + } + } +#endif + + #endregion + } + } + + internal partial struct FindPairsLayerSelfConfigUnrolled where T : struct, IFindPairsProcessor + { + internal static class FindPairsInternalUnrolled + { + #region Jobs + [BurstCompile] + public struct LayerSelfSingle : IJob + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute() + { + RunImmediate(layer, processor); + } + } + + [BurstCompile] + public struct LayerSelfPart1 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute(int index) + { + var bucket = layer.GetBucketSlices(index); + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + FindPairsSweepMethods.SelfSweepUnrolled(layer, bucket, index, ref drain); + } + } + + [BurstCompile] + public struct LayerSelfPart2 : IJob + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute() + { + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layer, layer, bucket, crossBucket, layer.BucketCount + i, ref drain); + } + } + } + + [BurstCompile] + public struct LayerSelfParallelUnsafe : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute(int i) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + if (i < layer.BucketCount) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.SelfSweepUnrolled(layer, bucket, i, ref drain); + } + else + { + i -= layer.BucketCount; + var bucket = layer.GetBucketSlices(i); + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweepUnrolled(layer, layer, bucket, crossBucket, i + layer.BucketCount, ref drain); + } + } + } + #endregion + + #region ImmediateMethods + public static void RunImmediate(CollisionLayer layer, T processor) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + int jobIndex = 0; + for (int i = 0; i < layer.BucketCount; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.SelfSweepUnrolled(layer, bucket, jobIndex++, ref drain, false); + } + + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layer, layer, bucket, crossBucket, jobIndex++, ref drain, false); + } + } + #endregion + + #region SafeChecks + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + [BurstCompile] + public struct LayerSelfPart2_WithSafety : IJobFor + { + [ReadOnly] public CollisionLayer layer; + public T processor; + + public void Execute(int index) + { + if (index == 0) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + var crossBucket = layer.GetBucketSlices(layer.BucketCount - 1); + for (int i = 0; i < layer.BucketCount - 1; i++) + { + var bucket = layer.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layer, layer, bucket, crossBucket, layer.BucketCount + i, ref drain); + } + } + else + { + EntityAliasCheck(layer); + } + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void EntityAliasCheck(CollisionLayer layer) + { + var hashSet = new NativeParallelHashSet(layer.Count, Allocator.Temp); + for (int i = 0; i < layer.Count; i++) + { + if (!hashSet.Add(layer.bodies[i].entity)) + { + var entity = layer.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using a layer containing more than one instance of Entity {entity}"); + } + } + } +#endif + + #endregion + + #region SweepUnrolledMethods + + #endregion SweepUnrolledMethods + } + } + + internal partial struct FindPairsLayerLayerConfigUnrolled where T : struct, IFindPairsProcessor + { + internal static class FindPairsInternalUnrolled + { + #region Jobs + [BurstCompile] + public struct LayerLayerSingle : IJob + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute() + { + RunImmediate(layerA, layerB, processor); + } + } + + [BurstCompile] + public struct LayerLayerPart1 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int index) + { + var bucketA = layerA.GetBucketSlices(index); + var bucketB = layerB.GetBucketSlices(index); + + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucketA, bucketB, index, ref drain); + } + } + + [BurstCompile] + public struct LayerLayerPart2 : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int index) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + if (index == 0) + { + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + for (int i = 0; i < layerB.BucketCount - 1; i++) + { + var bucket = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, crossBucket, bucket, layerA.BucketCount + i, ref drain); + } + } + else if (index == 1) + { + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerA.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucket, crossBucket, layerA.BucketCount + layerB.BucketCount + i, ref drain); + } + } + } + } + + [BurstCompile] + public struct LayerLayerParallelUnsafe : IJobFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int i) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + if (i < layerA.BucketCount) + { + var bucketA = layerA.GetBucketSlices(i); + var bucketB = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucketA, bucketB, i, ref drain); + } + else if (i < 2 * layerB.BucketCount - 1) + { + i -= layerB.BucketCount; + var bucket = layerB.GetBucketSlices(i); + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, crossBucket, bucket, i + layerB.BucketCount, ref drain); + } + else + { + var jobIndex = i; + i -= (2 * layerB.BucketCount - 1); + var bucket = layerA.GetBucketSlices(i); + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucket, crossBucket, jobIndex, ref drain); + } + } + } + #endregion + + #region ImmediateMethods + + public static void RunImmediate(CollisionLayer layerA, CollisionLayer layerB, T processor) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + int jobIndex = 0; + for (int i = 0; i < layerA.BucketCount; i++) + { + var bucketA = layerA.GetBucketSlices(i); + var bucketB = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucketA, bucketB, jobIndex++, ref drain, false); + } + + var crossBucketA = layerA.GetBucketSlices(layerA.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerB.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, crossBucketA, bucket, jobIndex++, ref drain, false); + } + + var crossBucketB = layerB.GetBucketSlices(layerB.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerA.GetBucketSlices(i); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucket, crossBucketB, jobIndex++, ref drain, false); + } + } + #endregion + + #region SafeChecks + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + + [BurstCompile] + public struct LayerLayerPart2_WithSafety : IJobParallelFor + { + [ReadOnly] public CollisionLayer layerA; + [ReadOnly] public CollisionLayer layerB; + public T processor; + + public void Execute(int index) + { + var drain = new FindPairsSweepMethods.FindPairsProcessorDrain(); + //drain.drainBuffer1024 = new NativeArray(1024, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + drain.processor = processor; + + //var marker0 = new Unity.Profiling.ProfilerMarker("Cross A Unrolled"); + //var marker1 = new Unity.Profiling.ProfilerMarker("Cross B Unrolled"); + + if (index == 0) + { + var crossBucket = layerA.GetBucketSlices(layerA.BucketCount - 1); + for (int i = 0; i < layerB.BucketCount - 1; i++) + { + var bucket = layerB.GetBucketSlices(i); + //marker0.Begin(); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, crossBucket, bucket, layerA.BucketCount + i, ref drain); + //marker0.End(); + } + } + else if (index == 1) + { + var crossBucket = layerB.GetBucketSlices(layerB.BucketCount - 1); + for (int i = 0; i < layerA.BucketCount - 1; i++) + { + var bucket = layerA.GetBucketSlices(i); + //var marker1Detailed = new Unity.Profiling.ProfilerMarker($"Cross B Unrolled {crossBucket.count} - {bucket.count}"); + //marker1Detailed.Begin(); + //marker1.Begin(); + FindPairsSweepMethods.BipartiteSweepUnrolled(layerA, layerB, bucket, crossBucket, layerA.BucketCount + layerB.BucketCount + i, ref drain); + //marker1.End(); + //marker1Detailed.End(); + } + } + else + { + EntityAliasCheck(layerA, layerB); + } + + //if (drain.maxCount > 1000) + // UnityEngine.Debug.Log($"hit count: {drain.maxCount}"); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void EntityAliasCheck(CollisionLayer layerA, CollisionLayer layerB) + { + var hashSet = new NativeParallelHashSet(layerA.Count + layerB.Count, Allocator.Temp); + for (int i = 0; i < layerA.Count; i++) + { + if (!hashSet.Add(layerA.bodies[i].entity)) + { + //Note: At this point, we know the issue lies exclusively in layerA. + var entity = layerA.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using a layer containing more than one instance of Entity {entity}"); + } + } + for (int i = 0; i < layerB.Count; i++) + { + if (!hashSet.Add(layerB.bodies[i].entity)) + { + //Note: At this point, it is unknown whether the repeating entity first showed up in layerA or layerB. + var entity = layerB.bodies[i].entity; + throw new InvalidOperationException( + $"A parallel FindPairs job was scheduled using two layers combined containing more than one instance of Entity {entity}"); + } + } + } +#endif + + #endregion + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsInternal.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsInternal.cs.meta new file mode 100644 index 0000000..0bab9ea --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsInternal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cdc8aeb9cdad26478ae1f9b74d7dc53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsSweepMethods.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsSweepMethods.cs new file mode 100644 index 0000000..028df3b --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsSweepMethods.cs @@ -0,0 +1,1661 @@ +using Unity.Burst; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static class FindPairsSweepMethods + { + #region Production Sweeps + public static void SelfSweep(CollisionLayer layer, BucketSlices bucket, int jobIndex, ref T processor, bool isThreadSafe = true) where T : struct, IFindPairsProcessor + { + Hint.Assume(bucket.xmins.Length == bucket.xmaxs.Length); + Hint.Assume(bucket.xmins.Length == bucket.yzminmaxs.Length); + Hint.Assume(bucket.xmins.Length == bucket.bodies.Length); + + var result = new FindPairsResult(layer, layer, bucket, bucket, jobIndex, isThreadSafe); + + int count = bucket.xmins.Length; + for (int i = 0; i < count - 1; i++) + { + var current = -bucket.yzminmaxs[i].zwxy; + for (int j = i + 1; j < count && bucket.xmins[j] <= bucket.xmaxs[i]; j++) + { + if (math.bitmask(current < bucket.yzminmaxs[j]) == 0) + { + result.SetBucketRelativePairIndices(i, j); + processor.Execute(in result); + } + } + } + } + + public static void BipartiteSweep(CollisionLayer layerA, + CollisionLayer layerB, + BucketSlices bucketA, + BucketSlices bucketB, + int jobIndex, + ref T processor, + bool isThreadSafe = true) where T : struct, + IFindPairsProcessor + { + int countA = bucketA.xmins.Length; + int countB = bucketB.xmins.Length; + if (countA == 0 || countB == 0) + return; + + Hint.Assume(bucketA.xmins.Length == bucketA.xmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.yzminmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.bodies.Length); + + Hint.Assume(bucketB.xmins.Length == bucketB.xmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.yzminmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.bodies.Length); + + var result = new FindPairsResult(layerA, layerB, bucketA, bucketB, jobIndex, isThreadSafe); + + //var outer0Marker = new Unity.Profiling.ProfilerMarker("Outer0"); + //var outer1Marker = new Unity.Profiling.ProfilerMarker("Outer1"); + + //outer0Marker.Begin(); + + //Check for b starting in a's x range + int bstart = 0; + for (int i = 0; i < countA; i++) + { + //Advance to b.xmin >= a.xmin + //Include equals case by stopping when equal + while (bstart < countB && bucketB.xmins[bstart] < bucketA.xmins[i]) + bstart++; + if (bstart >= countB) + break; + + var current = -bucketA.yzminmaxs[i].zwxy; + for (int j = bstart; j < countB && bucketB.xmins[j] <= bucketA.xmaxs[i]; j++) + { + if (math.bitmask(current < bucketB.yzminmaxs[j]) == 0) + { + result.SetBucketRelativePairIndices(i, j); + processor.Execute(in result); + } + } + } + + //outer0Marker.End(); + //outer1Marker.Begin(); + + //Check for a starting in b's x range + int astart = 0; + for (int i = 0; i < countB; i++) + { + //Advance to a.xmin > b.xmin + //Exclude equals case this time by continuing if equal + while (astart < countA && bucketA.xmins[astart] <= bucketB.xmins[i]) + astart++; + if (astart >= countA) + break; + + var current = -bucketB.yzminmaxs[i].zwxy; + for (int j = astart; j < countA && bucketA.xmins[j] <= bucketB.xmaxs[i]; j++) + { + if (math.bitmask(current < bucketA.yzminmaxs[j]) == 0) + { + result.SetBucketRelativePairIndices(j, i); + processor.Execute(in result); + } + } + } + + //outer1Marker.End(); + } + #endregion + + #region Sweep Stats + public static void SelfSweepStats(BucketSlices bucket, in FixedString128Bytes layerName) + { + int hitCount = 0; + int innerLoopEnterCount = 0; + int innerLoopTestCount = 0; + int innerLoopRunMin = int.MaxValue; + int innerLoopRunMax = 0; + int innerLoopZHits = 0; + + Hint.Assume(bucket.xmins.Length == bucket.xmaxs.Length); + Hint.Assume(bucket.xmins.Length == bucket.yzminmaxs.Length); + Hint.Assume(bucket.xmins.Length == bucket.bodies.Length); + + int count = bucket.xmins.Length; + for (int i = 0; i < count - 1; i++) + { + int runCount = 0; + var current = -bucket.yzminmaxs[i].zwxy; + for (int j = i + 1; j < count && bucket.xmins[j] <= bucket.xmaxs[i]; j++) + { + runCount++; + //float4 less = math.shuffle(current, + // bucket.yzminmaxs[j], + // math.ShuffleComponent.RightZ, + // math.ShuffleComponent.RightW, + // math.ShuffleComponent.LeftZ, + // math.ShuffleComponent.LeftW + // ); + //float4 more = math.shuffle(current, + // bucket.yzminmaxs[j], + // math.ShuffleComponent.LeftX, + // math.ShuffleComponent.LeftY, + // math.ShuffleComponent.RightX, + // math.ShuffleComponent.RightY + // ); + + if (math.bitmask(current < bucket.yzminmaxs[j]) == 0) + { + hitCount++; + } + if ((math.bitmask(current < bucket.yzminmaxs[j]) & 0xa) == 0) + innerLoopZHits++; + //if (less.y >= more.y && less.w >= more.w) + // innerLoopZHits++; + } + if (runCount > 0) + innerLoopEnterCount++; + innerLoopTestCount += runCount; + innerLoopRunMax = math.max(innerLoopRunMax, runCount); + innerLoopRunMin = math.min(innerLoopRunMin, runCount); + } + + //SelfSweepDualGenAndStats(bucket, in layerName); + UnityEngine.Debug.Log( + $"FindPairs Self Sweep stats for layer {layerName} at bucket index {bucket.bucketIndex} and count {bucket.count}\nHits: {hitCount}, inner loop enters: {innerLoopEnterCount}, inner loop tests: {innerLoopTestCount}, inner loop run (min, max): ({innerLoopRunMin}, {innerLoopRunMax}), inner loop z hits: {innerLoopZHits}"); + } + + public static void BipartiteSweepStats(BucketSlices bucketA, in FixedString128Bytes layerNameA, BucketSlices bucketB, in FixedString128Bytes layerNameB) + { + int hitCountA = 0; + int innerLoopEnterCountA = 0; + int innerLoopTestCountA = 0; + int innerLoopRunMinA = int.MaxValue; + int innerLoopRunMaxA = 0; + + int hitCountB = 0; + int innerLoopEnterCountB = 0; + int innerLoopTestCountB = 0; + int innerLoopRunMinB = int.MaxValue; + int innerLoopRunMaxB = 0; + + int countA = bucketA.xmins.Length; + int countB = bucketB.xmins.Length; + if (countA == 0 || countB == 0) + return; + + Hint.Assume(bucketA.xmins.Length == bucketA.xmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.yzminmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.bodies.Length); + + Hint.Assume(bucketB.xmins.Length == bucketB.xmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.yzminmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.bodies.Length); + + //Check for b starting in a's x range + int bstart = 0; + for (int i = 0; i < countA; i++) + { + //Advance to b.xmin >= a.xmin + //Include equals case by stopping when equal + while (bstart < countB && bucketB.xmins[bstart] < bucketA.xmins[i]) + bstart++; + if (bstart >= countB) + break; + + int runCount = 0; + var current = -bucketA.yzminmaxs[i].zwxy; + for (int j = bstart; j < countB && bucketB.xmins[j] <= bucketA.xmaxs[i]; j++) + { + runCount++; + + if (math.bitmask(current < bucketB.yzminmaxs[j]) == 0) + { + hitCountA++; + } + } + if (runCount > 0) + innerLoopEnterCountA++; + innerLoopTestCountA += runCount; + innerLoopRunMaxA = math.max(innerLoopRunMaxA, runCount); + innerLoopRunMinA = math.min(innerLoopRunMinA, runCount); + } + + //Check for a starting in b's x range + int astart = 0; + for (int i = 0; i < countB; i++) + { + //Advance to a.xmin > b.xmin + //Exclude equals case this time by continuing if equal + while (astart < countA && bucketA.xmins[astart] <= bucketB.xmins[i]) + astart++; + if (astart >= countA) + break; + + int runCount = 0; + var current = -bucketB.yzminmaxs[i].zwxy; + for (int j = astart; j < countA && bucketA.xmins[j] <= bucketB.xmaxs[i]; j++) + { + runCount++; + + if (math.bitmask(current < bucketA.yzminmaxs[j]) == 0) + { + hitCountB++; + } + } + if (runCount > 0) + innerLoopEnterCountB++; + innerLoopTestCountB += runCount; + innerLoopRunMaxB = math.max(innerLoopRunMaxB, runCount); + innerLoopRunMinB = math.min(innerLoopRunMinB, runCount); + } + + UnityEngine.Debug.Log( + $"FindPairs Bipartite Sweep stats for layerA {layerNameA} at bucket index {bucketA.bucketIndex} and count {bucketA.count}; and layerB {layerNameB} at bucket index {bucketB.bucketIndex} and count {bucketB.count}\n::A SWEEP B:: Hits: {hitCountA}, inner loop enters: {innerLoopEnterCountA}, inner loop tests: {innerLoopTestCountA}, inner loop run (min, max): ({innerLoopRunMinA}, {innerLoopRunMaxA})\n::B SWEEP A:: Hits: {hitCountB}, inner loop enters: {innerLoopEnterCountB}, inner loop tests: {innerLoopTestCountB}, inner loop run (min, max): ({innerLoopRunMinB}, {innerLoopRunMaxB})"); + } + #endregion + + #region Experimental Sweeps + public unsafe interface IFindPairsDrainable + { + public void SetBuckets(CollisionLayer layerA, CollisionLayer layerB, BucketSlices bucketA, BucketSlices bucketB, int jobIndex, bool isThreadSafe); + public ulong* AcquireDrainBuffer1024ForWrite(); + public void Drain(ulong count); + public void DrainStats(ulong count); + public void DirectInvoke(int a, int b); + } + + public unsafe struct FindPairsProcessorDrain : IFindPairsDrainable where T : struct, IFindPairsProcessor + { + public T processor; + + // Packed pairs. If [63:32] is negative, b[63:32], a[31:0] otherwise a[63:32], b[31:0] + //public NativeArray drainBuffer1024; + fixed ulong drainBuffer1024[1024]; + FindPairsResult m_result; + ulong m_combinedCount; + + public ulong maxCount; + + public void SetBuckets(CollisionLayer layerA, CollisionLayer layerB, BucketSlices bucketA, BucketSlices bucketB, int jobIndex, bool isThreadSafe) + { + m_result = new FindPairsResult(layerA, layerB, bucketA, bucketB, jobIndex, isThreadSafe); + m_combinedCount = (ulong)bucketA.count + (ulong)bucketB.count; + } + + public ulong* AcquireDrainBuffer1024ForWrite() + { + fixed (ulong* ptr = drainBuffer1024) + return ptr; + //return (ulong*)drainBuffer1024.GetUnsafePtr(); + } + + public void DirectInvoke(int a, int b) + { + m_result.SetBucketRelativePairIndices(a, b); + processor.Execute(in m_result); + } + + public void Drain(ulong count) + { + maxCount += count; + //var marker = new Unity.Profiling.ProfilerMarker("Drain"); + + //marker.Begin(); + + for (ulong i = 0; i < count; i++) + { + var pair = drainBuffer1024[(int)i]; + ulong shiftedA = pair >> 32; + + int b = (int)(pair & 0xffffffff); + + if (shiftedA >= m_combinedCount) + { + int a = (int)(shiftedA - m_combinedCount); + m_result.SetBucketRelativePairIndices(b, a); + processor.Execute(in m_result); + } + else + { + int a = (int)shiftedA; + m_result.SetBucketRelativePairIndices(a, b); + processor.Execute(in m_result); + } + } + + //marker.End(); + } + + public void DrainStats(ulong count) + { + maxCount += count; + + //var processNormal = new Unity.Profiling.ProfilerMarker("Normal"); + //var processFlipped = new Unity.Profiling.ProfilerMarker("Flipped"); + + for (ulong i = 0; i < count; i++) + { + var pair = drainBuffer1024[(int)i]; + ulong shiftedA = pair >> 32; + + int b = (int)(pair & 0xffffffff); + + if (shiftedA >= m_combinedCount) + { + int a = (int)(shiftedA - m_combinedCount); + + //processFlipped.Begin(); + m_result.SetBucketRelativePairIndices(b, a); + processor.Execute(in m_result); + //processFlipped.End(); + } + else + { + int a = (int)shiftedA; + m_result.SetBucketRelativePairIndices(a, b); + //processNormal.Begin(); + processor.Execute(in m_result); + //processNormal.End(); + } + } + } + } + + public static unsafe void SelfSweepUnrolled(CollisionLayer layer, BucketSlices bucket, int jobIndex, ref T drain, bool isThreadSafe = true) where T : struct, + IFindPairsDrainable + { + Hint.Assume(bucket.xmins.Length == bucket.xmaxs.Length); + Hint.Assume(bucket.xmins.Length == bucket.yzminmaxs.Length); + Hint.Assume(bucket.xmins.Length == bucket.bodies.Length); + + int count = bucket.xmins.Length; + ulong nextHitIndex = 0; + drain.SetBuckets(layer, layer, bucket, bucket, jobIndex, isThreadSafe); + var hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + var minMaxPtr = (float4*)bucket.yzminmaxs.GetUnsafeReadOnlyPtr(); + var xminsPtr = (float*)bucket.xmins.GetUnsafeReadOnlyPtr(); + + for (int i = 0; i < bucket.xmins.Length - 1; i++) + { + float4 current = -minMaxPtr[i].zwxy; + + float currentX = bucket.xmaxs[i]; + + ulong j = (ulong)i + 1; + + ulong pair = (((ulong)i) << 32) | j; + ulong final = (((ulong)i) << 32) | ((uint)bucket.xmins.Length); + + while (pair + 15 < final) + { + if (Hint.Unlikely(xminsPtr[(j + 15)] >= currentX)) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 3]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 4]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 5]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 6]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 7]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 8]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 9]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 10]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 11]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 12]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 13]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 14]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j + 15]) == 0) + nextHitIndex++; + pair++; + j += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + while (pair < final && xminsPtr[j] < currentX) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtr[j]) == 0) + nextHitIndex++; + pair++; + j++; + } + + if (nextHitIndex >= 1008) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + if (nextHitIndex > 0) + drain.Drain(nextHitIndex); + } + + public static unsafe void BipartiteSweepUnrolled2(CollisionLayer layerA, + CollisionLayer layerB, + BucketSlices bucketA, + BucketSlices bucketB, + int jobIndex, + ref T drain, + bool isThreadSafe = true) where T : struct, + IFindPairsDrainable + { + int countA = bucketA.xmins.Length; + int countB = bucketB.xmins.Length; + if (countA == 0 || countB == 0) + return; + + Hint.Assume(bucketA.xmins.Length == bucketA.xmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.yzminmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.bodies.Length); + + Hint.Assume(bucketB.xmins.Length == bucketB.xmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.yzminmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.bodies.Length); + + ulong nextHitIndex = 0; + drain.SetBuckets(layerA, layerB, bucketA, bucketB, jobIndex, isThreadSafe); + var hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + var minMaxPtrA = (float4*)bucketA.yzminmaxs.GetUnsafeReadOnlyPtr(); + var minMaxPtrB = (float4*)bucketB.yzminmaxs.GetUnsafeReadOnlyPtr(); + + //ulong tests = 0; + + //Check for b starting in a's x range + int bstart = 0; + for (int i = 0; i < countA; i++) + { + //Advance to b.xmin >= a.xmin + //Include equals case by stopping when equal + while (bstart < countB && bucketB.xmins[bstart] < bucketA.xmins[i]) + bstart++; + if (bstart >= countB) + break; + + float4 current = -minMaxPtrA[i].zwxy; + + float currentX = bucketA.xmaxs[i]; + + ulong j = (ulong)bstart; + + ulong pair = (((ulong)i) << 32) | j; + ulong final = (((ulong)i) << 32) | ((uint)countB); + + while (pair + 3 < final) + { + if (Hint.Unlikely(bucketB.xmins[(int)(j + 3)] >= currentX)) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 3]) == 0) + nextHitIndex++; + pair++; + + j += 4; + + //tests += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + while (pair < final && bucketB.xmins[(int)j] < currentX) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j]) == 0) + nextHitIndex++; + pair++; + j++; + //tests++; + } + + if (nextHitIndex >= 1008) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + //Check for a starting in b's x range + int astart = 0; + for (int i = 0; i < countB; i++) + { + //Advance to a.xmin > b.xmin + //Exclude equals case this time by continuing if equal + while (astart < countA && bucketA.xmins[astart] <= bucketB.xmins[i]) + astart++; + if (astart >= countA) + break; + + float4 current = -minMaxPtrB[i].zwxy; + + float currentX = bucketB.xmaxs[i]; + + ulong j = (ulong)astart; + + ulong bucketsSum = (ulong)bucketA.count + (ulong)bucketB.count; + + ulong pair = ((((ulong)i) + bucketsSum) << 32) | j; + ulong final = ((((ulong)i) + bucketsSum) << 32) | ((uint)countA); + + while (pair + 3 < final) + { + if (Hint.Unlikely(bucketA.xmins[(int)(j + 3)] >= currentX)) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 3]) == 0) + nextHitIndex++; + pair++; + + j += 4; + //tests += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + while (pair < final && bucketA.xmins[(int)j] < currentX) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j]) == 0) + nextHitIndex++; + pair++; + j++; + //tests++; + } + + if (nextHitIndex >= 1008) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + if (nextHitIndex > 0) + drain.Drain(nextHitIndex); + + //if (tests > 10000) + // UnityEngine.Debug.Log($"Unrolled tests: {tests}"); + } + + public static unsafe void BipartiteSweepUnrolled(CollisionLayer layerA, + CollisionLayer layerB, + in BucketSlices bucketA, + in BucketSlices bucketB, + int jobIndex, + ref T drain, + bool isThreadSafe = true) where T : struct, + IFindPairsDrainable + { + int countA = bucketA.xmins.Length; + int countB = bucketB.xmins.Length; + if (countA == 0 || countB == 0) + return; + + Hint.Assume(bucketA.xmins.Length == bucketA.xmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.yzminmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.bodies.Length); + + Hint.Assume(bucketB.xmins.Length == bucketB.xmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.yzminmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.bodies.Length); + + ulong nextHitIndex = 0; + drain.SetBuckets(layerA, layerB, bucketA, bucketB, jobIndex, isThreadSafe); + var hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + var minMaxPtrA = (float4*)bucketA.yzminmaxs.GetUnsafeReadOnlyPtr(); + var minMaxPtrB = (float4*)bucketB.yzminmaxs.GetUnsafeReadOnlyPtr(); + var xminsPtrA = (float*)bucketA.xmins.GetUnsafeReadOnlyPtr(); + var xminsPtrB = (float*)bucketB.xmins.GetUnsafeReadOnlyPtr(); + + //ulong tests = 0; + + //Check for b starting in a's x range + int bstart = 0; + for (int i = 0; i < countA; i++) + { + //Advance to b.xmin >= a.xmin + //Include equals case by stopping when equal + while (bstart < countB && xminsPtrB[bstart] < xminsPtrA[i]) + bstart++; + if (bstart >= countB) + break; + + float4 current = -minMaxPtrA[i].zwxy; + + float currentX = bucketA.xmaxs[i]; + + ulong j = (ulong)bstart; + + ulong pair = (((ulong)i) << 32) | j; + ulong final = (((ulong)i) << 32) | ((uint)countB); + + while (Hint.Likely(pair + 15 < final)) + { + if (xminsPtrB[(j + 15)] >= currentX) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 3]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 4]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 5]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 6]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 7]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 8]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 9]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 10]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 11]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 12]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 13]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 14]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 15]) == 0) + nextHitIndex++; + pair++; + j += 16; + + //tests += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + Hint.Assume((pair & 0xffffffff00000000) == (final & 0xffffffff00000000)); + Hint.Assume((pair & 0xffffffff) == j); + Hint.Assume(j <= int.MaxValue); + while (Hint.Likely(pair < final && xminsPtrB[j] < currentX)) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j]) == 0) + nextHitIndex++; + pair++; + j++; + //tests++; + } + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + //Check for a starting in b's x range + int astart = 0; + for (int i = 0; i < countB; i++) + { + //Advance to a.xmin > b.xmin + //Exclude equals case this time by continuing if equal + while (astart < countA && xminsPtrA[astart] <= xminsPtrB[i]) + astart++; + if (astart >= countA) + break; + + float4 current = -minMaxPtrB[i].zwxy; + + float currentX = bucketB.xmaxs[i]; + + ulong j = (ulong)astart; + + ulong bucketsSum = (ulong)bucketA.count + (ulong)bucketB.count; + + ulong pair = ((((ulong)i) + bucketsSum) << 32) | j; + ulong final = ((((ulong)i) + bucketsSum) << 32) | ((uint)countA); + + while (Hint.Likely(pair + 15 < final)) + { + if (xminsPtrA[(j + 15)] >= currentX) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 3]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 4]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 5]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 6]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 7]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 8]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 9]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 10]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 11]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 12]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 13]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 14]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 15]) == 0) + nextHitIndex++; + pair++; + j += 16; + //tests += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + Hint.Assume((pair & 0xffffffff00000000) == (final & 0xffffffff00000000)); + Hint.Assume((pair & 0xffffffff) == j); + Hint.Assume(j <= int.MaxValue); + while (Hint.Likely(pair < final && xminsPtrA[j] < currentX)) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j]) == 0) + nextHitIndex++; + pair++; + j++; + //tests++; + } + + if (nextHitIndex >= 1008) + { + drain.Drain(nextHitIndex); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + if (nextHitIndex > 0) + drain.Drain(nextHitIndex); + + //if (tests > 10000) + // UnityEngine.Debug.Log($"Unrolled tests: {tests}"); + } + + public static unsafe void BipartiteSweepUnrolledStats(CollisionLayer layerA, + CollisionLayer layerB, + BucketSlices bucketA, + BucketSlices bucketB, + int jobIndex, + ref T drain, + bool isThreadSafe = true) where T : struct, + IFindPairsDrainable + { + int countA = bucketA.xmins.Length; + int countB = bucketB.xmins.Length; + if (countA == 0 || countB == 0) + return; + + Hint.Assume(bucketA.xmins.Length == bucketA.xmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.yzminmaxs.Length); + Hint.Assume(bucketA.xmins.Length == bucketA.bodies.Length); + + Hint.Assume(bucketB.xmins.Length == bucketB.xmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.yzminmaxs.Length); + Hint.Assume(bucketB.xmins.Length == bucketB.bodies.Length); + + ulong nextHitIndex = 0; + drain.SetBuckets(layerA, layerB, bucketA, bucketB, jobIndex, isThreadSafe); + var hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + var minMaxPtrA = (float4*)bucketA.yzminmaxs.GetUnsafeReadOnlyPtr(); + var minMaxPtrB = (float4*)bucketB.yzminmaxs.GetUnsafeReadOnlyPtr(); + + //ulong tests = 0; + var inner0Marker = new Unity.Profiling.ProfilerMarker("Inner 0"); + var inner1Marker = new Unity.Profiling.ProfilerMarker("Inner 1"); + var lead0Marker = new Unity.Profiling.ProfilerMarker("Lead 0"); + var lead1Marker = new Unity.Profiling.ProfilerMarker("Lead 1"); + var cleanup0Marker = new Unity.Profiling.ProfilerMarker("Cleanup 0"); + var cleanup1Marker = new Unity.Profiling.ProfilerMarker("Cleanup 1"); + var drain0Marker = new Unity.Profiling.ProfilerMarker("Drain 0"); + var drain1Marker = new Unity.Profiling.ProfilerMarker("Drain 1"); + var outer0Marker = new Unity.Profiling.ProfilerMarker("Outer 0"); + var outer1Marker = new Unity.Profiling.ProfilerMarker("Outer 1"); + + outer0Marker.Begin(); + //Check for b starting in a's x range + int bstart = 0; + for (int i = 0; i < countA; i++) + { + //Advance to b.xmin >= a.xmin + //Include equals case by stopping when equal + //lead0Marker.Begin(); + while (bstart < countB && bucketB.xmins[bstart] < bucketA.xmins[i]) + bstart++; + //lead0Marker.End(); + if (bstart >= countB) + break; + + float4 current = -minMaxPtrA[i].zwxy; + + float currentX = bucketA.xmaxs[i]; + + ulong j = (ulong)bstart; + + ulong pair = (((ulong)i) << 32) | j; + ulong final = (((ulong)i) << 32) | ((uint)countB); + + //inner0Marker.Begin(); + while (pair + 15 < final) + { + if (Hint.Unlikely(bucketB.xmins[(int)(j + 15)] > currentX)) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 3]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 4]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 5]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 6]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 7]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 8]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 9]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 10]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 11]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 12]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 13]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 14]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j + 15]) == 0) + nextHitIndex++; + pair++; + j += 16; + + //tests += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + //drain0Marker.Begin(); + drain.Drain(nextHitIndex); + //drain0Marker.End(); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + + //if (nextHitIndex > 10) + //{ + // UnityEngine.Debug.Log($"i : {i}, j : {j}, drain: {nextHitIndex}"); + //} + } + //inner0Marker.End(); + + //cleanup0Marker.Begin(); + while (pair < final && bucketB.xmins[(int)j] <= currentX) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrB[j]) == 0) + nextHitIndex++; + pair++; + j++; + //tests++; + } + //cleanup0Marker.End(); + + if (nextHitIndex >= 1008) + { + //drain0Marker.Begin(); + drain.Drain(nextHitIndex); + //drain0Marker.End(); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + outer0Marker.End(); + outer1Marker.Begin(); + + //Check for a starting in b's x range + int astart = 0; + for (int i = 0; i < countB; i++) + { + //Advance to a.xmin > b.xmin + //Exclude equals case this time by continuing if equal + //lead1Marker.Begin(); + while (astart < countA && bucketA.xmins[astart] <= bucketB.xmins[i]) + astart++; + //lead1Marker.End(); + if (astart >= countA) + break; + + float4 current = -minMaxPtrB[i].zwxy; + + float currentX = bucketB.xmaxs[i]; + + ulong j = (ulong)astart; + + ulong bucketsSum = (ulong)bucketA.count + (ulong)bucketB.count; + + ulong pair = ((((ulong)i) + bucketsSum) << 32) | j; + ulong final = ((((ulong)i) + bucketsSum) << 32) | ((uint)countA); + + //inner1Marker.Begin(); + while (pair + 15 < final) + { + if (Hint.Unlikely(bucketA.xmins[(int)(j + 15)] > currentX)) + break; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 1]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 2]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 3]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 4]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 5]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 6]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 7]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 8]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 9]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 10]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 11]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 12]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 13]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 14]) == 0) + nextHitIndex++; + pair++; + + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j + 15]) == 0) + nextHitIndex++; + pair++; + j += 16; + //tests += 16; + + if (Hint.Unlikely(nextHitIndex >= 1008)) + { + //drain1Marker.Begin(); + drain.Drain(nextHitIndex); + //drain1Marker.End(); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + //inner1Marker.End(); + + //cleanup1Marker.Begin(); + while (pair < final && bucketA.xmins[(int)j] <= currentX) + { + hitCachePtr[nextHitIndex] = pair; + if (math.bitmask(current < minMaxPtrA[j]) == 0) + nextHitIndex++; + pair++; + j++; + //tests++; + } + //cleanup1Marker.End(); + + if (nextHitIndex >= 1008) + { + //drain1Marker.Begin(); + drain.Drain(nextHitIndex); + //drain1Marker.End(); + hitCachePtr = drain.AcquireDrainBuffer1024ForWrite(); + nextHitIndex = 0; + } + } + + outer1Marker.End(); + + drain0Marker.Begin(); + drain.Drain(nextHitIndex); + drain0Marker.End(); + + //if (tests > 10000) + // UnityEngine.Debug.Log($"Unrolled tests: {tests}"); + } + #endregion + + #region Broken + // Todo: Fix for sign flip + static void SelfSweepDualGenAndStats(BucketSlices bucket, in FixedString128Bytes layerName) + { + if (bucket.count <= 1) + return; + + var zToXMinsMaxes = new NativeArray(2 * bucket.count, Allocator.Temp); + var xs = new NativeArray(2 * bucket.count, Allocator.Temp); + + var xSort = new NativeArray(bucket.count * 2, Allocator.Temp); + var zSort = new NativeArray(bucket.count * 2, Allocator.Temp); + + for (int i = 0; i < bucket.count; i++) + { + var xmin = bucket.xmins[i]; + var xmax = bucket.xmaxs[i]; + var minYZmaxYZ = bucket.yzminmaxs[i]; + xSort[2 * i] = new Sortable { f = xmin, index = (uint)i }; + xSort[2 * i + 1] = new Sortable { f = xmax, index = (uint)i + (uint)bucket.count }; + + zSort[2 * i] = new Sortable { f = minYZmaxYZ.y, index = (uint)i }; + zSort[2 * i + 1] = new Sortable { f = minYZmaxYZ.w, index = (uint)i + (uint)bucket.count }; + } + + xSort.Sort(); + zSort.Sort(); + + for (int i = 0; i < xSort.Length; i++) + { + xs[i] = xSort[i].index; + zToXMinsMaxes[i] = zSort[i].index; + } + + var minYZmaxYZs = bucket.yzminmaxs; + + var zIntervals = new NativeList(minYZmaxYZs.Length, Allocator.Temp); + zIntervals.ResizeUninitialized(minYZmaxYZs.Length); + + var zBits = new NativeList(minYZmaxYZs.Length / 64 + 1, Allocator.Temp); + zBits.Resize(minYZmaxYZs.Length / 64 + 1, NativeArrayOptions.ClearMemory); + + { + int minBit = 0; + int index = 0; + for (int i = 0; i < zToXMinsMaxes.Length; i++) + { + if (zToXMinsMaxes[i] < minYZmaxYZs.Length) + { + ref var interval = ref zIntervals.ElementAt((int)zToXMinsMaxes[i]); + interval.index = index; + interval.min = minBit; + ref var bitField = ref zBits.ElementAt(index >> 6); + bitField.SetBits(index & 0x3f, true); + index++; + } + else + { + ref var interval = ref zIntervals.ElementAt((int)(zToXMinsMaxes[i] - (uint)minYZmaxYZs.Length)); + interval.max = index; + ref var bitField = ref zBits.ElementAt(interval.index >> 6); + bitField.SetBits(interval.index & 0x3f, false); + if (interval.index == minBit) + { + while (minBit <= index) + { + var scanBits = zBits.ElementAt(minBit >> 6); + var tzcnt = scanBits.CountTrailingZeros(); + if (tzcnt < 64) + { + minBit = (minBit & ~0x3f) + tzcnt; + break; + } + minBit = (minBit & ~0x3f) + 64; + } + minBit = math.min(minBit, index + 1); + } + } + } + } + + var zToXs = new NativeArray(minYZmaxYZs.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + int hitCount = 0; + int innerLoopEnterCount = 0; + int innerLoopTestCount = 0; + int innerLoopRunMin = int.MaxValue; + int innerLoopRunMax = 0; + int maxRunIntervalIndex = 0; + int touchedZeroBitfield = 0; + + for (int i = 0; i < xs.Length; i++) + { + if (xs[i] < minYZmaxYZs.Length) + { + int runCount = 0; + + var interval = zIntervals[(int)xs[i]]; + int minBitfield = interval.min >> 6; + int maxBitfield = interval.max >> 6; + if (minBitfield == maxBitfield) + { + int minBit = interval.min & 0x3f; + int maxBit = interval.max & 0x3f; + var bitField = zBits[minBitfield]; + if (minBit > 0) + bitField.SetBits(0, false, minBit); + + if (bitField.Value == 0) + touchedZeroBitfield++; + + for (var j = bitField.CountTrailingZeros(); j <= maxBit; bitField.SetBits(j, false), j = bitField.CountTrailingZeros()) + { + runCount++; + var currentIndex = (int)xs[i]; + var otherIndex = zToXs[j + 64 * minBitfield]; + + float4 less = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW + ); + float4 more = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY + ); + + if (math.bitmask(less < more) == 0) + { + //overlaps.Add(new EntityPair(entities[currentIndex], entities[otherIndex])); + hitCount++; + } + } + } + else + { + { + int minBit = interval.min & 0x3f; + var bitField = zBits[minBitfield]; + if (minBit > 0) + bitField.SetBits(0, false, minBit); + + if (bitField.Value == 0) + touchedZeroBitfield++; + + for (var j = bitField.CountTrailingZeros(); j < 64; bitField.SetBits(j, false), j = bitField.CountTrailingZeros()) + { + runCount++; + var currentIndex = (int)xs[i]; + var otherIndex = zToXs[j + 64 * minBitfield]; + + float4 less = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW + ); + float4 more = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY + ); + + if (math.bitmask(less < more) == 0) + { + //overlaps.Add(new EntityPair(entities[currentIndex], entities[otherIndex])); + hitCount++; + } + } + } + + for (int k = minBitfield + 1; k < maxBitfield; k++) + { + var bitField = zBits[k]; + + if (bitField.Value == 0) + touchedZeroBitfield++; + + for (var j = bitField.CountTrailingZeros(); j < 64; bitField.SetBits(j, false), j = bitField.CountTrailingZeros()) + { + runCount++; + var currentIndex = (int)xs[i]; + var otherIndex = zToXs[j + 64 * k]; + + float4 less = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW + ); + float4 more = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY + ); + + if (math.bitmask(less < more) == 0) + { + //overlaps.Add(new EntityPair(entities[currentIndex], entities[otherIndex])); + hitCount++; + } + } + } + + { + int maxBit = interval.max & 0x3f; + var bitField = zBits[maxBitfield]; + + if (bitField.Value == 0) + touchedZeroBitfield++; + + for (var j = bitField.CountTrailingZeros(); j <= maxBit; bitField.SetBits(j, false), j = bitField.CountTrailingZeros()) + { + runCount++; + var currentIndex = (int)xs[i]; + var otherIndex = zToXs[j + 64 * maxBitfield]; + + float4 less = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW + ); + float4 more = math.shuffle(minYZmaxYZs[currentIndex], + minYZmaxYZs[otherIndex], + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY + ); + + if (math.bitmask(less < more) == 0) + { + //overlaps.Add(new EntityPair(entities[currentIndex], entities[otherIndex])); + hitCount++; + } + } + } + } + + ref var currentBitfield = ref zBits.ElementAt(interval.index >> 6); + currentBitfield.SetBits(interval.index & 0x3f, true); + zToXs[interval.index] = (int)xs[i]; + + if (runCount > 0) + innerLoopEnterCount++; + innerLoopTestCount += runCount; + if (runCount > innerLoopRunMax) + maxRunIntervalIndex = (int)xs[i]; + innerLoopRunMax = math.max(innerLoopRunMax, runCount); + innerLoopRunMin = math.min(innerLoopRunMin, runCount); + } + else + { + var interval = zIntervals[(int)(xs[i] - minYZmaxYZs.Length)]; + ref var currentBitfield = ref zBits.ElementAt(interval.index >> 6); + currentBitfield.SetBits(interval.index & 0x3f, false); + } + } + var maxInterval = zIntervals[maxRunIntervalIndex]; + + UnityEngine.Debug.Log( + $"Dual Self Sweep stats for layer {layerName} at bucket index {bucket.bucketIndex} and count {bucket.count}\nHits: {hitCount}, inner loop enters: {innerLoopEnterCount}, inner loop tests: {innerLoopTestCount}, inner loop run (min, max): ({innerLoopRunMin}, {innerLoopRunMax}), maxInterval: ({maxInterval.min}, {maxInterval.index}, {maxInterval.max}), touched zero bitfields: {touchedZeroBitfield}"); + } + + struct ZInterval + { + public int index; + public int min; + public int max; + } + + struct Sortable : System.IComparable + { + public float f; + public uint index; + + public int CompareTo(Sortable other) + { + var result = f.CompareTo(other.f); + if (result == 0) + return index.CompareTo(other.index); + return result; + } + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsSweepMethods.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsSweepMethods.cs.meta new file mode 100644 index 0000000..7849451 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/FindPairsSweepMethods.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec6658f30d5be1843a3a82c003a568b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.InflatedSupportMaps.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.InflatedSupportMaps.cs new file mode 100644 index 0000000..c93ebcf --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.InflatedSupportMaps.cs @@ -0,0 +1,359 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + internal struct InflatedSupportMap + { + NativeArray m_x; + NativeArray m_y; + NativeArray m_z; + NativeArray m_uninflatedSupports; + NativeArray m_offsetIds; + NativeArray m_offsets; + + internal float3 GetSupport(float3 direction, out int index) + { + float bestDot = float.MinValue; + int bestIndex = 0; + int count = m_x.Length; + float dx = direction.x; + float dy = direction.y; + float dz = direction.z; + for (int i = 0; i < count; i++) + { + float dot = m_x[i] * dx + m_y[i] * dy + m_z[i] * dz; + bool isBetter = dot > bestDot; + bestDot = math.select(bestDot, dot, isBetter); + bestIndex = math.select(bestIndex, i, isBetter); + } + index = bestIndex; + return new float3(m_x[bestIndex], m_y[bestIndex], m_z[bestIndex]); + } + + internal SupportPoint GetSupport(int index) => m_uninflatedSupports[index]; + + internal int GetOffsetId(int index) => m_offsetIds[index]; + internal float3 GetOffset(int index) => m_offsets[GetOffsetId(index)]; + + internal InflatedSupportMap(NativeArray x, + NativeArray y, + NativeArray z, + NativeArray uninflatedSupports, + NativeArray offsetIds, + NativeArray offsets) + { + m_x = x; + m_y = y; + m_z = z; + m_uninflatedSupports = uninflatedSupports; + m_offsetIds = offsetIds; + m_offsets = offsets; + } + } + + // Pass around by ref if necessary. Otherwise keep as local variable. + internal unsafe struct CapsuleBoxInflatedSupportMap + { + // pointsPerFace * faces * capPoints + edges * pointsPerEdge * capPoints * directionsOfCross + const int k_elements = 4 * 6 * 2 + 12 * 2 * 2 * 2; + const int k_offsets = 6 + 3 * 2; // 6 faces + 3 axes * 2 directions + private fixed float m_x[k_elements]; + private fixed float m_y[k_elements]; + private fixed float m_z[k_elements]; + private fixed uint m_uninflatedSupports[k_elements * 4]; + private fixed int m_offsetIds[k_elements]; + private fixed float m_offsets[k_offsets * 3]; + + CapsuleCollider m_capsuleInBoxSpace; + BoxCollider m_box; + + internal InflatedSupportMap GetInflatedSupportMap() + { + fixed (void* px = m_x) + fixed (void* py = m_y) + fixed (void* pz = m_z) + fixed (void* pus = m_uninflatedSupports) + fixed (void* poids = m_offsetIds) + fixed (void* po = m_offsets) + + { + var x = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(px, k_elements, Allocator.None); + var y = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(py, k_elements, Allocator.None); + var z = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(pz, k_elements, Allocator.None); + var us = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(pus, k_elements, Allocator.None); + var oids = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(poids, k_elements, Allocator.None); + var o = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(po, k_offsets, Allocator.None); + + return new InflatedSupportMap(x, y, z, us, oids, o); + } + } + + internal CapsuleBoxInflatedSupportMap(CapsuleCollider capsuleInBoxSpace, BoxCollider box) + { + UnityEngine.Assertions.Assert.IsTrue(sizeof(SupportPoint) == 16 && UnsafeUtility.AlignOf() <= 16); + + m_capsuleInBoxSpace = capsuleInBoxSpace; + m_box = box; + + int i = 0; + + int capA = 0; + int capB = 1; + bool neg = true; + bool pos = false; + int boxTopLeftFront = math.bitmask(new bool4(neg, pos, neg, false)); + int boxTopRightFront = math.bitmask(new bool4(pos, pos, neg, false)); + int boxTopLeftBack = math.bitmask(new bool4(neg, pos, pos, false)); + int boxTopRightBack = math.bitmask(new bool4(pos, pos, pos, false)); + int boxBottomLeftFront = math.bitmask(new bool4(neg, neg, neg, false)); + int boxBottomRightFront = math.bitmask(new bool4(pos, neg, neg, false)); + int boxBottomLeftBack = math.bitmask(new bool4(neg, neg, pos, false)); + int boxBottomRightBack = math.bitmask(new bool4(pos, neg, pos, false)); + + int up = 0; //math.up() + int down = 1; //math.down() + int left = 2; //math.left() + int right = 3; //math.right() + int forward = 4; //math.forward() + int backward = 5; //math.back() + + float3 edge = math.normalizesafe(capsuleInBoxSpace.pointB - capsuleInBoxSpace.pointA); + int posX = 6; //math.cross(right, edge) + int posY = 7; //math.cross(up, edge) + int posZ = 8; //math.cross(forward, edge) + int negX = 9; //posX + int negY = 10; //posY + int negZ = 11; //-posZ + + fixed (void* offsets = m_offsets) + { + int j = 0; + UnsafeUtility.WriteArrayElement(offsets, j++, math.up()); + UnsafeUtility.WriteArrayElement(offsets, j++, math.down()); + UnsafeUtility.WriteArrayElement(offsets, j++, math.left()); + UnsafeUtility.WriteArrayElement(offsets, j++, math.right()); + UnsafeUtility.WriteArrayElement(offsets, j++, math.forward()); + UnsafeUtility.WriteArrayElement(offsets, j++, math.back()); + + float3 px = math.cross(right, edge);; + float3 py = math.cross(up, edge); + float3 pz = math.cross(forward, edge); + + UnsafeUtility.WriteArrayElement(offsets, j++, px); + UnsafeUtility.WriteArrayElement(offsets, j++, py); + UnsafeUtility.WriteArrayElement(offsets, j++, pz); + UnsafeUtility.WriteArrayElement(offsets, j++, -px); + UnsafeUtility.WriteArrayElement(offsets, j++, -py); + UnsafeUtility.WriteArrayElement(offsets, j++, -pz); + } + + // Cap A and faces + BuildElement(i++, boxTopLeftFront, capA, up); + BuildElement(i++, boxTopRightFront, capA, up); + BuildElement(i++, boxTopLeftBack, capA, up); + BuildElement(i++, boxTopRightBack, capA, up); + + BuildElement(i++, boxBottomLeftFront, capA, down); + BuildElement(i++, boxBottomRightFront, capA, down); + BuildElement(i++, boxBottomLeftBack, capA, down); + BuildElement(i++, boxBottomRightBack, capA, down); + + BuildElement(i++, boxTopLeftFront, capA, left); + BuildElement(i++, boxTopLeftBack, capA, left); + BuildElement(i++, boxBottomLeftFront, capA, left); + BuildElement(i++, boxBottomLeftBack, capA, left); + + BuildElement(i++, boxTopRightFront, capA, right); + BuildElement(i++, boxTopRightBack, capA, right); + BuildElement(i++, boxBottomRightFront, capA, right); + BuildElement(i++, boxBottomRightBack, capA, right); + + BuildElement(i++, boxTopLeftFront, capA, forward); + BuildElement(i++, boxTopRightFront, capA, forward); + BuildElement(i++, boxBottomLeftFront, capA, forward); + BuildElement(i++, boxBottomRightFront, capA, forward); + + BuildElement(i++, boxTopLeftBack, capA, backward); + BuildElement(i++, boxTopRightBack, capA, backward); + BuildElement(i++, boxBottomLeftBack, capA, backward); + BuildElement(i++, boxBottomRightBack, capA, backward); + + // Cap B and faces + BuildElement(i++, boxTopLeftFront, capB, up); + BuildElement(i++, boxTopRightFront, capB, up); + BuildElement(i++, boxTopLeftBack, capB, up); + BuildElement(i++, boxTopRightBack, capB, up); + + BuildElement(i++, boxBottomLeftFront, capB, down); + BuildElement(i++, boxBottomRightFront, capB, down); + BuildElement(i++, boxBottomLeftBack, capB, down); + BuildElement(i++, boxBottomRightBack, capB, down); + + BuildElement(i++, boxTopLeftFront, capB, left); + BuildElement(i++, boxTopLeftBack, capB, left); + BuildElement(i++, boxBottomLeftFront, capB, left); + BuildElement(i++, boxBottomLeftBack, capB, left); + + BuildElement(i++, boxTopRightFront, capB, right); + BuildElement(i++, boxTopRightBack, capB, right); + BuildElement(i++, boxBottomRightFront, capB, right); + BuildElement(i++, boxBottomRightBack, capB, right); + + BuildElement(i++, boxTopLeftFront, capB, backward); + BuildElement(i++, boxTopRightFront, capB, backward); + BuildElement(i++, boxBottomLeftFront, capB, backward); + BuildElement(i++, boxBottomRightFront, capB, backward); + + BuildElement(i++, boxTopLeftBack, capB, forward); + BuildElement(i++, boxTopRightBack, capB, forward); + BuildElement(i++, boxBottomLeftBack, capB, forward); + BuildElement(i++, boxBottomRightBack, capB, forward); + + // Cap A and pos edges + BuildElement(i++, boxTopLeftFront, capA, posY); + BuildElement(i++, boxTopRightFront, capA, posY); + BuildElement(i++, boxTopLeftBack, capA, posY); + BuildElement(i++, boxTopRightBack, capA, posY); + + BuildElement(i++, boxBottomLeftFront, capA, posY); + BuildElement(i++, boxBottomRightFront, capA, posY); + BuildElement(i++, boxBottomLeftBack, capA, posY); + BuildElement(i++, boxBottomRightBack, capA, posY); + + BuildElement(i++, boxTopLeftFront, capA, posX); + BuildElement(i++, boxTopLeftBack, capA, posX); + BuildElement(i++, boxBottomLeftFront, capA, posX); + BuildElement(i++, boxBottomLeftBack, capA, posX); + + BuildElement(i++, boxTopRightFront, capA, posX); + BuildElement(i++, boxTopRightBack, capA, posX); + BuildElement(i++, boxBottomRightFront, capA, posX); + BuildElement(i++, boxBottomRightBack, capA, posX); + + BuildElement(i++, boxTopLeftFront, capA, posZ); + BuildElement(i++, boxTopRightFront, capA, posZ); + BuildElement(i++, boxBottomLeftFront, capA, posZ); + BuildElement(i++, boxBottomRightFront, capA, posZ); + + BuildElement(i++, boxTopLeftBack, capA, posZ); + BuildElement(i++, boxTopRightBack, capA, posZ); + BuildElement(i++, boxBottomLeftBack, capA, posZ); + BuildElement(i++, boxBottomRightBack, capA, posZ); + + // Cap A and neg edges + BuildElement(i++, boxTopLeftFront, capA, negY); + BuildElement(i++, boxTopRightFront, capA, negY); + BuildElement(i++, boxTopLeftBack, capA, negY); + BuildElement(i++, boxTopRightBack, capA, negY); + + BuildElement(i++, boxBottomLeftFront, capA, negY); + BuildElement(i++, boxBottomRightFront, capA, negY); + BuildElement(i++, boxBottomLeftBack, capA, negY); + BuildElement(i++, boxBottomRightBack, capA, negY); + + BuildElement(i++, boxTopLeftFront, capA, negX); + BuildElement(i++, boxTopLeftBack, capA, negX); + BuildElement(i++, boxBottomLeftFront, capA, negX); + BuildElement(i++, boxBottomLeftBack, capA, negX); + + BuildElement(i++, boxTopRightFront, capA, negX); + BuildElement(i++, boxTopRightBack, capA, negX); + BuildElement(i++, boxBottomRightFront, capA, negX); + BuildElement(i++, boxBottomRightBack, capA, negX); + + BuildElement(i++, boxTopLeftFront, capA, negZ); + BuildElement(i++, boxTopRightFront, capA, negZ); + BuildElement(i++, boxBottomLeftFront, capA, negZ); + BuildElement(i++, boxBottomRightFront, capA, negZ); + + BuildElement(i++, boxTopLeftBack, capA, negZ); + BuildElement(i++, boxTopRightBack, capA, negZ); + BuildElement(i++, boxBottomLeftBack, capA, negZ); + BuildElement(i++, boxBottomRightBack, capA, negZ); + + // Cap B and pos edges + BuildElement(i++, boxTopLeftFront, capB, posY); + BuildElement(i++, boxTopRightFront, capB, posY); + BuildElement(i++, boxTopLeftBack, capB, posY); + BuildElement(i++, boxTopRightBack, capB, posY); + + BuildElement(i++, boxBottomLeftFront, capB, posY); + BuildElement(i++, boxBottomRightFront, capB, posY); + BuildElement(i++, boxBottomLeftBack, capB, posY); + BuildElement(i++, boxBottomRightBack, capB, posY); + + BuildElement(i++, boxTopLeftFront, capB, posX); + BuildElement(i++, boxTopLeftBack, capB, posX); + BuildElement(i++, boxBottomLeftFront, capB, posX); + BuildElement(i++, boxBottomLeftBack, capB, posX); + + BuildElement(i++, boxTopRightFront, capB, posX); + BuildElement(i++, boxTopRightBack, capB, posX); + BuildElement(i++, boxBottomRightFront, capB, posX); + BuildElement(i++, boxBottomRightBack, capB, posX); + + BuildElement(i++, boxTopLeftFront, capB, posZ); + BuildElement(i++, boxTopRightFront, capB, posZ); + BuildElement(i++, boxBottomLeftFront, capB, posZ); + BuildElement(i++, boxBottomRightFront, capB, posZ); + + BuildElement(i++, boxTopLeftBack, capB, posZ); + BuildElement(i++, boxTopRightBack, capB, posZ); + BuildElement(i++, boxBottomLeftBack, capB, posZ); + BuildElement(i++, boxBottomRightBack, capB, posZ); + + // Cap B and neg edges + BuildElement(i++, boxTopLeftFront, capB, negY); + BuildElement(i++, boxTopRightFront, capB, negY); + BuildElement(i++, boxTopLeftBack, capB, negY); + BuildElement(i++, boxTopRightBack, capB, negY); + + BuildElement(i++, boxBottomLeftFront, capB, negY); + BuildElement(i++, boxBottomRightFront, capB, negY); + BuildElement(i++, boxBottomLeftBack, capB, negY); + BuildElement(i++, boxBottomRightBack, capB, negY); + + BuildElement(i++, boxTopLeftFront, capB, negX); + BuildElement(i++, boxTopLeftBack, capB, negX); + BuildElement(i++, boxBottomLeftFront, capB, negX); + BuildElement(i++, boxBottomLeftBack, capB, negX); + + BuildElement(i++, boxTopRightFront, capB, negX); + BuildElement(i++, boxTopRightBack, capB, negX); + BuildElement(i++, boxBottomRightFront, capB, negX); + BuildElement(i++, boxBottomRightBack, capB, negX); + + BuildElement(i++, boxTopLeftFront, capB, negZ); + BuildElement(i++, boxTopRightFront, capB, negZ); + BuildElement(i++, boxBottomLeftFront, capB, negZ); + BuildElement(i++, boxBottomRightFront, capB, negZ); + + BuildElement(i++, boxTopLeftBack, capB, negZ); + BuildElement(i++, boxTopRightBack, capB, negZ); + BuildElement(i++, boxBottomLeftBack, capB, negZ); + BuildElement(i++, boxBottomRightBack, capB, negZ); + } + + private void BuildElement(int index, int boxIndex, int capsuleIndex, int offsetDirection) + { + SupportPoint support = default; + support.id = ((uint)capsuleIndex << 16) | (uint)boxIndex; + support.pos = + math.select(m_capsuleInBoxSpace.pointA, m_capsuleInBoxSpace.pointB, + capsuleIndex != 0) - (m_box.center + math.select(m_box.halfSize, -m_box.halfSize, (new uint3(1, 2, 4) & (uint)boxIndex) != 0)); + float3 inflatedPos = support.pos + offsetDirection * m_capsuleInBoxSpace.radius; + m_x[index] = inflatedPos.x; + m_y[index] = inflatedPos.y; + m_z[index] = inflatedPos.z; + fixed (void* us = m_uninflatedSupports) + UnsafeUtility.WriteArrayElement(us, index, support); + m_offsetIds[index] = offsetDirection; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.InflatedSupportMaps.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.InflatedSupportMaps.cs.meta new file mode 100644 index 0000000..bf1522d --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.InflatedSupportMaps.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f814bd092f0d7944b133267e0b805c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.Supports.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.Supports.cs new file mode 100644 index 0000000..bc4ad2f --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.Supports.cs @@ -0,0 +1,238 @@ +using Unity.Mathematics; + +// This file contains all the support mapping and collider-specific utilty functions used by GJK, EPA, and MPR + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + internal struct SupportPoint + { + public float3 pos; + public uint id; + + public int idA => (int)(id >> 16); + public int idB => (int)(id & 0xffff); + } + + private static float3 GetSupport(in Collider collider, int id) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + return collider.m_sphere.center; + } + case ColliderType.Capsule: + { + return math.select(collider.m_capsule.pointA, collider.m_capsule.pointB, id > 0); + } + case ColliderType.Box: + { + bool3 isNegative = (new int3(1, 2, 4) & id) != 0; + return collider.m_box.center + math.select(collider.m_box.halfSize, -collider.m_box.halfSize, isNegative); + } + case ColliderType.Triangle: + { + var temp = math.select(collider.m_triangle.pointA, collider.m_triangle.pointB, id > 0); + return math.select(temp, collider.m_triangle.pointC, id > 1); + } + case ColliderType.Convex: + { + ref var blob = ref collider.m_convex.convexColliderBlob.Value; + return new float3(blob.verticesX[id], blob.verticesY[id], blob.verticesZ[id]) * collider.m_convex.scale; + } + default: return float3.zero; + } + } + + private static SupportPoint GetSupport(in Collider collider, float3 direction) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + return new SupportPoint { pos = collider.m_sphere.center, id = 0 }; + } + case ColliderType.Capsule: + { + bool aWins = math.dot(collider.m_capsule.pointA, direction) >= math.dot(collider.m_capsule.pointB, direction); + return new SupportPoint + { + pos = math.select(collider.m_capsule.pointB, collider.m_capsule.pointA, aWins), + id = math.select(1u, 0u, aWins) + }; + } + case ColliderType.Box: + { + bool4 isNegative = new bool4(direction < 0f, false); + return new SupportPoint + { + pos = collider.m_box.center + math.select(collider.m_box.halfSize, -collider.m_box.halfSize, isNegative.xyz), + id = (uint)math.bitmask(isNegative) + }; + } + case ColliderType.Triangle: + { + simdFloat3 triPoints = new simdFloat3(collider.m_triangle.pointA, collider.m_triangle.pointB, collider.m_triangle.pointC, collider.m_triangle.pointA); + float3 dot = simd.dot(triPoints, direction).xyz; + int id = math.tzcnt(math.bitmask(new bool4(math.cmax(dot) == dot, true))); + return new SupportPoint + { + pos = triPoints[id], + id = (uint)id + }; + } + case ColliderType.Convex: + { + ref var blob = ref collider.m_convex.convexColliderBlob.Value; + int id = 0; + float bestDot = float.MinValue; + float3 scaledDirection = direction * collider.m_convex.scale; + for (int i = 0; i < blob.verticesX.Length; i++) + { + float dot = scaledDirection.x * blob.verticesX[i] + scaledDirection.y * blob.verticesY[i] + scaledDirection.z * blob.verticesZ[i]; + if (dot > bestDot) + { + bestDot = dot; + id = i; + } + } + return new SupportPoint + { + pos = new float3(blob.verticesX[id], blob.verticesY[id], blob.verticesZ[id]) * collider.m_convex.scale, + id = (uint)id + }; + } + default: return default; + } + } + + private static SupportPoint GetSupport(in Collider collider, float3 direction, RigidTransform bInA) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + return new SupportPoint { pos = math.transform(bInA, collider.m_sphere.center), id = 0 }; + } + case ColliderType.Capsule: + { + float3 directionInA = math.rotate(math.inverse(bInA.rot), direction); + bool aWins = math.dot(collider.m_capsule.pointA, directionInA) >= math.dot(collider.m_capsule.pointB, directionInA); + return new SupportPoint + { + pos = math.transform(bInA, math.select(collider.m_capsule.pointB, collider.m_capsule.pointA, aWins)), + id = math.select(1u, 0u, aWins) + }; + } + case ColliderType.Box: + { + float3 directionInA = math.rotate(math.inverse(bInA.rot), direction); + bool4 isNegative = new bool4(directionInA < 0f, false); + return new SupportPoint + { + pos = math.transform(bInA, collider.m_box.center + math.select(collider.m_box.halfSize, -collider.m_box.halfSize, isNegative.xyz)), + id = (uint)math.bitmask(isNegative) + }; + } + case ColliderType.Triangle: + { + float3 directionInA = math.rotate(math.inverse(bInA.rot), direction); + simdFloat3 triPoints = new simdFloat3(collider.m_triangle.pointA, collider.m_triangle.pointB, collider.m_triangle.pointC, collider.m_triangle.pointA); + float3 dot = simd.dot(triPoints, directionInA).xyz; + int id = math.tzcnt(math.bitmask(new bool4(math.cmax(dot) == dot, true))); + return new SupportPoint + { + pos = math.transform(bInA, triPoints[id]), + id = (uint)id + }; + } + case ColliderType.Convex: + { + float3 scaledDirectionInA = math.rotate(math.inverse(bInA.rot), direction) * collider.m_convex.scale; + ref var blob = ref collider.m_convex.convexColliderBlob.Value; + int id = 0; + float bestDot = float.MinValue; + for (int i = 0; i < blob.verticesX.Length; i++) + { + float dot = scaledDirectionInA.x * blob.verticesX[i] + scaledDirectionInA.y * blob.verticesY[i] + scaledDirectionInA.z * blob.verticesZ[i]; + if (dot > bestDot) + { + bestDot = dot; + id = i; + } + } + return new SupportPoint + { + pos = math.transform(bInA, new float3(blob.verticesX[id], blob.verticesY[id], blob.verticesZ[id]) * collider.m_convex.scale), + id = (uint)id + }; + } + default: return default; + } + } + + private static SupportPoint GetSupport(in Collider colliderA, in Collider colliderB, float3 direction, RigidTransform bInA) + { + var a = GetSupport(in colliderA, direction); + var b = GetSupport(in colliderB, -direction, bInA); + return new SupportPoint + { + pos = a.pos - b.pos, + id = (a.id << 16) | b.id + }; + } + + private static SupportPoint Get3DSupportFromPlanar(in Collider colliderA, in Collider colliderB, RigidTransform bInA, SupportPoint planarSupport) + { + var a = GetSupport(in colliderA, planarSupport.idA); + var b = GetSupport(in colliderB, planarSupport.idB); + return new SupportPoint + { + pos = a - math.transform(bInA, b), + id = planarSupport.id + }; + } + + private static Aabb GetCsoAabb(in Collider colliderA, in Collider colliderB, RigidTransform bInA) + { + var aabbA = Physics.AabbFrom(in colliderA, RigidTransform.identity); + var aabbB = Physics.AabbFrom(in colliderB, in bInA); + return new Aabb(aabbA.min - aabbB.max, aabbA.max - aabbB.min); + } + + private static float GetRadialPadding(in Collider collider) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + return collider.m_sphere.radius; + } + case ColliderType.Capsule: + { + return collider.m_capsule.radius; + } + case ColliderType.Box: return 0f; + case ColliderType.Triangle: return 0f; + case ColliderType.Convex: return 0f; + default: return 0f; + } + } + + private static SupportPoint GetPlanarSupport(in Collider colliderA, in Collider colliderB, float3 direction, RigidTransform bInA, float3 planeNormal) + { + var a = GetSupport(in colliderA, direction); + var b = GetSupport(in colliderB, -direction, bInA); + float3 supportPos = a.pos - b.pos; + + return new SupportPoint + { + pos = supportPos - math.dot(planeNormal, supportPos) * planeNormal, + id = (a.id << 16) | b.id + }; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.Supports.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.Supports.cs.meta new file mode 100644 index 0000000..572e19b --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/GjkEpaMpr.Supports.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f36186de4d673d4fb4c4263b1e54c8e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQueryProcessors.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQueryProcessors.cs new file mode 100644 index 0000000..3930491 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQueryProcessors.cs @@ -0,0 +1,305 @@ +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +//Todo: Stream types, single schedulers, scratchlists, and inflations +namespace Latios.Psyshock +{ + internal static class LayerQueryProcessors + { + public unsafe struct RaycastClosestImmediateProcessor : IFindObjectsProcessor + { + private Ray m_ray; + private RaycastResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public RaycastClosestImmediateProcessor(Ray ray, ref RaycastResult result, ref LayerBodyInfo info) + { + m_ray = ray; + m_resultPtr = (RaycastResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndex = -1; + } + + public void Execute(in FindObjectsResult result) + { + var hit = Physics.Raycast(m_ray, result.collider, result.transform, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + m_ray.end = m_resultPtr->position; + } + } + } + + public unsafe struct RaycastAnyImmediateProcessor : IFindObjectsProcessor + { + private Ray m_ray; + private RaycastResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public RaycastAnyImmediateProcessor(Ray ray, ref RaycastResult result, ref LayerBodyInfo info) + { + m_ray = ray; + m_resultPtr = (RaycastResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndex = -1; + } + + public void Execute(in FindObjectsResult result) + { + if (m_resultPtr->subColliderIndex >= 0) + return; + + var hit = Physics.Raycast(m_ray, result.collider, result.transform, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + } + } + } + + public unsafe struct PointDistanceClosestImmediateProcessor : IFindObjectsProcessor + { + private float3 m_point; + private float m_maxDistance; + private PointDistanceResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public PointDistanceClosestImmediateProcessor(float3 point, float maxDistance, ref PointDistanceResult result, ref LayerBodyInfo info) + { + m_point = point; + m_maxDistance = maxDistance; + m_resultPtr = (PointDistanceResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndex = -1; + } + + public void Execute(in FindObjectsResult result) + { + var hit = Physics.DistanceBetween(m_point, result.collider, result.transform, m_maxDistance, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + m_maxDistance = m_resultPtr->distance; + } + } + } + + public unsafe struct PointDistanceAnyImmediateProcessor : IFindObjectsProcessor + { + private float3 m_point; + private float m_maxDistance; + private PointDistanceResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public PointDistanceAnyImmediateProcessor(float3 point, float maxDistance, ref PointDistanceResult result, ref LayerBodyInfo info) + { + m_point = point; + m_maxDistance = maxDistance; + m_resultPtr = (PointDistanceResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndex = -1; + } + + public void Execute(in FindObjectsResult result) + { + if (m_resultPtr->subColliderIndex >= 0) + return; + + var hit = Physics.DistanceBetween(m_point, result.collider, result.transform, m_maxDistance, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + } + } + } + + public unsafe struct ColliderDistanceClosestImmediateProcessor : IFindObjectsProcessor + { + private Collider m_collider; + private RigidTransform m_transform; + private float m_maxDistance; + private ColliderDistanceResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public ColliderDistanceClosestImmediateProcessor(Collider collider, + RigidTransform transform, + float maxDistance, + ref ColliderDistanceResult result, + ref LayerBodyInfo info) + { + m_collider = collider; + m_transform = transform; + m_maxDistance = maxDistance; + m_resultPtr = (ColliderDistanceResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndexB = -1; + } + + public void Execute(in FindObjectsResult result) + { + var hit = Physics.DistanceBetween(in m_collider, in m_transform, result.collider, result.transform, m_maxDistance, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + m_maxDistance = m_resultPtr->distance; + } + } + } + + public unsafe struct ColliderDistanceAnyImmediateProcessor : IFindObjectsProcessor + { + private Collider m_collider; + private RigidTransform m_transform; + private float m_maxDistance; + private ColliderDistanceResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public ColliderDistanceAnyImmediateProcessor(Collider collider, RigidTransform transform, float maxDistance, ref ColliderDistanceResult result, ref LayerBodyInfo info) + { + m_collider = collider; + m_transform = transform; + m_maxDistance = maxDistance; + m_resultPtr = (ColliderDistanceResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndexB = -1; + } + + public void Execute(in FindObjectsResult result) + { + if (m_resultPtr->subColliderIndexB >= 0) + return; + + var hit = Physics.DistanceBetween(in m_collider, in m_transform, result.collider, result.transform, m_maxDistance, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + } + } + } + + public unsafe struct ColliderCastClosestImmediateProcessor : IFindObjectsProcessor + { + private Collider m_collider; + private RigidTransform m_start; + private float3 m_end; + private ColliderCastResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public ColliderCastClosestImmediateProcessor(Collider collider, + RigidTransform start, + float3 end, + ref ColliderCastResult result, + ref LayerBodyInfo info) + { + m_collider = collider; + m_start = start; + m_end = end; + m_resultPtr = (ColliderCastResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndexOnTarget = -1; + } + + public void Execute(in FindObjectsResult result) + { + var hit = Physics.ColliderCast(in m_collider, in m_start, m_end, result.collider, result.transform, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + m_end = m_resultPtr->distance * math.normalize(m_end - m_start.pos) + m_start.pos; + } + } + } + + public unsafe struct ColliderCastAnyImmediateProcessor : IFindObjectsProcessor + { + private Collider m_collider; + private RigidTransform m_start; + private float3 m_end; + private ColliderCastResult* m_resultPtr; + private LayerBodyInfo* m_infoPtr; + + public ColliderCastAnyImmediateProcessor(Collider collider, + RigidTransform start, + float3 end, + ref ColliderCastResult result, + ref LayerBodyInfo info) + { + m_collider = collider; + m_start = start; + m_end = end; + m_resultPtr = (ColliderCastResult*)UnsafeUtility.AddressOf(ref result); + m_infoPtr = (LayerBodyInfo*)UnsafeUtility.AddressOf(ref info); + m_resultPtr->subColliderIndexOnTarget = -1; + } + + public void Execute(in FindObjectsResult result) + { + if (m_resultPtr->subColliderIndexOnTarget >= 0) + return; + + var hit = Physics.ColliderCast(in m_collider, in m_start, m_end, result.collider, result.transform, out var newResult); + if (hit) + { + *m_resultPtr = newResult; + *m_infoPtr = new LayerBodyInfo + { + body = result.body, + bodyIndex = result.bodyIndex, + aabb = result.aabb + }; + } + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQueryProcessors.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQueryProcessors.cs.meta new file mode 100644 index 0000000..2a62c29 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQueryProcessors.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9ac2be1d7a7eb884c83baa5871256c6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQuerySweepMethods.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQuerySweepMethods.cs new file mode 100644 index 0000000..0a49c29 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQuerySweepMethods.cs @@ -0,0 +1,143 @@ +using Unity.Burst; +using Unity.Burst.CompilerServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + [BurstCompile] + internal static class LayerQuerySweepMethods + { + public static void AabbSweep(Aabb aabb, CollisionLayer layer, ref T processor) where T : struct, IFindObjectsProcessor + { + if (math.any(math.isnan(aabb.min) | math.isnan(aabb.max))) + return; + + int3 minBucket = math.int3(math.floor((aabb.min - layer.worldMin) / layer.worldAxisStride)); + int3 maxBucket = math.int3(math.floor((aabb.max - layer.worldMin) / layer.worldAxisStride)); + minBucket = math.clamp(minBucket, 0, layer.worldSubdivisionsPerAxis - 1); + maxBucket = math.clamp(maxBucket, 0, layer.worldSubdivisionsPerAxis - 1); + + for (int i = minBucket.x; i <= maxBucket.x; i++) + { + for (int j = minBucket.y; j <= maxBucket.y; j++) + { + for (int k = minBucket.z; k <= maxBucket.z; k++) + { + var bucketIndex = (i * layer.worldSubdivisionsPerAxis.y + j) * layer.worldSubdivisionsPerAxis.z + k; + AabbSweepBucket(aabb, layer, layer.GetBucketSlices(bucketIndex), ref processor); + } + } + } + + AabbSweepBucket(aabb, layer, layer.GetBucketSlices(layer.BucketCount - 1), ref processor); + } + + private static void AabbSweepBucket(Aabb aabb, CollisionLayer layer, BucketSlices bucket, ref T processor) where T : struct, IFindObjectsProcessor + { + if (bucket.count == 0) + return; + + var context = new AabbSweepRecursiveContext + { + bucket = bucket, + qxmin = aabb.min.x, + qyzMinMax = new float4(aabb.max.yz, -aabb.min.yz), + result = new FindObjectsResult(layer, bucket, 0, false) + }; + + var qxmax = aabb.max.x; + + var linearSweepStartIndex = BinarySearchFirstGreaterOrEqual(in bucket.xmins, context.qxmin); + + for (int indexInBucket = linearSweepStartIndex; indexInBucket < bucket.count && bucket.xmins[indexInBucket] <= qxmax; indexInBucket++) + { + if (math.bitmask(context.qyzMinMax < bucket.yzminmaxs[indexInBucket]) == 0) + { + context.result.SetBucketRelativeIndex(indexInBucket); + processor.Execute(in context.result); + } + } + + SearchTree(0, ref context, ref processor); + } + + private static unsafe int BinarySearchFirstGreaterOrEqual(in NativeArray array, float searchValue) + { + return BinarySearchFirstGreaterOrEqual((float*)array.GetUnsafeReadOnlyPtr(), array.Length, searchValue); + } + + // Returns count if nothing is greater or equal + // The following function is a C# and Burst adaptation of Paul-Virak Khuong and Pat Morin's + // optimized sequential order binary search: https://github.com/patmorin/arraylayout/blob/master/src/sorted_array.h + // This code is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0) + private static unsafe int BinarySearchFirstGreaterOrEqual(float* array, [AssumeRange(0, int.MaxValue)] int count, float searchValue) + { + for (int i = 1; i < count; i++) + { + Hint.Assume(array[i] >= array[i - 1]); + } + + var basePtr = array; + uint n = (uint)count; + while (Hint.Likely(n > 1)) + { + var half = n / 2; + n -= half; + var newPtr = &basePtr[half]; + + // As of Burst 1.8.0 prev 2 + // Burst never loads &basePtr[half] into a register for newPtr, and instead uses dual register addressing instead. + // Because of this, instead of loading into the register, performing the comparison, using a cmov, and then a jump, + // Burst immediately performs the comparison, conditionally jumps, uses a lea, and then a jump. + // This is technically less instructions on average. But branch prediction may suffer as a result. + basePtr = *newPtr < searchValue ? newPtr : basePtr; + } + + if (*basePtr < searchValue) + basePtr++; + + return (int)(basePtr - array); + } + + private struct AabbSweepRecursiveContext + { + public FindObjectsResult result; + public BucketSlices bucket; + public float4 qyzMinMax; + public float qxmin; + } + + private static void SearchTree(uint currentIndex, ref AabbSweepRecursiveContext context, ref T processor) where T : struct, IFindObjectsProcessor + { + if (currentIndex >= context.bucket.count) + return; + + var node = context.bucket.intervalTree[(int)currentIndex]; + if (context.qxmin >= node.subtreeXmax) + return; + + SearchTree(GetLeftChildIndex(currentIndex), ref context, ref processor); + + if (context.qxmin < node.xmin) + return; + + if (context.qxmin > node.xmin && context.qxmin <= node.xmax) + { + if (math.bitmask(context.qyzMinMax < context.bucket.yzminmaxs[node.bucketRelativeBodyIndex]) == 0) + { + context.result.SetBucketRelativeIndex(node.bucketRelativeBodyIndex); + processor.Execute(in context.result); + } + } + + SearchTree(GetRightChildIndex(currentIndex), ref context, ref processor); + } + + public static uint GetLeftChildIndex(uint currentIndex) => 2 * currentIndex + 1; + public static uint GetRightChildIndex(uint currentIndex) => 2 * currentIndex + 2; + public static uint GetParentIndex(uint currentIndex) => (currentIndex - 1) / 2; + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQuerySweepMethods.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQuerySweepMethods.cs.meta new file mode 100644 index 0000000..76c76be --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/LayerQuerySweepMethods.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3089281cd42975d4e98038450135f8a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.Convex.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.Convex.cs new file mode 100644 index 0000000..a79e8e4 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.Convex.cs @@ -0,0 +1,208 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + public static bool PointConvexDistance(float3 point, ConvexCollider convex, float maxDistance, out PointDistanceResultInternal result) + { + ref var blob = ref convex.convexColliderBlob.Value; + float3 invScale = math.rcp(convex.scale); + var dimensions = math.countbits(math.bitmask(new bool4(math.isfinite(invScale), false))); + + if (dimensions == 3) + { + float maxSignedDistance = float.MinValue; + int bestPlaneIndex = 0; + float3 scaledPoint = point * invScale; + + for (int i = 0; i < blob.facePlaneX.Length; i++) + { + float dot = scaledPoint.x * blob.facePlaneX[i] + scaledPoint.y * blob.facePlaneY[i] + scaledPoint.z * blob.facePlaneZ[i] + blob.facePlaneDist[i]; + if (dot > maxSignedDistance) + { + maxSignedDistance = dot; + bestPlaneIndex = i; + } + } + + var localNormal = new float3(blob.facePlaneX[bestPlaneIndex], blob.facePlaneY[bestPlaneIndex], blob.facePlaneZ[bestPlaneIndex]); + if (maxSignedDistance < 0f) + { + var localHit = localNormal * -maxSignedDistance + scaledPoint; + result.hitpoint = localHit * convex.scale; + result.distance = math.distance(result.hitpoint, point); + result.normal = math.normalize(localNormal * invScale); + return result.distance <= maxDistance; + } + + var edgeRange = blob.edgeIndicesInFacesStartsAndCounts[bestPlaneIndex]; + float maxEdgeSignedDistance = float.MinValue; + int bestFaceEdgeIndex = 0; + for (int i = 0; i < edgeRange.y; i++) + { + float dot = math.dot(scaledPoint.xyz1(), blob.faceEdgeOutwardPlanes[i + edgeRange.x]); + if (dot > maxEdgeSignedDistance) + { + maxEdgeSignedDistance = dot; + bestFaceEdgeIndex = i; + } + } + + if (maxEdgeSignedDistance < 0f) + { + var localHit = localNormal * -maxSignedDistance + scaledPoint; + result.hitpoint = localHit * convex.scale; + result.distance = math.distance(result.hitpoint, point); + result.normal = math.normalize(localNormal * invScale); + return result.distance <= maxDistance; + } + + var edgeVertices = blob.vertexIndicesInEdges[blob.edgeIndicesInFaces[bestFaceEdgeIndex + edgeRange.x]]; + float3 vertexA = new float3(blob.verticesX[edgeVertices.x], blob.verticesY[edgeVertices.x], blob.verticesZ[edgeVertices.x]); + float3 vertexB = new float3(blob.verticesX[edgeVertices.y], blob.verticesY[edgeVertices.y], blob.verticesZ[edgeVertices.y]); + + float3 ab = vertexB - vertexA; + float3 ap = scaledPoint - vertexA; + float edgeDot = math.dot(ap, ab); + + if (edgeDot <= 0f) + { + result.hitpoint = vertexA * convex.scale; + result.distance = math.distance(result.hitpoint, point); + result.normal = math.normalize(blob.vertexNormals[edgeVertices.x] * invScale); + return result.distance <= maxDistance; + } + + float edgeLengthSq = math.lengthsq(ab); + + if (edgeDot >= edgeLengthSq) + { + result.hitpoint = vertexB * convex.scale; + result.distance = math.distance(result.hitpoint, point); + result.normal = math.normalize(blob.vertexNormals[edgeVertices.y] * invScale); + return result.distance <= maxDistance; + } + + result.hitpoint = (vertexA + ab * edgeDot / edgeLengthSq) * convex.scale; + result.distance = math.distance(result.hitpoint, point); + result.normal = math.normalize(blob.edgeNormals[blob.edgeIndicesInFaces[bestFaceEdgeIndex + edgeRange.x]] * invScale); + return result.distance <= maxDistance; + } + else if (dimensions == 0) + { + result.hitpoint = 0f; + result.distance = math.length(point); + result.normal = math.normalizesafe(point, new float3(0f, 1f, 0f)); + return result.distance <= maxDistance; + } + else if (dimensions == 1) + { + float min = float.MaxValue; + float max = float.MinValue; + float position = 0f; + Aabb scaledAabb = new Aabb(blob.localAabb.min * convex.scale, blob.localAabb.max * convex.scale); + + if (math.isfinite(invScale).x) + { + min = scaledAabb.min.x; + max = scaledAabb.max.x; + position = point.x; + } + else if (math.isfinite(invScale).y) + { + min = scaledAabb.min.y; + max = scaledAabb.max.y; + position = point.y; + } + else if (math.isfinite(invScale).z) + { + min = scaledAabb.min.z; + max = scaledAabb.max.z; + position = point.z; + } + + float3 mask = math.select(0f, 1f, math.isfinite(invScale)); + if (position <= min) + { + result.hitpoint = min * mask; + result.distance = math.distance(point, result.hitpoint); + result.normal = -mask; + return result.distance <= maxDistance; + } + if (position >= max) + { + result.hitpoint = max * mask; + result.distance = math.distance(point, result.hitpoint); + result.normal = mask; + return result.distance <= maxDistance; + } + result.hitpoint = position * mask; + result.distance = math.distance(point, result.hitpoint); + result.normal = math.normalizesafe(point - result.hitpoint, math.select(-mask, mask, position >= (min + mask) / 2f)); + return result.distance <= maxDistance; + } + else //if (dimensions == 2) + { + //Todo: + var mask = math.select(1f, 0f, math.isfinite(invScale)); + if (math.abs(math.csum(point * mask)) > maxDistance) + { + result = default; + return false; + } + + var flipMask = 1f - mask; + var diff = blob.localAabb.max - blob.localAabb.min; + diff *= mask; + var hitPoint = flipMask * point; + + var inflateRay = new Ray(hitPoint - diff, hitPoint + diff); + var inflateConvex = convex; + inflateConvex.scale = math.select(1f, convex.scale, math.isfinite(invScale)); + if (RaycastConvex(inflateRay, inflateConvex, out _, out _)) + { + result.hitpoint = hitPoint; + result.distance = math.abs(math.csum(point * mask)); + result.normal = math.select(-1f, 1f, point >= 0f) * mask; + return true; + } + + result = default; + result.distance = float.MaxValue; + + for (int i = 0; i < blob.vertexIndicesInEdges.Length; i++) + { + var indices = blob.vertexIndicesInEdges[i]; + float3 vertexA = new float3(blob.verticesX[indices.x], blob.verticesY[indices.x], blob.verticesZ[indices.x]); + float3 vertexB = new float3(blob.verticesX[indices.y], blob.verticesY[indices.y], blob.verticesZ[indices.y]); + vertexA *= flipMask; + vertexB *= flipMask; + + float3 edge = vertexB - vertexA; + float3 ap = point - vertexA; + float dot = math.dot(ap, edge); + float edgeLengthSq = math.lengthsq(edge); + dot = math.clamp(dot, 0f, edgeLengthSq); + float3 pointOnSegment = vertexA + edge * dot / edgeLengthSq; + float newDistance = math.distance(pointOnSegment, point); + + if (newDistance < result.distance) + { + result.distance = newDistance; + result.hitpoint = pointOnSegment; + } + } + + if (result.distance <= maxDistance) + { + result.normal = math.select(-1f, 1f, point >= 0f) * mask; + return true; + } + return false; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.Convex.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.Convex.cs.meta new file mode 100644 index 0000000..b15e888 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.Convex.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba71332217909f44585030b3b2f4ea37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.cs new file mode 100644 index 0000000..7142e7f --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.cs @@ -0,0 +1,415 @@ +using System; +using Unity.Mathematics; + +//A lot of this code has been commented out as I wrote it before I got distance query operations working in a real project. +//Consequently, I want to vet them before I uncomment them. + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + public struct PointDistanceResultInternal + { + public float3 hitpoint; + public float distance; + public float3 normal; + } + + //All algorithms return a negative distance if inside the collider. + public static bool PointSphereDistance(float3 point, SphereCollider sphere, float maxDistance, out PointDistanceResultInternal result) + { + float3 delta = sphere.center - point; + float pcDistanceSq = math.lengthsq(delta); //point center distance + bool distanceIsZero = pcDistanceSq == 0.0f; + float invPCDistance = math.select(math.rsqrt(pcDistanceSq), 0.0f, distanceIsZero); + float3 inNormal = math.select(delta * invPCDistance, new float3(0, 1, 0), distanceIsZero); // choose an arbitrary normal when the distance is zero + float distance = pcDistanceSq * invPCDistance - sphere.radius; + result = new PointDistanceResultInternal + { + hitpoint = point + inNormal * distance, + distance = distance, + normal = -inNormal, + }; + return distance <= maxDistance; + } + + public static bool PointCapsuleDistance(float3 point, CapsuleCollider capsule, float maxDistance, out PointDistanceResultInternal result) + { + //Strategy: Project p onto the capsule's line clamped to the segment. Then inflate point on line as sphere + float3 edge = capsule.pointB - capsule.pointA; + float3 ap = point - capsule.pointA; + float dot = math.dot(ap, edge); + float edgeLengthSq = math.lengthsq(edge); + dot = math.clamp(dot, 0f, edgeLengthSq); + float3 pointOnSegment = capsule.pointA + edge * dot / edgeLengthSq; + SphereCollider sphere = new SphereCollider(pointOnSegment, capsule.radius); + return PointSphereDistance(point, sphere, maxDistance, out result); + } + + /*public static bool PointCylinderDistance(float3 point, CylinderCollider cylinder, float maxDistance, out PointDistanceResultInternal result) + { + //Strategy: Project p onto the capsule's line. + //If on the segment, do point vs sphere. + //Otherwise do point vs disk + float3 edge = cylinder.pointB - cylinder.pointA; + float3 ap = point - cylinder.pointA; + float3 unitEdge = math.normalize(edge); + float dot = math.dot(ap, unitEdge); //dot is distance of projected point from pointA + float3 pointOnLine = cylinder.pointA + unitEdge * dot; + if (dot < 0f) + { + //Todo: Optimize math + float3 pointOnLineToPoint = point - pointOnLine; //This gives us our direction from the center of the cap to the edge towards the query point. + if (math.lengthsq(pointOnLineToPoint) > cylinder.radius * cylinder.radius) + { + float3 roundNormal = math.normalize(pointOnLineToPoint); + float3 capNormal = -unitEdge; + result.normal = (roundNormal + capNormal) / math.SQRT2; //Summing orthogonal unit vectors has a length of sqrt2 + result.hitpoint = cylinder.pointA + roundNormal * cylinder.radius; + result.distance = math.distance(point, result.hitpoint); + } + else + { + result.normal = -unitEdge; + result.distance = -dot; + result.hitpoint = point + unitEdge * result.distance; + } + return result.distance <= maxDistance; + } + if (dot * dot > math.lengthsq(edge)) + { + //Todo: Optimize math + float3 pointOnLineToPoint = point - pointOnLine; //This gives us our direction from the center of the cap to the edge towards the query point. + if (math.lengthsq(pointOnLineToPoint) > cylinder.radius * cylinder.radius) + { + float3 roundNormal = math.normalize(pointOnLineToPoint); + float3 capNormal = unitEdge; + result.normal = (roundNormal + capNormal) / math.SQRT2; //Summing orthogonal unit vectors has a length of sqrt2 + result.hitpoint = cylinder.pointB + roundNormal * cylinder.radius; + result.distance = math.distance(point, result.hitpoint); + } + else + { + result.normal = unitEdge; + result.distance = math.distance(pointOnLine, cylinder.pointB); + result.hitpoint = point - unitEdge * result.distance; + } + return result.distance <= maxDistance; + } + else + { + SphereCollider sphere = new SphereCollider(pointOnLine, cylinder.radius); + return DistanceBetween(point, sphere, maxDistance, out result); + } + }*/ + + public static bool PointBoxDistance(float3 point, BoxCollider box, float maxDistance, out PointDistanceResultInternal result) + { + //Idea: The positive octant of the box contains 7 feature regions: 3 faces, 3 edges, and inside. + //The other octants are identical except with flipped signs. So if we unflip signs, + //calculate the distance for these 7 regions, and then flip signs again, we get a valid result. + //We use feature regions rather than feature types to avoid swizzling since that would require a second branch. + float3 osPoint = point - box.center; //os = origin space + bool3 isNegative = osPoint < 0f; + float3 ospPoint = math.select(osPoint, -osPoint, isNegative); //osp = origin space positive + int region = math.csum(math.select(new int3(4, 2, 1), int3.zero, ospPoint < box.halfSize)); + switch (region) + { + case 0: + { + //Inside the box. Get the closest wall. + float3 delta = box.halfSize - ospPoint; + float min = math.cmin(delta); + bool3 minMask = min == delta; + //Prioritize y first, then z, then x if multiple distances perfectly match. + //Todo: Should this be configurabe? + minMask.xz &= !minMask.y; + minMask.x &= !minMask.z; + result.hitpoint = math.select(ospPoint, box.halfSize, minMask); + result.distance = -min; + result.normal = math.select(0f, 1f, minMask); + break; + } + case 1: + { + //xy in box, z outside + //Closest feature is the z-face + result.distance = ospPoint.z - box.halfSize.z; + result.hitpoint = new float3(ospPoint.xy, box.halfSize.z); + result.normal = new float3(0f, 0f, 1f); + break; + } + case 2: + { + //xz in box, y outside + //Closest feature is the y-face + result.distance = ospPoint.y - box.halfSize.y; + result.hitpoint = new float3(ospPoint.x, box.halfSize.y, ospPoint.z); + result.normal = new float3(0f, 1f, 0f); + break; + } + case 3: + { + //x in box, yz outside + //Closest feature is the x-axis edge + result.distance = math.distance(ospPoint.yz, box.halfSize.yz); + result.hitpoint = new float3(ospPoint.x, box.halfSize.yz); + result.normal = new float3(0f, math.SQRT2 / 2f, math.SQRT2 / 2f); + break; + } + case 4: + { + //yz in box, x outside + //Closest feature is the x-face + result.distance = ospPoint.x - box.halfSize.x; + result.hitpoint = new float3(box.halfSize.x, ospPoint.yz); + result.normal = new float3(1f, 0f, 0f); + break; + } + case 5: + { + //y in box, xz outside + //Closest feature is the y-axis edge + result.distance = math.distance(ospPoint.xz, box.halfSize.xz); + result.hitpoint = new float3(box.halfSize.x, ospPoint.y, box.halfSize.y); + result.normal = new float3(math.SQRT2 / 2f, 0f, math.SQRT2 / 2f); + break; + } + case 6: + { + //z in box, xy outside + //Closest feature is the z-axis edge + result.distance = math.distance(ospPoint.xy, box.halfSize.xy); + result.hitpoint = new float3(box.halfSize.xy, ospPoint.z); + result.normal = new float3(math.SQRT2 / 2f, math.SQRT2 / 2f, 0f); + break; + } + default: + { + //xyz outside box + ////Closest feature is the osp corner + result.distance = math.distance(ospPoint, box.halfSize); + result.hitpoint = box.halfSize; + result.normal = math.normalize(math.float3(1f)); + break; + } + } + result.hitpoint = math.select(result.hitpoint, -result.hitpoint, isNegative) + box.center; + result.normal = math.select(result.normal, -result.normal, isNegative); + return result.distance <= maxDistance; + } + + //Distance is unsigned, triangle is "double-sided" + public static bool PointTriangleDistance(float3 point, TriangleCollider triangle, float maxDistance, out PointDistanceResultInternal result) + { + float3 ab = triangle.pointB - triangle.pointA; + float3 bc = triangle.pointC - triangle.pointB; + float3 ca = triangle.pointA - triangle.pointC; + float3 ap = point - triangle.pointA; + float3 bp = point - triangle.pointB; + float3 cp = point - triangle.pointC; + + //project point onto plane + //if clockwise, normal faces "up" + float3 planeNormal = math.normalize(math.cross(ab, ca)); + float projectionDot = math.dot(planeNormal, point - triangle.pointA); + float3 projectedPoint = point - projectionDot * planeNormal; + + //calculate edge planes aligned with triangle normal without the normalization (since it isn't required) + //normals face "inward" + float3 abUnnormal = math.cross(ab, planeNormal); + float3 bcUnnormal = math.cross(bc, planeNormal); + float3 caUnnormal = math.cross(ca, planeNormal); + + float3 dots = new float3(math.dot(abUnnormal, ap), + math.dot(bcUnnormal, bp), + math.dot(caUnnormal, cp)); + int region = math.csum(math.select(new int3(1, 2, 4), int3.zero, dots >= 0f)); //Todo: bitmask? + switch (region) + { + case 0: + { + //all inside, hit plane + result.hitpoint = projectedPoint; + result.distance = math.abs(projectionDot); + break; + } + case 1: + { + //outside ab plane + float abLengthSq = math.lengthsq(ab); + float dot = math.clamp(math.dot(ap, ab), 0f, abLengthSq); + result.hitpoint = triangle.pointA + ab * dot / abLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + case 2: + { + //outside bc plane + float bcLengthSq = math.lengthsq(bc); + float dot = math.clamp(math.dot(bp, bc), 0f, bcLengthSq); + result.hitpoint = triangle.pointB + bc * dot / bcLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + case 3: + { + //outside ab and bc so closest to point b + result.hitpoint = triangle.pointB; + result.distance = math.distance(point, triangle.pointB); + break; + } + case 4: + { + //outside ca plane + float caLengthSq = math.lengthsq(ca); + float dot = math.clamp(math.dot(cp, ca), 0f, caLengthSq); + result.hitpoint = triangle.pointC + ca * dot / caLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + case 5: + { + //outside ab and ca so closest to point a + result.hitpoint = triangle.pointA; + result.distance = math.distance(point, triangle.pointA); + break; + } + case 6: + { + //outside bc and ca so closest to point c + result.hitpoint = triangle.pointC; + result.distance = math.distance(point, triangle.pointC); + break; + } + case 7: + { + //on all three edges at once because the cross product was 0 + CapsuleCollider capAB = new CapsuleCollider(triangle.pointA, triangle.pointB, 0f); + bool hitAB = PointCapsuleDistance(point, capAB, maxDistance, out var resultAB); + CapsuleCollider capBC = new CapsuleCollider(triangle.pointB, triangle.pointC, 0f); + bool hitBC = PointCapsuleDistance(point, capBC, maxDistance, out var resultBC); + CapsuleCollider capCA = new CapsuleCollider(triangle.pointC, triangle.pointA, 0f); + bool hitCA = PointCapsuleDistance(point, capCA, maxDistance, out var resultCA); + if (!hitAB && !hitBC && !hitCA) + { + result = resultCA; + break; + } + + result = default; + result.distance = float.MaxValue; + + if (hitAB) + result = resultAB; + if (hitBC && resultBC.distance < result.distance) + result = resultBC; + if (hitCA && resultCA.distance < result.distance) + result = resultCA; + break; + } + default: + { + //How the heck did we get here? + //throw new InvalidOperationException(); + result.hitpoint = projectedPoint; + result.distance = 2f * maxDistance; + break; + } + } + result.normal = math.select(planeNormal, -planeNormal, math.dot(result.hitpoint - point, planeNormal) < 0); + return result.distance <= maxDistance; + } + + //Distance is unsigned, quad is "double-sided" + public static bool PointQuadDistance(float3 point, simdFloat3 quadPoints, float maxDistance, out PointDistanceResultInternal result) + { + simdFloat3 abcd = new simdFloat3(quadPoints.a, quadPoints.b, quadPoints.c, quadPoints.d); + simdFloat3 abbccdda = abcd.bcda - abcd.abcd; + simdFloat3 abcdp = point - abcd; + + //project point onto plane + //if clockwise, normal faces "up" + float3 planeNormal = math.normalize(math.cross(abbccdda.a, abbccdda.d)); //ab, da + float projectionDot = math.dot(planeNormal, abcdp.a); + float3 projectedPoint = point - projectionDot * planeNormal; + + //calculate edge planes aligned with quad normal without the normalization (since it isn't required) + //normals face "inward" + simdFloat3 abbccddaUnnormal = simd.cross(abbccdda, planeNormal); + float4 dotsEdges = simd.dot(abbccddaUnnormal, abcdp); + + if (math.bitmask(dotsEdges < 0f) == 0) //if (math.all(dotsEdges >= 0)) + { + result.hitpoint = projectedPoint; + result.distance = math.abs(projectionDot); + } + else + { + float3 acUnnormal = math.cross(quadPoints.c - quadPoints.a, planeNormal); //faces D + float3 bdUnnormal = math.cross(quadPoints.d - quadPoints.b, planeNormal); //faces A + float2 dotsDiags = new float2(math.dot(acUnnormal, abcdp.a), math.dot(bdUnnormal, abcdp.b)); + int region = math.csum(math.select(new int2(1, 2), int2.zero, dotsDiags >= 0)); //Todo: bitmask? + switch (region) + { + case 0: + { + //closest to da + var da = abbccdda.d; + var dp = abcdp.d; + float daLengthSq = math.lengthsq(da); + float dot = math.clamp(math.dot(dp, da), 0f, daLengthSq); + result.hitpoint = quadPoints.d + da * dot / daLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + case 1: + { + //closest to ab + var ab = abbccdda.a; + var ap = abcdp.a; + float abLengthSq = math.lengthsq(ab); + float dot = math.clamp(math.dot(ap, ab), 0f, abLengthSq); + result.hitpoint = quadPoints.a + ab * dot / abLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + case 2: + { + //closest to bc + var bc = abbccdda.b; + var bp = abcdp.b; + float bcLengthSq = math.lengthsq(bc); + float dot = math.clamp(math.dot(bp, bc), 0f, bcLengthSq); + result.hitpoint = quadPoints.b + bc * dot / bcLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + case 3: + { + //closest to cd + var cd = abbccdda.c; + var cp = abcdp.c; + float cdLengthSq = math.lengthsq(cd); + float dot = math.clamp(math.dot(cp, cd), 0f, cdLengthSq); + result.hitpoint = quadPoints.c + cd * dot / cdLengthSq; + result.distance = math.distance(point, result.hitpoint); + break; + } + default: + { + //How the heck did we get here? + //throw new InvalidOperationException(); + result.hitpoint = projectedPoint; + result.distance = maxDistance * 2f; + break; + } + } + } + + result.normal = math.select(planeNormal, -planeNormal, math.dot(result.hitpoint - point, planeNormal) < 0); + return result.distance <= maxDistance; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.cs.meta new file mode 100644 index 0000000..50a6ed0 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/PointDistance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 360aab3da4ee8fd429eda04f69f82a9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/QueriesLowLevelUtils.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/QueriesLowLevelUtils.cs new file mode 100644 index 0000000..204dffa --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/QueriesLowLevelUtils.cs @@ -0,0 +1,439 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static class QueriesLowLevelUtils + { + //Todo: Copied from Unity.Physics. I still don't fully understand this, but it is working correctly for degenerate segments somehow. + //I tested with parallel segments, segments with 0-length edges and a few other weird things. It holds up with pretty good accuracy. + //I'm not sure where the NaNs or infinities disappear. But they do. + // Find the closest points on a pair of line segments + internal static void SegmentSegment(float3 pointA, float3 edgeA, float3 pointB, float3 edgeB, out float3 closestAOut, out float3 closestBOut) + { + // Find the closest point on edge A to the line containing edge B + float3 diff = pointB - pointA; + + float r = math.dot(edgeA, edgeB); + float s1 = math.dot(edgeA, diff); + float s2 = math.dot(edgeB, diff); + float lengthASq = math.lengthsq(edgeA); + float lengthBSq = math.lengthsq(edgeB); + + float invDenom, invLengthASq, invLengthBSq; + { + float denom = lengthASq * lengthBSq - r * r; + float3 inv = 1.0f / new float3(denom, lengthASq, lengthBSq); + invDenom = inv.x; + invLengthASq = inv.y; + invLengthBSq = inv.z; + } + + float fracA = (s1 * lengthBSq - s2 * r) * invDenom; + fracA = math.clamp(fracA, 0.0f, 1.0f); + + // Find the closest point on edge B to the point on A just found + float fracB = fracA * (invLengthBSq * r) - invLengthBSq * s2; + fracB = math.clamp(fracB, 0.0f, 1.0f); + + // If the point on B was clamped then there may be a closer point on A to the edge + fracA = fracB * (invLengthASq * r) + invLengthASq * s1; + fracA = math.clamp(fracA, 0.0f, 1.0f); + + closestAOut = pointA + fracA * edgeA; + closestBOut = pointB + fracB * edgeB; + } + + internal static void SegmentSegment(simdFloat3 pointA, simdFloat3 edgeA, simdFloat3 pointB, simdFloat3 edgeB, out simdFloat3 closestAOut, out simdFloat3 closestBOut) + { + simdFloat3 diff = pointB - pointA; + + float4 r = simd.dot(edgeA, edgeB); + float4 s1 = simd.dot(edgeA, diff); + float4 s2 = simd.dot(edgeB, diff); + float4 lengthASq = simd.lengthsq(edgeA); + float4 lengthBSq = simd.lengthsq(edgeB); + + float4 invDenom, invLengthASq, invLengthBSq; + { + float4 denom = lengthASq * lengthBSq - r * r; + invDenom = 1.0f / denom; + invLengthASq = 1.0f / lengthASq; + invLengthBSq = 1.0f / lengthBSq; + } + + float4 fracA = (s1 * lengthBSq - s2 * r) * invDenom; + fracA = math.clamp(fracA, 0.0f, 1.0f); + + float4 fracB = fracA * (invLengthBSq * r) - invLengthBSq * s2; + fracB = math.clamp(fracB, 0.0f, 1.0f); + + fracA = fracB * invLengthASq * r + invLengthASq * s1; + fracA = math.clamp(fracA, 0.0f, 1.0f); + + closestAOut = pointA + fracA * edgeA; + closestBOut = pointB + fracB * edgeB; + } + + internal static bool4 SegmentSegmentInvalidateEndpoints(simdFloat3 pointA, + simdFloat3 edgeA, + simdFloat3 pointB, + simdFloat3 edgeB, + out simdFloat3 closestAOut, + out simdFloat3 closestBOut) + { + simdFloat3 diff = pointB - pointA; + + float4 r = simd.dot(edgeA, edgeB); + float4 s1 = simd.dot(edgeA, diff); + float4 s2 = simd.dot(edgeB, diff); + float4 lengthASq = simd.lengthsq(edgeA); + float4 lengthBSq = simd.lengthsq(edgeB); + + float4 invDenom, invLengthASq, invLengthBSq; + { + float4 denom = lengthASq * lengthBSq - r * r; + invDenom = 1.0f / denom; + invLengthASq = 1.0f / lengthASq; + invLengthBSq = 1.0f / lengthBSq; + } + + float4 fracA = (s1 * lengthBSq - s2 * r) * invDenom; + fracA = math.clamp(fracA, 0.0f, 1.0f); + + float4 fracB = fracA * (invLengthBSq * r) - invLengthBSq * s2; + fracB = math.clamp(fracB, 0.0f, 1.0f); + + fracA = fracB * invLengthASq * r + invLengthASq * s1; + fracA = math.clamp(fracA, 0.0f, 1.0f); + + closestAOut = pointA + fracA * edgeA; + closestBOut = pointB + fracB * edgeB; + + return fracA != 0f & fracA != 1f & fracB != 0f & fracB != 1f; + } + + // Returns true for each segment pair whose result does not include an endpoint on either segment of the pair. + internal static void OriginAabb8PointsWithEspilonFudge(float3 aabb, + simdFloat3 points03, + simdFloat3 points47, + out float3 closestAOut, + out float3 closestBOut, + out float axisDistanceOut) + { + //Step 1: Find the minimum axis distance + bool4 minXMask0347 = points03.x <= points47.x; + bool4 minYMask0347 = points03.y <= points47.y; + bool4 minZMask0347 = points03.z <= points47.z; + + var minX0347 = simd.select(points47, points03, minXMask0347); + var maxX0347 = simd.select(points03, points47, minXMask0347); + var minY0347 = simd.select(points47, points03, minYMask0347); + var maxY0347 = simd.select(points03, points47, minYMask0347); + var minZ0347 = simd.select(points47, points03, minZMask0347); + var maxZ0347 = simd.select(points03, points47, minZMask0347); + + float minXValue = math.cmin(minX0347.x); + float maxXValue = math.cmax(maxX0347.x); + float minYValue = math.cmin(minY0347.y); + float maxYValue = math.cmax(maxY0347.y); + float minZValue = math.cmin(minZ0347.z); + float maxZValue = math.cmax(maxZ0347.z); + + float3 minValues = new float3(minXValue, minYValue, minZValue); + float3 maxValues = new float3(maxXValue, maxYValue, maxZValue); + + float3 distancesToMin = maxValues + aabb; + float3 distancesToMax = aabb - minValues; + float3 bestDistances = math.min(distancesToMin, distancesToMax); + float bestDistance = math.cmin(bestDistances); + bool3 bestAxisMask = bestDistance == bestDistances; + + //Step 2: Find the point that matches the bestDistance for the bestDistanceMask and has the least deviation when clamped to the AABB + simdFloat3 distancesToMin03 = points03 + aabb; + simdFloat3 distancesToMax03 = aabb - points03; + simdFloat3 distancesToMin47 = points47 + aabb; + simdFloat3 distancesToMax47 = aabb - points47; + + // Because we bias away from the edge-edge case, nearly parallel edge-faces might report the wrong point. + // So we add an epsilon fudge and capture the absolute distance + bool4 matchesMinX03 = (math.abs(bestDistance - distancesToMin03.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesMinY03 = (math.abs(bestDistance - distancesToMin03.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesMinZ03 = (math.abs(bestDistance - distancesToMin03.z) < math.EPSILON) & bestAxisMask.z; + bool4 matchesX03 = matchesMinX03 | (math.abs(bestDistance - distancesToMax03.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesY03 = matchesMinY03 | (math.abs(bestDistance - distancesToMax03.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesZ03 = matchesMinZ03 | (math.abs(bestDistance - distancesToMax03.z) < math.EPSILON) & bestAxisMask.z; + + bool4 matchesMinX47 = (math.abs(bestDistance - distancesToMin47.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesMinY47 = (math.abs(bestDistance - distancesToMin47.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesMinZ47 = (math.abs(bestDistance - distancesToMin47.z) < math.EPSILON) & bestAxisMask.z; + bool4 matchesX47 = matchesMinX47 | (math.abs(bestDistance - distancesToMax47.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesY47 = matchesMinY47 | (math.abs(bestDistance - distancesToMax47.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesZ47 = matchesMinZ47 | (math.abs(bestDistance - distancesToMax47.z) < math.EPSILON) & bestAxisMask.z; + + float4 diffXValues03 = points03.x - math.clamp(points03.x, -aabb.x, aabb.x); + float4 diffYValues03 = points03.y - math.clamp(points03.y, -aabb.y, aabb.y); + float4 diffZValues03 = points03.z - math.clamp(points03.z, -aabb.z, aabb.z); + float4 diffXValues47 = points47.x - math.clamp(points47.x, -aabb.x, aabb.x); + float4 diffYValues47 = points47.y - math.clamp(points47.y, -aabb.y, aabb.y); + float4 diffZValues47 = points47.z - math.clamp(points47.z, -aabb.z, aabb.z); + + float4 distSqYZ03 = math.select(float.MaxValue, diffYValues03 * diffYValues03 + diffZValues03 * diffZValues03, matchesX03); + float4 distSqXZ03 = math.select(float.MaxValue, diffXValues03 * diffXValues03 + diffZValues03 * diffZValues03, matchesY03); + float4 distSqXY03 = math.select(float.MaxValue, diffXValues03 * diffXValues03 + diffYValues03 * diffYValues03, matchesZ03); + float4 distSqYZ47 = math.select(float.MaxValue, diffYValues47 * diffYValues47 + diffZValues47 * diffZValues47, matchesX47); + float4 distSqXZ47 = math.select(float.MaxValue, diffXValues47 * diffXValues47 + diffZValues47 * diffZValues47, matchesY47); + float4 distSqXY47 = math.select(float.MaxValue, diffXValues47 * diffXValues47 + diffYValues47 * diffYValues47, matchesZ47); + + bool4 useY03 = distSqXZ03 < distSqYZ03; + float4 bestDistSq03 = math.select(distSqYZ03, distSqXZ03, useY03); + bool4 matchesMin03 = math.select(matchesMinX03, matchesMinY03, useY03); + bool4 useZ03 = distSqXY03 < bestDistSq03; + bestDistSq03 = math.select(bestDistSq03, distSqXY03, useZ03); + matchesMin03 = math.select(matchesMin03, matchesMinZ03, useZ03); + float bestDistSqFrom03 = math.cmin(bestDistSq03); + int index03 = math.clamp(math.tzcnt(math.bitmask(bestDistSqFrom03 == bestDistSq03)), 0, 3); + + bool4 useY47 = distSqXZ47 < distSqYZ47; + float4 bestDistSq47 = math.select(distSqYZ47, distSqXZ47, useY47); + bool4 matchesMin47 = math.select(matchesMinX47, matchesMinY47, useY47); + bool4 useZ47 = distSqXY47 < bestDistSq47; + bestDistSq47 = math.select(bestDistSq47, distSqXY47, useZ47); + matchesMin47 = math.select(matchesMin47, matchesMinZ47, useZ47); + float bestDistSqFrom47 = math.cmin(bestDistSq47); + int index47 = math.clamp(math.tzcnt(math.bitmask(bestDistSqFrom47 == bestDistSq47)), 0, 3) + 4; + + bool use47 = bestDistSqFrom47 < bestDistSqFrom03; + math.ShuffleComponent bestIndex = (math.ShuffleComponent)math.select(index03, index47, use47); + bool4 matchesMin = math.select(matchesMin03, matchesMin47, use47); + bool useMin = matchesMin[((int)bestIndex) & 3]; + + closestBOut = simd.shuffle(points03, points47, bestIndex); + closestAOut = math.select(closestBOut, math.select(aabb, -aabb, useMin), bestAxisMask); + closestAOut = math.clamp(closestAOut, -aabb, aabb); + axisDistanceOut = -bestDistance; + } + + internal static void OriginAabb8PointsWithEspilonFudgeDebug(float3 aabb, + simdFloat3 points03, + simdFloat3 points47, + out float3 closestAOut, + out float3 closestBOut, + out float axisDistanceOut) + { + UnityEngine.Debug.Log( + $"Begin OriginAabb8PointsWithEpsilonFudge. aabb: {aabb}, points: {points03.a}, {points03.b}, {points03.c}, {points03.d}, {points47.a}, {points47.b}, {points47.c}, {points47.d}"); + + //Step 1: Find the minimum axis distance + bool4 minXMask0347 = points03.x <= points47.x; + bool4 minYMask0347 = points03.y <= points47.y; + bool4 minZMask0347 = points03.z <= points47.z; + UnityEngine.Debug.Log($"minXMask0347: {minXMask0347}, minYMask0347: {minYMask0347}, minZMask0347: {minZMask0347}"); + + var minX0347 = simd.select(points47, points03, minXMask0347); + var maxX0347 = simd.select(points03, points47, minXMask0347); + var minY0347 = simd.select(points47, points03, minYMask0347); + var maxY0347 = simd.select(points03, points47, minYMask0347); + var minZ0347 = simd.select(points47, points03, minZMask0347); + var maxZ0347 = simd.select(points03, points47, minZMask0347); + + float minXValue = math.cmin(minX0347.x); + float maxXValue = math.cmax(maxX0347.x); + float minYValue = math.cmin(minY0347.y); + float maxYValue = math.cmax(maxY0347.y); + float minZValue = math.cmin(minZ0347.z); + float maxZValue = math.cmax(maxZ0347.z); + + float3 minValues = new float3(minXValue, minYValue, minZValue); + float3 maxValues = new float3(maxXValue, maxYValue, maxZValue); + + float3 distancesToMin = maxValues + aabb; + float3 distancesToMax = aabb - minValues; + float3 bestDistances = math.min(distancesToMin, distancesToMax); + float bestDistance = math.cmin(bestDistances); + bool3 bestAxisMask = bestDistance == bestDistances; + UnityEngine.Debug.Log( + $"minValues: {minValues}, maxValues: {maxValues}, distancesToMin: {distancesToMin}, distancesToMax: {distancesToMax}, bestDistance: {bestDistance}, bestAxisMask: {bestAxisMask}"); + + //Step 2: Find the point that matches the bestDistance for the bestDistanceMask and has the least deviation when clamped to the AABB + simdFloat3 distancesToMin03 = points03 + aabb; + simdFloat3 distancesToMax03 = aabb - points03; + simdFloat3 distancesToMin47 = points47 + aabb; + simdFloat3 distancesToMax47 = aabb - points47; + + // Because we bias away from the edge-edge case, nearly parallel edge-faces might report the wrong point. + // So we add an epsilon fudge and capture the absolute distance + bool4 matchesMinX03 = (math.abs(bestDistance - distancesToMin03.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesMinY03 = (math.abs(bestDistance - distancesToMin03.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesMinZ03 = (math.abs(bestDistance - distancesToMin03.z) < math.EPSILON) & bestAxisMask.z; + bool4 matchesX03 = matchesMinX03 | (math.abs(bestDistance - distancesToMax03.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesY03 = matchesMinY03 | (math.abs(bestDistance - distancesToMax03.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesZ03 = matchesMinZ03 | (math.abs(bestDistance - distancesToMax03.z) < math.EPSILON) & bestAxisMask.z; + + bool4 matchesMinX47 = (math.abs(bestDistance - distancesToMin47.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesMinY47 = (math.abs(bestDistance - distancesToMin47.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesMinZ47 = (math.abs(bestDistance - distancesToMin47.z) < math.EPSILON) & bestAxisMask.z; + bool4 matchesX47 = matchesMinX47 | (math.abs(bestDistance - distancesToMax47.x) < math.EPSILON) & bestAxisMask.x; + bool4 matchesY47 = matchesMinY47 | (math.abs(bestDistance - distancesToMax47.y) < math.EPSILON) & bestAxisMask.y; + bool4 matchesZ47 = matchesMinZ47 | (math.abs(bestDistance - distancesToMax47.z) < math.EPSILON) & bestAxisMask.z; + UnityEngine.Debug.Log( + $"matchesX03: {matchesX03}, matchesY03: {matchesY03}, matchesZ03: {matchesZ03}, matchesX47: {matchesX47}, matchesY47: {matchesY47}, matchesZ47: {matchesZ47}"); + + float4 diffXValues03 = points03.x - math.clamp(points03.x, -aabb.x, aabb.x); + float4 diffYValues03 = points03.y - math.clamp(points03.y, -aabb.y, aabb.y); + float4 diffZValues03 = points03.z - math.clamp(points03.z, -aabb.z, aabb.z); + float4 diffXValues47 = points47.x - math.clamp(points47.x, -aabb.x, aabb.x); + float4 diffYValues47 = points47.y - math.clamp(points47.y, -aabb.y, aabb.y); + float4 diffZValues47 = points47.z - math.clamp(points47.z, -aabb.z, aabb.z); + UnityEngine.Debug.Log( + $"diffXValues03: {diffXValues03}, diffYValues03: {diffYValues03}, diffZValues03: {diffZValues03}, diffXValues47: {diffXValues47}, diffYValues47: {diffYValues47}, diffZValues47: {diffZValues47}"); + + float4 distSqYZ03 = math.select(float.MaxValue, diffYValues03 * diffYValues03 + diffZValues03 * diffZValues03, matchesX03); + float4 distSqXZ03 = math.select(float.MaxValue, diffXValues03 * diffXValues03 + diffZValues03 * diffZValues03, matchesY03); + float4 distSqXY03 = math.select(float.MaxValue, diffXValues03 * diffXValues03 + diffYValues03 * diffYValues03, matchesZ03); + float4 distSqYZ47 = math.select(float.MaxValue, diffYValues47 * diffYValues47 + diffZValues47 * diffZValues47, matchesX47); + float4 distSqXZ47 = math.select(float.MaxValue, diffXValues47 * diffXValues47 + diffZValues47 * diffZValues47, matchesY47); + float4 distSqXY47 = math.select(float.MaxValue, diffXValues47 * diffXValues47 + diffYValues47 * diffYValues47, matchesZ47); + UnityEngine.Debug.Log( + $"distSqYZ03: {distSqYZ03}, distSqXZ03: {distSqXZ03}, distSqXY03: {distSqXY03}, distSqYZ47: {distSqYZ47}, distSqXZ47: {distSqXZ47}, distSqXY47: {distSqXY47}"); + + bool4 useY03 = distSqXZ03 < distSqYZ03; + float4 bestDistSq03 = math.select(distSqYZ03, distSqXZ03, useY03); + bool4 matchesMin03 = math.select(matchesMinX03, matchesMinY03, useY03); + bool4 useZ03 = distSqXY03 < bestDistSq03; + bestDistSq03 = math.select(bestDistSq03, distSqXY03, useZ03); + matchesMin03 = math.select(matchesMin03, matchesMinZ03, useZ03); + float bestDistSqFrom03 = math.cmin(bestDistSq03); + int index03 = math.clamp(math.tzcnt(math.bitmask(bestDistSqFrom03 == bestDistSq03)), 0, 3); + UnityEngine.Debug.Log( + $"useY03: {useY03}, useZ03: {useZ03}, bestDistSq03: {bestDistSq03}, matchesMin03: {matchesMin03}, bestDistSqFrom03: {bestDistSqFrom03}, index03: {index03}"); + + bool4 useY47 = distSqXZ47 < distSqYZ47; + float4 bestDistSq47 = math.select(distSqYZ47, distSqXZ47, useY47); + bool4 matchesMin47 = math.select(matchesMinX47, matchesMinY47, useY47); + bool4 useZ47 = distSqXY47 < bestDistSq47; + bestDistSq47 = math.select(bestDistSq47, distSqXY47, useZ47); + matchesMin47 = math.select(matchesMin47, matchesMinZ47, useZ47); + float bestDistSqFrom47 = math.cmin(bestDistSq47); + int index47 = math.clamp(math.tzcnt(math.bitmask(bestDistSqFrom47 == bestDistSq47)), 0, 3) + 4; + + UnityEngine.Debug.Log( + $"useY47: {useY47}, useZ47: {useZ47}, bestDistSq47: {bestDistSq47}, matchesMin47: {matchesMin47}, bestDistSqFrom47: {bestDistSqFrom47}, index47: {index47}"); + + bool use47 = bestDistSqFrom47 < bestDistSqFrom03; + math.ShuffleComponent bestIndex = (math.ShuffleComponent)math.select(index03, index47, use47); + bool4 matchesMin = math.select(matchesMin03, matchesMin47, use47); + bool useMin = matchesMin[((int)bestIndex) & 3]; + UnityEngine.Debug.Log($"use47: {use47}, bestIndex: {bestIndex}, matchesMin: {matchesMin}, useMin: {useMin}"); + + closestBOut = simd.shuffle(points03, points47, bestIndex); + closestAOut = math.select(closestBOut, math.select(aabb, -aabb, useMin), bestAxisMask); + closestAOut = math.clamp(closestAOut, -aabb, aabb); + axisDistanceOut = -bestDistance; + } + + /*internal static void OriginAabb8BoxPoints(float3 aabbExtents, float3 boxCenter, simdFloat3 points03, simdFloat3 points47, out int closestAIndexOut, out float3 closestBOut, out float signedDistanceOut) + { + // This is basically the point vs box algorithm brute-forced and vectorized. + bool4 isNegativeX03 = points03.x < 0f; + bool4 isNegativeY03 = points03.y < 0f; + bool4 isNegativeZ03 = points03.z < 0f; + bool4 isNegativeX47 = points47.x < 0f; + bool4 isNegativeY47 = points47.y < 0f; + bool4 isNegativeZ47 = points47.z < 0f; + simdFloat3 ospPoints03 = simd.select(points03, -points03, isNegativeX03, isNegativeY03, isNegativeZ03); + simdFloat3 ospPoints47 = simd.select(points47, -points47, isNegativeX47, isNegativeY47, isNegativeZ47); + int4 region03 = math.select(4, int4.zero, ospPoints03.x < aabbExtents.x); + int4 region47 = math.select(4, int4.zero, ospPoints47.x < aabbExtents.x); + region03 += math.select(2, int4.zero, ospPoints03.y < aabbExtents.y); + region47 += math.select(2, int4.zero, ospPoints47.y < aabbExtents.y); + region03 += math.select(1, int4.zero, ospPoints03.z < aabbExtents.z); + region47 += math.select(1, int4.zero, ospPoints47.z < aabbExtents.z); + + // Region 0: Inside the box. Closest feature is face. + simdFloat3 delta03 = aabbExtents - ospPoints03; + simdFloat3 delta47 = aabbExtents - ospPoints47; + float4 min03 = simd.cminxyz(delta03); + float4 min47 = simd.cminxyz(delta47); + bool4 minMaskX03 = min03 == delta03.x; + bool4 minMaskX47 = min47 == delta47.x; + bool4 minMaskY03 = min03 == delta03.y; + bool4 minMaskY47 = min47 == delta47.y; + bool4 minMaskZ03 = min03 == delta03.z; + bool4 minMaskZ47 = min47 == delta47.z; + simdFloat3 closestB03 = simd.select(ospPoints03, new simdFloat3(aabbExtents), minMaskX03, minMaskY03, minMaskZ03); + simdFloat3 closestB47 = simd.select(ospPoints47, new simdFloat3(aabbExtents), minMaskX47, minMaskY47, minMaskZ47); + float4 signedDistance03 = -min03; + float4 signedDistance47 = -min47; + + // Region 1: xy in box, z outside. Closest feature is the z-face. + signedDistance03 = math.select(signedDistance03, ospPoints03.z - aabbExtents.z, region03 == 1); + signedDistance47 = math.select(signedDistance47, ospPoints47.z - aabbExtents.z, region47 == 1); + closestB03 = simd.select(closestB03, new simdFloat3(closestB03.x, closestB03.y, aabbExtents.z), region03 == 1); + closestB47 = simd.select(closestB47, new simdFloat3(closestB47.x, closestB47.y, aabbExtents.z), region47 == 1); + + // Region 2: xz in box, y outside. Closest feature is the y-face. + signedDistance03 = math.select(signedDistance03, ospPoints03.y - aabbExtents.y, region03 == 2); + signedDistance47 = math.select(signedDistance47, ospPoints47.y - aabbExtents.y, region47 == 2); + closestB03 = simd.select(closestB03, new simdFloat3(closestB03.x, aabbExtents.y, closestB03.z), region03 == 2); + closestB47 = simd.select(closestB47, new simdFloat3(closestB47.x, aabbExtents.y, closestB47.z), region47 == 2); + + // Region 3: x in box, yz outside. Closest feature is the x-axis edge. + simdFloat3 diffSq03 = ospPoints03 - aabbExtents; + simdFloat3 diffSq47 = ospPoints47 - aabbExtents; + diffSq03 *= diffSq03; + diffSq47 *= diffSq47; + signedDistance03 = math.select(signedDistance03, math.sqrt(diffSq03.y + diffSq03.z), region03 == 3); + signedDistance47 = math.select(signedDistance47, math.sqrt(diffSq47.y + diffSq47.z), region47 == 3); + closestB03 = simd.select(closestB03, new simdFloat3(closestB03.x, aabbExtents.y, aabbExtents.z), region03 == 3); + closestB47 = simd.select(closestB47, new simdFloat3(closestB47.x, aabbExtents.y, aabbExtents.z), region47 == 3); + + // Region 4: yz in box, x outside. Closest feature is the x-face. + signedDistance03 = math.select(signedDistance03, ospPoints03.x - aabbExtents.x, region03 == 4); + signedDistance47 = math.select(signedDistance47, ospPoints47.x - aabbExtents.x, region47 == 4); + closestB03 = simd.select(closestB03, new simdFloat3(aabbExtents.x, closestB03.y, closestB03.z), region03 == 4); + closestB47 = simd.select(closestB47, new simdFloat3(aabbExtents.x, closestB47.y, closestB47.z), region47 == 4); + + // Region 5: y in box, xz outside. Closest feature is the y-axis edge. + signedDistance03 = math.select(signedDistance03, math.sqrt(diffSq03.x + diffSq03.z), region03 == 5); + signedDistance47 = math.select(signedDistance47, math.sqrt(diffSq47.x + diffSq47.z), region47 == 5); + closestB03 = simd.select(closestB03, new simdFloat3(aabbExtents.x, closestB03.y, aabbExtents.z), region03 == 5); + closestB47 = simd.select(closestB47, new simdFloat3(aabbExtents.x, closestB47.y, aabbExtents.z), region47 == 5); + + // Region 6: z in box, xy outside. Closest feature is the z-axis edge. + signedDistance03 = math.select(signedDistance03, math.sqrt(diffSq03.x + diffSq03.y), region03 == 6); + signedDistance47 = math.select(signedDistance47, math.sqrt(diffSq47.x + diffSq47.y), region47 == 6); + closestB03 = simd.select(closestB03, new simdFloat3(aabbExtents.x, aabbExtents.y, closestB03.z), region03 == 6); + closestB47 = simd.select(closestB47, new simdFloat3(aabbExtents.x, aabbExtents.y, closestB47.z), region47 == 6); + + // Region 7: xyz outside box. Closest feature is the corner. + signedDistance03 = math.select(signedDistance03, simd.length(diffSq03), region03 == 7); + signedDistance47 = math.select(signedDistance47, simd.length(diffSq47), region47 == 7); + closestB03 = simd.select(closestB03, new simdFloat3(aabbExtents), region03 == 7); + closestB47 = simd.select(closestB47, new simdFloat3(aabbExtents), region47 == 7); + + // Expand into the full octant space + closestB03 = simd.select(closestB03, -closestB03, isNegativeX03, isNegativeY03, isNegativeZ03); + closestB47 = simd.select(closestB47, -closestB47, isNegativeX47, isNegativeY47, isNegativeZ47); + + // We now need to account for punchthrough, which + } + */ + + public static bool4 ArePointsInsideObbPlusEpsilon(simdFloat3 points, simdFloat3 obbNormals, float3 distances, float3 halfWidths) + { + float3 positives = distances + halfWidths + math.EPSILON; + float3 negatives = distances - halfWidths - math.EPSILON; + var dots = simd.dot(points, obbNormals.a); + bool4 resultsX = (dots <= positives.x) & (dots >= negatives.x); + resultsX |= (-dots <= positives.x) & (-dots >= negatives.x); + dots = simd.dot(points, obbNormals.b); + bool4 resultsY = (dots <= positives.y) & (dots >= negatives.y); + resultsY |= (-dots <= positives.y) & (-dots >= negatives.y); + dots = simd.dot(points, obbNormals.c); + bool4 resultsZ = (dots <= positives.z) & (dots >= negatives.z); + resultsZ |= (-dots <= positives.z) & (-dots >= negatives.z); + return resultsX & resultsY & resultsZ; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/QueriesLowLevelUtils.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/QueriesLowLevelUtils.cs.meta new file mode 100644 index 0000000..c5b5834 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/QueriesLowLevelUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7099ed4c026746947a3890b98b9830ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/Raycasts.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/Raycasts.cs new file mode 100644 index 0000000..1bfaf80 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/Raycasts.cs @@ -0,0 +1,789 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + internal static partial class SpatialInternal + { + public static bool RaycastAabb(Ray ray, Aabb aabb, out float fraction) + { + //slab clipping method + float3 l = aabb.min - ray.start; + float3 h = aabb.max - ray.start; + float3 nearT = l * ray.reciprocalDisplacement; + float3 farT = h * ray.reciprocalDisplacement; + + float3 near = math.min(nearT, farT); + float3 far = math.max(nearT, farT); + + float nearMax = math.cmax(math.float4(near, 0f)); + float farMin = math.cmin(math.float4(far, 1f)); + + fraction = nearMax; + + return (nearMax <= farMin) & (l.x <= h.x); + } + + public static bool RaycastSphere(Ray ray, SphereCollider sphere, out float fraction, out float3 normal) + { + float3 delta = ray.start - sphere.center; + float a = math.dot(ray.displacement, ray.displacement); + float b = 2f * math.dot(ray.displacement, delta); + float c = math.dot(delta, delta) - sphere.radius * sphere.radius; + float discriminant = b * b - 4f * a * c; + bool hit = discriminant >= 0f & c >= 0f; //Unlike Unity.Physics, we ignore inside hits. + discriminant = math.abs(discriminant); + float sqrtDiscriminant = math.sqrt(discriminant); + float root1 = (-b - sqrtDiscriminant) / (2f * a); + float root2 = (-b + sqrtDiscriminant) / (2f * a); + float rootmin = math.min(root1, root2); + float rootmax = math.max(root1, root2); + bool rootminValid = rootmin >= 0f & rootmin <= 1f; + bool rootmaxValid = rootmax >= 0f & rootmax <= 1f; + fraction = math.select(rootmax, rootmin, rootminValid); + normal = (delta + ray.displacement * fraction) / sphere.radius; //hit point to center divided by radius = normalize normal of sphere at hit + bool aRootIsValid = rootminValid | rootmaxValid; + return hit & aRootIsValid; + } + + public static bool4 Raycast4Spheres(simdFloat3 rayStart, simdFloat3 rayDisplacement, simdFloat3 center, float4 radius, out float4 fraction, out simdFloat3 normal) + { + simdFloat3 delta = rayStart - center; + float4 a = simd.dot(rayDisplacement, rayDisplacement); + float4 b = 2f * simd.dot(rayDisplacement, delta); + float4 c = simd.dot(delta, delta) - radius * radius; + float4 discriminant = b * b - 4f * a * c; + bool4 hit = discriminant >= 0f & c >= 0f; //Unlike Unity.Physics, we ignore inside hits. + discriminant = math.abs(discriminant); + float4 sqrtDiscriminant = math.sqrt(discriminant); + float4 root1 = (-b - sqrtDiscriminant) / (2f * a); + float4 root2 = (-b + sqrtDiscriminant) / (2f * a); + float4 rootmin = math.min(root1, root2); + float4 rootmax = math.max(root1, root2); + bool4 rootminValid = rootmin >= 0f & rootmin <= 1f; + bool4 rootmaxValid = rootmax >= 0f & rootmax <= 1f; + fraction = math.select(rootmax, rootmin, rootminValid); + normal = (delta + rayDisplacement * fraction) / radius; + bool4 aRootIsValid = rootminValid | rootmaxValid; + return hit & aRootIsValid; + } + + public static bool RaycastCapsule(Ray ray, CapsuleCollider capsule, out float fraction, out float3 normal) + { + float axisLength = mathex.GetLengthAndNormal(capsule.pointB - capsule.pointA, out float3 axis); + SphereCollider sphere1 = new SphereCollider(capsule.pointA, capsule.radius); + + // Ray vs infinite cylinder + { + float directionDotAxis = math.dot(ray.displacement, axis); + float originDotAxis = math.dot(ray.start - capsule.pointA, axis); + float3 rayDisplacement2D = ray.displacement - axis * directionDotAxis; + float3 rayOrigin2D = ray.start - axis * originDotAxis; + Ray rayIn2d = new Ray(rayOrigin2D, rayOrigin2D + rayDisplacement2D); + + if (RaycastSphere(rayIn2d, sphere1, out float cylinderFraction, out normal)) + { + float t = originDotAxis + cylinderFraction * directionDotAxis; // distance of the hit from Vertex0 along axis + if (t >= 0.0f && t <= axisLength) + { + fraction = cylinderFraction; + return true; + } + } + } + + //Ray vs caps + SphereCollider sphere2 = new SphereCollider(capsule.pointB, capsule.radius); + bool hit1 = RaycastSphere(ray, sphere1, out float fraction1, out float3 normal1); + bool hit2 = RaycastSphere(ray, sphere2, out float fraction2, out float3 normal2); + fraction1 = hit1 ? fraction1 : fraction1 + 1f; + fraction2 = hit2 ? fraction2 : fraction2 + 1f; + fraction = math.select(fraction2, fraction1, fraction1 < fraction2); + normal = math.select(normal2, normal1, fraction1 < fraction2); + return hit1 | hit2; + } + + public static bool4 Raycast4Capsules(Ray ray, simdFloat3 capA, simdFloat3 capB, float4 capRadius, out float4 fraction, out simdFloat3 normal) + { + float4 axisLength = mathex.GetLengthAndNormal(capB - capA, out simdFloat3 axis); + // Ray vs infinite cylinder + float4 directionDotAxis = simd.dot(ray.displacement, axis); + float4 originDotAxis = simd.dot(ray.start - capA, axis); + simdFloat3 rayDisplacement2D = ray.displacement - axis * directionDotAxis; + simdFloat3 rayOrigin2D = ray.start - axis * originDotAxis; + bool4 hitCylinder = Raycast4Spheres(rayOrigin2D, rayDisplacement2D, capA, capRadius, out float4 cylinderFraction, out simdFloat3 cylinderNormal); + float4 t = originDotAxis + cylinderFraction * directionDotAxis; + hitCylinder &= t >= 0f & t <= axisLength; + + // Ray vs caps + bool4 hitCapA = Raycast4Spheres(new simdFloat3(ray.start), new simdFloat3(ray.displacement), capA, capRadius, out float4 capAFraction, out simdFloat3 capANormal); + bool4 hitCapB = Raycast4Spheres(new simdFloat3(ray.start), new simdFloat3(ray.displacement), capB, capRadius, out float4 capBFraction, out simdFloat3 capBNormal); + + // Find best result + cylinderFraction = math.select(2f, cylinderFraction, hitCylinder); + capAFraction = math.select(2f, capAFraction, hitCapA); + capBFraction = math.select(2f, capBFraction, hitCapB); + + normal = simd.select(cylinderNormal, capANormal, capAFraction < cylinderFraction); + fraction = math.select(cylinderFraction, capAFraction, capAFraction < cylinderFraction); + normal = simd.select(normal, capBNormal, capBFraction < fraction); + fraction = math.select(fraction, capBFraction, capBFraction < fraction); + return fraction <= 1f; + } + + //Note: Unity.Physics does not have an equivalent for this. It raycasts against the convex polygon. + public static bool RaycastBox(Ray ray, BoxCollider box, out float fraction, out float3 normal) + { + Aabb aabb = new Aabb(box.center - box.halfSize, box.center + box.halfSize); + if (RaycastAabb(ray, aabb, out fraction)) + { + //Idea: Calculate the distance from the hitpoint to each plane of the AABB. + //The smallest distance is what we consider the plane we actually hit. + //Also, mask out planes whose normal does not face against the ray. + //Todo: Is that last step necessary? + float3 hitpoint = ray.start + ray.displacement * fraction; + bool3 signPositive = ray.displacement > 0f; + bool3 signNegative = ray.displacement < 0f; + float3 alignedFaces = math.select(aabb.min, aabb.max, signNegative); + float3 faceDistances = math.abs(alignedFaces - hitpoint) + math.select(float.MaxValue, 0f, signNegative | signPositive); //mask out faces the ray is parallel with + float closestFaceDistance = math.cmin(faceDistances); + normal = math.select(float3.zero, new float3(1f), closestFaceDistance == faceDistances) * math.select(-1f, 1f, signNegative); //The normal should be opposite to the ray direction + return true; + } + else + { + normal = float3.zero; + return false; + } + } + + public static bool RaycastRoundedBox(Ray ray, BoxCollider box, float radius, out float fraction, out float3 normal) + { + // Early out if inside hit + if (PointBoxDistance(ray.start, box, radius, out _)) + { + fraction = default; + normal = default; + return false; + } + + var outerBox = box; + outerBox.halfSize += radius; + bool hitOuter = RaycastBox(ray, outerBox, out fraction, out normal); + var hitPoint = math.lerp(ray.start, ray.end, fraction); + + if (hitOuter && math.all(math.abs(normal) > 0.9f | (hitPoint >= box.center - box.halfSize & hitPoint <= box.center + box.halfSize))) + { + // We hit a flat surface of the box. We have our result already. + return true; + } + else if (!hitOuter && !math.all(ray.start >= outerBox.center - outerBox.halfSize & ray.start <= outerBox.center + outerBox.halfSize)) + { + // Our ray missed the outer box. + return false; + } + + // Our ray either hit near an edge of the outer box or started inside the box. From this point it must hit a capsule surrounding an edge. + simdFloat3 bTopPoints = default; + simdFloat3 bBottomPoints = default; + bTopPoints.x = math.select(-box.halfSize.x, box.halfSize.x, new bool4(true, true, false, false)); + bBottomPoints.x = bTopPoints.x; + bBottomPoints.y = -box.halfSize.y; + bTopPoints.y = box.halfSize.y; + bTopPoints.z = math.select(-box.halfSize.z, box.halfSize.z, new bool4(true, false, true, false)); + bBottomPoints.z = bTopPoints.z; + bTopPoints += box.center; + bBottomPoints += box.center; + + simdFloat3 bLeftPoints = simd.shuffle(bTopPoints, + bBottomPoints, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.LeftW, + math.ShuffleComponent.RightZ, + math.ShuffleComponent.RightW); + simdFloat3 bRightPoints = simd.shuffle(bTopPoints, + bBottomPoints, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightY); + simdFloat3 bFrontPoints = simd.shuffle(bTopPoints, + bBottomPoints, + math.ShuffleComponent.LeftY, + math.ShuffleComponent.LeftW, + math.ShuffleComponent.RightY, + math.ShuffleComponent.RightW); + simdFloat3 bBackPoints = simd.shuffle(bTopPoints, + bBottomPoints, + math.ShuffleComponent.LeftX, + math.ShuffleComponent.LeftZ, + math.ShuffleComponent.RightX, + math.ShuffleComponent.RightZ); + + var topBottomHits = Raycast4Capsules(ray, bTopPoints, bBottomPoints, radius, out float4 topBottomFractions, out simdFloat3 topBottomNormals); + var leftRightHits = Raycast4Capsules(ray, bLeftPoints, bRightPoints, radius, out float4 leftRightFractions, out simdFloat3 leftRightNormals); + var frontBackHits = Raycast4Capsules(ray, bFrontPoints, bBackPoints, radius, out float4 frontBackFractions, out simdFloat3 frontBackNormals); + + topBottomFractions = math.select(2f, topBottomFractions, topBottomHits); + leftRightFractions = math.select(2f, leftRightFractions, leftRightHits); + frontBackFractions = math.select(2f, frontBackFractions, frontBackHits); + + simdFloat3 bestNormals = simd.select(topBottomNormals, leftRightNormals, leftRightFractions < topBottomFractions); + float4 bestFractions = math.select(topBottomFractions, leftRightFractions, leftRightFractions < topBottomFractions); + bestNormals = simd.select(bestNormals, frontBackNormals, frontBackFractions < bestFractions); + bestFractions = math.select(bestFractions, frontBackFractions, frontBackFractions < bestFractions); + bestNormals = simd.select(bestNormals, bestNormals.badc, bestFractions.yxwz < bestFractions); + bestFractions = math.select(bestFractions, bestFractions.yxwz, bestFractions.yxwz < bestFractions); + normal = math.select(bestNormals.a, bestNormals.c, bestFractions.z < bestFractions.x); + fraction = math.select(bestFractions.x, bestFractions.z, bestFractions.z < bestFractions.x); + return fraction <= 1f; + } + + // Mostly from Unity.Physics but handles more edge cases + // Todo: Reduce branches + public static bool RaycastTriangle(Ray ray, simdFloat3 triPoints, out float fraction, out float3 outNormal) + { + simdFloat3 abbcca = triPoints.bcaa - triPoints; + float3 ab = abbcca.a; + float3 ca = triPoints.a - triPoints.c; + float3 normal = math.cross(ab, ca); + float3 aStart = ray.start - triPoints.a; + float3 aEnd = ray.end - triPoints.a; + + float nDotAStart = math.dot(normal, aStart); + float nDotAEnd = math.dot(normal, aEnd); + float productOfDots = nDotAStart * nDotAEnd; + + if (productOfDots < 0f) + { + // The start and end are on opposite sides of the infinite plane. + fraction = nDotAStart / (nDotAStart - nDotAEnd); + + // These edge normals are relative to the ray, not the plane normal. + simdFloat3 edgeNormals = simd.cross(abbcca, ray.displacement); + + // This is the midpoint of the segment to the start point, avoiding the divide by two. + float3 doubleStart = ray.start + ray.start; + simdFloat3 r = doubleStart - (triPoints + triPoints.bcaa); + float3 dots = simd.dot(r, edgeNormals).xyz; + outNormal = math.select(normal, -normal, nDotAStart >= 0f); + return math.all(dots <= 0f) || math.all(dots >= 0f); + } + else if (nDotAStart == 0f && nDotAEnd == 0f) + { + // The start and end are both on the infinite plane or the tri is degenerate. + + // Check for the degenerate case + if (math.all(normal == 0f)) + { + normal = math.cross(triPoints.a - ray.start, ab); + if (math.dot(normal, ray.displacement) != 0f) + { + fraction = 2f; + outNormal = default; + return false; + } + } + + // Make sure the start isn't on the tri. + simdFloat3 edgeNormals = simd.cross(abbcca, normal); + float3 doubleStart = ray.start + ray.start; + simdFloat3 r = doubleStart - (triPoints + triPoints.bcaa); + float3 dots = simd.dot(r, edgeNormals).xyz; + if (math.all(dots <= 0f) || math.all(dots >= 0f)) + { + fraction = 2f; + outNormal = default; + return false; + } + + // Todo: This is a rare case, so we are going to do something crazy to avoid trying to solve + // line intersections in 3D space. + // Instead, inflate the plane along the normal and raycast against the planes + // In the case that the ray passes through one of the plane edges, this recursion will reach + // three levels deep, and then a full plane will be constructed against the ray. + var negPoints = triPoints - normal; + var posPoints = triPoints + normal; + var quadA = new simdFloat3(negPoints.a, posPoints.a, posPoints.b, negPoints.b); + var quadB = new simdFloat3(negPoints.b, posPoints.b, posPoints.c, negPoints.c); + var quadC = new simdFloat3(negPoints.c, posPoints.c, posPoints.a, negPoints.a); + bool3 hits = default; + float3 fractions = default; + hits.x = RaycastQuad(ray, quadA, out fractions.x); + hits.y = RaycastQuad(ray, quadB, out fractions.y); + hits.z = RaycastQuad(ray, quadC, out fractions.z); + fractions = math.select(2f, fractions, hits); + fraction = math.cmin(fractions); + + float3 bestEdge = abbcca[math.min(2, math.csum(math.select(0, new int3(0, 1, 2), fraction == fractions)))]; + outNormal = math.cross(bestEdge, normal); + outNormal = math.select(outNormal, -outNormal, math.dot(outNormal, ray.displacement) >= 0f); + + return math.any(hits); + } + else if (nDotAStart == 0f) + { + // The start of the ray is on the infinite plane + // And since we ignore inside hits, we ignore this too. + fraction = 2f; + outNormal = default; + return false; + } + else if (nDotAEnd == 0f) + { + // The end of the ray is on the infinite plane + fraction = 1f; + simdFloat3 edgeNormals = simd.cross(abbcca, normal); + float3 doubleEnd = ray.end + ray.end; + simdFloat3 r = doubleEnd - (triPoints + triPoints.bcda); + float3 dots = simd.dot(r, edgeNormals).xyz; + outNormal = math.select(normal, -normal, nDotAStart >= 0f); + return math.all(dots <= 0f) || math.all(dots >= 0f); + } + else + { + fraction = 2f; + outNormal = default; + return false; + } + } + + public static bool RaycastRoundedTriangle(Ray ray, simdFloat3 triPoints, float radius, out float fraction, out float3 normal) + { + // Make sure the ray doesn't start inside. + if (PointTriangleDistance(ray.start, new TriangleCollider(triPoints.a, triPoints.b, triPoints.c), radius, out _)) + { + fraction = 2f; + normal = default; + return false; + } + + float3 ab = triPoints.b - triPoints.a; + float3 ca = triPoints.a - triPoints.c; + float3 triNormal = math.cross(ab, ca); + triNormal = math.select(triNormal, -triNormal, math.dot(triNormal, ray.displacement) > 0f); + + // Catch degenerate tri here + bool triFaceHit = math.any(triNormal); + float triFraction = 2f; + if (triFaceHit) + triFaceHit = RaycastTriangle(ray, triPoints + math.normalize(triNormal) * radius, out triFraction, out _); + triFraction = math.select(2f, triFraction, triFaceHit); + bool4 capsuleHits = Raycast4Capsules(ray, triPoints, triPoints.bcaa, radius, out float4 capsuleFractions, out simdFloat3 capsuleNormals); + capsuleFractions = math.select(2f, capsuleFractions, capsuleHits); + simdFloat3 bestNormals = simd.select(capsuleNormals, capsuleNormals.bacc, capsuleFractions.yxzz < capsuleFractions); + float4 bestFractions = math.select(capsuleFractions, capsuleFractions.yxzz, capsuleFractions.yxzz < capsuleFractions); + normal = math.select(bestNormals.a, bestNormals.c, bestFractions.z < bestFractions.x); + fraction = math.select(bestFractions.x, bestFractions.z, bestFractions.z < bestFractions.x); + normal = math.select(normal, triNormal, triFraction < fraction); + fraction = math.select(fraction, triFraction, triFraction < fraction); + return fraction <= 1f; + } + + // Mostly from Unity.Physics but handles more edge cases + // Todo: Reduce branches + public static bool RaycastQuad(Ray ray, simdFloat3 quadPoints, out float fraction) + { + simdFloat3 abbccdda = quadPoints.bcda - quadPoints; + float3 ab = abbccdda.a; + float3 ca = quadPoints.a - quadPoints.c; + float3 normal = math.cross(ab, ca); + float3 aStart = ray.start - quadPoints.a; + float3 aEnd = ray.end - quadPoints.a; + + float nDotAStart = math.dot(normal, aStart); + float nDotAEnd = math.dot(normal, aEnd); + float productOfDots = nDotAStart * nDotAEnd; + + if (productOfDots < 0f) + { + // The start and end are on opposite sides of the infinite plane. + fraction = nDotAStart / (nDotAStart - nDotAEnd); + + // These edge normals are relative to the ray, not the plane normal. + simdFloat3 edgeNormals = simd.cross(abbccdda, ray.displacement); + + // This is the midpoint of the segment to the start point, avoiding the divide by two. + float3 doubleStart = ray.start + ray.start; + simdFloat3 r = doubleStart - (quadPoints + quadPoints.bcda); + float4 dots = simd.dot(r, edgeNormals); + return math.all(dots <= 0f) || math.all(dots >= 0f); + } + else if (nDotAStart == 0f && nDotAEnd == 0f) + { + // The start and end are both on the infinite plane or the quad is degenerate. + + // Check for the degenerate case + if (math.all(normal == 0f)) + { + normal = math.cross(quadPoints.a - ray.start, ab); + if (math.dot(normal, ray.displacement) != 0f) + { + fraction = 2f; + return false; + } + } + + // Make sure the start isn't on the quad. + simdFloat3 edgeNormals = simd.cross(abbccdda, normal); + float3 doubleStart = ray.start + ray.start; + simdFloat3 r = doubleStart - (quadPoints + quadPoints.bcda); + float4 dots = simd.dot(r, edgeNormals); + if (math.all(dots <= 0f) || math.all(dots >= 0f)) + { + fraction = 2f; + return false; + } + + // Todo: This is a rare case, so we are going to do something crazy to avoid trying to solve + // line intersections in 3D space. + // Instead, inflate the plane along the normal and raycast against the planes + // In the case that the ray passes through one of the plane edges, this recursion will reach + // three levels deep, and then a full plane will be constructed against the ray. + var negPoints = quadPoints - normal; + var posPoints = quadPoints + normal; + var quadA = new simdFloat3(negPoints.a, posPoints.a, posPoints.b, negPoints.b); + var quadB = new simdFloat3(negPoints.b, posPoints.b, posPoints.c, negPoints.c); + var quadC = new simdFloat3(negPoints.c, posPoints.c, posPoints.d, negPoints.d); + var quadD = new simdFloat3(negPoints.d, posPoints.d, posPoints.a, negPoints.a); + bool4 hits = default; + float4 fractions = default; + hits.x = RaycastQuad(ray, quadA, out fractions.x); + hits.y = RaycastQuad(ray, quadB, out fractions.y); + hits.z = RaycastQuad(ray, quadC, out fractions.z); + hits.w = RaycastQuad(ray, quadD, out fractions.w); + fractions = math.select(2f, fractions, hits); + fraction = math.cmin(fractions); + return math.any(hits); + } + else if (nDotAStart == 0f) + { + // The start of the ray is on the infinite plane + // And since we ignore inside hits, we ignore this too. + fraction = 2f; + return false; + } + else if (nDotAEnd == 0f) + { + // The end of the ray is on the infinite plane + fraction = 1f; + simdFloat3 edgeNormals = simd.cross(abbccdda, normal); + float3 doubleEnd = ray.end + ray.end; + simdFloat3 r = doubleEnd - (quadPoints + quadPoints.bcda); + float4 dots = simd.dot(r, edgeNormals); + return math.all(dots <= 0f) || math.all(dots >= 0f); + } + else + { + fraction = 2f; + return false; + } + } + + public static bool RaycastRoundedQuad(Ray ray, simdFloat3 quadPoints, float radius, out float fraction, out float3 normal) + { + // Make sure the ray doesn't start inside. + if (PointQuadDistance(ray.start, quadPoints, radius, out _)) + { + fraction = 2f; + normal = default; + return false; + } + + float3 ab = quadPoints.b - quadPoints.a; + float3 ca = quadPoints.a - quadPoints.c; + float3 quadNormal = math.cross(ab, ca); + quadNormal = math.select(quadNormal, -quadNormal, math.dot(quadNormal, ray.displacement) > 0f); + + // Catch degenerate quad here + bool quadFaceHit = math.any(quadNormal); + float quadFraction = 2f; + if (quadFaceHit) + quadFaceHit = RaycastQuad(ray, quadPoints + math.normalize(quadNormal) * radius, out quadFraction); + quadFraction = math.select(2f, quadFraction, quadFaceHit); + bool4 capsuleHits = Raycast4Capsules(ray, quadPoints, quadPoints.bcda, radius, out float4 capsuleFractions, out simdFloat3 capsuleNormals); + capsuleFractions = math.select(2f, capsuleFractions, capsuleHits); + simdFloat3 bestNormals = simd.select(capsuleNormals, capsuleNormals.badc, capsuleFractions.yxwz < capsuleFractions); + float4 bestFractions = math.select(capsuleFractions, capsuleFractions.yxwz, capsuleFractions.yxwz < capsuleFractions); + normal = math.select(bestNormals.a, bestNormals.c, bestFractions.z < bestFractions.x); + fraction = math.select(bestFractions.x, bestFractions.z, bestFractions.z < bestFractions.x); + normal = math.select(normal, quadNormal, quadFraction < fraction); + fraction = math.select(fraction, quadFraction, quadFraction < fraction); + return fraction <= 1f; + } + + public static bool RaycastConvex(Ray ray, ConvexCollider convex, out float fraction, out float3 normal) + { + ref var blob = ref convex.convexColliderBlob.Value; + var scaledAabb = new Aabb(blob.localAabb.min * convex.scale, blob.localAabb.max * convex.scale); + + if (!RaycastAabb(ray, scaledAabb, out float aabbFraction)) + { + if (!math.all(ray.start >= scaledAabb.min & ray.end <= scaledAabb.max)) + { + fraction = 2f; + normal = default; + return false; + } + } + + float3 invScale = math.rcp(convex.scale); + var dimensions = math.countbits(math.bitmask(new bool4(math.isfinite(invScale), false))); + + if (dimensions == 3) + { + fraction = -2f; + float exitFraction = 2f; + int bestIndex = 0; + bool inside = true; + var scaledRay = new Ray(ray.start * invScale, ray.end * invScale); + + for (int i = 0; i < blob.facePlaneX.Length; i++) + { + // These are signed distances to the plane from start/end points respectively + float startDot = scaledRay.start.x * blob.facePlaneX[i] + scaledRay.start.y * blob.facePlaneY[i] + scaledRay.start.z * blob.facePlaneZ[i] + + blob.facePlaneDist[i]; + float endDot = scaledRay.end.x * blob.facePlaneX[i] + scaledRay.end.y * blob.facePlaneY[i] + scaledRay.end.z * blob.facePlaneZ[i] + blob.facePlaneDist[i]; + + // If the ray is completely outside the plane or starts on the plane and moves away, then it misses. + if (startDot >= 0f && endDot >= 0f) + { + // If the ray is coplaner, just skip + if (startDot == 0f && endDot == 0f) + continue; + + normal = default; + fraction = 2f; + return false; + } + + // This is the distance of the ray start to the plane divided by the length of the ray projected onto the plane's normal. + float newFraction = startDot / (startDot - endDot); + + if (newFraction > fraction && startDot > 0f) + { + fraction = newFraction; + bestIndex = i; + } + else if (newFraction < exitFraction && endDot > 0f) + { + exitFraction = newFraction; + } + inside &= startDot < 0f; + } + if (inside || exitFraction < fraction) + { + normal = default; + fraction = 2f; + return false; + } + + normal = new float3(blob.facePlaneX[bestIndex], blob.facePlaneY[bestIndex], blob.facePlaneZ[bestIndex]); + return true; + } + else if (dimensions == 0) + { + SphereCollider sphere = new SphereCollider(0f, 0f); + return RaycastSphere(ray, sphere, out fraction, out normal); + } + else if (dimensions == 1) + { + CapsuleCollider capsule = new CapsuleCollider(scaledAabb.min, scaledAabb.max, 0f); + return RaycastCapsule(ray, capsule, out fraction, out normal); + } + else if (dimensions == 2) + { + // From the AABB check we know the ray crosses the plane. So now we just need to figure out if the ray hits + // the geometry. + var hitPoint = ray.start + ray.displacement * aabbFraction; + + var mask = math.select(1f, 0f, math.isfinite(invScale)); + var diff = blob.localAabb.max - blob.localAabb.min; + diff *= mask; + var rayStart = hitPoint - diff + blob.localAabb.min * mask; + + var inflateRay = new Ray(rayStart, rayStart + diff * 3f); + var inflateConvex = convex; + inflateConvex.scale = math.select(1f, convex.scale, math.isfinite(invScale)); + if (RaycastConvex(inflateRay, inflateConvex, out _, out _)) + { + fraction = aabbFraction; + normal = math.normalize(mask * ray.displacement); + return true; + } + } + fraction = 2f; + normal = default; + return false; + } + + // Scale is applied before radius + public static bool RaycastRoundedConvex(Ray ray, ConvexCollider convex, float radius, out float fraction) + { + ref var blob = ref convex.convexColliderBlob.Value; + var scale = convex.scale; + var scaledAabb = new Aabb(blob.localAabb.min * scale - radius, blob.localAabb.max * scale + radius); + + if (!RaycastAabb(ray, scaledAabb, out _)) + { + if (!math.all(ray.start >= scaledAabb.min & ray.end <= scaledAabb.max)) + { + fraction = 2f; + return false; + } + } + + float3 invScale = math.rcp(scale); + var dimensions = math.countbits(math.bitmask(new bool4(math.isfinite(invScale), false))); + + if (dimensions == 3) + { + fraction = -2f; + float exitFraction = 2f; + int bestIndex = 0; + bool inside = true; + var scaledRay = new Ray(ray.start * invScale, ray.end * invScale); + + for (int i = 0; i < blob.facePlaneX.Length; i++) + { + float startDot = scaledRay.start.x * blob.facePlaneX[i] + scaledRay.start.y * blob.facePlaneY[i] + scaledRay.start.z * blob.facePlaneZ[i] + + blob.facePlaneDist[i] - radius; + float endDot = scaledRay.end.x * blob.facePlaneX[i] + scaledRay.end.y * blob.facePlaneY[i] + scaledRay.end.z * blob.facePlaneZ[i] + blob.facePlaneDist[i] - + radius; + // If the ray is completely outside the plane or starts on the plane and moves away, then it misses. + if (startDot >= 0f && endDot >= 0f) + { + // If the ray is coplaner, just skip + if (startDot == 0f && endDot == 0f) + continue; + + fraction = 2f; + return false; + } + + // This is the distance of the ray start to the plane divided by the length of the ray projected onto the plane's normal. + float newFraction = startDot / (startDot - endDot); + + if (newFraction > fraction && startDot > 0f) + { + fraction = newFraction; + bestIndex = i; + } + else if (newFraction < exitFraction && endDot > 0f) + { + exitFraction = newFraction; + } + inside &= startDot < 0f; + } + + if (inside || exitFraction < fraction) + { + fraction = 2f; + return false; + } + + // We know the inflated hit face, but we don't know if it hit the rounded part or not yet. + float3 scaledPoint = scaledRay.start + scaledRay.displacement * fraction; + var edgeRange = blob.edgeIndicesInFacesStartsAndCounts[bestIndex]; + bool hitEdge = false; + bool nearsEdge = false; + for (int i = 0; i < edgeRange.y; i++) + { + float dot = math.dot(scaledPoint.xyz1(), blob.faceEdgeOutwardPlanes[i + edgeRange.x]); + if (dot > 0f) + { + nearsEdge = true; + var indices = blob.vertexIndicesInEdges[blob.edgeIndicesInFaces[i + edgeRange.x]]; + var cap = new CapsuleCollider(new float3(blob.verticesX[indices.x], blob.verticesY[indices.x], blob.verticesZ[indices.x]), + new float3(blob.verticesX[indices.y], blob.verticesY[indices.y], blob.verticesZ[indices.y]), radius); + if (RaycastCapsule(scaledRay, cap, out float newFraction, out _)) + { + if (!hitEdge) + { + fraction = newFraction; + hitEdge = true; + } + fraction = math.min(fraction, newFraction); + } + } + } + + return nearsEdge == hitEdge; + } + else if (dimensions == 0) + { + SphereCollider sphere = new SphereCollider(0f, radius); + return RaycastSphere(ray, sphere, out fraction, out _); + } + else if (dimensions == 1) + { + CapsuleCollider capsule = new CapsuleCollider(scaledAabb.min + radius, scaledAabb.max - radius, 0f); + return RaycastCapsule(ray, capsule, out fraction, out _); + } + else if (dimensions == 2) + { + // We need to identify if the ray hits one of the planar surfaces. + var mask = math.select(1f, 0f, math.isfinite(invScale)); + float maxStart = math.dot(ray.start.xyz1(), new float4(mask, -radius)); + float maxEnd = math.dot(ray.end.xyz1(), new float4(mask, -radius)); + float minStart = math.dot(ray.start.xyz1(), new float4(-mask, radius)); + float minEnd = math.dot(ray.end.xyz1(), new float4(-mask, radius)); + if (maxStart > 0f && maxEnd <= 0f) + { + // We might have a planar hit on the max side of the AABB, so find the plane hit and raycast the original object. + float planarFraction = maxStart / (maxStart - maxEnd); + float3 hitPoint = ray.start + ray.displacement * planarFraction; + + var diff = blob.localAabb.max - blob.localAabb.min + 2f * radius; + diff *= mask; + var rayStart = hitPoint - diff + blob.localAabb.min * mask; + + var inflateRay = new Ray(rayStart, rayStart + diff * 3f); + var inflateConvex = convex; + inflateConvex.scale = math.select(1f, convex.scale, math.isfinite(invScale)); + if (RaycastConvex(inflateRay, inflateConvex, out _, out _)) + { + fraction = planarFraction; + return true; + } + } + else if (minStart > 0f && minEnd <= 0f) + { + // We might have a planar hit on the max side of the AABB, so find the plane hit and raycast the original object. + float planarFraction = minStart / (minStart - minEnd); + float3 hitPoint = ray.start + ray.displacement * planarFraction; + + var diff = blob.localAabb.max - blob.localAabb.min + 2f * radius; + diff *= mask; + var rayStart = hitPoint - diff + blob.localAabb.min * mask; + + var inflateRay = new Ray(rayStart, rayStart + diff * 3f); + var inflateConvex = convex; + inflateConvex.scale = math.select(1f, convex.scale, math.isfinite(invScale)); + if (RaycastConvex(inflateRay, inflateConvex, out _, out _)) + { + fraction = planarFraction; + return true; + } + } + + fraction = 2f; + bool hit = false; + for (int i = 0; i < blob.vertexIndicesInEdges.Length; i++) + { + var indices = blob.vertexIndicesInEdges[i]; + var cap = new CapsuleCollider(new float3(blob.verticesX[indices.x], blob.verticesY[indices.x], blob.verticesZ[indices.x]) * scale, + new float3(blob.verticesX[indices.y], blob.verticesY[indices.y], blob.verticesZ[indices.y]) * scale, radius); + if (RaycastCapsule(ray, cap, out float newFraction, out _)) + { + hit = true; + fraction = math.min(fraction, newFraction); + } + } + return hit; + } + fraction = 2f; + return false; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/Raycasts.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/Raycasts.cs.meta new file mode 100644 index 0000000..1890906 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Internal/Queries/Raycasts.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23d6a2c7f47210642804115fafeb59cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Latios.Psyshock.asmdef b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Latios.Psyshock.asmdef new file mode 100644 index 0000000..0035a4d --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Latios.Psyshock.asmdef @@ -0,0 +1,23 @@ +{ + "name": "Latios.Psyshock", + "rootNamespace": "Latios.Psyshock", + "references": [ + "Latios.Core", + "Unity.Entities", + "Unity.Collections", + "Unity.Mathematics", + "Unity.Jobs", + "Unity.Transforms", + "Unity.Entities.Hybrid", + "Unity.Burst" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Latios.Psyshock.asmdef.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Latios.Psyshock.asmdef.meta new file mode 100644 index 0000000..716de23 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Latios.Psyshock.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2de22fb7141426546b7382812669d73f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial.meta new file mode 100644 index 0000000..ba96d82 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a07de205b0973cc46a28ac792efd937e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders.meta new file mode 100644 index 0000000..561544e --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 988459766b8f2834da80638234a88910 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders/Physics.BuildCollisionLayer.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders/Physics.BuildCollisionLayer.cs new file mode 100644 index 0000000..1f7abd8 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders/Physics.BuildCollisionLayer.cs @@ -0,0 +1,764 @@ +using System; +using System.Diagnostics; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Latios.Psyshock +{ + /// + /// A struct defining the type handles used when building a CollisionLayer from an EntityQuery. + /// All handles are ReadOnly. You can cache this structure inside a system to accelerate scheduling costs, + /// but you must also ensure the handles are updating during each OnUpdate before building any CollisionLayer. + /// + public struct BuildCollisionLayerTypeHandles + { + [ReadOnly] public ComponentTypeHandle collider; + [ReadOnly] public ComponentTypeHandle translation; + [ReadOnly] public ComponentTypeHandle rotation; + [ReadOnly] public ComponentTypeHandle scale; + [ReadOnly] public ComponentTypeHandle parent; + [ReadOnly] public ComponentTypeHandle localToWorld; + [ReadOnly] public EntityTypeHandle entity; + + /// + /// Constructs the BuildCollsionLayer type handles using a managed system + /// + public BuildCollisionLayerTypeHandles(ComponentSystemBase system) + { + collider = system.GetComponentTypeHandle(true); + translation = system.GetComponentTypeHandle(true); + rotation = system.GetComponentTypeHandle(true); + scale = system.GetComponentTypeHandle(true); + parent = system.GetComponentTypeHandle(true); + localToWorld = system.GetComponentTypeHandle(true); + entity = system.GetEntityTypeHandle(); + } + + /// + /// Constructs the BuildCollisionLayer type handles using a SystemState + /// + public BuildCollisionLayerTypeHandles(ref SystemState system) + { + collider = system.GetComponentTypeHandle(true); + translation = system.GetComponentTypeHandle(true); + rotation = system.GetComponentTypeHandle(true); + scale = system.GetComponentTypeHandle(true); + parent = system.GetComponentTypeHandle(true); + localToWorld = system.GetComponentTypeHandle(true); + entity = system.GetEntityTypeHandle(); + } + + /// + /// Updates the type handles using a managed system + /// + public void Update(SystemBase system) + { + collider.Update(system); + translation.Update(system); + rotation.Update(system); + scale.Update(system); + parent.Update(system); + localToWorld.Update(system); + entity = system.GetEntityTypeHandle(); + } + + /// + /// Updates the type handles using a SystemState + /// + public void Update(ref SystemState system) + { + collider.Update(ref system); + translation.Update(ref system); + rotation.Update(ref system); + scale.Update(ref system); + parent.Update(ref system); + localToWorld.Update(ref system); + entity = system.GetEntityTypeHandle(); + } + } + + /// + /// The config object used in Physics.BuildCollisionLayer fluent chains + /// + public struct BuildCollisionLayerConfig + { + internal BuildCollisionLayerTypeHandles typeGroup; + internal EntityQuery query; + + internal NativeArray aabbs; + internal NativeArray bodies; + + internal NativeArray remapSrcIndices; + + internal CollisionLayerSettings settings; + + internal bool hasQueryData; + internal bool hasBodiesArray; + internal bool hasAabbsArray; + internal bool hasRemapSrcIndices; + + internal int count; + + /// + /// The default CollisionLayerSettings used when none is specified. + /// These settings divide the world into 8 cells associated with the 8 octants of world space + /// + public static readonly CollisionLayerSettings defaultSettings = new CollisionLayerSettings + { + worldAabb = new Aabb(new float3(-1f), new float3(1f)), + worldSubdivisionsPerAxis = new int3(2, 2, 2) + }; + } + + public static partial class Physics + { + /// + /// Adds the necessary components to an EntityQuery to ensure proper building of a CollisionLayer + /// + public static FluentQuery PatchQueryForBuildingCollisionLayer(this FluentQuery fluent) + { + return fluent.WithAllWeak(); + } + + #region Starters + /// + /// Creates a new CollisionLayer by extracting collider and transform data from the entities in an EntityQuery. + /// This is a start of a fluent chain. + /// + /// The EntityQuery from which to extract collider and transform data + /// The system used for extracting ComponentTypeHandles + public static BuildCollisionLayerConfig BuildCollisionLayer(EntityQuery query, ComponentSystemBase system) + { + var config = new BuildCollisionLayerConfig(); + config.query = query; + config.typeGroup = new BuildCollisionLayerTypeHandles(system); + config.hasQueryData = true; + config.settings = BuildCollisionLayerConfig.defaultSettings; + config.count = query.CalculateEntityCount(); + return config; + } + + /// + /// Creates a new CollisionLayer by extracting collider and transform data from the entities in an EntityQuery. + /// This is a start of a fluent chain. + /// + /// The EntityQuery from which to extract collider and transform data + /// The system used for extracting ComponentTypeHandles + public static BuildCollisionLayerConfig BuildCollisionLayer(EntityQuery query, ref SystemState system) + { + var config = new BuildCollisionLayerConfig(); + config.query = query; + config.typeGroup = new BuildCollisionLayerTypeHandles(ref system); + config.hasQueryData = true; + config.settings = BuildCollisionLayerConfig.defaultSettings; + config.count = query.CalculateEntityCount(); + return config; + } + + /// + /// Creates a new CollisionLayer by extracting collider and transform data from the entities in an EntityQuery. + /// This is a start of a fluent chain. + /// + /// The EntityQuery from which to extract collider and transform data + /// Cached type handles that must be updated before invoking this method + public static BuildCollisionLayerConfig BuildCollisionLayer(EntityQuery query, in BuildCollisionLayerTypeHandles requiredTypeHandles) + { + var config = new BuildCollisionLayerConfig(); + config.query = query; + config.typeGroup = requiredTypeHandles; + config.hasQueryData = true; + config.settings = BuildCollisionLayerConfig.defaultSettings; + config.count = query.CalculateEntityCount(); + return config; + } + + /// + /// Creates a new CollisionLayer using the collider and transform data provided by the bodies array + /// + /// The array of ColliderBody instances which should be baked into the CollisionLayer + public static BuildCollisionLayerConfig BuildCollisionLayer(NativeArray bodies) + { + var config = new BuildCollisionLayerConfig(); + config.bodies = bodies; + config.hasBodiesArray = true; + config.settings = BuildCollisionLayerConfig.defaultSettings; + config.count = bodies.Length; + return config; + } + + /// + /// Creates a new CollisionLayer using the collider and transform data provided by the bodies array, + /// but uses the AABBs provided by the overrideAabbs array instead of calculating AABBs from the bodies + /// + /// The array of ColliderBody instances which should be baked into the CollisionLayer + /// The array of AABBs parallel to the bodies array specifying different AABBs to use for each body + public static BuildCollisionLayerConfig BuildCollisionLayer(NativeArray bodies, NativeArray overrideAabbs) + { + var config = new BuildCollisionLayerConfig(); + ValidateOverrideAabbsAreRightLength(overrideAabbs, bodies.Length, false); + + config.aabbs = overrideAabbs; + config.bodies = bodies; + config.hasAabbsArray = true; + config.hasBodiesArray = true; + config.settings = BuildCollisionLayerConfig.defaultSettings; + config.count = bodies.Length; + return config; + } + #endregion + + #region FluentChain + /// + /// Specifies the CollisionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithSettings(this BuildCollisionLayerConfig config, CollisionLayerSettings settings) + { + config.settings = settings; + return config; + } + + /// + /// Specifies the worldAabb member of the CollisionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithWorldBounds(this BuildCollisionLayerConfig config, Aabb worldAabb) + { + config.settings.worldAabb = worldAabb; + return config; + } + + /// + /// Specifies the worldSubdivisionsPerAxis member of the CollisionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithSubdivisions(this BuildCollisionLayerConfig config, int3 subdivisions) + { + config.settings.worldSubdivisionsPerAxis = subdivisions; + return config; + } + + /// + /// Specifies the worldAabb.min of the CollsionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithWorldMin(this BuildCollisionLayerConfig config, float x, float y, float z) + { + config.settings.worldAabb.min = new float3(x, y, z); + return config; + } + + /// + /// Specifies the worldAabb.max of the CollsionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithWorldMax(this BuildCollisionLayerConfig config, float x, float y, float z) + { + config.settings.worldAabb.max = new float3(x, y, z); + return config; + } + + /// + /// Specifies the worldAabb of the CollsionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithWorldBounds(this BuildCollisionLayerConfig config, float3 min, float3 max) + { + var aabb = new Aabb(min, max); + return config.WithWorldBounds(aabb); + } + + /// + /// Specifies the worldSubdivisionsPerAxis of the CollisionLayerSettings which should be used when building the CollisionLayer + /// + public static BuildCollisionLayerConfig WithSubdivisions(this BuildCollisionLayerConfig config, int x, int y, int z) + { + return config.WithSubdivisions(new int3(x, y, z)); + } + + /// + /// Specifies a NativeArray that should be written to where when indexed by a bodyIndex in a FindPairsResult or FindObjects result + /// specifies the original array index of entityInQueryIndex of the ColliderBody in the layer + /// + public static BuildCollisionLayerConfig WithRemapArray(this BuildCollisionLayerConfig config, NativeArray remapSrcIndices) + { + ValidateRemapArrayIsRightLength(remapSrcIndices, config.count, config.hasQueryData); + + config.remapSrcIndices = remapSrcIndices; + config.hasRemapSrcIndices = true; + return config; + } + + /// + /// Specifies a NativeArray that should be allocated and written to where when indexed by a bodyIndex in a FindPairsResult or FindObjects result + /// specifies the original array index of entityInQueryIndex of the ColliderBody in the layer + /// + /// The generated array containing source indices + /// The allocator to use for allocating the array + public static BuildCollisionLayerConfig WithRemapArray(this BuildCollisionLayerConfig config, + out NativeArray remapSrcIndices, + AllocatorManager.AllocatorHandle allocator) + { + remapSrcIndices = CollectionHelper.CreateNativeArray(config.count, allocator, NativeArrayOptions.UninitializedMemory); + + config.remapSrcIndices = remapSrcIndices; + config.hasRemapSrcIndices = true; + return config; + } + + #endregion + + #region Schedulers + /// + /// Generates the CollisionLayer immediately without using the Job System + /// + /// The generated CollisionLayer + /// The allocator to use for allocating the CollisionLayer + public static void RunImmediate(this BuildCollisionLayerConfig config, out CollisionLayer layer, AllocatorManager.AllocatorHandle allocator) + { + config.ValidateSettings(); + + if (config.hasQueryData) + { + ThrowEntityQueryInImmediateMode(); + layer = default; + } + else if (config.hasAabbsArray && config.hasBodiesArray) + { + layer = new CollisionLayer(config.bodies.Length, config.settings, allocator); + if (config.hasRemapSrcIndices) + BuildCollisionLayerInternal.BuildImmediate(ref layer, config.remapSrcIndices, config.bodies, config.aabbs); + else + { + var remapArray = new NativeArray(layer.Count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + BuildCollisionLayerInternal.BuildImmediate(ref layer, remapArray, config.bodies, config.aabbs); + } + } + else if (config.hasBodiesArray) + { + layer = new CollisionLayer(config.bodies.Length, config.settings, allocator); + if (config.hasRemapSrcIndices) + BuildCollisionLayerInternal.BuildImmediate(ref layer, config.remapSrcIndices, config.bodies, config.aabbs); + else + { + var remapArray = new NativeArray(layer.Count, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + BuildCollisionLayerInternal.BuildImmediate(ref layer, remapArray, config.bodies, config.aabbs); + } + } + else + { + ThrowUnknownConfiguration(); + layer = default; + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ThrowEntityQueryInImmediateMode() + { + throw new InvalidOperationException("Running immediate mode on an EntityQuery is not supported. Use Run instead."); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ThrowUnknownConfiguration() + { + throw new InvalidOperationException("Something went wrong with the BuildCollisionError configuration."); + } + + /// + /// Generates the CollisionLayer on the same thread using Burst + /// + /// The generated CollisionLayer + /// The allocator to use for allocating the CollisionLayer + public static void Run(this BuildCollisionLayerConfig config, out CollisionLayer layer, AllocatorManager.AllocatorHandle allocator) + { + config.ValidateSettings(); + + if (config.hasQueryData) + { + int count = config.count; + layer = new CollisionLayer(count, config.settings, allocator); + var layerIndices = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var aos = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var xMinMaxs = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + NativeArray remapSrcIndices = config.hasRemapSrcIndices ? config.remapSrcIndices : new NativeArray(count, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + + new BuildCollisionLayerInternal.Part1FromQueryJob + { + typeGroup = config.typeGroup, + layer = layer, + layerIndices = layerIndices, + colliderAoS = aos, + xMinMaxs = xMinMaxs + }.Run(config.query); + + new BuildCollisionLayerInternal.Part2Job + { + layer = layer, + layerIndices = layerIndices + }.Run(); + + new BuildCollisionLayerInternal.Part3Job + { + layerIndices = layerIndices, + unsortedSrcIndices = remapSrcIndices + }.Run(count); + + new BuildCollisionLayerInternal.Part4Job + { + bucketStartAndCounts = layer.bucketStartsAndCounts, + unsortedSrcIndices = remapSrcIndices, + trees = layer.intervalTrees, + xMinMaxs = xMinMaxs + }.Run(layer.BucketCount); + + new BuildCollisionLayerInternal.Part5FromQueryJob + { + colliderAoS = aos, + layer = layer, + remapSrcIndices = remapSrcIndices + }.Run(count); + + if (!config.hasRemapSrcIndices) + remapSrcIndices.Dispose(); + } + else if (config.hasAabbsArray && config.hasBodiesArray) + { + layer = new CollisionLayer(config.aabbs.Length, config.settings, allocator); + if (config.hasRemapSrcIndices) + { + new BuildCollisionLayerInternal.BuildFromDualArraysSingleWithRemapJob + { + layer = layer, + aabbs = config.aabbs, + bodies = config.bodies, + remapSrcIndices = config.remapSrcIndices + }.Run(); + } + else + { + new BuildCollisionLayerInternal.BuildFromDualArraysSingleJob + { + layer = layer, + aabbs = config.aabbs, + bodies = config.bodies + }.Run(); + } + } + else if (config.hasBodiesArray) + { + layer = new CollisionLayer(config.bodies.Length, config.settings, allocator); + if (config.hasRemapSrcIndices) + { + new BuildCollisionLayerInternal.BuildFromColliderArraySingleWithRemapJob + { + layer = layer, + bodies = config.bodies, + remapSrcIndices = config.remapSrcIndices + }.Run(); + } + else + { + new BuildCollisionLayerInternal.BuildFromColliderArraySingleJob + { + layer = layer, + bodies = config.bodies + }.Run(); + } + } + else + throw new InvalidOperationException("Something went wrong with the BuildCollisionError configuration."); + } + + /// + /// Generates the CollisionLayer inside a single-threaded job using Burst + /// + /// The generated CollisionLayer + /// The allocator to use for allocating the CollisionLayer + /// A JobHandle the scheduled job should wait on + /// The JobHandle associated with the scheduled builder job + public static JobHandle ScheduleSingle(this BuildCollisionLayerConfig config, + out CollisionLayer layer, + AllocatorManager.AllocatorHandle allocator, + JobHandle inputDeps = default) + { + config.ValidateSettings(); + + var jh = inputDeps; + + if (config.hasQueryData) + { + int count = config.count; + layer = new CollisionLayer(count, config.settings, allocator); + var layerIndices = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var aos = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var xMinMaxs = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + NativeArray remapSrcIndices = config.hasRemapSrcIndices ? config.remapSrcIndices : new NativeArray(count, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + + jh = new BuildCollisionLayerInternal.Part1FromQueryJob + { + typeGroup = config.typeGroup, + layer = layer, + layerIndices = layerIndices, + colliderAoS = aos, + xMinMaxs = xMinMaxs + }.Schedule(config.query, jh); + + jh = new BuildCollisionLayerInternal.Part2Job + { + layer = layer, + layerIndices = layerIndices + }.Schedule(jh); + + jh = new BuildCollisionLayerInternal.Part3Job + { + layerIndices = layerIndices, + unsortedSrcIndices = remapSrcIndices + }.Schedule(jh); + + jh = new BuildCollisionLayerInternal.Part4Job + { + bucketStartAndCounts = layer.bucketStartsAndCounts, + unsortedSrcIndices = remapSrcIndices, + trees = layer.intervalTrees, + xMinMaxs = xMinMaxs + }.Schedule(jh); + + jh = new BuildCollisionLayerInternal.Part5FromQueryJob + { + colliderAoS = aos, + layer = layer, + remapSrcIndices = remapSrcIndices + }.Schedule(jh); + + if (!config.hasRemapSrcIndices) + jh = remapSrcIndices.Dispose(jh); + return jh; + } + else if (config.hasAabbsArray && config.hasBodiesArray) + { + layer = new CollisionLayer(config.aabbs.Length, config.settings, allocator); + if (config.hasRemapSrcIndices) + { + jh = new BuildCollisionLayerInternal.BuildFromDualArraysSingleWithRemapJob + { + layer = layer, + aabbs = config.aabbs, + bodies = config.bodies, + remapSrcIndices = config.remapSrcIndices + }.Schedule(jh); + } + else + { + jh = new BuildCollisionLayerInternal.BuildFromDualArraysSingleJob + { + layer = layer, + aabbs = config.aabbs, + bodies = config.bodies + }.Schedule(jh); + } + return jh; + } + else if (config.hasBodiesArray) + { + layer = new CollisionLayer(config.bodies.Length, config.settings, allocator); + if (config.hasRemapSrcIndices) + { + jh = new BuildCollisionLayerInternal.BuildFromColliderArraySingleWithRemapJob + { + layer = layer, + bodies = config.bodies, + remapSrcIndices = config.remapSrcIndices + }.Schedule(jh); + } + else + { + jh = new BuildCollisionLayerInternal.BuildFromColliderArraySingleJob + { + layer = layer, + bodies = config.bodies + }.Schedule(jh); + } + return jh; + } + else + throw new InvalidOperationException("Something went wrong with the BuildCollisionError configuration."); + } + + /// + /// Generates the CollisionLayer inside a sequence of multi-threaded jobs using Burst + /// + /// The generated CollisionLayer + /// The allocator to use for allocating the CollisionLayer + /// A JobHandle the scheduled jobs should wait on + /// The JobHandle associated with the scheduled builder jobs + public static JobHandle ScheduleParallel(this BuildCollisionLayerConfig config, + out CollisionLayer layer, + AllocatorManager.AllocatorHandle allocator, + JobHandle inputDeps = default) + { + config.ValidateSettings(); + + var jh = inputDeps; + + if (config.hasQueryData) + { + int count = config.query.CalculateEntityCount(); + layer = new CollisionLayer(count, config.settings, allocator); + var layerIndices = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var xMinMaxs = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var aos = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + NativeArray remapSrcIndices = config.hasRemapSrcIndices ? config.remapSrcIndices : new NativeArray(count, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + + jh = new BuildCollisionLayerInternal.Part1FromQueryJob + { + layer = layer, + typeGroup = config.typeGroup, + layerIndices = layerIndices, + xMinMaxs = xMinMaxs, + colliderAoS = aos + }.ScheduleParallel(config.query, jh); + + jh = new BuildCollisionLayerInternal.Part2Job + { + layer = layer, + layerIndices = layerIndices + }.Schedule(jh); + + jh = new BuildCollisionLayerInternal.Part3Job + { + layerIndices = layerIndices, + unsortedSrcIndices = remapSrcIndices + }.Schedule(count, 512, jh); + + jh = new BuildCollisionLayerInternal.Part4Job + { + unsortedSrcIndices = remapSrcIndices, + xMinMaxs = xMinMaxs, + trees = layer.intervalTrees, + bucketStartAndCounts = layer.bucketStartsAndCounts + }.Schedule(layer.BucketCount, 1, jh); + + jh = new BuildCollisionLayerInternal.Part5FromQueryJob + { + layer = layer, + colliderAoS = aos, + remapSrcIndices = remapSrcIndices + }.Schedule(count, 128, jh); + + if (!config.hasRemapSrcIndices) + jh = remapSrcIndices.Dispose(jh); + + return jh; + } + else if (config.hasBodiesArray) + { + layer = new CollisionLayer(config.bodies.Length, config.settings, allocator); + int count = config.bodies.Length; + var layerIndices = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var xMinMaxs = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + NativeArray remapSrcIndices = config.hasRemapSrcIndices ? config.remapSrcIndices : new NativeArray(count, + Allocator.TempJob, + NativeArrayOptions.UninitializedMemory); + + NativeArray aabbs = config.hasAabbsArray ? config.aabbs : new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + if (config.hasAabbsArray) + { + jh = new BuildCollisionLayerInternal.Part1FromDualArraysJob + { + layer = layer, + aabbs = aabbs, + layerIndices = layerIndices, + xMinMaxs = xMinMaxs + }.Schedule(count, 64, jh); + } + else + { + jh = new BuildCollisionLayerInternal.Part1FromColliderBodyArrayJob + { + layer = layer, + aabbs = aabbs, + colliderBodies = config.bodies, + layerIndices = layerIndices, + xMinMaxs = xMinMaxs + }.Schedule(count, 64, jh); + } + + jh = new BuildCollisionLayerInternal.Part2Job + { + layer = layer, + layerIndices = layerIndices + }.Schedule(jh); + + jh = new BuildCollisionLayerInternal.Part3Job + { + layerIndices = layerIndices, + unsortedSrcIndices = remapSrcIndices + }.Schedule(count, 512, jh); + + jh = new BuildCollisionLayerInternal.Part4Job + { + bucketStartAndCounts = layer.bucketStartsAndCounts, + unsortedSrcIndices = remapSrcIndices, + trees = layer.intervalTrees, + xMinMaxs = xMinMaxs + }.Schedule(layer.BucketCount, 1, jh); + + jh = new BuildCollisionLayerInternal.Part5FromArraysJob + { + aabbs = aabbs, + bodies = config.bodies, + layer = layer, + remapSrcIndices = remapSrcIndices + }.Schedule(count, 128, jh); + + if ((!config.hasAabbsArray) && (!config.hasRemapSrcIndices)) + jh = JobHandle.CombineDependencies(remapSrcIndices.Dispose(jh), aabbs.Dispose(jh)); + else if (!config.hasRemapSrcIndices) + jh = remapSrcIndices.Dispose(jh); + else if (!config.hasAabbsArray) + jh = aabbs.Dispose(jh); + + return jh; + } + else + throw new InvalidOperationException("Something went wrong with the BuildCollisionError configuration."); + } + #endregion + + #region Validators + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ValidateSettings(this BuildCollisionLayerConfig config) + { + if (math.any(config.settings.worldAabb.min > config.settings.worldAabb.max)) + throw new InvalidOperationException("BuildCollisionLayer requires a valid worldBounds AABB"); + if (math.any(config.settings.worldSubdivisionsPerAxis < 1)) + throw new InvalidOperationException("BuildCollisionLayer requires positive Subdivision values per axis"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ValidateOverrideAabbsAreRightLength(NativeArray aabbs, int count, bool query) + { + if (aabbs.Length != count) + throw new InvalidOperationException( + $"The number of elements in overrideAbbs does not match the { (query ? "number of entities in the query" : "number of bodies in the bodies array")}"); + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ValidateRemapArrayIsRightLength(NativeArray remap, int count, bool query) + { + if (remap.Length != count) + throw new InvalidOperationException( + $"The number of elements in remapSrcArray does not match the { (query ? "number of entities in the query" : "number of bodies in the bodies array")}"); + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders/Physics.BuildCollisionLayer.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders/Physics.BuildCollisionLayer.cs.meta new file mode 100644 index 0000000..68a9250 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Builders/Physics.BuildCollisionLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5dc6366dccf4d34582423693d468d01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers.meta new file mode 100644 index 0000000..e357d98 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f38737c30cfda3341bb102b0b4ae2909 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.CombineAabb.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.CombineAabb.cs new file mode 100644 index 0000000..ff6eaa3 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.CombineAabb.cs @@ -0,0 +1,30 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + /// + /// Creates a new AABB by computing an AABB that encapsulates both inputs + /// + /// A point to encapsulate + /// An AABB to encapsulate + /// A new computed AABB + public static Aabb CombineAabb(float3 point, Aabb aabb) + { + return new Aabb(math.min(point, aabb.min), math.max(point, aabb.max)); + } + + /// + /// Creates a new AABB by computing an AABB that encapsulates both inputs + /// + /// An AABB to encapsulate + /// Another AABB to encapsulate + /// + public static Aabb CombineAabb(Aabb a, Aabb b) + { + return new Aabb(math.min(a.min, b.min), math.max(a.max, b.max)); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.CombineAabb.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.CombineAabb.cs.meta new file mode 100644 index 0000000..78eeef5 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.CombineAabb.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d35e48414771c464fbd691cf7dcee944 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.ScaleCollider.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.ScaleCollider.cs new file mode 100644 index 0000000..0cf518e --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.ScaleCollider.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + public static Collider ScaleCollider(in Collider collider, PhysicsScale scale) + { + switch (collider.type) + { + case ColliderType.Sphere: return ScaleCollider((SphereCollider)collider, scale); + case ColliderType.Capsule: return ScaleCollider((CapsuleCollider)collider, scale); + case ColliderType.Box: return ScaleCollider((BoxCollider)collider, scale); + case ColliderType.Triangle: return ScaleCollider((TriangleCollider)collider, scale); + case ColliderType.Convex: return ScaleCollider((ConvexCollider)collider, scale); + case ColliderType.Compound: return ScaleCollider((CompoundCollider)collider, scale); + default: ThrowUnsupportedType(); return new Collider(); + } + } + + public static SphereCollider ScaleCollider(SphereCollider sphere, PhysicsScale scale) + { + CheckNoOrUniformScale(scale, ColliderType.Sphere); + sphere.center *= scale.scale.x; + sphere.radius *= scale.scale.x; + return sphere; + } + + public static CapsuleCollider ScaleCollider(CapsuleCollider capsule, PhysicsScale scale) + { + CheckNoOrUniformScale(scale, ColliderType.Capsule); + capsule.pointA *= scale.scale.x; + capsule.pointB *= scale.scale.x; + capsule.radius *= scale.scale.x; + return capsule; + } + + public static BoxCollider ScaleCollider(BoxCollider box, PhysicsScale scale) + { + CheckNoOrValidScale(scale, ColliderType.Box); + box.center *= scale.scale; + box.halfSize *= scale.scale; + return box; + } + + public static TriangleCollider ScaleCollider(TriangleCollider triangle, PhysicsScale scale) + { + CheckNoOrValidScale(scale, ColliderType.Triangle); + triangle.pointA *= scale.scale; + triangle.pointB *= scale.scale; + triangle.pointC *= scale.scale; + return triangle; + } + + public static ConvexCollider ScaleCollider(ConvexCollider convex, PhysicsScale scale) + { + CheckNoOrValidScale(scale, ColliderType.Convex); + convex.scale *= scale.scale; + return convex; + } + + public static CompoundCollider ScaleCollider(CompoundCollider compound, PhysicsScale scale) + { + CheckNoOrUniformScale(scale, ColliderType.Compound); + compound.scale *= scale.scale.x; + return compound; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void CheckNoOrUniformScale(PhysicsScale scale, ColliderType type) + { + if (scale.state == PhysicsScale.State.NonComputable | scale.state == PhysicsScale.State.NonUniform) + { + switch (type) + { + case ColliderType.Sphere: throw new InvalidOperationException("Sphere Collider must be scaled with no scale or uniform scale."); + case ColliderType.Capsule: throw new InvalidOperationException("Capsule Collider must be scaled with no scale or uniform scale."); + case ColliderType.Box: throw new InvalidOperationException("Box Collider must be scaled with no scale or uniform scale."); + case ColliderType.Compound: throw new InvalidOperationException("Compound Collider must be scaled with no scale or uniform scale."); + default: throw new InvalidOperationException("Failed to scale unknown collider type."); + } + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void CheckNoOrValidScale(PhysicsScale scale, ColliderType type) + { + if (scale.state == PhysicsScale.State.NonComputable) + { + throw new InvalidOperationException("The collider cannot be scaled with a noncomputable scale"); + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.ScaleCollider.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.ScaleCollider.cs.meta new file mode 100644 index 0000000..f5d69d8 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.ScaleCollider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5672063158ce184bbc671fee20f7380 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.TransformAabb.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.TransformAabb.cs new file mode 100644 index 0000000..1988957 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.TransformAabb.cs @@ -0,0 +1,21 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + public static Aabb TransformAabb(float4x4 transform, float3 center, float3 extents) + { + float3 worldCenter = math.transform(transform, center); + float3 worldExtents = LatiosMath.RotateExtents(extents, transform.c0.xyz, transform.c1.xyz, transform.c2.xyz); + return new Aabb(worldCenter - worldExtents, worldCenter + worldExtents); + } + + public static void GetCenterExtents(Aabb aabb, out float3 center, out float3 extents) + { + center = (aabb.min + aabb.max) / 2f; + extents = aabb.max - center; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.TransformAabb.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.TransformAabb.cs.meta new file mode 100644 index 0000000..5291b0f --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Modifiers/Physics.TransformAabb.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c34a2519c9e75bd4eb98112138963a8a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries.meta new file mode 100644 index 0000000..12cbe69 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65926351232db9b4abadd1a76b93f7f4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Aabbs.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Aabbs.cs new file mode 100644 index 0000000..f82e387 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Aabbs.cs @@ -0,0 +1,168 @@ +using System; +using System.Diagnostics; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + #region Rays + public static Aabb AabbFrom(in Ray ray) + { + return new Aabb(math.min(ray.start, ray.end), math.max(ray.start, ray.end)); + } + + public static Aabb AabbFrom(float3 rayStart, float3 rayEnd) + { + return new Aabb(math.min(rayStart, rayEnd), math.max(rayStart, rayEnd)); + } + #endregion + + #region Colliders + public static Aabb AabbFrom(in SphereCollider sphere, in RigidTransform transform) + { + float3 wc = math.transform(transform, sphere.center); + Aabb aabb = new Aabb(wc - sphere.radius, wc + sphere.radius); + return aabb; + } + + public static Aabb AabbFrom(in CapsuleCollider capsule, in RigidTransform transform) + { + float3 a = math.transform(transform, capsule.pointA); + float3 b = math.transform(transform, capsule.pointB); + return new Aabb(math.min(a, b) - capsule.radius, math.max(a, b) + capsule.radius); + } + + public static Aabb AabbFrom(in BoxCollider box, in RigidTransform transform) + { + return TransformAabb(new float4x4(transform), box.center, box.halfSize); + } + + public static Aabb AabbFrom(in TriangleCollider triangle, in RigidTransform transform) + { + var transformedTriangle = simd.transform(transform, new simdFloat3(triangle.pointA, triangle.pointB, triangle.pointC, triangle.pointA)); + var aabb = new Aabb(math.min(transformedTriangle.a, transformedTriangle.b), math.max(transformedTriangle.a, transformedTriangle.b)); + return CombineAabb(transformedTriangle.c, aabb); + } + + public static Aabb AabbFrom(in ConvexCollider convex, in RigidTransform transform) + { + var local = convex.convexColliderBlob.Value.localAabb; + float3 c = (local.min + local.max) / 2f; + BoxCollider box = new BoxCollider(c, local.max - c); + return AabbFrom(ScaleCollider(box, new PhysicsScale(convex.scale)), transform); + } + + public static Aabb AabbFrom(in CompoundCollider compound, in RigidTransform transform) + { + var local = compound.compoundColliderBlob.Value.localAabb; + float3 c = (local.min + local.max) / 2f; + BoxCollider box = new BoxCollider(c, local.max - c); + return AabbFrom(ScaleCollider(box, new PhysicsScale(compound.scale)), transform); + } + #endregion + + #region ColliderCasts + public static Aabb AabbFrom(in SphereCollider sphereToCast, in RigidTransform castStart, float3 castEnd) + { + var aabbStart = AabbFrom(sphereToCast, castStart); + var diff = castEnd - castStart.pos; + var aabbEnd = new Aabb(aabbStart.min + diff, aabbStart.max + diff); + return CombineAabb(aabbStart, aabbEnd); + } + + public static Aabb AabbFrom(in CapsuleCollider capsuleToCast, in RigidTransform castStart, float3 castEnd) + { + var aabbStart = AabbFrom(capsuleToCast, castStart); + var diff = castEnd - castStart.pos; + var aabbEnd = new Aabb(aabbStart.min + diff, aabbStart.max + diff); + return CombineAabb(aabbStart, aabbEnd); + } + + public static Aabb AabbFrom(in BoxCollider boxToCast, in RigidTransform castStart, float3 castEnd) + { + var aabbStart = AabbFrom(boxToCast, castStart); + var diff = castEnd - castStart.pos; + var aabbEnd = new Aabb(aabbStart.min + diff, aabbStart.max + diff); + return CombineAabb(aabbStart, aabbEnd); + } + + public static Aabb AabbFrom(in TriangleCollider triangleToCast, in RigidTransform castStart, float3 castEnd) + { + var aabbStart = AabbFrom(triangleToCast, castStart); + var diff = castEnd - castStart.pos; + var aabbEnd = new Aabb(aabbStart.min + diff, aabbStart.max + diff); + return CombineAabb(aabbStart, aabbEnd); + } + + public static Aabb AabbFrom(in ConvexCollider convexToCast, in RigidTransform castStart, float3 castEnd) + { + var aabbStart = AabbFrom(convexToCast, castStart); + var diff = castEnd - castStart.pos; + var aabbEnd = new Aabb(aabbStart.min + diff, aabbStart.max + diff); + return CombineAabb(aabbStart, aabbEnd); + } + + public static Aabb AabbFrom(in CompoundCollider compoundToCast, in RigidTransform castStart, float3 castEnd) + { + var aabbStart = AabbFrom(compoundToCast, castStart); + var diff = castEnd - castStart.pos; + var aabbEnd = new Aabb(aabbStart.min + diff, aabbStart.max + diff); + return CombineAabb(aabbStart, aabbEnd); + } + #endregion + + #region Dispatch + public static Aabb AabbFrom(in Collider collider, in RigidTransform transform) + { + switch (collider.type) + { + case ColliderType.Sphere: + return AabbFrom(in collider.m_sphere, in transform); + case ColliderType.Capsule: + return AabbFrom(in collider.m_capsule, in transform); + case ColliderType.Box: + return AabbFrom(in collider.m_box, in transform); + case ColliderType.Triangle: + return AabbFrom(in collider.m_triangle, in transform); + case ColliderType.Convex: + return AabbFrom(in collider.m_convex, in transform); + case ColliderType.Compound: + return AabbFrom(in collider.m_compound, in transform); + default: + ThrowUnsupportedType(); + return new Aabb(); + } + } + + public static Aabb AabbFrom(in Collider colliderToCast, in RigidTransform castStart, float3 castEnd) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + return AabbFrom(in colliderToCast.m_sphere, in castStart, castEnd); + case ColliderType.Capsule: + return AabbFrom(in colliderToCast.m_capsule, in castStart, castEnd); + case ColliderType.Box: + return AabbFrom(in colliderToCast.m_box, in castStart, castEnd); + case ColliderType.Triangle: + return AabbFrom(in colliderToCast.m_triangle, in castStart, castEnd); + case ColliderType.Convex: + return AabbFrom(in colliderToCast.m_convex, in castStart, castEnd); + case ColliderType.Compound: + return AabbFrom(in colliderToCast.m_compound, in castStart, castEnd); + default: + ThrowUnsupportedType(); + return new Aabb(); + } + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private static void ThrowUnsupportedType() + { + throw new InvalidOperationException("Collider type not supported yet"); + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Aabbs.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Aabbs.cs.meta new file mode 100644 index 0000000..1bbd7c8 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Aabbs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7fbc8e33d4abdf84d8b3549db12f7acb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCast.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCast.cs new file mode 100644 index 0000000..4cc3591 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCast.cs @@ -0,0 +1,1465 @@ +using System.Diagnostics; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + #region Sphere + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + var cso = targetSphere; + cso.radius += sphereToCast.radius; + var start = math.transform(castStart, sphereToCast.center); + var ray = new Ray(start, start + castEnd - castStart.pos); + bool hit = Raycast(ray, cso, targetSphereTransform, out var raycastResult); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos += raycastResult.position - start; + DistanceBetween(in sphereToCast, in hitTransform, in targetSphere, in targetSphereTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + var cso = targetCapsule; + cso.radius += sphereToCast.radius; + var start = math.transform(castStart, sphereToCast.center); + var ray = new Ray(start, start + castEnd - castStart.pos); + bool hit = Raycast(ray, cso, targetCapsuleTransform, out var raycastResult); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos += raycastResult.position - start; + DistanceBetween(in sphereToCast, in hitTransform, in targetCapsule, in targetCapsuleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in BoxCollider targetBox, + in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + var targetBoxTransformInverse = math.inverse(targetBoxTransform); + var casterInTargetSpace = math.mul(targetBoxTransformInverse, castStart); + var start = math.transform(casterInTargetSpace, sphereToCast.center); + var ray = new Ray(start, start + math.rotate(targetBoxTransformInverse, castEnd - castStart.pos)); + bool hit = SpatialInternal.RaycastRoundedBox(ray, targetBox, sphereToCast.radius, out var fraction, out var normal); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in sphereToCast, in hitTransform, in targetBox, in targetBoxTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + var targetTriangleTransformInverse = math.inverse(targetTriangleTransform); + var casterInTargetSpace = math.mul(targetTriangleTransformInverse, castStart); + var start = math.transform(casterInTargetSpace, sphereToCast.center); + var ray = new Ray(start, start + math.rotate(targetTriangleTransformInverse, castEnd - castStart.pos)); + bool hit = + SpatialInternal.RaycastRoundedTriangle(ray, + targetTriangle.AsSimdFloat3(), + sphereToCast.radius, + out var fraction, + out _); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in sphereToCast, in hitTransform, in targetTriangle, in targetTriangleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + var targetConvexTransformInverse = math.inverse(targetConvexTransform); + var casterInTargetSpace = math.mul(targetConvexTransformInverse, castStart); + var start = math.transform(casterInTargetSpace, sphereToCast.center); + var ray = new Ray(start, start + math.rotate(targetConvexTransformInverse, castEnd - castStart.pos)); + bool hit = + SpatialInternal.RaycastRoundedConvex(ray, + targetConvex, + sphereToCast.radius, + out var fraction); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in sphereToCast, in hitTransform, in targetConvex, in targetConvexTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in sphereToCast, in castStart, in targetCompound, in targetCompoundTransform, 0f, out _)) + { + return false; + } + + ref var blob = ref targetCompound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = targetCompound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= targetCompound.scale; + bool newHit = ColliderCast(in sphereToCast, in castStart, castEnd, ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(targetCompoundTransform, blobTransform), + out var newResult); + + newResult.subColliderIndexOnTarget = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + #endregion + + #region Capsule + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + var cso = capsuleToCast; + cso.radius += targetSphere.radius; + var castReverse = castStart.pos - castEnd; + var start = math.transform(targetSphereTransform, targetSphere.center); + var ray = new Ray(start, start + castReverse); + bool hit = Raycast(ray, cso, castStart, out var raycastResult); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos -= raycastResult.position - start; + DistanceBetween(in capsuleToCast, in hitTransform, in targetSphere, in targetSphereTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + var csoRadius = capsuleToCast.radius + targetCapsule.radius; + var casterAInWorldSpace = math.transform(castStart, capsuleToCast.pointA); + var casterBInWorldSpace = math.transform(castStart, capsuleToCast.pointB); + var targetAInWorldSpace = math.transform(targetCapsuleTransform, targetCapsule.pointA); + var targetBInWorldSpace = math.transform(targetCapsuleTransform, targetCapsule.pointB); + var cso = new simdFloat3(casterAInWorldSpace - targetAInWorldSpace, + casterAInWorldSpace - targetBInWorldSpace, + casterBInWorldSpace - targetBInWorldSpace, + casterBInWorldSpace - targetAInWorldSpace); + var ray = new Ray(0f, castStart.pos - castEnd); + bool hit = SpatialInternal.RaycastRoundedQuad(ray, cso, csoRadius, out float fraction, out float3 normal); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in capsuleToCast, in hitTransform, in targetCapsule, in targetCapsuleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in BoxCollider targetBox, + in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + if (DistanceBetween(in capsuleToCast, in castStart, in targetBox, in targetBoxTransform, 0f, out _)) + { + result = default; + return false; + } + + var targetBoxTransformInverse = math.inverse(targetBoxTransform); + var casterInTargetSpace = math.mul(targetBoxTransformInverse, castStart); + var inflatedBoxAabb = new Aabb(targetBox.center - targetBox.halfSize - capsuleToCast.radius, targetBox.center + targetBox.halfSize + capsuleToCast.radius); + + var startA = math.transform(casterInTargetSpace, capsuleToCast.pointA); + var rayA = new Ray(startA, startA + math.rotate(targetBoxTransformInverse, castEnd - castStart.pos)); + bool hitA = SpatialInternal.RaycastAabb(rayA, inflatedBoxAabb, out var fractionA); + var hitpointA = math.lerp(rayA.start, rayA.end, fractionA) - targetBox.center; + hitA &= math.countbits(math.bitmask(new bool4(math.abs(hitpointA) > targetBox.halfSize, false))) == 1; + fractionA = math.select(2f, fractionA, hitA); + var startB = math.transform(casterInTargetSpace, capsuleToCast.pointB); + var rayB = new Ray(startB, startB + math.rotate(targetBoxTransformInverse, castEnd - castStart.pos)); + bool hitB = SpatialInternal.RaycastAabb(rayB, inflatedBoxAabb, out var fractionB); + var hitpointB = math.lerp(rayB.start, rayB.end, fractionB) - targetBox.center; + hitB &= math.countbits(math.bitmask(new bool4(math.abs(hitpointB) > targetBox.halfSize, false))) == 1; + fractionB = math.select(2f, fractionB, hitB); + + var ray = new Ray(0f, math.rotate(targetBoxTransformInverse, castStart.pos - castEnd)); + bool4 hitX; + float4 fractionsX; + float3 targetA = targetBox.center - targetBox.halfSize; + float3 targetB = targetBox.center + new float3(targetBox.halfSize.x, -targetBox.halfSize.y, -targetBox.halfSize.z); + simdFloat3 cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.x = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsX.x, out _); + targetA = targetBox.center + new float3(-targetBox.halfSize.x, -targetBox.halfSize.y, targetBox.halfSize.z); + targetB = targetBox.center + new float3(targetBox.halfSize.x, -targetBox.halfSize.y, targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.y = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsX.y, out _); + targetA = targetBox.center + new float3(-targetBox.halfSize.x, targetBox.halfSize.y, -targetBox.halfSize.z); + targetB = targetBox.center + new float3( targetBox.halfSize.x, targetBox.halfSize.y, -targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.z = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsX.z, out _); + targetA = targetBox.center + new float3(-targetBox.halfSize.x, targetBox.halfSize.y, targetBox.halfSize.z); + targetB = targetBox.center + targetBox.halfSize; + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.w = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsX.w, out _); + fractionsX = math.select(2f, fractionsX, hitX); + + bool4 hitY; + float4 fractionsY; + targetA = targetBox.center - targetBox.halfSize; + targetB = targetBox.center + new float3(-targetBox.halfSize.x, targetBox.halfSize.y, -targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.x = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsY.x, out _); + targetA = targetBox.center + new float3(-targetBox.halfSize.x, -targetBox.halfSize.y, targetBox.halfSize.z); + targetB = targetBox.center + new float3(-targetBox.halfSize.x, targetBox.halfSize.y, targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.y = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsY.y, out _); + targetA = targetBox.center + new float3(targetBox.halfSize.x, -targetBox.halfSize.y, -targetBox.halfSize.z); + targetB = targetBox.center + new float3(targetBox.halfSize.x, targetBox.halfSize.y, -targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.z = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsY.z, out _); + targetA = targetBox.center + new float3(targetBox.halfSize.x, -targetBox.halfSize.y, targetBox.halfSize.z); + targetB = targetBox.center + targetBox.halfSize; + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.w = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsY.w, out _); + fractionsY = math.select(2f, fractionsY, hitY); + + bool4 hitZ; + float4 fractionsZ; + targetA = targetBox.center - targetBox.halfSize; + targetB = targetBox.center + new float3(-targetBox.halfSize.x, -targetBox.halfSize.y, targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.x = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsZ.x, out _); + targetA = targetBox.center + new float3(-targetBox.halfSize.x, targetBox.halfSize.y, -targetBox.halfSize.z); + targetB = targetBox.center + new float3(-targetBox.halfSize.x, targetBox.halfSize.y, targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.y = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsZ.y, out _); + targetA = targetBox.center + new float3(targetBox.halfSize.x, -targetBox.halfSize.y, -targetBox.halfSize.z); + targetB = targetBox.center + new float3(targetBox.halfSize.x, -targetBox.halfSize.y, targetBox.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.z = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsZ.z, out _); + targetA = targetBox.center + new float3(targetBox.halfSize.x, targetBox.halfSize.y, -targetBox.halfSize.z); + targetB = targetBox.center + targetBox.halfSize; + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.w = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsZ.w, out _); + fractionsZ = math.select(2f, fractionsZ, hitZ); + + bool hit = math.any(hitX | hitY | hitZ) | hitA | hitB; + float fraction = math.min(math.min(fractionA, fractionB), math.cmin(math.min(fractionsX, math.min(fractionsY, fractionsZ)))); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in capsuleToCast, in hitTransform, in targetBox, in targetBoxTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + if (DistanceBetween(in capsuleToCast, in castStart, in targetTriangle, in targetTriangleTransform, 0f, out _)) + { + result = default; + return false; + } + + var targetTriangleTransformInverse = math.inverse(targetTriangleTransform); + var casterInTargetSpace = math.mul(targetTriangleTransformInverse, castStart); + var triPoints = targetTriangle.AsSimdFloat3(); + + var startA = math.transform(casterInTargetSpace, capsuleToCast.pointA); + var rayA = new Ray(startA, startA + math.rotate(targetTriangleTransformInverse, castEnd - castStart.pos)); + bool hitA = SpatialInternal.RaycastRoundedTriangle(rayA, triPoints, capsuleToCast.radius, out var fractionA, out _); + fractionA = math.select(2f, fractionA, hitA); + var startB = math.transform(casterInTargetSpace, capsuleToCast.pointB); + var rayB = new Ray(startB, startB + math.rotate(targetTriangleTransformInverse, castEnd - castStart.pos)); + bool hitB = SpatialInternal.RaycastRoundedTriangle(rayB, triPoints, capsuleToCast.radius, out var fractionB, out _); + fractionB = math.select(2f, fractionB, hitB); + + var ray = new Ray(0f, math.rotate(targetTriangleTransformInverse, castStart.pos - castEnd)); + bool3 hitEdge; + float3 fractionsEdge; + simdFloat3 startSimd = new simdFloat3(startA, startA, startB, startB); + simdFloat3 cso = startSimd - triPoints.abba; + hitEdge.x = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsEdge.x, out _); + cso = startSimd - triPoints.bccb; + hitEdge.y = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsEdge.y, out _); + cso = startSimd - triPoints.caac; + hitEdge.z = SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out fractionsEdge.z, out _); + fractionsEdge = math.select(2f, fractionsEdge, hitEdge); + + bool hit = math.any(hitEdge) | hitA | hitB; + float fraction = math.min(math.min(fractionA, fractionB), math.cmin(fractionsEdge)); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in capsuleToCast, in hitTransform, in targetTriangle, in targetTriangleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + if (DistanceBetween(in capsuleToCast, in castStart, in targetConvex, in targetConvexTransform, 0f, out _)) + { + result = default; + return false; + } + + var targetConvexTransformInverse = math.inverse(targetConvexTransform); + var casterInTargetSpace = math.mul(targetConvexTransformInverse, castStart); + + var startA = math.transform(casterInTargetSpace, capsuleToCast.pointA); + var rayA = new Ray(startA, startA + math.rotate(targetConvexTransformInverse, castEnd - castStart.pos)); + bool hitA = SpatialInternal.RaycastRoundedConvex(rayA, targetConvex, capsuleToCast.radius, out var fractionA); + fractionA = math.select(2f, fractionA, hitA); + var startB = math.transform(casterInTargetSpace, capsuleToCast.pointB); + var rayB = new Ray(startB, startB + math.rotate(targetConvexTransformInverse, castEnd - castStart.pos)); + bool hitB = SpatialInternal.RaycastRoundedConvex(rayB, targetConvex, capsuleToCast.radius, out var fractionB); + fractionB = math.select(2f, fractionB, hitB); + + var ray = new Ray(0f, math.rotate(targetConvexTransformInverse, castStart.pos - castEnd)); + simdFloat3 startSimd = new simdFloat3(startA, startA, startB, startB); + int bestEdgeIndex = -1; + float bestFraction = 2f; + ref var blob = ref targetConvex.convexColliderBlob.Value; + + var capEdge = startB - startA; + var capEdgeCrossRay = math.normalizesafe(math.cross(capEdge, ray.displacement), float3.zero); + if (capEdgeCrossRay.Equals(float3.zero)) + { + // The capsule aligns with the ray. We already have this case tested. + } + else + { + // We need four culling planes around the capsule + var correctedCapEdge = math.normalize(math.cross(ray.displacement, capEdgeCrossRay)); + var ta = math.select(startB, startA, math.dot(capEdgeCrossRay, startA) >= math.dot(capEdgeCrossRay, startB)); + var tb = math.select(startB, startA, math.dot(-capEdgeCrossRay, startA) >= math.dot(-capEdgeCrossRay, startB)); + var tc = math.select(startB, startA, math.dot(correctedCapEdge, startA) >= math.dot(correctedCapEdge, startB)); + var td = math.select(startB, startA, math.dot(-correctedCapEdge, startA) >= math.dot(-correctedCapEdge, startB)); + Plane planeA = new Plane(capEdgeCrossRay, -math.dot(capEdgeCrossRay, ta + capEdgeCrossRay * capsuleToCast.radius)); + Plane planeB = new Plane(-capEdgeCrossRay, -math.dot(-capEdgeCrossRay, tb - capEdgeCrossRay * capsuleToCast.radius)); + Plane planeC = new Plane(correctedCapEdge, -math.dot(correctedCapEdge, tc + correctedCapEdge * capsuleToCast.radius)); + Plane planeD = new Plane(-correctedCapEdge, -math.dot(-correctedCapEdge, td - correctedCapEdge * capsuleToCast.radius)); + float4 obbX = new float4(planeA.normal.x, planeB.normal.x, planeC.normal.x, planeD.normal.x); + float4 obbY = new float4(planeA.normal.y, planeB.normal.y, planeC.normal.y, planeD.normal.y); + float4 obbZ = new float4(planeA.normal.z, planeB.normal.z, planeC.normal.z, planeD.normal.z); + float4 obbD = new float4(planeA.distanceFromOrigin, planeB.distanceFromOrigin, planeC.distanceFromOrigin, planeD.distanceFromOrigin); + + for (int i = 0; i < blob.vertexIndicesInEdges.Length; i++) + { + var indices = blob.vertexIndicesInEdges[i]; + var ax = blob.verticesX[indices.x] * targetConvex.scale.x; + var ay = blob.verticesY[indices.x] * targetConvex.scale.y; + var az = blob.verticesZ[indices.x] * targetConvex.scale.z; + var bx = blob.verticesX[indices.y] * targetConvex.scale.x; + var by = blob.verticesY[indices.y] * targetConvex.scale.y; + var bz = blob.verticesZ[indices.y] * targetConvex.scale.z; + + bool4 isAOutside = obbX * ax + obbY * ay + obbZ * az + obbD > 0f; + bool4 isBOutside = obbX * bx + obbY * by + obbZ * bz + obbD > 0f; + if (math.any(isAOutside & isBOutside)) + continue; + + var edgePoints = new simdFloat3(new float4(ax, bx, bx, ax), new float4(ay, by, by, ay), new float4(az, bz, bz, az)); + var cso = startSimd - edgePoints; + if (SpatialInternal.RaycastRoundedQuad(ray, cso, capsuleToCast.radius, out var edgeFraction, out _)) + { + if (edgeFraction < bestFraction) + { + bestFraction = edgeFraction; + bestEdgeIndex = i; + } + } + } + } + + bool hit = (bestEdgeIndex >= 0) | hitA | hitB; + float fraction = math.min(math.min(fractionA, fractionB), bestFraction); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in capsuleToCast, in hitTransform, in targetConvex, in targetConvexTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in capsuleToCast, in castStart, in targetCompound, in targetCompoundTransform, 0f, out _)) + { + return false; + } + ref var blob = ref targetCompound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = targetCompound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= targetCompound.scale; + bool newHit = ColliderCast(in capsuleToCast, in castStart, castEnd, ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(targetCompoundTransform, blobTransform), + out var newResult); + + newResult.subColliderIndexOnTarget = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + #endregion + + #region Box + public static bool ColliderCast(in BoxCollider boxToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + var castReverse = castStart.pos - castEnd; + var worldToCasterSpace = math.inverse(castStart); + var start = math.transform(targetSphereTransform, targetSphere.center); + var ray = new Ray(math.transform(worldToCasterSpace, start), math.transform(worldToCasterSpace, start + castReverse)); + bool hit = SpatialInternal.RaycastRoundedBox(ray, boxToCast, targetSphere.radius, out var fraction, out _); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in boxToCast, in hitTransform, in targetSphere, in targetSphereTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in BoxCollider boxToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + if (DistanceBetween(in boxToCast, in castStart, in targetCapsule, in targetCapsuleTransform, 0f, out _)) + { + result = default; + return false; + } + + var castStartInverse = math.inverse(castStart); + var targetInCasterSpace = math.mul(castStartInverse, targetCapsuleTransform); + var inflatedBoxAabb = new Aabb(boxToCast.center - boxToCast.halfSize - targetCapsule.radius, boxToCast.center + boxToCast.halfSize + targetCapsule.radius); + + var targetA = math.transform(targetInCasterSpace, targetCapsule.pointA); + var rayA = new Ray(targetA, targetA - math.rotate(castStartInverse, castEnd - castStart.pos)); + bool hitA = SpatialInternal.RaycastAabb(rayA, inflatedBoxAabb, out var fractionA); + var hitpointA = math.lerp(rayA.start, rayA.end, fractionA) - boxToCast.center; + hitA &= math.countbits(math.bitmask(new bool4(math.abs(hitpointA) > boxToCast.halfSize, false))) == 1; + fractionA = math.select(2f, fractionA, hitA); + var targetB = math.transform(targetInCasterSpace, targetCapsule.pointB); + var rayB = new Ray(targetB, targetB - math.rotate(castStartInverse, castEnd - castStart.pos)); + bool hitB = SpatialInternal.RaycastAabb(rayB, inflatedBoxAabb, out var fractionB); + var hitpointB = math.lerp(rayB.start, rayB.end, fractionB) - boxToCast.center; + hitB &= math.countbits(math.bitmask(new bool4(math.abs(hitpointB) > boxToCast.halfSize, false))) == 1; + fractionB = math.select(2f, fractionB, hitB); + + var ray = new Ray(0f, math.rotate(castStartInverse, castStart.pos - castEnd)); + bool4 hitX; + float4 fractionsEdge; + float3 startA = boxToCast.center - boxToCast.halfSize; + float3 startB = boxToCast.center + new float3(boxToCast.halfSize.x, -boxToCast.halfSize.y, -boxToCast.halfSize.z); + simdFloat3 cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.x = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.x, out _); + startA = boxToCast.center + new float3(-boxToCast.halfSize.x, -boxToCast.halfSize.y, boxToCast.halfSize.z); + startB = boxToCast.center + new float3(boxToCast.halfSize.x, -boxToCast.halfSize.y, boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.y = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.y, out _); + startA = boxToCast.center + new float3(-boxToCast.halfSize.x, boxToCast.halfSize.y, -boxToCast.halfSize.z); + startB = boxToCast.center + new float3(boxToCast.halfSize.x, boxToCast.halfSize.y, -boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.z = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.z, out _); + startA = boxToCast.center + new float3(-boxToCast.halfSize.x, boxToCast.halfSize.y, boxToCast.halfSize.z); + startB = boxToCast.center + boxToCast.halfSize; + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitX.w = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.w, out _); + fractionsEdge = math.select(2f, fractionsEdge, hitX); + + bool4 hitY; + float4 fractionsY; + startA = boxToCast.center - boxToCast.halfSize; + startB = boxToCast.center + new float3(-boxToCast.halfSize.x, boxToCast.halfSize.y, -boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.x = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsY.x, out _); + startA = boxToCast.center + new float3(-boxToCast.halfSize.x, -boxToCast.halfSize.y, boxToCast.halfSize.z); + startB = boxToCast.center + new float3(-boxToCast.halfSize.x, boxToCast.halfSize.y, boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.y = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsY.y, out _); + startA = boxToCast.center + new float3(boxToCast.halfSize.x, -boxToCast.halfSize.y, -boxToCast.halfSize.z); + startB = boxToCast.center + new float3(boxToCast.halfSize.x, boxToCast.halfSize.y, -boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.z = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsY.z, out _); + startA = boxToCast.center + new float3(boxToCast.halfSize.x, -boxToCast.halfSize.y, boxToCast.halfSize.z); + startB = boxToCast.center + boxToCast.halfSize; + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitY.w = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsY.w, out _); + fractionsY = math.select(2f, fractionsY, hitY); + + bool4 hitZ; + float4 fractionsZ; + startA = boxToCast.center - boxToCast.halfSize; + startB = boxToCast.center + new float3(-boxToCast.halfSize.x, -boxToCast.halfSize.y, boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.x = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsZ.x, out _); + startA = boxToCast.center + new float3(-boxToCast.halfSize.x, boxToCast.halfSize.y, -boxToCast.halfSize.z); + startB = boxToCast.center + new float3(-boxToCast.halfSize.x, boxToCast.halfSize.y, boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.y = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsZ.y, out _); + startA = boxToCast.center + new float3(boxToCast.halfSize.x, -boxToCast.halfSize.y, -boxToCast.halfSize.z); + startB = boxToCast.center + new float3(boxToCast.halfSize.x, -boxToCast.halfSize.y, boxToCast.halfSize.z); + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.z = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsZ.z, out _); + startA = boxToCast.center + new float3(boxToCast.halfSize.x, boxToCast.halfSize.y, -boxToCast.halfSize.z); + startB = boxToCast.center + boxToCast.halfSize; + cso = new simdFloat3(startA - targetA, startA - targetB, startB - targetB, startB - targetA); + hitZ.w = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsZ.w, out _); + fractionsZ = math.select(2f, fractionsZ, hitZ); + + bool hit = math.any(hitX | hitY | hitZ) | hitA | hitB; + float fraction = math.min(math.min(fractionA, fractionB), math.cmin(math.min(fractionsEdge, math.min(fractionsY, fractionsZ)))); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in boxToCast, in hitTransform, in targetCapsule, in targetCapsuleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in BoxCollider boxToCast, in RigidTransform castStart, float3 castEnd, in BoxCollider targetBox, in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(boxToCast, in castStart, castEnd, targetBox, in targetBoxTransform, out result); + } + + public static bool ColliderCast(in BoxCollider boxToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(boxToCast, in castStart, castEnd, targetTriangle, in targetTriangleTransform, out result); + } + + public static bool ColliderCast(in BoxCollider boxToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(boxToCast, in castStart, castEnd, targetConvex, in targetConvexTransform, out result); + } + + public static bool ColliderCast(in BoxCollider boxToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in boxToCast, in castStart, in targetCompound, in targetCompoundTransform, 0f, out _)) + { + return false; + } + ref var blob = ref targetCompound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = targetCompound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= targetCompound.scale; + bool newHit = ColliderCast(in boxToCast, in castStart, castEnd, ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(targetCompoundTransform, blobTransform), + out var newResult); + + newResult.subColliderIndexOnTarget = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + #endregion + + #region Triangle + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + var castReverse = castStart.pos - castEnd; + var worldToCasterSpace = math.inverse(castStart); + var start = math.transform(targetSphereTransform, targetSphere.center); + var ray = new Ray(math.transform(worldToCasterSpace, start), math.transform(worldToCasterSpace, start + castReverse)); + bool hit = SpatialInternal.RaycastRoundedTriangle(ray, triangleToCast.AsSimdFloat3(), targetSphere.radius, out var fraction, out _); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in triangleToCast, in hitTransform, in targetSphere, in targetSphereTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + if (DistanceBetween(in triangleToCast, in castStart, in targetCapsule, in targetCapsuleTransform, 0f, out _)) + { + result = default; + return false; + } + + var castStartInverse = math.inverse(castStart); + var targetInCasterSpace = math.mul(castStartInverse, targetCapsuleTransform); + var triPoints = triangleToCast.AsSimdFloat3(); + + var targetA = math.transform(targetInCasterSpace, targetCapsule.pointA); + var rayA = new Ray(targetA, targetA - math.rotate(castStartInverse, castEnd - castStart.pos)); + bool hitA = SpatialInternal.RaycastRoundedTriangle(rayA, triPoints, targetCapsule.radius, out var fractionA, out _); + fractionA = math.select(2f, fractionA, hitA); + var targetB = math.transform(targetInCasterSpace, targetCapsule.pointB); + var rayB = new Ray(targetB, targetB - math.rotate(castStartInverse, castEnd - castStart.pos)); + bool hitB = SpatialInternal.RaycastRoundedTriangle(rayB, triPoints, targetCapsule.radius, out var fractionB, out _); + fractionB = math.select(2f, fractionB, hitB); + + var ray = new Ray(0f, math.rotate(castStartInverse, castStart.pos - castEnd)); + bool3 hitEdge; + float3 fractionsEdge; + simdFloat3 targetSimd = new simdFloat3(targetA, targetB, targetA, targetB); + simdFloat3 cso = triPoints.aabb - targetSimd; + hitEdge.x = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.x, out _); + cso = triPoints.bbcc - targetSimd; + hitEdge.y = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.y, out _); + cso = triPoints.ccaa - targetSimd; + hitEdge.z = SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out fractionsEdge.z, out _); + fractionsEdge = math.select(2f, fractionsEdge, hitEdge); + + bool hit = math.any(hitEdge) | hitA | hitB; + float fraction = math.min(math.min(fractionA, fractionB), math.cmin(fractionsEdge)); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in triangleToCast, in hitTransform, in targetCapsule, in targetCapsuleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in BoxCollider targetBox, + in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(triangleToCast, in castStart, castEnd, targetBox, in targetBoxTransform, out result); + } + + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(triangleToCast, in castStart, castEnd, targetTriangle, in targetTriangleTransform, out result); + } + + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(triangleToCast, in castStart, castEnd, targetConvex, in targetConvexTransform, out result); + } + + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in triangleToCast, in castStart, in targetCompound, in targetCompoundTransform, 0f, out _)) + { + return false; + } + ref var blob = ref targetCompound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = targetCompound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= targetCompound.scale; + bool newHit = ColliderCast(in triangleToCast, in castStart, castEnd, ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(targetCompoundTransform, blobTransform), + out var newResult); + + newResult.subColliderIndexOnTarget = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + #endregion + + #region Convex + public static bool ColliderCast(in ConvexCollider convexToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + var castReverse = castStart.pos - castEnd; + var worldToCasterSpace = math.inverse(castStart); + var start = math.transform(targetSphereTransform, targetSphere.center); + var ray = new Ray(math.transform(worldToCasterSpace, start), math.transform(worldToCasterSpace, start + castReverse)); + bool hit = SpatialInternal.RaycastRoundedConvex(ray, convexToCast, targetSphere.radius, out var fraction); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in convexToCast, in hitTransform, in targetSphere, in targetSphereTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + // Todo: Capsule + public static bool ColliderCast(in ConvexCollider convexToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + if (DistanceBetween(in convexToCast, in castStart, in targetCapsule, in targetCapsuleTransform, 0f, out _)) + { + result = default; + return false; + } + + var castStartInverse = math.inverse(castStart); + var targetInCasterSpace = math.mul(castStartInverse, targetCapsuleTransform); + + var targetA = math.transform(targetInCasterSpace, targetCapsule.pointA); + var rayA = new Ray(targetA, targetA - math.rotate(castStartInverse, castEnd - castStart.pos)); + bool hitA = SpatialInternal.RaycastRoundedConvex(rayA, convexToCast, targetCapsule.radius, out var fractionA); + fractionA = math.select(2f, fractionA, hitA); + var targetB = math.transform(targetInCasterSpace, targetCapsule.pointB); + var rayB = new Ray(targetB, targetB - math.rotate(castStartInverse, castEnd - castStart.pos)); + bool hitB = SpatialInternal.RaycastRoundedConvex(rayB, convexToCast, targetCapsule.radius, out var fractionB); + fractionB = math.select(2f, fractionB, hitB); + + var ray = new Ray(0f, math.rotate(castStartInverse, castStart.pos - castEnd)); + simdFloat3 targetSimd = new simdFloat3(targetA, targetB, targetB, targetA); + int bestEdgeIndex = -1; + float bestFraction = 2f; + ref var blob = ref convexToCast.convexColliderBlob.Value; + + var capEdge = targetB - targetA; + var capEdgeCrossRay = math.normalizesafe(math.cross(capEdge, ray.displacement), float3.zero); + if (capEdgeCrossRay.Equals(float3.zero)) + { + // The capsule aligns with the ray. We already have this case tested. + } + else + { + // We need four culling planes around the capsule + var correctedCapEdge = math.normalize(math.cross(ray.displacement, capEdgeCrossRay)); + var ta = math.select(targetB, targetA, math.dot(capEdgeCrossRay, targetA) >= math.dot(capEdgeCrossRay, targetB)); + var tb = math.select(targetB, targetA, math.dot(-capEdgeCrossRay, targetA) >= math.dot(-capEdgeCrossRay, targetB)); + var tc = math.select(targetB, targetA, math.dot(correctedCapEdge, targetA) >= math.dot(correctedCapEdge, targetB)); + var td = math.select(targetB, targetA, math.dot(-correctedCapEdge, targetA) >= math.dot(-correctedCapEdge, targetB)); + Plane planeA = new Plane(capEdgeCrossRay, -math.dot(capEdgeCrossRay, ta + capEdgeCrossRay * targetCapsule.radius)); + Plane planeB = new Plane(-capEdgeCrossRay, -math.dot(-capEdgeCrossRay, tb - capEdgeCrossRay * targetCapsule.radius)); + Plane planeC = new Plane(correctedCapEdge, -math.dot(correctedCapEdge, tc + correctedCapEdge * targetCapsule.radius)); + Plane planeD = new Plane(-correctedCapEdge, -math.dot(-correctedCapEdge, td - correctedCapEdge * targetCapsule.radius)); + float4 obbX = new float4(planeA.normal.x, planeB.normal.x, planeC.normal.x, planeD.normal.x); + float4 obbY = new float4(planeA.normal.y, planeB.normal.y, planeC.normal.y, planeD.normal.y); + float4 obbZ = new float4(planeA.normal.z, planeB.normal.z, planeC.normal.z, planeD.normal.z); + float4 obbD = new float4(planeA.distanceFromOrigin, planeB.distanceFromOrigin, planeC.distanceFromOrigin, planeD.distanceFromOrigin); + + for (int i = 0; i < blob.vertexIndicesInEdges.Length; i++) + { + var indices = blob.vertexIndicesInEdges[i]; + var ax = blob.verticesX[indices.x] * convexToCast.scale.x; + var ay = blob.verticesY[indices.x] * convexToCast.scale.y; + var az = blob.verticesZ[indices.x] * convexToCast.scale.z; + var bx = blob.verticesX[indices.y] * convexToCast.scale.x; + var by = blob.verticesY[indices.y] * convexToCast.scale.y; + var bz = blob.verticesZ[indices.y] * convexToCast.scale.z; + + bool4 isAOutside = obbX * ax + obbY * ay + obbZ * az + obbD > 0f; + bool4 isBOutside = obbX * bx + obbY * by + obbZ * bz + obbD > 0f; + if (math.any(isAOutside & isBOutside)) + continue; + + var edgePoints = new simdFloat3(new float4(ax, ax, bx, bx), new float4(ay, ay, by, by), new float4(az, az, bz, bz)); + var cso = edgePoints - targetSimd; + if (SpatialInternal.RaycastRoundedQuad(ray, cso, targetCapsule.radius, out var edgeFraction, out _)) + { + if (edgeFraction < bestFraction) + { + bestFraction = edgeFraction; + bestEdgeIndex = i; + } + } + } + } + + bool hit = (bestEdgeIndex > 0) | hitA | hitB; + float fraction = math.min(math.min(fractionA, fractionB), bestFraction); + if (hit) + { + var hitTransform = castStart; + hitTransform.pos = math.lerp(castStart.pos, castEnd, fraction); + DistanceBetween(in convexToCast, in hitTransform, in targetCapsule, in targetCapsuleTransform, 1f, out var distanceResult); + result = new ColliderCastResult + { + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = distanceResult.subColliderIndexA, + subColliderIndexOnTarget = distanceResult.subColliderIndexB, + distance = math.distance(hitTransform.pos, castStart.pos) + }; + return true; + } + result = default; + return false; + } + + public static bool ColliderCast(in ConvexCollider convexToCast, in RigidTransform castStart, float3 castEnd, in BoxCollider targetBox, in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(convexToCast, in castStart, castEnd, targetBox, in targetBoxTransform, out result); + } + + public static bool ColliderCast(in ConvexCollider convexToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(convexToCast, in castStart, castEnd, targetTriangle, in targetTriangleTransform, out result); + } + + public static bool ColliderCast(in ConvexCollider convexToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + return ColliderCastMpr(convexToCast, in castStart, castEnd, targetConvex, in targetConvexTransform, out result); + } + + public static bool ColliderCast(in ConvexCollider convexToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in convexToCast, in castStart, in targetCompound, in targetCompoundTransform, 0f, out _)) + { + return false; + } + ref var blob = ref targetCompound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = targetCompound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= targetCompound.scale; + bool newHit = ColliderCast(in convexToCast, in castStart, castEnd, ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(targetCompoundTransform, blobTransform), + out var newResult); + + newResult.subColliderIndexOnTarget = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + #endregion + + #region Compound + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in compoundToCast, in castStart, in targetSphere, in targetSphereTransform, 0f, out _)) + { + return false; + } + ref var blob = ref compoundToCast.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundToCast.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundToCast.scale; + var start = math.mul(castStart, blobTransform); + bool newHit = ColliderCast(ScaleCollider(in blob.colliders[i], compoundScale), + start, start.pos + (castEnd - castStart.pos), + in targetSphere, + in targetSphereTransform, + out var newResult); + + newResult.subColliderIndexOnCaster = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in compoundToCast, in castStart, in targetCapsule, in targetCapsuleTransform, 0f, out _)) + { + return false; + } + ref var blob = ref compoundToCast.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundToCast.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundToCast.scale; + var start = math.mul(castStart, blobTransform); + bool newHit = ColliderCast(ScaleCollider(in blob.colliders[i], compoundScale), + start, start.pos + (castEnd - castStart.pos), + in targetCapsule, + in targetCapsuleTransform, + out var newResult); + + newResult.subColliderIndexOnCaster = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in BoxCollider targetBox, + in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in compoundToCast, in castStart, in targetBox, in targetBoxTransform, 0f, out _)) + { + return false; + } + ref var blob = ref compoundToCast.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundToCast.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundToCast.scale; + var start = math.mul(castStart, blobTransform); + bool newHit = ColliderCast(ScaleCollider(in blob.colliders[i], compoundScale), + start, start.pos + (castEnd - castStart.pos), + in targetBox, + in targetBoxTransform, + out var newResult); + + newResult.subColliderIndexOnCaster = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in compoundToCast, in castStart, in targetTriangle, in targetTriangleTransform, 0f, out _)) + { + return false; + } + ref var blob = ref compoundToCast.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundToCast.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundToCast.scale; + var start = math.mul(castStart, blobTransform); + bool newHit = ColliderCast(ScaleCollider(in blob.colliders[i], compoundScale), + start, start.pos + (castEnd - castStart.pos), + in targetTriangle, + in targetTriangleTransform, + out var newResult); + + newResult.subColliderIndexOnCaster = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in compoundToCast, in castStart, in targetConvex, in targetConvexTransform, 0f, out _)) + { + return false; + } + ref var blob = ref compoundToCast.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundToCast.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundToCast.scale; + var start = math.mul(castStart, blobTransform); + bool newHit = ColliderCast(ScaleCollider(in blob.colliders[i], compoundScale), + start, start.pos + (castEnd - castStart.pos), + in targetConvex, + in targetConvexTransform, + out var newResult); + + newResult.subColliderIndexOnCaster = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + if (DistanceBetween(in compoundToCast, in castStart, in targetCompound, in targetCompoundTransform, 0f, out _)) + { + return false; + } + ref var blob = ref compoundToCast.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundToCast.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundToCast.scale; + var start = math.mul(castStart, blobTransform); + bool newHit = ColliderCast(ScaleCollider(in blob.colliders[i], compoundScale), + start, start.pos + (castEnd - castStart.pos), + in targetCompound, + in targetCompoundTransform, + out var newResult); + + newResult.subColliderIndexOnCaster = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + #endregion + + #region Layer + + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in CollisionLayer layer, + out ColliderCastResult result, + out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.ColliderCastClosestImmediateProcessor(colliderToCast, castStart, castEnd, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(in colliderToCast, in castStart, castEnd), layer, processor).RunImmediate(); + var hit = result.subColliderIndexOnTarget >= 0; + result.subColliderIndexOnTarget = math.max(result.subColliderIndexOnTarget, 0); + return hit; + } + + public static bool ColliderCastAny(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in CollisionLayer layer, + out ColliderCastResult result, + out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.ColliderCastAnyImmediateProcessor(colliderToCast, castStart, castEnd, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(in colliderToCast, in castStart, castEnd), layer, processor).RunImmediate(); + var hit = result.subColliderIndexOnTarget >= 0; + result.subColliderIndexOnTarget = math.max(result.subColliderIndexOnTarget, 0); + return hit; + } + + #endregion + + #region Internal + internal static bool ColliderCastMpr(in Collider colliderToCast, in RigidTransform castStart, float3 castEnd, in Collider targetCollider, in RigidTransform targetTransform, + out ColliderCastResult result) + { + var castStartInverse = math.inverse(castStart); + var targetInCasterSpaceTransform = math.mul(castStartInverse, targetTransform); + var castDirection = math.rotate(castStartInverse, castEnd - castStart.pos); + var normalizedCastDirection = math.normalize(castDirection); + bool hit = SpatialInternal.MprCastNoRoundness(in colliderToCast, + in targetCollider, + in targetInCasterSpaceTransform, + normalizedCastDirection, + math.length(castDirection), + out float distanceOfImpact, + out bool somethingWentWrong); + CheckMprResolved(somethingWentWrong); + if (!hit || distanceOfImpact <= 0f) + { + result = default; + return false; + } + + var castHitOffset = math.rotate(castStart, normalizedCastDirection * distanceOfImpact); + var casterHitTransform = castStart; + casterHitTransform.pos += castHitOffset; + DistanceBetween(in colliderToCast, in casterHitTransform, in targetCollider, in targetTransform, float.MaxValue, out var distanceResult); + + result = new ColliderCastResult + { + distance = distanceOfImpact, + hitpointOnCaster = distanceResult.hitpointA, + hitpointOnTarget = distanceResult.hitpointB, + normalOnCaster = distanceResult.normalA, + normalOnTarget = distanceResult.normalB, + subColliderIndexOnCaster = 0, + subColliderIndexOnTarget = 0 + }; + + return true; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + internal static void CheckMprResolved(bool somethingWentWrong) + { + if (somethingWentWrong) + UnityEngine.Debug.LogWarning("MPR failed to resolve within the allotted number of iterations. If you see this, please report a bug."); + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCast.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCast.cs.meta new file mode 100644 index 0000000..f4834c6 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCast.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da268d7c007236c4aab6efd9ea88e60a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.gen.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.gen.cs new file mode 100644 index 0000000..3007947 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.gen.cs @@ -0,0 +1,521 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Physics/Utilities/Physics.ColliderCastDispatch.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in SphereCollider targetSphere, + in RigidTransform targetSphereTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(colliderToCast.m_sphere, in castStart, castEnd, in targetSphere, in targetSphereTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(colliderToCast.m_capsule, in castStart, castEnd, in targetSphere, in targetSphereTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(colliderToCast.m_box, in castStart, castEnd, in targetSphere, in targetSphereTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(colliderToCast.m_triangle, in castStart, castEnd, in targetSphere, in targetSphereTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(colliderToCast.m_convex, in castStart, castEnd, in targetSphere, in targetSphereTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(colliderToCast.m_compound, in castStart, castEnd, in targetSphere, in targetSphereTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in SphereCollider sphereToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (targetCollider.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in sphereToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in sphereToCast, in castStart, castEnd, in targetCollider.m_capsule, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in sphereToCast, in castStart, castEnd, in targetCollider.m_box, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in sphereToCast, in castStart, castEnd, in targetCollider.m_triangle, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in sphereToCast, in castStart, castEnd, in targetCollider.m_convex, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in sphereToCast, in castStart, castEnd, in targetCollider.m_compound, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in CapsuleCollider targetCapsule, + in RigidTransform targetCapsuleTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in colliderToCast.m_sphere, in castStart, castEnd, in targetCapsule, in targetCapsuleTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in colliderToCast.m_capsule, in castStart, castEnd, in targetCapsule, in targetCapsuleTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in colliderToCast.m_box, in castStart, castEnd, in targetCapsule, in targetCapsuleTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in colliderToCast.m_triangle, in castStart, castEnd, in targetCapsule, in targetCapsuleTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in colliderToCast.m_convex, in castStart, castEnd, in targetCapsule, in targetCapsuleTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in colliderToCast.m_compound, in castStart, castEnd, in targetCapsule, in targetCapsuleTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in CapsuleCollider capsuleToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (targetCollider.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in capsuleToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in capsuleToCast, in castStart, castEnd, in targetCollider.m_capsule, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in capsuleToCast, in castStart, castEnd, in targetCollider.m_box, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in capsuleToCast, in castStart, castEnd, in targetCollider.m_triangle, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in capsuleToCast, in castStart, castEnd, in targetCollider.m_convex, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in capsuleToCast, in castStart, castEnd, in targetCollider.m_compound, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in BoxCollider targetBox, + in RigidTransform targetBoxTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in colliderToCast.m_sphere, in castStart, castEnd, in targetBox, in targetBoxTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in colliderToCast.m_capsule, in castStart, castEnd, in targetBox, in targetBoxTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in colliderToCast.m_box, in castStart, castEnd, in targetBox, in targetBoxTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in colliderToCast.m_triangle, in castStart, castEnd, in targetBox, in targetBoxTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in colliderToCast.m_convex, in castStart, castEnd, in targetBox, in targetBoxTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in colliderToCast.m_compound, in castStart, castEnd, in targetBox, in targetBoxTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in BoxCollider boxToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (targetCollider.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in boxToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in boxToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in boxToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in boxToCast, castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in boxToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in boxToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in TriangleCollider targetTriangle, + in RigidTransform targetTriangleTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in colliderToCast.m_sphere, in castStart, castEnd, in targetTriangle, in targetTriangleTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in colliderToCast.m_capsule, in castStart, castEnd, in targetTriangle, in targetTriangleTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in colliderToCast.m_box, in castStart, castEnd, in targetTriangle, in targetTriangleTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in colliderToCast.m_triangle, in castStart, castEnd, in targetTriangle, in targetTriangleTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in colliderToCast.m_convex, in castStart, castEnd, in targetTriangle, in targetTriangleTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in colliderToCast.m_compound, in castStart, castEnd, in targetTriangle, in targetTriangleTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in TriangleCollider triangleToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (targetCollider.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in triangleToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in triangleToCast, in castStart, castEnd, in targetCollider.m_capsule, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in triangleToCast, in castStart, castEnd, in targetCollider.m_box, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in triangleToCast, in castStart, castEnd, in targetCollider.m_triangle, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in triangleToCast, in castStart, castEnd, in targetCollider.m_convex, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in triangleToCast, in castStart, castEnd, in targetCollider.m_compound, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in ConvexCollider targetConvex, + in RigidTransform targetConvexTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in colliderToCast.m_sphere, in castStart, castEnd, in targetConvex, in targetConvexTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in colliderToCast.m_capsule, in castStart, castEnd, in targetConvex, in targetConvexTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in colliderToCast.m_box, in castStart, castEnd, in targetConvex, in targetConvexTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in colliderToCast.m_triangle, in castStart, castEnd, in targetConvex, in targetConvexTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in colliderToCast.m_convex, in castStart, castEnd, in targetConvex, in targetConvexTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in colliderToCast.m_compound, in castStart, castEnd, in targetConvex, in targetConvexTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in ConvexCollider convexToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (targetCollider.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in convexToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in convexToCast, in castStart, castEnd, in targetCollider.m_capsule, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in convexToCast, in castStart, castEnd, in targetCollider.m_box, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in convexToCast, in castStart, castEnd, in targetCollider.m_triangle, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in convexToCast, in castStart, castEnd, in targetCollider.m_convex, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in convexToCast, in castStart, castEnd, in targetCollider.m_compound, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in CompoundCollider targetCompound, + in RigidTransform targetCompoundTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in colliderToCast.m_sphere, in castStart, castEnd, in targetCompound, in targetCompoundTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in colliderToCast.m_capsule, in castStart, castEnd, in targetCompound, in targetCompoundTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in colliderToCast.m_box, in castStart, castEnd, in targetCompound, in targetCompoundTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in colliderToCast.m_triangle, in castStart, castEnd, in targetCompound, in targetCompoundTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in colliderToCast.m_convex, in castStart, castEnd, in targetCompound, in targetCompoundTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in colliderToCast.m_compound, in castStart, castEnd, in targetCompound, in targetCompoundTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in CompoundCollider compoundToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (targetCollider.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in compoundToCast, in castStart, castEnd, in targetCollider.m_sphere, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in compoundToCast, in castStart, castEnd, in targetCollider.m_capsule, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in compoundToCast, in castStart, castEnd, in targetCollider.m_box, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in compoundToCast, in castStart, castEnd, in targetCollider.m_triangle, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in compoundToCast, in castStart, castEnd, in targetCollider.m_convex, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in compoundToCast, in castStart, castEnd, in targetCollider.m_compound, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + + public static bool ColliderCast(in Collider colliderToCast, + in RigidTransform castStart, + float3 castEnd, + in Collider targetCollider, + in RigidTransform targetTransform, + out ColliderCastResult result) + { + switch (colliderToCast.type) + { + case ColliderType.Sphere: + { + return ColliderCast(in colliderToCast.m_sphere, in castStart, castEnd, in targetCollider, in targetTransform, out result); + } + case ColliderType.Capsule: + { + return ColliderCast(in colliderToCast.m_capsule, in castStart, castEnd, in targetCollider, in targetTransform, out result); + } + case ColliderType.Box: + { + return ColliderCast(in colliderToCast.m_box, in castStart, castEnd, in targetCollider, in targetTransform, out result); + } + case ColliderType.Triangle: + { + return ColliderCast(in colliderToCast.m_triangle, in castStart, castEnd, in targetCollider, in targetTransform, out result); + } + case ColliderType.Convex: + { + return ColliderCast(in colliderToCast.m_convex, in castStart, castEnd, in targetCollider, in targetTransform, out result); + } + case ColliderType.Compound: + { + return ColliderCast(in colliderToCast.m_compound, in castStart, castEnd, in targetCollider, in targetTransform, out result); + } + default: + result = default; + return false; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.gen.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.gen.cs.meta new file mode 100644 index 0000000..f74a92a --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e0fe5bd2d03e264bbf2fa48bb571029 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.tt b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.tt new file mode 100644 index 0000000..53680e5 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.tt @@ -0,0 +1,113 @@ +<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ output extension=".gen.cs" #> +<#@ assembly name="System.Collections" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Collections.Generic" #> +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Physics/Utilities/Physics.ColliderCastDispatch.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { +<# +{ + var allTypes = new string[] + { + "Sphere", + "Capsule", + "Box", + "Triangle", + //"Quad", + "Convex", + "Compound" + }; + + foreach (var type in allTypes) + { + string lowerType = type.ToLower(); +#> + public static bool ColliderCast(Collider colliderToCast, RigidTransform castStart, float3 castEnd, <#=type#>Collider target<#=type#>, RigidTransform target<#=type#>Transform, out ColliderCastResult result) + { + switch (colliderToCast.type) + { +<# + foreach (var type2 in allTypes) + { +#> + case ColliderType.<#=type2#>: + { + <#=type2#>Collider col = colliderToCast; + return ColliderCast(col, castStart, castEnd, target<#=type#>, target<#=type#>Transform, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } + + public static bool ColliderCast(<#=type#>Collider <#=lowerType#>ToCast, RigidTransform castStart, float3 castEnd, Collider targetCollider, RigidTransform targetTransform, out ColliderCastResult result) + { + switch (targetCollider.type) + { +<# + foreach (var type2 in allTypes) + { +#> + case ColliderType.<#=type2#>: + { + <#=type2#>Collider col = targetCollider; + return ColliderCast(<#=lowerType#>ToCast, castStart, castEnd, col, targetTransform, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } +<# + } +#> + + public static bool ColliderCast(Collider colliderToCast, RigidTransform castStart, float3 castEnd, Collider targetCollider, RigidTransform targetTransform, out ColliderCastResult result) + { + switch (colliderToCast.type) + { +<# + foreach (var type in allTypes) + { +#> + case ColliderType.<#=type#>: + { + <#=type#>Collider colA = colliderToCast; + return ColliderCast(colA, castStart, castEnd, targetCollider, targetTransform, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } +<# +} +#> + } +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.tt.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.tt.meta new file mode 100644 index 0000000..8086398 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.ColliderCastDispatch.tt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 716dc11d52cb8fe44be9ca072744ec20 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetween.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetween.cs new file mode 100644 index 0000000..4e76b28 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetween.cs @@ -0,0 +1,774 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + #region Sphere + public static bool DistanceBetween(in SphereCollider sphereA, + in RigidTransform aTransform, + in SphereCollider sphereB, + in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var aWorldToLocal = math.inverse(aTransform); + var bInASpaceTransform = math.mul(aWorldToLocal, bTransform); + SphereCollider bInASpace = new SphereCollider(math.transform(bInASpaceTransform, sphereB.center), sphereB.radius); + bool hit = + SpatialInternal.SphereSphereDistance(sphereA, bInASpace, maxDistance, out SpatialInternal.ColliderDistanceResultInternal localResult); + result = new ColliderDistanceResult + { + hitpointA = math.transform(aTransform, localResult.hitpointA), + hitpointB = math.transform(aTransform, localResult.hitpointB), + normalA = math.rotate(aTransform, localResult.normalA), + normalB = math.rotate(aTransform, localResult.normalB), + distance = localResult.distance + }; + return hit; + } + #endregion + + #region Capsule + public static bool DistanceBetween(in CapsuleCollider capsule, + in RigidTransform capsuleTransform, + in SphereCollider sphere, + in RigidTransform sphereTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var capWorldToLocal = math.inverse(capsuleTransform); + var sphereInCapSpaceTransfrom = math.mul(capWorldToLocal, sphereTransform); + float3 sphereCenterInCapSpace = math.transform(sphereInCapSpaceTransfrom, sphere.center); + SphereCollider sphereInCapSpace = new SphereCollider(sphereCenterInCapSpace, sphere.radius); + bool hit = SpatialInternal.CapsuleSphereDistance(capsule, + sphereInCapSpace, + maxDistance, + out SpatialInternal.ColliderDistanceResultInternal localResult); + result = new ColliderDistanceResult + { + hitpointA = math.transform(capsuleTransform, localResult.hitpointA), + hitpointB = math.transform(capsuleTransform, localResult.hitpointB), + normalA = math.rotate(capsuleTransform, localResult.normalA), + normalB = math.rotate(capsuleTransform, localResult.normalB), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(in SphereCollider sphere, + in RigidTransform sphereTransform, + in CapsuleCollider capsule, + in RigidTransform capsuleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in capsule, in capsuleTransform, in sphere, in sphereTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in CapsuleCollider capsuleA, + in RigidTransform aTransform, + in CapsuleCollider capsuleB, + in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var aWorldToLocal = math.inverse(aTransform); + var BinASpaceTransform = math.mul(aWorldToLocal, bTransform); + CapsuleCollider BinASpace = new CapsuleCollider(math.transform(BinASpaceTransform, capsuleB.pointA), + math.transform(BinASpaceTransform, capsuleB.pointB), + capsuleB.radius); + bool hit = SpatialInternal.CapsuleCapsuleDistance(capsuleA, BinASpace, maxDistance, out SpatialInternal.ColliderDistanceResultInternal localResult); + result = BinAResultToWorld(in localResult, in aTransform); + return hit; + } + #endregion + + #region Box + public static bool DistanceBetween(in BoxCollider box, + in RigidTransform boxTransform, + in SphereCollider sphere, + in RigidTransform sphereTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var boxWorldToLocal = math.inverse(boxTransform); + var sphereInBoxSpaceTransform = math.mul(boxWorldToLocal, sphereTransform); + float3 sphereCenterInBoxSpace = math.transform(sphereInBoxSpaceTransform, sphere.center); + SphereCollider sphereInBoxSpace = new SphereCollider(sphereCenterInBoxSpace, sphere.radius); + bool hit = SpatialInternal.BoxSphereDistance(box, + sphereInBoxSpace, + maxDistance, + out SpatialInternal.ColliderDistanceResultInternal localResult); + result = new ColliderDistanceResult + { + hitpointA = math.transform(boxTransform, localResult.hitpointA), + hitpointB = math.transform(boxTransform, localResult.hitpointB), + normalA = math.rotate(boxTransform, localResult.normalA), + normalB = math.rotate(boxTransform, localResult.normalB), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(in SphereCollider sphere, + in RigidTransform sphereTransform, + in BoxCollider box, + in RigidTransform boxTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in box, in boxTransform, in sphere, in sphereTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in BoxCollider box, + in RigidTransform boxTransform, + in CapsuleCollider capsule, + in RigidTransform capsuleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var boxWorldToLocal = math.inverse(boxTransform); + var capInBoxSpaceTransform = math.mul(boxWorldToLocal, capsuleTransform); + var capsuleInBoxSpace = new CapsuleCollider(math.transform(capInBoxSpaceTransform, capsule.pointA), + math.transform(capInBoxSpaceTransform, capsule.pointB), + capsule.radius); + bool hit = SpatialInternal.BoxCapsuleDistance(box, capsuleInBoxSpace, maxDistance, out SpatialInternal.ColliderDistanceResultInternal localResult); + result = BinAResultToWorld(in localResult, in boxTransform); + + return hit; + } + + public static bool DistanceBetween(in CapsuleCollider capsule, + in RigidTransform capsuleTransform, + in BoxCollider box, + in RigidTransform boxTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in box, in boxTransform, in capsule, in capsuleTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in BoxCollider boxA, + in RigidTransform aTransform, + in BoxCollider boxB, + in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var aWorldToLocal = math.inverse(aTransform); + var bWorldToLocal = math.inverse(bTransform); + var bInASpaceTransform = math.mul(aWorldToLocal, bTransform); + var aInBSpaceTransform = math.mul(bWorldToLocal, aTransform); + var hit = SpatialInternal.BoxBoxDistance(boxA, + boxB, + bInASpaceTransform, + aInBSpaceTransform, + maxDistance, + out SpatialInternal.ColliderDistanceResultInternal localResult); + result = BinAResultToWorld(in localResult, in aTransform); + return hit; + } + #endregion + + #region Triangle + public static bool DistanceBetween(in TriangleCollider triangle, + in RigidTransform triangleTransform, + in SphereCollider sphere, + in RigidTransform sphereTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var triangleWorldToLocal = math.inverse(triangleTransform); + var sphereInTriangleSpaceTransform = math.mul(triangleWorldToLocal, sphereTransform); + float3 sphereCenterInTriangleSpace = math.transform(sphereInTriangleSpaceTransform, sphere.center); + SphereCollider sphereInTriangleSpace = new SphereCollider(sphereCenterInTriangleSpace, sphere.radius); + bool hit = SpatialInternal.TriangleSphereDistance(triangle, + sphereInTriangleSpace, + maxDistance, + out SpatialInternal.ColliderDistanceResultInternal localResult); + result = new ColliderDistanceResult + { + hitpointA = math.transform(triangleTransform, localResult.hitpointA), + hitpointB = math.transform(triangleTransform, localResult.hitpointB), + normalA = math.rotate(triangleTransform, localResult.normalA), + normalB = math.rotate(triangleTransform, localResult.normalB), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(in SphereCollider sphere, + in RigidTransform sphereTransform, + in TriangleCollider triangle, + in RigidTransform triangleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in triangle, in triangleTransform, in sphere, in sphereTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in TriangleCollider triangle, in RigidTransform triangleTransform, in CapsuleCollider capsule, in RigidTransform capsuleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var triangleWorldToLocal = math.inverse(triangleTransform); + var capInTriangleSpaceTransform = math.mul(triangleWorldToLocal, capsuleTransform); + var capsuleInTriangleSpace = new CapsuleCollider(math.transform(capInTriangleSpaceTransform, capsule.pointA), + math.transform(capInTriangleSpaceTransform, capsule.pointB), + capsule.radius); + bool hit = SpatialInternal.TriangleCapsuleDistance(triangle, capsuleInTriangleSpace, maxDistance, out SpatialInternal.ColliderDistanceResultInternal localResult); + result = BinAResultToWorld(in localResult, in triangleTransform); + + return hit; + } + + public static bool DistanceBetween(in CapsuleCollider capsule, in RigidTransform capsuleTransform, in TriangleCollider triangle, in RigidTransform triangleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in triangle, in triangleTransform, in capsule, in capsuleTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in TriangleCollider triangle, in RigidTransform triangleTransform, in BoxCollider box, in RigidTransform boxTransform, + float maxDistance, + out ColliderDistanceResult result) + { + // Todo: SAT algorithm similar to box vs box. + result = DistanceBetweenGjk(triangle, in triangleTransform, box, in boxTransform); + return result.distance <= maxDistance; + } + + public static bool DistanceBetween(in BoxCollider box, in RigidTransform boxTransform, in TriangleCollider triangle, in RigidTransform triangleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in triangle, in triangleTransform, in box, in boxTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in TriangleCollider triangleA, in RigidTransform aTransform, in TriangleCollider triangleB, in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + result = DistanceBetweenGjk(triangleA, in aTransform, triangleB, in bTransform); + return result.distance <= maxDistance; + } + #endregion + + #region Convex + public static bool DistanceBetween(in ConvexCollider convex, in RigidTransform convexTransform, in SphereCollider sphere, in RigidTransform sphereTransform, + float maxDistance, + out ColliderDistanceResult result) + { + var convexWorldToLocal = math.inverse(convexTransform); + var sphereInConvexSpaceTransform = math.mul(convexWorldToLocal, sphereTransform); + float3 sphereCenterInConvexSpace = math.transform(sphereInConvexSpaceTransform, sphere.center); + SphereCollider sphereInConvexSpace = new SphereCollider(sphereCenterInConvexSpace, sphere.radius); + bool hit = SpatialInternal.ConvexSphereDistance(convex, + sphereInConvexSpace, + maxDistance, + out SpatialInternal.ColliderDistanceResultInternal localResult); + result = new ColliderDistanceResult + { + hitpointA = math.transform(convexTransform, localResult.hitpointA), + hitpointB = math.transform(convexTransform, localResult.hitpointB), + normalA = math.rotate(convexTransform, localResult.normalA), + normalB = math.rotate(convexTransform, localResult.normalB), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(in SphereCollider sphere, in RigidTransform sphereTransform, in ConvexCollider convex, in RigidTransform convexTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in convex, in convexTransform, in sphere, in sphereTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in ConvexCollider convex, in RigidTransform convexTransform, in CapsuleCollider capsule, in RigidTransform capsuleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + result = DistanceBetweenGjk(convex, in convexTransform, capsule, in capsuleTransform); + return result.distance <= maxDistance; + } + + public static bool DistanceBetween(in CapsuleCollider capsule, in RigidTransform capsuleTransform, in ConvexCollider convex, in RigidTransform convexTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in convex, in convexTransform, in capsule, in capsuleTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in ConvexCollider convex, in RigidTransform convexTransform, in BoxCollider box, in RigidTransform boxTransform, + float maxDistance, + out ColliderDistanceResult result) + { + result = DistanceBetweenGjk(convex, in convexTransform, box, in boxTransform); + return result.distance <= maxDistance; + } + + public static bool DistanceBetween(in BoxCollider box, in RigidTransform boxTransform, in ConvexCollider convex, in RigidTransform convexTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in convex, in convexTransform, in box, in boxTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in ConvexCollider convex, in RigidTransform convexTransform, in TriangleCollider triangle, in RigidTransform triangleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + result = DistanceBetweenGjk(convex, in convexTransform, triangle, in triangleTransform); + return result.distance <= maxDistance; + } + + public static bool DistanceBetween(in TriangleCollider triangle, in RigidTransform triangleTransform, in ConvexCollider convex, in RigidTransform convexTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in convex, in convexTransform, in triangle, in triangleTransform, maxDistance, out ColliderDistanceResult flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in ConvexCollider convexA, in RigidTransform aTransform, in ConvexCollider convexB, in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + result = DistanceBetweenGjk(convexA, in aTransform, convexB, in bTransform); + return result.distance <= maxDistance; + } + #endregion + + #region Compound + public static bool DistanceBetween(in CompoundCollider compound, in RigidTransform compoundTransform, in SphereCollider sphere, in RigidTransform sphereTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compound.scale; + bool newHit = DistanceBetween(ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(compoundTransform, blobTransform), + in sphere, + in sphereTransform, + math.min(result.distance, maxDistance), + out var newResult); + + newResult.subColliderIndexA = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool DistanceBetween(in SphereCollider sphere, in RigidTransform sphereTransform, in CompoundCollider compound, in RigidTransform compoundTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in compound, in compoundTransform, in sphere, in sphereTransform, maxDistance, out var flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in CompoundCollider compound, in RigidTransform compoundTransform, in CapsuleCollider capsule, in RigidTransform capsuleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compound.scale; + bool newHit = DistanceBetween(ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(compoundTransform, blobTransform), + in capsule, + in capsuleTransform, + math.min(result.distance, maxDistance), + out var newResult); + + newResult.subColliderIndexA = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool DistanceBetween(in CapsuleCollider capsule, in RigidTransform capsuleTransform, in CompoundCollider compound, in RigidTransform compoundTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in compound, in compoundTransform, in capsule, in capsuleTransform, maxDistance, out var flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in CompoundCollider compound, in RigidTransform compoundTransform, in BoxCollider box, in RigidTransform boxTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compound.scale; + bool newHit = DistanceBetween(ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(compoundTransform, blobTransform), + in box, + in boxTransform, + maxDistance, + out var newResult); + + newResult.subColliderIndexA = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool DistanceBetween(in BoxCollider box, in RigidTransform boxTransform, in CompoundCollider compound, in RigidTransform compoundTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in compound, in compoundTransform, in box, in boxTransform, maxDistance, out var flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in CompoundCollider compound, in RigidTransform compoundTransform, in TriangleCollider triangle, in RigidTransform triangleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compound.scale; + bool newHit = DistanceBetween(ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(compoundTransform, blobTransform), + in triangle, + in triangleTransform, + maxDistance, + out var newResult); + + newResult.subColliderIndexA = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool DistanceBetween(in TriangleCollider triangle, in RigidTransform triangleTransform, in CompoundCollider compound, in RigidTransform compoundTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in compound, in compoundTransform, in triangle, in triangleTransform, maxDistance, out var flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in CompoundCollider compound, in RigidTransform compoundTransform, in ConvexCollider convex, in RigidTransform convexTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compound.scale; + bool newHit = DistanceBetween(ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(compoundTransform, blobTransform), + in convex, + in convexTransform, + maxDistance, + out var newResult); + + newResult.subColliderIndexA = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool DistanceBetween(in ConvexCollider convex, in RigidTransform convexTransform, in CompoundCollider compound, in RigidTransform compoundTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = DistanceBetween(in compound, in compoundTransform, in convex, in convexTransform, maxDistance, out var flipResult); + result = FlipResult(in flipResult); + return hit; + } + + public static bool DistanceBetween(in CompoundCollider compoundA, in RigidTransform aTransform, in CompoundCollider compoundB, in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compoundA.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compoundA.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compoundA.scale; + bool newHit = DistanceBetween(ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(aTransform, blobTransform), + in compoundB, + in bTransform, + math.min(result.distance, maxDistance), + out var newResult); + + newResult.subColliderIndexA = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + #endregion + + public static bool DistanceBetween(Collider collider, + RigidTransform transform, + CollisionLayer layer, + float maxDistance, + out ColliderDistanceResult result, + out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.ColliderDistanceClosestImmediateProcessor(collider, transform, maxDistance, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(in collider, in transform), layer, processor).RunImmediate(); + var hit = result.subColliderIndexB >= 0; + result.subColliderIndexB = math.max(result.subColliderIndexB, 0); + return hit; + } + + public static bool DistanceBetweenAny(Collider collider, + RigidTransform transform, + CollisionLayer layer, + float maxDistance, + out ColliderDistanceResult result, + out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.ColliderDistanceAnyImmediateProcessor(collider, transform, maxDistance, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(in collider, in transform), layer, processor).RunImmediate(); + var hit = result.subColliderIndexB >= 0; + result.subColliderIndexB = math.max(result.subColliderIndexB, 0); + return hit; + } + + #region Layer + + #endregion + + private static ColliderDistanceResult FlipResult(in ColliderDistanceResult resultToFlip) + { + return new ColliderDistanceResult + { + hitpointA = resultToFlip.hitpointB, + hitpointB = resultToFlip.hitpointA, + normalA = resultToFlip.normalB, + normalB = resultToFlip.normalA, + distance = resultToFlip.distance, + subColliderIndexA = resultToFlip.subColliderIndexB, + subColliderIndexB = resultToFlip.subColliderIndexA + }; + } + + private static ColliderDistanceResult BinAResultToWorld(in SpatialInternal.ColliderDistanceResultInternal BinAResult, in RigidTransform aTransform) + { + return new ColliderDistanceResult + { + hitpointA = math.transform(aTransform, BinAResult.hitpointA), + hitpointB = math.transform(aTransform, BinAResult.hitpointB), + normalA = math.rotate(aTransform, BinAResult.normalA), + normalB = math.rotate(aTransform, BinAResult.normalB), + distance = BinAResult.distance + }; + } + + private static ColliderDistanceResult DistanceBetweenGjk(in Collider colliderA, in RigidTransform aTransform, in Collider colliderB, in RigidTransform bTransform) + { + var bInATransform = math.mul(math.inverse(aTransform), bTransform); + var gjkResult = SpatialInternal.DoGjkEpa(colliderA, colliderB, bInATransform); + var epsilon = gjkResult.normalizedOriginToClosestCsoPoint * math.select(1e-4f, -1e-4f, gjkResult.distance < 0f); + DistanceBetween(gjkResult.hitpointOnAInASpace + epsilon, in colliderA, RigidTransform.identity, float.MaxValue, out var closestOnA); + DistanceBetween(gjkResult.hitpointOnBInASpace - epsilon, in colliderB, in bInATransform, float.MaxValue, out var closestOnB); + return BinAResultToWorld(new SpatialInternal.ColliderDistanceResultInternal + { + distance = gjkResult.distance, + hitpointA = gjkResult.hitpointOnAInASpace, + hitpointB = gjkResult.hitpointOnBInASpace, + normalA = closestOnA.normal, + normalB = closestOnB.normal + }, aTransform); + } + + #region Point + public static bool DistanceBetween(float3 point, in SphereCollider sphere, in RigidTransform sphereTransform, float maxDistance, out PointDistanceResult result) + { + var pointInSphereSpace = math.transform(math.inverse(sphereTransform), point); + bool hit = SpatialInternal.PointSphereDistance(pointInSphereSpace, sphere, maxDistance, out var localResult); + result = new PointDistanceResult + { + hitpoint = math.transform(sphereTransform, localResult.hitpoint), + normal = math.rotate(sphereTransform, localResult.normal), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(float3 point, in CapsuleCollider capsule, in RigidTransform capsuleTransform, float maxDistance, out PointDistanceResult result) + { + var pointInCapSpace = math.transform(math.inverse(capsuleTransform), point); + bool hit = SpatialInternal.PointCapsuleDistance(pointInCapSpace, capsule, maxDistance, out var localResult); + result = new PointDistanceResult + { + hitpoint = math.transform(capsuleTransform, localResult.hitpoint), + normal = math.rotate(capsuleTransform, localResult.normal), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(float3 point, in BoxCollider box, in RigidTransform boxTransform, float maxDistance, out PointDistanceResult result) + { + var pointInBoxSpace = math.transform(math.inverse(boxTransform), point); + bool hit = SpatialInternal.PointBoxDistance(pointInBoxSpace, box, maxDistance, out var localResult); + result = new PointDistanceResult + { + hitpoint = math.transform(boxTransform, localResult.hitpoint), + normal = math.rotate(boxTransform, localResult.normal), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(float3 point, in TriangleCollider triangle, in RigidTransform triangleTransform, float maxDistance, out PointDistanceResult result) + { + var pointInTriangleSpace = math.transform(math.inverse(triangleTransform), point); + bool hit = SpatialInternal.PointTriangleDistance(pointInTriangleSpace, triangle, maxDistance, out var localResult); + result = new PointDistanceResult + { + hitpoint = math.transform(triangleTransform, localResult.hitpoint), + normal = math.rotate(triangleTransform, localResult.normal), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(float3 point, in ConvexCollider convex, in RigidTransform convexTransform, float maxDistance, out PointDistanceResult result) + { + var pointInConvexSpace = math.transform(math.inverse(convexTransform), point); + bool hit = SpatialInternal.PointConvexDistance(pointInConvexSpace, convex, maxDistance, out var localResult); + result = new PointDistanceResult + { + hitpoint = math.transform(convexTransform, localResult.hitpoint), + normal = math.rotate(convexTransform, localResult.normal), + distance = localResult.distance + }; + return hit; + } + + public static bool DistanceBetween(float3 point, in CompoundCollider compound, in RigidTransform compoundTransform, float maxDistance, out PointDistanceResult result) + { + bool hit = false; + result = default; + result.distance = float.MaxValue; + ref var blob = ref compound.compoundColliderBlob.Value; + var compoundScale = new PhysicsScale { scale = compound.scale, state = PhysicsScale.State.Uniform }; + for (int i = 0; i < blob.colliders.Length; i++) + { + var blobTransform = blob.transforms[i]; + blobTransform.pos *= compound.scale; + bool newHit = DistanceBetween(point, + ScaleCollider(in blob.colliders[i], compoundScale), + math.mul(compoundTransform, blobTransform), + math.min(result.distance, maxDistance), + out var newResult); + + newResult.subColliderIndex = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool DistanceBetween(float3 point, in CollisionLayer layer, float maxDistance, out PointDistanceResult result, out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.PointDistanceClosestImmediateProcessor(point, maxDistance, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(point - maxDistance, point + maxDistance), layer, processor).RunImmediate(); + var hit = result.subColliderIndex >= 0; + result.subColliderIndex = math.max(result.subColliderIndex, 0); + return hit; + } + + public static bool DistanceBetweenAny(float3 point, in CollisionLayer layer, float maxDistance, out PointDistanceResult result, out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.PointDistanceAnyImmediateProcessor(point, maxDistance, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(point - maxDistance, point + maxDistance), layer, processor).RunImmediate(); + var hit = result.subColliderIndex >= 0; + result.subColliderIndex = math.max(result.subColliderIndex, 0); + return hit; + } + #endregion + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetween.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetween.cs.meta new file mode 100644 index 0000000..e0b88a1 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetween.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf44b911bdcda3e409a66b51d50de9a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.gen.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.gen.cs new file mode 100644 index 0000000..105ac47 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.gen.cs @@ -0,0 +1,639 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Physics/Utilities/Physics.DistanceBetweenDispatch.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + public static bool DistanceBetween(in Collider collider, + in RigidTransform colliderTransform, + in SphereCollider sphere, + in RigidTransform sphereTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in collider.m_sphere, in colliderTransform, in sphere, in sphereTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in collider.m_capsule, in colliderTransform, in sphere, in sphereTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in collider.m_box, in colliderTransform, in sphere, in sphereTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in collider.m_triangle, in colliderTransform, in sphere, in sphereTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in collider.m_convex, in colliderTransform, in sphere, in sphereTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in collider.m_compound, in colliderTransform, in sphere, in sphereTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in SphereCollider sphere, + in RigidTransform sphereTransform, + in Collider collider, + in RigidTransform colliderTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in sphere, in sphereTransform, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in sphere, in sphereTransform, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in sphere, in sphereTransform, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in sphere, in sphereTransform, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in sphere, in sphereTransform, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in sphere, in sphereTransform, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + public static bool DistanceBetween(in Collider collider, + in RigidTransform colliderTransform, + in CapsuleCollider capsule, + in RigidTransform capsuleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in collider.m_sphere, in colliderTransform, in capsule, in capsuleTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in collider.m_capsule, in colliderTransform, in capsule, in capsuleTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in collider.m_box, in colliderTransform, in capsule, in capsuleTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in collider.m_triangle, in colliderTransform, in capsule, in capsuleTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in collider.m_convex, in colliderTransform, in capsule, in capsuleTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in collider.m_compound, in colliderTransform, in capsule, in capsuleTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in CapsuleCollider capsule, + in RigidTransform capsuleTransform, + in Collider collider, + in RigidTransform colliderTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in capsule, in capsuleTransform, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in capsule, in capsuleTransform, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in capsule, in capsuleTransform, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in capsule, in capsuleTransform, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in capsule, in capsuleTransform, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in capsule, in capsuleTransform, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + public static bool DistanceBetween(in Collider collider, + in RigidTransform colliderTransform, + in BoxCollider box, + in RigidTransform boxTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in collider.m_sphere, in colliderTransform, in box, in boxTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in collider.m_capsule, in colliderTransform, in box, in boxTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in collider.m_box, in colliderTransform, in box, in boxTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in collider.m_triangle, in colliderTransform, in box, in boxTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in collider.m_convex, in colliderTransform, in box, in boxTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in collider.m_compound, in colliderTransform, in box, in boxTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in BoxCollider box, + in RigidTransform boxTransform, + in Collider collider, + in RigidTransform colliderTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in box, in boxTransform, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in box, in boxTransform, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in box, in boxTransform, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in box, in boxTransform, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in box, in boxTransform, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in box, in boxTransform, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + public static bool DistanceBetween(in Collider collider, + in RigidTransform colliderTransform, + in TriangleCollider triangle, + in RigidTransform triangleTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in collider.m_sphere, in colliderTransform, in triangle, in triangleTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in collider.m_capsule, in colliderTransform, in triangle, in triangleTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in collider.m_box, in colliderTransform, in triangle, in triangleTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in collider.m_triangle, in colliderTransform, in triangle, in triangleTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in collider.m_convex, in colliderTransform, in triangle, in triangleTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in collider.m_compound, in colliderTransform, in triangle, in triangleTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in TriangleCollider triangle, + in RigidTransform triangleTransform, + in Collider collider, + in RigidTransform colliderTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in triangle, in triangleTransform, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in triangle, in triangleTransform, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in triangle, in triangleTransform, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in triangle, in triangleTransform, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in triangle, in triangleTransform, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in triangle, in triangleTransform, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + public static bool DistanceBetween(in Collider collider, + in RigidTransform colliderTransform, + in ConvexCollider convex, + in RigidTransform convexTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in collider.m_sphere, in colliderTransform, in convex, in convexTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in collider.m_capsule, in colliderTransform, in convex, in convexTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in collider.m_box, in colliderTransform, in convex, in convexTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in collider.m_triangle, in colliderTransform, in convex, in convexTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in collider.m_convex, in colliderTransform, in convex, in convexTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in collider.m_compound, in colliderTransform, in convex, in convexTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in ConvexCollider convex, + in RigidTransform convexTransform, + in Collider collider, + in RigidTransform colliderTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in convex, in convexTransform, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in convex, in convexTransform, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in convex, in convexTransform, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in convex, in convexTransform, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in convex, in convexTransform, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in convex, in convexTransform, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + public static bool DistanceBetween(in Collider collider, + in RigidTransform colliderTransform, + in CompoundCollider compound, + in RigidTransform compoundTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in collider.m_sphere, in colliderTransform, in compound, in compoundTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in collider.m_capsule, in colliderTransform, in compound, in compoundTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in collider.m_box, in colliderTransform, in compound, in compoundTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in collider.m_triangle, in colliderTransform, in compound, in compoundTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in collider.m_convex, in colliderTransform, in compound, in compoundTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in collider.m_compound, in colliderTransform, in compound, in compoundTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in CompoundCollider compound, + in RigidTransform compoundTransform, + in Collider collider, + in RigidTransform colliderTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return DistanceBetween(in compound, in compoundTransform, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return DistanceBetween(in compound, in compoundTransform, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return DistanceBetween(in compound, in compoundTransform, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return DistanceBetween(in compound, in compoundTransform, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return DistanceBetween(in compound, in compoundTransform, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return DistanceBetween(in compound, in compoundTransform, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in Collider colliderA, + in RigidTransform aTransform, + in Collider colliderB, + in RigidTransform bTransform, + float maxDistance, + out ColliderDistanceResult result) + { + switch (colliderA.type) + { + case ColliderType.Sphere: + { + SphereCollider colA = colliderA; + return DistanceBetween(in colliderA.m_sphere, in aTransform, in colliderB, in bTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider colA = colliderA; + return DistanceBetween(in colliderA.m_sphere, in aTransform, in colliderB, in bTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider colA = colliderA; + return DistanceBetween(in colliderA.m_sphere, in aTransform, in colliderB, in bTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider colA = colliderA; + return DistanceBetween(in colliderA.m_sphere, in aTransform, in colliderB, in bTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider colA = colliderA; + return DistanceBetween(in colliderA.m_sphere, in aTransform, in colliderB, in bTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider colA = colliderA; + return DistanceBetween(in colliderA.m_sphere, in aTransform, in colliderB, in bTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(in float3 point, in Collider collider, in RigidTransform colliderTransform, float maxDistance, out PointDistanceResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider colA = collider; + return DistanceBetween(point, in collider.m_sphere, in colliderTransform, maxDistance, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider colA = collider; + return DistanceBetween(point, in collider.m_capsule, in colliderTransform, maxDistance, out result); + } + case ColliderType.Box: + { + BoxCollider colA = collider; + return DistanceBetween(point, in collider.m_box, in colliderTransform, maxDistance, out result); + } + case ColliderType.Triangle: + { + TriangleCollider colA = collider; + return DistanceBetween(point, in collider.m_triangle, in colliderTransform, maxDistance, out result); + } + case ColliderType.Convex: + { + ConvexCollider colA = collider; + return DistanceBetween(point, in collider.m_convex, in colliderTransform, maxDistance, out result); + } + case ColliderType.Compound: + { + CompoundCollider colA = collider; + return DistanceBetween(point, in collider.m_compound, in colliderTransform, maxDistance, out result); + } + default: + result = default; + return false; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.gen.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.gen.cs.meta new file mode 100644 index 0000000..1a4ff3b --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd67ec1e186c04d458fc789d59db8352 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.tt b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.tt new file mode 100644 index 0000000..9e5c473 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.tt @@ -0,0 +1,135 @@ +<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ output extension=".gen.cs" #> +<#@ assembly name="System.Collections" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Collections.Generic" #> +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Physics/Utilities/Physics.DistanceBetweenDispatch.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { +<# +{ + var allTypes = new string[] + { + "Sphere", + "Capsule", + "Box", + "Triangle", + //"Quad", + "Convex", + "Compound" + }; + + foreach (var type in allTypes) + { + string lowerType = type.ToLower(); +#> + public static bool DistanceBetween(Collider collider, RigidTransform colliderTransform, <#=type#>Collider <#=lowerType#>, RigidTransform <#=lowerType#>Transform, float maxDistance, out ColliderDistanceResult result) + { + switch (collider.type) + { +<# + foreach (var type2 in allTypes) + { +#> + case ColliderType.<#=type2#>: + { + <#=type2#>Collider col = collider; + return DistanceBetween(col, colliderTransform, <#=lowerType#>, <#=lowerType#>Transform, maxDistance, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(<#=type#>Collider <#=lowerType#>, RigidTransform <#=lowerType#>Transform, Collider collider, RigidTransform colliderTransform, float maxDistance, out ColliderDistanceResult result) + { + switch (collider.type) + { +<# + foreach (var type2 in allTypes) + { +#> + case ColliderType.<#=type2#>: + { + <#=type2#>Collider col = collider; + return DistanceBetween(<#=lowerType#>, <#=lowerType#>Transform, col, colliderTransform, maxDistance, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } +<# + } +#> + + public static bool DistanceBetween(Collider colliderA, RigidTransform aTransform, Collider colliderB, RigidTransform bTransform, float maxDistance, out ColliderDistanceResult result) + { + switch (colliderA.type) + { +<# + foreach (var type in allTypes) + { +#> + case ColliderType.<#=type#>: + { + <#=type#>Collider colA = colliderA; + return DistanceBetween(colA, aTransform, colliderB, bTransform, maxDistance, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } + + public static bool DistanceBetween(float3 point, Collider collider, RigidTransform colliderTransform, float maxDistance, out PointDistanceResult result) + { + switch (collider.type) + { +<# + foreach (var type in allTypes) + { +#> + case ColliderType.<#=type#>: + { + <#=type#>Collider colA = collider; + return DistanceBetween(point, colA, colliderTransform, maxDistance, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } +<# +} +#> + } +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.tt.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.tt.meta new file mode 100644 index 0000000..f43d64a --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.DistanceBetweenDispatch.tt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9eac22d97a4e2f440bc57fc02ece3037 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindObjects.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindObjects.cs new file mode 100644 index 0000000..59b4967 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindObjects.cs @@ -0,0 +1,139 @@ +using System; +using System.Diagnostics; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +//Todo: Stream types, caches, scratchlists, and inflations +namespace Latios.Psyshock +{ + /// + /// An interface whose Execute method is invoked for each pair found in a FindPairs operations. + /// + public interface IFindObjectsProcessor + { + void Execute(in FindObjectsResult result); + } + + [NativeContainer] + public struct FindObjectsResult + { + public CollisionLayer layer => m_layer; + public ColliderBody body => m_bucket.bodies[m_bodyIndexRelative]; + public Collider collider => body.collider; + public RigidTransform transform => body.transform; + public int bodyIndex => m_bodyIndexRelative + m_bucket.count; + public int jobIndex => m_jobIndex; + + private CollisionLayer m_layer; + private BucketSlices m_bucket; + private int m_bodyIndexRelative; + private int m_jobIndex; + private bool m_isThreadSafe; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public SafeEntity entity => new SafeEntity + { + entity = new Entity + { + Index = math.select(-body.entity.Index, body.entity.Index, m_isThreadSafe), + Version = body.entity.Version + } + }; +#else + public SafeEntity entity => new SafeEntity { entity = body.entity }; +#endif + + public Aabb aabb + { + get + { + var yzminmax = m_bucket.yzminmaxs[m_bodyIndexRelative]; + var xmin = m_bucket.xmins[m_bodyIndexRelative]; + var xmax = m_bucket.xmaxs[m_bodyIndexRelative]; + return new Aabb(new float3(xmin, yzminmax.xy), new float3(xmax, -yzminmax.zw)); + } + } + + internal FindObjectsResult(CollisionLayer layer, BucketSlices bucket, int jobIndex, bool isThreadSafe) + { + m_layer = layer; + m_bucket = bucket; + m_jobIndex = jobIndex; + m_isThreadSafe = isThreadSafe; + m_bodyIndexRelative = 0; + } + + internal void SetBucketRelativeIndex(int index) + { + m_bodyIndexRelative = index; + } + //Todo: Shorthands for calling narrow phase distance and manifold queries + } + + public static partial class Physics + { + public static FindObjectsConfig FindObjects(Aabb aabb, in CollisionLayer layer, in T processor) where T : struct, IFindObjectsProcessor + { + return new FindObjectsConfig + { + processor = processor, + layer = layer, + aabb = aabb, + disableEntityAliasChecks = false + }; + } + } + + public partial struct FindObjectsConfig where T : struct, IFindObjectsProcessor + { + internal T processor; + + internal CollisionLayer layer; + + internal Aabb aabb; + + internal bool disableEntityAliasChecks; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + internal FindObjectsConfig WithoutEntityAliasingChecks() // Internal until a use case for parallel jobs is created + { + disableEntityAliasChecks = true; + return this; + } + #endregion + + #region Schedulers + + public void RunImmediate() + { + FindObjectsInternal.RunImmediate(aabb, layer, processor); + } + + public void Run() + { + new FindObjectsInternal.Single + { + layer = layer, + processor = processor, + aabb = aabb + }.Run(); + } + + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + return new FindObjectsInternal.Single + { + layer = layer, + processor = processor, + aabb = aabb + }.Schedule(inputDeps); + } + #endregion Schedulers + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindObjects.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindObjects.cs.meta new file mode 100644 index 0000000..75bc345 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindObjects.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f13d0f7054febe4aa9b317e90eeb61a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindPairs.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindPairs.cs new file mode 100644 index 0000000..cf3f7e0 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindPairs.cs @@ -0,0 +1,842 @@ +using System; +using System.Diagnostics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; +using Unity.Mathematics; + +//Todo: Stream types, caches, scratchlists, and inflations +namespace Latios.Psyshock +{ + /// + /// An interface whose Execute method is invoked for each pair found in a FindPairs operations. + /// + public interface IFindPairsProcessor + { + void Execute(in FindPairsResult result); + } + + [NativeContainer] + public struct FindPairsResult + { + public CollisionLayer layerA => m_layerA; + public CollisionLayer layerB => m_layerB; + public ColliderBody bodyA => m_layerA.bodies[indexA]; + public ColliderBody bodyB => m_layerB.bodies[indexB]; + public Collider colliderA => bodyA.collider; + public Collider colliderB => bodyB.collider; + public RigidTransform transformA => bodyA.transform; + public RigidTransform transformB => bodyB.transform; + public int indexA => m_bodyAIndex; + public int indexB => m_bodyBIndex; + public int jobIndex => m_jobIndex; + + private CollisionLayer m_layerA; + private CollisionLayer m_layerB; + private int m_bucketStartA; + private int m_bucketStartB; + private int m_bodyAIndex; + private int m_bodyBIndex; + private int m_jobIndex; + private bool m_isThreadSafe; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public SafeEntity entityA => new SafeEntity + { + entity = new Entity + { + Index = math.select(-bodyA.entity.Index, bodyA.entity.Index, m_isThreadSafe), + Version = bodyA.entity.Version + } + }; + public SafeEntity entityB => new SafeEntity + { + entity = new Entity + { + Index = math.select(-bodyB.entity.Index, bodyB.entity.Index, m_isThreadSafe), + Version = bodyB.entity.Version + } + }; +#else + public SafeEntity entityA => new SafeEntity { entity = bodyA.entity }; + public SafeEntity entityB => new SafeEntity { entity = bodyB.entity }; +#endif + + public Aabb aabbA + { + get { + var yzminmax = m_layerA.yzminmaxs[m_bodyAIndex]; + var xmin = m_layerA.xmins[ m_bodyAIndex]; + var xmax = m_layerA.xmaxs[ m_bodyAIndex]; + return new Aabb(new float3(xmin, yzminmax.xy), new float3(xmax, -yzminmax.zw)); + } + } + + public Aabb aabbB + { + get + { + var yzminmax = m_layerB.yzminmaxs[m_bodyBIndex]; + var xmin = m_layerB.xmins[ m_bodyBIndex]; + var xmax = m_layerB.xmaxs[ m_bodyBIndex]; + return new Aabb(new float3(xmin, yzminmax.xy), new float3(xmax, -yzminmax.zw)); + } + } + + internal FindPairsResult(CollisionLayer layerA, CollisionLayer layerB, BucketSlices bucketA, BucketSlices bucketB, int jobIndex, bool isThreadSafe) + { + m_layerA = layerA; + m_layerB = layerB; + m_bucketStartA = bucketA.bucketGlobalStart; + m_bucketStartB = bucketB.bucketGlobalStart; + m_jobIndex = jobIndex; + m_isThreadSafe = isThreadSafe; + m_bodyAIndex = 0; + m_bodyBIndex = 0; + } + + internal static FindPairsResult CreateGlobalResult(CollisionLayer layerA, CollisionLayer layerB, int jobIndex, bool isThreadSafe) + { + return new FindPairsResult + { + m_layerA = layerA, + m_layerB = layerB, + m_bucketStartA = 0, + m_bucketStartB = 0, + m_jobIndex = jobIndex, + m_isThreadSafe = isThreadSafe, + m_bodyAIndex = 0, + m_bodyBIndex = 0, + }; + } + + internal void SetBucketRelativePairIndices(int aIndex, int bIndex) + { + m_bodyAIndex = aIndex + m_bucketStartA; + m_bodyBIndex = bIndex + m_bucketStartB; + } + //Todo: Shorthands for calling narrow phase distance and manifold queries + } + + public static partial class Physics + { + /// + /// Request a FindPairs broadphase operation to report pairs within the layer. + /// This is the start of a fluent expression. + /// + /// The layer in which pairs should be detected + /// The job-like struct which should process each pair found + public static FindPairsLayerSelfConfig FindPairs(in CollisionLayer layer, in T processor) where T : struct, IFindPairsProcessor + { + return new FindPairsLayerSelfConfig + { + processor = processor, + layer = layer, + disableEntityAliasChecks = false + }; + } + + /// + /// Request a FindPairs broadphase operation to report pairs between the two layers. + /// Only pairs containing one element from layerA and one element from layerB will be reported. + /// This is the start of a fluent expression. + /// + /// The first layer in which pairs should be detected + /// The second layer in which pairs should be detected + /// The job-like struct which should process each pair found + public static FindPairsLayerLayerConfig FindPairs(in CollisionLayer layerA, in CollisionLayer layerB, in T processor) where T : struct, IFindPairsProcessor + { + CheckLayersAreCompatible(layerA, layerB); + return new FindPairsLayerLayerConfig + { + processor = processor, + layerA = layerA, + layerB = layerB, + disableEntityAliasChecks = false + }; + } + + /// + /// Request a FindPairs broadphase operation to report pairs within the layer. + /// This is the start of a fluent expression. + /// + /// The layer in which pairs should be detected + /// The job-like struct which should process each pair found + internal static FindPairsLayerSelfConfigUnrolled FindPairsUnrolled(in CollisionLayer layer, in T processor) where T : struct, IFindPairsProcessor + { + return new FindPairsLayerSelfConfigUnrolled + { + processor = processor, + layer = layer, + disableEntityAliasChecks = false + }; + } + + /// + /// Request a FindPairs broadphase operation to report pairs between the two layers. + /// Only pairs containing one element from layerA and one element from layerB will be reported. + /// This is the start of a fluent expression. + /// + /// The first layer in which pairs should be detected + /// The second layer in which pairs should be detected + /// The job-like struct which should process each pair found + internal static FindPairsLayerLayerConfigUnrolled FindPairsUnrolled(in CollisionLayer layerA, in CollisionLayer layerB, in T processor) where T : struct, + IFindPairsProcessor + { + CheckLayersAreCompatible(layerA, layerB); + return new FindPairsLayerLayerConfigUnrolled + { + processor = processor, + layerA = layerA, + layerB = layerB, + disableEntityAliasChecks = false + }; + } + + #region SafetyChecks + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckLayersAreCompatible(in CollisionLayer layerA, in CollisionLayer layerB) + { + if (math.any(layerA.worldMin != layerB.worldMin | layerA.worldAxisStride != layerB.worldAxisStride | layerA.worldSubdivisionsPerAxis != + layerB.worldSubdivisionsPerAxis)) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + throw new InvalidOperationException( + "The two layers used in the FindPairs operation are not compatible. Please ensure the layers were constructed with identical settings."); +#endif + } + } + #endregion + } + + public partial struct FindPairsLayerSelfConfig where T : struct, IFindPairsProcessor + { + internal T processor; + + internal CollisionLayer layer; + + internal bool disableEntityAliasChecks; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + public FindPairsLayerSelfConfig WithoutEntityAliasingChecks() + { + disableEntityAliasChecks = true; + return this; + } + + /// + /// Enables usage of a cache for pairs involving the cross bucket. + /// This increases processing time and memory usage, but may decrease latency. + /// + /// The type of allocator to use for the cache + public FindPairsLayerSelfWithCrossCacheConfig WithCrossCache(Allocator cacheAllocator = Allocator.TempJob) + { + return new FindPairsLayerSelfWithCrossCacheConfig + { + layer = layer, + disableEntityAliasChecks = disableEntityAliasChecks, + processor = processor, + allocator = cacheAllocator + }; + } + #endregion + + #region Schedulers + /// + /// Run the FindPairs operation without using a job. This method can be invoked from inside a job. + /// + public void RunImmediate() + { + FindPairsInternal.RunImmediate(layer, processor); + } + + /// + /// Run the FindPairs operation on the main thread using a Bursted job. + /// + public void Run() + { + new FindPairsInternal.LayerSelfSingle + { + layer = layer, + processor = processor + }.Run(); + } + + /// + /// Run the FindPairs operation on a single worker thread. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + return new FindPairsInternal.LayerSelfSingle + { + layer = layer, + processor = processor + }.Schedule(inputDeps); + } + + /// + /// Run the FindPairs operation using multiple worker threads in multiple phases. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// The final JobHandle for the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + JobHandle jh = new FindPairsInternal.LayerSelfPart1 + { + layer = layer, + processor = processor + }.Schedule(layer.BucketCount, 1, inputDeps); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (disableEntityAliasChecks) + { + jh = new FindPairsInternal.LayerSelfPart2 + { + layer = layer, + processor = processor + }.Schedule(jh); + } + else + { + jh = new FindPairsInternal.LayerSelfPart2_WithSafety + { + layer = layer, + processor = processor + }.ScheduleParallel(2, 1, jh); + } +#else + jh = new FindPairsInternal.LayerSelfPart2 + { + layer = layerA, + processor = processor + }.Schedule(jh); +#endif + return jh; + } + + /// + /// Run the FindPairs operation using multiple worker threads all at once without entity or body index thread-safety. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleParallelUnsafe(JobHandle inputDeps = default) + { + return new FindPairsInternal.LayerSelfParallelUnsafe + { + layer = layer, + processor = processor + }.ScheduleParallel(2 * layer.BucketCount - 1, 1, inputDeps); + } + #endregion Schedulers + } + + public partial struct FindPairsLayerSelfWithCrossCacheConfig + { + internal T processor; + + internal CollisionLayer layer; + + internal bool disableEntityAliasChecks; + + internal Allocator allocator; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + public FindPairsLayerSelfWithCrossCacheConfig WithoutEntityAliasingChecks() + { + disableEntityAliasChecks = true; + return this; + } + #endregion + + #region Schedulers + /// + /// Run the FindPairs operation using multiple worker threads in multiple phases. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// The final JobHandle for the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + var cache = new NativeStream(layer.BucketCount - 1, allocator); + JobHandle jh = new FindPairsInternal.LayerSelfPart1 + { + layer = layer, + processor = processor, + cache = cache.AsWriter() + }.Schedule(2 * layer.BucketCount - 1, 1, inputDeps); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (disableEntityAliasChecks) + { + jh = new FindPairsInternal.LayerSelfPart2 + { + layer = layer, + processor = processor, + cache = cache.AsReader() + }.Schedule(jh); + } + else + { + jh = new FindPairsInternal.LayerSelfPart2_WithSafety + { + layer = layer, + processor = processor, + cache = cache.AsReader() + }.ScheduleParallel(2, 1, jh); + } +#else + jh = new FindPairsInternal.LayerSelfPart2 + { + layer = layerA, + processor = processor, + cache = cache.AsReader() + }.Schedule(jh); +#endif + jh = cache.Dispose(jh); + return jh; + } + #endregion + } + + public partial struct FindPairsLayerLayerConfig where T : struct, IFindPairsProcessor + { + internal T processor; + + internal CollisionLayer layerA; + internal CollisionLayer layerB; + + internal bool disableEntityAliasChecks; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + public FindPairsLayerLayerConfig WithoutEntityAliasingChecks() + { + disableEntityAliasChecks = true; + return this; + } + + /// + /// Enables usage of a cache for pairs involving the cross bucket. + /// This increases processing time and memory usage, but may decrease latency. + /// + /// The type of allocator to use for the cache + public FindPairsLayerLayerWithCrossCacheConfig WithCrossCache(Allocator cacheAllocator = Allocator.TempJob) + { + return new FindPairsLayerLayerWithCrossCacheConfig + { + layerA = layerA, + layerB = layerB, + disableEntityAliasChecks = disableEntityAliasChecks, + processor = processor, + allocator = cacheAllocator + }; + } + #endregion + + #region Schedulers + /// + /// Run the FindPairs operation without using a job. This method can be invoked from inside a job. + /// + public void RunImmediate() + { + FindPairsInternal.RunImmediate(layerA, layerB, processor); + } + + /// + /// Run the FindPairs operation on the main thread using a Bursted job. + /// + public void Run() + { + new FindPairsInternal.LayerLayerSingle + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Run(); + } + + /// + /// Run the FindPairs operation on a single worker thread. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + return new FindPairsInternal.LayerLayerSingle + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(inputDeps); + } + + /// + /// Run the FindPairs operation using multiple worker threads in multiple phases. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// The final JobHandle for the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + JobHandle jh = new FindPairsInternal.LayerLayerPart1 + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(layerB.BucketCount, 1, inputDeps); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (disableEntityAliasChecks) + { + jh = new FindPairsInternal.LayerLayerPart2 + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(2, 1, jh); + } + else + { + jh = new FindPairsInternal.LayerLayerPart2_WithSafety + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(3, 1, jh); + } +#else + jh = new FindPairsInternal.LayerLayerPart2 + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(2, 1, jh); +#endif + return jh; + } + + /// + /// Run the FindPairs operation using multiple worker threads all at once without entity or body index thread-safety. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleParallelUnsafe(JobHandle inputDeps = default) + { + return new FindPairsInternal.LayerLayerParallelUnsafe + { + layerA = layerA, + layerB = layerB, + processor = processor + }.ScheduleParallel(3 * layerA.BucketCount - 2, 1, inputDeps); + } + #endregion Schedulers + } + + public partial struct FindPairsLayerLayerWithCrossCacheConfig where T : struct, IFindPairsProcessor + { + internal T processor; + + internal CollisionLayer layerA; + internal CollisionLayer layerB; + + internal bool disableEntityAliasChecks; + + internal Allocator allocator; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + public FindPairsLayerLayerWithCrossCacheConfig WithoutEntityAliasingChecks() + { + disableEntityAliasChecks = true; + return this; + } + #endregion + + #region Schedulers + /// + /// Run the FindPairs operation using multiple worker threads in multiple phases. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// The final JobHandle for the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + var cache = new NativeStream(layerA.BucketCount * 2 - 2, allocator); + + JobHandle jh = new FindPairsInternal.LayerLayerPart1 + { + layerA = layerA, + layerB = layerB, + processor = processor, + cache = cache.AsWriter() + }.Schedule(3 * layerB.BucketCount - 2, 1, inputDeps); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (disableEntityAliasChecks) + { + jh = new FindPairsInternal.LayerLayerPart2 + { + layerA = layerA, + layerB = layerB, + processor = processor, + cache = cache.AsReader() + }.Schedule(2, 1, jh); + } + else + { + jh = new FindPairsInternal.LayerLayerPart2_WithSafety + { + layerA = layerA, + layerB = layerB, + processor = processor, + cache = cache.AsReader() + }.Schedule(3, 1, jh); + } +#else + jh = new FindPairsInternal.LayerLayerPart2 + { + layerA = layerA, + layerB = layerB, + processor = processor, + cache = cache.AsReader() + }.Schedule(2, 1, jh); +#endif + jh = cache.Dispose(jh); + return jh; + } + #endregion + } + + internal partial struct FindPairsLayerSelfConfigUnrolled where T : struct, IFindPairsProcessor + { + internal T processor; + + internal CollisionLayer layer; + + internal bool disableEntityAliasChecks; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + public FindPairsLayerSelfConfigUnrolled WithoutEntityAliasingChecks() + { + disableEntityAliasChecks = true; + return this; + } + #endregion + + #region Schedulers + /// + /// Run the FindPairs operation without using a job. This method can be invoked from inside a job. + /// + public void RunImmediate() + { + FindPairsInternalUnrolled.RunImmediate(layer, processor); + } + + /// + /// Run the FindPairs operation on the main thread using a Bursted job. + /// + public void Run() + { + new FindPairsInternalUnrolled.LayerSelfSingle + { + layer = layer, + processor = processor + }.Run(); + } + + /// + /// Run the FindPairs operation on a single worker thread. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + return new FindPairsInternalUnrolled.LayerSelfSingle + { + layer = layer, + processor = processor + }.Schedule(inputDeps); + } + + /// + /// Run the FindPairs operation using multiple worker threads in multiple phases. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// The final JobHandle for the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + JobHandle jh = new FindPairsInternalUnrolled.LayerSelfPart1 + { + layer = layer, + processor = processor + }.Schedule(layer.BucketCount, 1, inputDeps); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (disableEntityAliasChecks) + { + jh = new FindPairsInternalUnrolled.LayerSelfPart2 + { + layer = layer, + processor = processor + }.Schedule(jh); + } + else + { + jh = new FindPairsInternalUnrolled.LayerSelfPart2_WithSafety + { + layer = layer, + processor = processor + }.ScheduleParallel(2, 1, jh); + } +#else + jh = new FindPairsInternal.LayerSelfPart2 + { + layer = layerA, + processor = processor + }.Schedule(jh); +#endif + return jh; + } + + /// + /// Run the FindPairs operation using multiple worker threads all at once without entity or body index thread-safety. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleParallelUnsafe(JobHandle inputDeps = default) + { + return new FindPairsInternalUnrolled.LayerSelfParallelUnsafe + { + layer = layer, + processor = processor + }.ScheduleParallel(2 * layer.BucketCount - 1, 1, inputDeps); + } + #endregion Schedulers + } + + internal partial struct FindPairsLayerLayerConfigUnrolled where T : struct, IFindPairsProcessor + { + internal T processor; + + internal CollisionLayer layerA; + internal CollisionLayer layerB; + + internal bool disableEntityAliasChecks; + + #region Settings + /// + /// Disables entity aliasing checks on parallel jobs when safety checks are enabled. Use this only when entities can be aliased but body indices must be thread-safe. + /// + public FindPairsLayerLayerConfigUnrolled WithoutEntityAliasingChecks() + { + disableEntityAliasChecks = true; + return this; + } + #endregion + + #region Schedulers + /// + /// Run the FindPairs operation without using a job. This method can be invoked from inside a job. + /// + public void RunImmediate() + { + FindPairsInternalUnrolled.RunImmediate(layerA, layerB, processor); + } + + /// + /// Run the FindPairs operation on the main thread using a Bursted job. + /// + public void Run() + { + new FindPairsInternalUnrolled.LayerLayerSingle + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Run(); + } + + /// + /// Run the FindPairs operation on a single worker thread. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleSingle(JobHandle inputDeps = default) + { + return new FindPairsInternalUnrolled.LayerLayerSingle + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(inputDeps); + } + + /// + /// Run the FindPairs operation using multiple worker threads in multiple phases. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// The final JobHandle for the scheduled jobs + public JobHandle ScheduleParallel(JobHandle inputDeps = default) + { + JobHandle jh = new FindPairsInternalUnrolled.LayerLayerPart1 + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(layerB.BucketCount, 1, inputDeps); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (disableEntityAliasChecks) + { + jh = new FindPairsInternalUnrolled.LayerLayerPart2 + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(2, 1, jh); + } + else + { + jh = new FindPairsInternalUnrolled.LayerLayerPart2_WithSafety + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(3, 1, jh); + } +#else + jh = new FindPairsInternal.LayerLayerPart2 + { + layerA = layerA, + layerB = layerB, + processor = processor + }.Schedule(2, 1, jh); +#endif + return jh; + } + + /// + /// Run the FindPairs operation using multiple worker threads all at once without entity or body index thread-safety. + /// + /// The input dependencies for any layers or processors used in the FindPairs operation + /// A JobHandle for the scheduled job + public JobHandle ScheduleParallelUnsafe(JobHandle inputDeps = default) + { + return new FindPairsInternalUnrolled.LayerLayerParallelUnsafe + { + layerA = layerA, + layerB = layerB, + processor = processor + }.ScheduleParallel(3 * layerA.BucketCount - 2, 1, inputDeps); + } + #endregion Schedulers + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindPairs.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindPairs.cs.meta new file mode 100644 index 0000000..80eb14d --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.FindPairs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a96ba19b0086014e997698fd4c3e3a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Raycast.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Raycast.cs new file mode 100644 index 0000000..9d0e0b3 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Raycast.cs @@ -0,0 +1,149 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + public static bool Raycast(float3 start, float3 end, in SphereCollider sphere, in RigidTransform sphereTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in sphere, in sphereTransform, out result); + } + + public static bool Raycast(Ray ray, in SphereCollider sphere, in RigidTransform sphereTransform, out RaycastResult result) + { + //Todo: No need to apply rotation to ray for sphere. + var rayInSphereSpace = Ray.TransformRay(math.inverse(sphereTransform), ray); + bool hit = SpatialInternal.RaycastSphere(rayInSphereSpace, sphere, out float fraction, out float3 normal); + result.position = math.lerp(ray.start, ray.end, fraction); + result.normal = math.rotate(sphereTransform, normal); + result.distance = math.distance(ray.start, result.position); + result.subColliderIndex = 0; + return hit; + } + + public static bool Raycast(float3 start, float3 end, in CapsuleCollider capsule, in RigidTransform capsuleTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in capsule, in capsuleTransform, out result); + } + + public static bool Raycast(Ray ray, in CapsuleCollider capsule, in RigidTransform capsuleTransform, out RaycastResult result) + { + var rayInCapsuleSpace = Ray.TransformRay(math.inverse(capsuleTransform), ray); + bool hit = SpatialInternal.RaycastCapsule(rayInCapsuleSpace, capsule, out float fraction, out float3 normal); + result.position = math.lerp(ray.start, ray.end, fraction); + result.normal = math.rotate(capsuleTransform, normal); + result.distance = math.distance(ray.start, result.position); + result.subColliderIndex = 0; + return hit; + } + + public static bool Raycast(float3 start, float3 end, in BoxCollider box, in RigidTransform boxTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in box, in boxTransform, out result); + } + + public static bool Raycast(Ray ray, in BoxCollider box, in RigidTransform boxTransform, out RaycastResult result) + { + var rayInBoxSpace = Ray.TransformRay(math.inverse(boxTransform), ray); + bool hit = SpatialInternal.RaycastBox(rayInBoxSpace, box, out float fraction, out float3 normal); + result.position = math.lerp(ray.start, ray.end, fraction); + result.normal = math.rotate(boxTransform, normal); + result.distance = math.distance(ray.start, result.position); + result.subColliderIndex = 0; + return hit; + } + + public static bool Raycast(float3 start, float3 end, in TriangleCollider triangle, in RigidTransform triangleTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in triangle, in triangleTransform, out result); + } + + public static bool Raycast(Ray ray, in TriangleCollider triangle, in RigidTransform triangleTransform, out RaycastResult result) + { + var rayInTriangleSpace = Ray.TransformRay(math.inverse(triangleTransform), ray); + bool hit = SpatialInternal.RaycastTriangle(rayInTriangleSpace, + new simdFloat3(triangle.pointA, triangle.pointB, triangle.pointC, triangle.pointC), + out float fraction, + out float3 normal); + result.position = math.lerp(ray.start, ray.end, fraction); + result.normal = math.rotate(triangleTransform, normal); + result.distance = math.distance(ray.start, result.position); + result.subColliderIndex = 0; + return hit; + } + + public static bool Raycast(float3 start, float3 end, in ConvexCollider convex, in RigidTransform convexTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in convex, in convexTransform, out result); + } + + public static bool Raycast(Ray ray, in ConvexCollider convex, in RigidTransform convexTransform, out RaycastResult result) + { + var rayInConvexSpace = Ray.TransformRay(math.inverse(convexTransform), ray); + bool hit = SpatialInternal.RaycastConvex(rayInConvexSpace, convex, out float fraction, out float3 normal); + result.position = math.lerp(ray.start, ray.end, fraction); + result.normal = math.rotate(convexTransform, normal); + result.distance = math.distance(ray.start, result.position); + result.subColliderIndex = 0; + return hit; + } + + public static bool Raycast(float3 start, float3 end, in CompoundCollider compound, in RigidTransform compoundTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in compound, in compoundTransform, out result); + } + + public static bool Raycast(Ray ray, in CompoundCollider compound, in RigidTransform compoundTransform, out RaycastResult result) + { + result = default; + result.distance = float.MaxValue; + bool hit = false; + var rayInCompoundSpace = Ray.TransformRay(math.inverse(compoundTransform), ray); + var scaledRay = new Ray(rayInCompoundSpace.start / compound.scale, rayInCompoundSpace.end / compound.scale); + ref var blob = ref compound.compoundColliderBlob.Value; + for (int i = 0; i < blob.colliders.Length; i++) + { + var newHit = Raycast(scaledRay, blob.colliders[i], blob.transforms[i], out var newResult); + newResult.subColliderIndex = i; + newHit &= newResult.distance < result.distance; + hit |= newHit; + result = newHit ? newResult : result; + } + return hit; + } + + public static bool Raycast(float3 start, float3 end, in CollisionLayer layer, out RaycastResult result, out LayerBodyInfo layerBodyInfo) + { + return Raycast(new Ray(start, end), in layer, out result, out layerBodyInfo); + } + + public static bool Raycast(Ray ray, in CollisionLayer layer, out RaycastResult result, out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.RaycastClosestImmediateProcessor(ray, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(ray), layer, processor).RunImmediate(); + var hit = result.subColliderIndex >= 0; + result.subColliderIndex = math.max(result.subColliderIndex, 0); + return hit; + } + + public static bool RaycastAny(float3 start, float3 end, in CollisionLayer layer, out RaycastResult result, out LayerBodyInfo layerBodyInfo) + { + return RaycastAny(new Ray(start, end), in layer, out result, out layerBodyInfo); + } + + public static bool RaycastAny(Ray ray, in CollisionLayer layer, out RaycastResult result, out LayerBodyInfo layerBodyInfo) + { + result = default; + layerBodyInfo = default; + var processor = new LayerQueryProcessors.RaycastAnyImmediateProcessor(ray, ref result, ref layerBodyInfo); + FindObjects(AabbFrom(ray), layer, processor).RunImmediate(); + var hit = result.subColliderIndex >= 0; + result.subColliderIndex = math.max(result.subColliderIndex, 0); + return hit; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Raycast.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Raycast.cs.meta new file mode 100644 index 0000000..ae45990 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.Raycast.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 611256edbf63ea44493c1c4cb0800002 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.gen.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.gen.cs new file mode 100644 index 0000000..c331a42 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.gen.cs @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Physics/Utilities/Physics.RaycastDisplatch.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { + public static bool Raycast(float3 start, float3 end, in Collider collider, in RigidTransform colliderTransform, out RaycastResult result) + { + return Raycast(new Ray(start, end), in collider, in colliderTransform, out result); + } + + public static bool Raycast(Ray ray, in Collider collider, in RigidTransform colliderTransform, out RaycastResult result) + { + switch (collider.type) + { + case ColliderType.Sphere: + { + SphereCollider col = collider; + return Raycast(ray, in collider.m_sphere, in colliderTransform, out result); + } + case ColliderType.Capsule: + { + CapsuleCollider col = collider; + return Raycast(ray, in collider.m_capsule, in colliderTransform, out result); + } + case ColliderType.Box: + { + BoxCollider col = collider; + return Raycast(ray, in collider.m_box, in colliderTransform, out result); + } + case ColliderType.Triangle: + { + TriangleCollider col = collider; + return Raycast(ray, in collider.m_triangle, in colliderTransform, out result); + } + case ColliderType.Convex: + { + ConvexCollider col = collider; + return Raycast(ray, in collider.m_convex, in colliderTransform, out result); + } + case ColliderType.Compound: + { + CompoundCollider col = collider; + return Raycast(ray, in collider.m_compound, in colliderTransform, out result); + } + default: + result = default; + return false; + } + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.gen.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.gen.cs.meta new file mode 100644 index 0000000..484074d --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e96e546a7810ad74db9ccfb7b7e8faea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.tt b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.tt new file mode 100644 index 0000000..787ddc8 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.tt @@ -0,0 +1,61 @@ +<#/*THIS IS A T4 FILE - see t4_text_templating.md for what it is and how to run codegen*/#> +<#@ output extension=".gen.cs" #> +<#@ assembly name="System.Collections" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Collections.Generic" #> +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// TextTransform Physics/Utilities/Physics.RaycastDisplatch.tt +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public static partial class Physics + { +<# +{ + var allTypes = new string[] + { + "Sphere", + "Capsule", + "Box", + "Triangle", + //"Quad" + "Convex", + "Compound" + }; +#> + public static bool Raycast(Ray ray, Collider collider, RigidTransform colliderTransform, out RaycastResult result) + { + switch (collider.type) + { +<# + foreach (var type in allTypes) + { +#> + case ColliderType.<#=type#>: + { + <#=type#>Collider col = collider; + return Raycast(ray, col, colliderTransform, out result); + } +<# + } +#> + default: + result = default; + return false; + } + } +<# +} +#> + } +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.tt.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.tt.meta new file mode 100644 index 0000000..92a41ba --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Spatial/Queries/Physics.RaycastDispatch.tt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5625983522796e14a865a8c9f1283c6b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types.meta new file mode 100644 index 0000000..1301180 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5396dc651fac79946a2ddf50b2389e58 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Aabb.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Aabb.cs new file mode 100644 index 0000000..39157fd --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Aabb.cs @@ -0,0 +1,22 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A Min-Max representation of an Axis-Aligned Bounding Box + /// + [Serializable] + public struct Aabb + { + public float3 min; + public float3 max; + + public Aabb(float3 min, float3 max) + { + this.min = min; + this.max = max; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Aabb.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Aabb.cs.meta new file mode 100644 index 0000000..59e5d27 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Aabb.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b76461307e7ccd04080672ad85c6f9ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/ColliderBody.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/ColliderBody.cs new file mode 100644 index 0000000..d0e9e99 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/ColliderBody.cs @@ -0,0 +1,16 @@ +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A struct which represents the data associated with each AABB in a CollisionLayer + /// + public struct ColliderBody + { + public Collider collider; + public RigidTransform transform; + public Entity entity; + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/ColliderBody.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/ColliderBody.cs.meta new file mode 100644 index 0000000..b035391 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/ColliderBody.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c76e3f6028b93b44cab76e7bcc90ea67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders.meta new file mode 100644 index 0000000..b7829c4 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6c8112cf75bd98d498f04c92394f930d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/BoxColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/BoxColliderPsyshock.cs new file mode 100644 index 0000000..e77297e --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/BoxColliderPsyshock.cs @@ -0,0 +1,22 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A local coordinate space axis-alinged box using center & extents representation + /// + [Serializable] + public struct BoxCollider + { + public float3 center; + public float3 halfSize; + + public BoxCollider(float3 center, float3 halfSize) + { + this.center = center; + this.halfSize = halfSize; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/BoxColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/BoxColliderPsyshock.cs.meta new file mode 100644 index 0000000..49ade5a --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/BoxColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57188b8d166c6d749b4901ebfc6775b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CapsuleColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CapsuleColliderPsyshock.cs new file mode 100644 index 0000000..f9f4f2e --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CapsuleColliderPsyshock.cs @@ -0,0 +1,25 @@ +using System; +using Unity.Mathematics; + +//Note: A == B seems to work with SegmentSegmentDistance +namespace Latios.Psyshock +{ + /// + /// A capsule composed of a segment and an inflated radius around the segment + /// + [Serializable] + public struct CapsuleCollider + { + public float3 pointA; + public float radius; + public float3 pointB; + + public CapsuleCollider(float3 pointA, float3 pointB, float radius) + { + this.pointA = pointA; + this.pointB = pointB; + this.radius = radius; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CapsuleColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CapsuleColliderPsyshock.cs.meta new file mode 100644 index 0000000..eccceae --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CapsuleColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec9de6969f69970418c9fbbbe70104f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CompoundColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CompoundColliderPsyshock.cs new file mode 100644 index 0000000..3da2e0e --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CompoundColliderPsyshock.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A uniformly scaled collection of colliders and their relative transforms. + /// + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct CompoundCollider + { + [FieldOffset(0)] public BlobAssetReference compoundColliderBlob; + [FieldOffset(8)] public float scale; + + public CompoundCollider(BlobAssetReference compoundColliderBlob, float scale = 1f) + { + this.compoundColliderBlob = compoundColliderBlob; + this.scale = scale; + } + } + + internal struct BlobCollider + { +#pragma warning disable CS0649 + internal float4x4 storage; +#pragma warning restore CS0649 + } + + //Todo: Use some acceleration structure in a future version + /// + /// A blob asset composed of a collection of colliders and their transforms in a unified coordinate space + /// + public struct CompoundColliderBlob + { + internal BlobArray blobColliders; + public BlobArray transforms; + public Aabb localAabb; + + public unsafe ref BlobArray colliders => ref UnsafeUtility.AsRef >(UnsafeUtility.AddressOf(ref blobColliders)); + //ref UnsafeUtility.As, BlobArray>(ref blobColliders); + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CompoundColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CompoundColliderPsyshock.cs.meta new file mode 100644 index 0000000..1af78c0 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/CompoundColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff5bffaa16dbee04bad29ce4f72828cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/ConvexColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/ConvexColliderPsyshock.cs new file mode 100644 index 0000000..5cee62c --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/ConvexColliderPsyshock.cs @@ -0,0 +1,53 @@ +using System; +using System.Runtime.InteropServices; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct ConvexCollider + { + [FieldOffset(0)] public BlobAssetReference convexColliderBlob; + [FieldOffset(8)] public float3 scale; + + public ConvexCollider(BlobAssetReference convexColliderBlob) : this(convexColliderBlob, new float3(1f, 1f, 1f)) { + } + + public ConvexCollider(BlobAssetReference convexColliderBlob, float3 scale) + { + this.convexColliderBlob = convexColliderBlob; + this.scale = scale; + } + } + + public struct ConvexColliderBlob + { + public BlobArray verticesX; + public BlobArray verticesY; + public BlobArray verticesZ; + public BlobArray vertexNormals; + + public BlobArray vertexIndicesInEdges; + public BlobArray edgeNormals; + + // outward normals and distance to origin + public BlobArray facePlaneX; + public BlobArray facePlaneY; + public BlobArray facePlaneZ; + public BlobArray facePlaneDist; + + // xyz normal w, signed distance + public BlobArray faceEdgeOutwardPlanes; + + public BlobArray edgeIndicesInFaces; + public BlobArray edgeIndicesInFacesStartsAndCounts; + + public Aabb localAabb; + + public FixedString128Bytes meshName; + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/ConvexColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/ConvexColliderPsyshock.cs.meta new file mode 100644 index 0000000..7fc2492 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/ConvexColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 862d6fca74da2a24086c1fa8973ac6f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/SphereColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/SphereColliderPsyshock.cs new file mode 100644 index 0000000..65b16fc --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/SphereColliderPsyshock.cs @@ -0,0 +1,22 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A sphere defined by a center and a radius + /// + [Serializable] + public struct SphereCollider + { + public float3 center; + public float radius; + + public SphereCollider(float3 center, float radius) + { + this.center = center; + this.radius = radius; + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/SphereColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/SphereColliderPsyshock.cs.meta new file mode 100644 index 0000000..479eb26 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/SphereColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62dd8471aa34c324a81847276afe2643 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/TriangleColliderPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/TriangleColliderPsyshock.cs new file mode 100644 index 0000000..b9ea675 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/TriangleColliderPsyshock.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A triangle defined by three distinct points + /// + [Serializable] + public struct TriangleCollider + { + public float3 pointA; + public float3 pointB; + public float3 pointC; + + public TriangleCollider(float3 pointA, float3 pointB, float3 pointC) + { + this.pointA = pointA; + this.pointB = pointB; + this.pointC = pointC; + } + + public simdFloat3 AsSimdFloat3() + { + return new simdFloat3(pointA, pointB, pointC, pointA); + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/TriangleColliderPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/TriangleColliderPsyshock.cs.meta new file mode 100644 index 0000000..6d69fdc --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/Colliders/TriangleColliderPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7188b435e0acef469ba0f00913106dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/CollisionLayer.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/CollisionLayer.cs new file mode 100644 index 0000000..41f8746 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/CollisionLayer.cs @@ -0,0 +1,219 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// The settings used to create a CollisionLayer + /// + /// + /// A collision layer divides a worldAabb into cells. All element AABBs get binned into cells + /// which reduces the number of tests and improves parallelism. AABBs that span across multiple + /// cells will be categorized in a "catch-all" cell that is tested against all other cells. + /// Each cell contains its own additional acceleration structures. For extremely high element + /// counts, a cell with several thousand elements may be acceptable. + /// There is often a "sweet spot" for reducing the number of elements per cell without too many + /// elements ending up in the "catch-all", and this will lead to the best performance. + /// Element AABBs outside of the worldAabb will be binned into surface cells based on their + /// projection to the surface of worldAabb. What this means is that CollisionLayerSettings + /// in no way affect the correctness of the algorithms and only serve as a way to tune the + /// mechanisms for better performance. It is recommended to ignore outliers and focus the + /// worldAabb to encapsulate the majority of the elements. + /// + public struct CollisionLayerSettings + { + /// + /// An AABB which defines the bounds of the subdivision grid. + /// Elements do not necessarily need to fit inside of it. + /// + public Aabb worldAabb; + /// + /// How many "cells" to divide the worldAabb into. + /// + public int3 worldSubdivisionsPerAxis; + } + + /// + /// A spatial query acceleration structure composed of native containers + /// + /// + /// This spatial query structure is composed of "cells" where each cell contains a batch of + /// elements sorted by their AABB's minimum x component along with an interval tree of x-axis + /// spans. Testing a full cell uses a highly optimized single-axis sweep-and-prune. + /// Immediate queries use a combination of sweeping algorithms and traversal of the interval tree. + /// Cells do not have a maximum capacity, but are are composed of spans of arrays. + /// A CollisionLayer uses O(n) memory and has O(n) build times. + /// It is possible (and often recommended) to build many CollisionLayers and test them against + /// each other, as long as the CollisionLayers were built with the same CollisionLayerSettings. + /// AABBs with NaN components are placed in a special cell that is never tested. + /// + public struct CollisionLayer : INativeDisposable + { + internal NativeArray bucketStartsAndCounts; + [NativeDisableParallelForRestriction] internal NativeArray xmins; + [NativeDisableParallelForRestriction] internal NativeArray xmaxs; + [NativeDisableParallelForRestriction] internal NativeArray yzminmaxs; + [NativeDisableParallelForRestriction] internal NativeArray intervalTrees; + [NativeDisableParallelForRestriction] internal NativeArray bodies; + internal float3 worldMin; + internal float3 worldAxisStride; + internal int3 worldSubdivisionsPerAxis; + + internal CollisionLayer(int bodyCount, CollisionLayerSettings settings, AllocatorManager.AllocatorHandle allocator) + { + worldMin = settings.worldAabb.min; + worldAxisStride = (settings.worldAabb.max - worldMin) / settings.worldSubdivisionsPerAxis; + worldSubdivisionsPerAxis = settings.worldSubdivisionsPerAxis; + + bucketStartsAndCounts = CollectionHelper.CreateNativeArray( + settings.worldSubdivisionsPerAxis.x * settings.worldSubdivisionsPerAxis.y * settings.worldSubdivisionsPerAxis.z + 2, + allocator, + NativeArrayOptions.UninitializedMemory); + xmins = CollectionHelper.CreateNativeArray(bodyCount, allocator, NativeArrayOptions.UninitializedMemory); + xmaxs = CollectionHelper.CreateNativeArray(bodyCount, allocator, NativeArrayOptions.UninitializedMemory); + yzminmaxs = CollectionHelper.CreateNativeArray(bodyCount, allocator, NativeArrayOptions.UninitializedMemory); + intervalTrees = CollectionHelper.CreateNativeArray(bodyCount, allocator, NativeArrayOptions.UninitializedMemory); + bodies = CollectionHelper.CreateNativeArray(bodyCount, allocator, NativeArrayOptions.UninitializedMemory); + } + + /// + /// Copy a CollisionLayer + /// + /// The layer to copy from + /// The allocator to use for the new layer + public CollisionLayer(in CollisionLayer sourceLayer, AllocatorManager.AllocatorHandle allocator) + { + worldMin = sourceLayer.worldMin; + worldAxisStride = sourceLayer.worldAxisStride; + worldSubdivisionsPerAxis = sourceLayer.worldSubdivisionsPerAxis; + + bucketStartsAndCounts = CollectionHelper.CreateNativeArray(sourceLayer.bucketStartsAndCounts, allocator); + xmins = CollectionHelper.CreateNativeArray(sourceLayer.xmins, allocator); + xmaxs = CollectionHelper.CreateNativeArray(sourceLayer.xmaxs, allocator); + yzminmaxs = CollectionHelper.CreateNativeArray(sourceLayer.yzminmaxs, allocator); + intervalTrees = CollectionHelper.CreateNativeArray(sourceLayer.intervalTrees, allocator); + bodies = CollectionHelper.CreateNativeArray(sourceLayer.bodies, allocator); + } + + /// + /// Disposes the layer immediately + /// + public void Dispose() + { + bucketStartsAndCounts.Dispose(); + xmins.Dispose(); + xmaxs.Dispose(); + yzminmaxs.Dispose(); + intervalTrees.Dispose(); + bodies.Dispose(); + } + + /// + /// Disposes the layer using jobs + /// + /// A JobHandle to wait upon before disposing + /// The final jobHandle of the disposed layers + public unsafe JobHandle Dispose(JobHandle inputDeps) + { + JobHandle* deps = stackalloc JobHandle[6] + { + bucketStartsAndCounts.Dispose(inputDeps), + xmins.Dispose(inputDeps), + xmaxs.Dispose(inputDeps), + yzminmaxs.Dispose(inputDeps), + intervalTrees.Dispose(inputDeps), + bodies.Dispose(inputDeps) + }; + return Unity.Jobs.LowLevel.Unsafe.JobHandleUnsafeUtility.CombineDependencies(deps, 6); + } + + /// + /// The number of elements in the layer + /// + public int Count => xmins.Length; + /// + /// The number of cells in the layer, including the "catch-all" cell but ignoring the NaN cell + /// + public int BucketCount => bucketStartsAndCounts.Length - 1; // For algorithmic purposes, we pretend the nan bucket doesn't exist. + /// + /// True if the CollisionLayer has been created + /// + public bool IsCreated => bucketStartsAndCounts.IsCreated; + + internal BucketSlices GetBucketSlices(int bucketIndex) + { + int start = bucketStartsAndCounts[bucketIndex].x; + int count = bucketStartsAndCounts[bucketIndex].y; + + return new BucketSlices + { + xmins = xmins.GetSubArray(start, count), + xmaxs = xmaxs.GetSubArray(start, count), + yzminmaxs = yzminmaxs.GetSubArray(start, count), + intervalTree = intervalTrees.GetSubArray(start, count), + bodies = bodies.GetSubArray(start, count), + bucketIndex = bucketIndex, + bucketGlobalStart = start + }; + } + } + + internal struct BucketSlices + { + public NativeArray xmins; + public NativeArray xmaxs; + public NativeArray yzminmaxs; + public NativeArray intervalTree; + public NativeArray bodies; + public int count => xmins.Length; + public int bucketIndex; + public int bucketGlobalStart; + } + + internal struct IntervalTreeNode + { + public float xmin; + public float xmax; + public float subtreeXmax; + public int bucketRelativeBodyIndex; + } + + /*public struct RayQueryLayer : IDisposable + * { + * public NativeArray bucketRanges; + * public NativeArray xmin; + * public NativeArray xmax; + * public NativeArray yzminmax; + * public NativeArray entity; + * public NativeArray ray; + * public float gridSpacing; + * public int gridCells1DFromOrigin; + * + * public RayQueryLayer(EntityQuery query, int gridCells1DFromOrigin, float worldHalfExtent, Allocator allocator) + * { + * this.gridCells1DFromOrigin = gridCells1DFromOrigin; + * gridSpacing = worldHalfExtent / gridCells1DFromOrigin; + * int entityCount = query.CalculateLength(); + * bucketRanges = CollectionHelper.CreateNativeArray(gridCells1DFromOrigin * gridCells1DFromOrigin + 1, allocator, NativeArrayOptions.UninitializedMemory); + * xmin = CollectionHelper.CreateNativeArray(entityCount, allocator, NativeArrayOptions.UninitializedMemory); + * xmax = CollectionHelper.CreateNativeArray(entityCount, allocator, NativeArrayOptions.UninitializedMemory); + * yzminmax = CollectionHelper.CreateNativeArray(entityCount, allocator, NativeArrayOptions.UninitializedMemory); + * entity = CollectionHelper.CreateNativeArray(entityCount, allocator, NativeArrayOptions.UninitializedMemory); + * ray = CollectionHelper.CreateNativeArray(entityCount, allocator, NativeArrayOptions.UninitializedMemory); + * } + * + * public void Dispose() + * { + * bucketRanges.Dispose(); + * xmin.Dispose(); + * xmax.Dispose(); + * yzminmax.Dispose(); + * entity.Dispose(); + * ray.Dispose(); + * } + * }*/ +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/CollisionLayer.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/CollisionLayer.cs.meta new file mode 100644 index 0000000..fe7ffd9 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/CollisionLayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e5fa2fffb4a0324d8781415e2dca6c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/PhysicsComponentDataFromEntity.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/PhysicsComponentDataFromEntity.cs new file mode 100644 index 0000000..b1bb814 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/PhysicsComponentDataFromEntity.cs @@ -0,0 +1,178 @@ +using System; +using System.Diagnostics; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + //Specifying this as a NativeContainer prevents this value from being stored in a NativeContainer. + /// + /// A struct representing an Entity that can potentially index a PhysicsComponentDataFromEntity safely in parallel, + /// or will throw an error when safety checks are enabled and the safety cannot be guaranteed. + /// This type can be implicitly converted to the Entity type. + /// + [NativeContainer] + public struct SafeEntity + { + internal Entity entity; + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + public static implicit operator Entity(SafeEntity e) => new Entity + { + Index = math.abs(e.entity.Index), Version = e.entity.Version + }; +#else + public static implicit operator Entity(SafeEntity e) => e.entity; +#endif + } + + /// + /// A struct which wraps ComponentDataFromEntity and allows for performing + /// Read-Write access in parallel using SafeEntity types when it is guaranteed safe to do so. + /// You can implicitly cast a ComponentDataFromEntity to this type. + /// + /// A type implementing IComponentData + public struct PhysicsComponentDataFromEntity where T : struct, IComponentData + { + [NativeDisableParallelForRestriction] + internal ComponentDataFromEntity cdfe; + + /// + /// Reads or writes the component on the entity represented by safeEntity. + /// When safety checks are enabled, this throws when parallel safety cannot + /// be guaranteed. + /// + /// A safeEntity representing an entity that may be safe to access + /// + public T this[SafeEntity safeEntity] + { + get + { + ValidateSafeEntityIsSafe(safeEntity); + return cdfe[safeEntity.entity]; + } + set + { + ValidateSafeEntityIsSafe(safeEntity); + cdfe[safeEntity.entity] = value; + } + } + + /// + /// Fetches the component on the entity represented by safeEntity if the entity has the component. + /// When safety checks are enabled, this throws when parallel safety cannot + /// be guaranteed. + /// + /// A safeEntity representing an entity that may be safe to access + /// The fetched component + /// True if the entity had the component + public bool TryGetComponent(SafeEntity safeEntity, out T componentData) + { + ValidateSafeEntityIsSafe(safeEntity); + return cdfe.TryGetComponent(safeEntity, out componentData); + } + + /// + /// Checks if the entity represented by SafeEntity has the component specified. + /// This check is always valid regardless of whether such a component would be + /// safe to access. + /// + public bool HasComponent(SafeEntity safeEntity) => cdfe.HasComponent(safeEntity); + + /// + /// This is identical to ComponentDataFromEntity.DidChange(). + /// Note that neither method is deterministic and both can be prone to race conditions. + /// + public bool DidChange(SafeEntity safeEntity, uint version) => cdfe.DidChange(safeEntity, version); + + public static implicit operator PhysicsComponentDataFromEntity(ComponentDataFromEntity componentDataFromEntity) + { + return new PhysicsComponentDataFromEntity { cdfe = componentDataFromEntity }; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void ValidateSafeEntityIsSafe(SafeEntity safeEntity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (safeEntity.entity.Index < 0) + { + throw new InvalidOperationException("PhysicsComponentDataFromEntity cannot be used inside a RunImmediate context. Use ComponentDataFromEntity instead."); + } +#endif + } + } + + /// + /// A struct which wraps BufferFromEntity and allows for performing + /// Read-Write access in parallel using SafeEntity types when it is guaranteed safe to do so. + /// You can implicitly cast a BufferFromEntity to this type. + /// + /// A type implementing IComponentData + public struct PhysicsBufferFromEntity where T : struct, IBufferElementData + { + [NativeDisableParallelForRestriction] + internal BufferFromEntity bfe; + + /// + /// Gets a reference to the buffer on the entity represented by safeEntity. + /// When safety checks are enabled, this throws when parallel safety cannot + /// be guaranteed. + /// + /// A safeEntity representing an entity that may be safe to access + /// + public DynamicBuffer this[SafeEntity safeEntity] + { + get + { + ValidateSafeEntityIsSafe(safeEntity); + return bfe[safeEntity.entity]; + } + } + + /// + /// Fetches the buffer on the entity represented by safeEntity if the entity has the buffer type. + /// When safety checks are enabled, this throws when parallel safety cannot + /// be guaranteed. + /// + /// A safeEntity representing an entity that may be safe to access + /// The fetched buffer + /// True if the entity had the buffer type + public bool TryGetComponent(SafeEntity safeEntity, out DynamicBuffer bufferData) + { + ValidateSafeEntityIsSafe(safeEntity); + return bfe.TryGetBuffer(safeEntity, out bufferData); + } + + /// + /// Checks if the entity represented by SafeEntity has the buffer type specified. + /// This check is always valid regardless of whether such a buffer would be + /// safe to access. + /// + public bool HasComponent(SafeEntity safeEntity) => bfe.HasComponent(safeEntity.entity); + + /// + /// This is identical to BufferFromEntity.DidChange(). + /// Note that neither method is deterministic and both can be prone to race conditions. + /// + public bool DidChange(SafeEntity safeEntity, uint version) => bfe.DidChange(safeEntity, version); + + public static implicit operator PhysicsBufferFromEntity(BufferFromEntity bufferFromEntity) + { + return new PhysicsBufferFromEntity { bfe = bufferFromEntity }; + } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void ValidateSafeEntityIsSafe(SafeEntity safeEntity) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (safeEntity.entity.Index < 0) + { + throw new InvalidOperationException("PhysicsBufferFromEntity cannot be used inside a RunImmediate context. Use BufferFromEntity instead."); + } +#endif + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/PhysicsComponentDataFromEntity.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/PhysicsComponentDataFromEntity.cs.meta new file mode 100644 index 0000000..e3eb973 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/PhysicsComponentDataFromEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 553a0ba4e57b1b14292959c2e670f1ee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/QueryResults.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/QueryResults.cs new file mode 100644 index 0000000..1ed3536 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/QueryResults.cs @@ -0,0 +1,139 @@ +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + public struct PointDistanceResult + { + /// + /// The nearest point on the collider in world space + /// + public float3 hitpoint; + /// + /// The distance from the point to the collider, negative if the point was inside the collider + /// + public float distance; + /// + /// The outward normal of the collider at the nearest point on the surface in world space + /// + public float3 normal; + /// + /// If the collider is composed of multiple primitives, this is the index of the primitive that generated the result + /// + public int subColliderIndex; + } + + public struct ColliderDistanceResult + { + /// + /// The nearest point on colliderA in world space + /// + public float3 hitpointA; + /// + /// The nearest point on colliderB in world space + /// + public float3 hitpointB; + /// + /// The outward normal of colliderA at hitpointA in world space + /// + public float3 normalA; + /// + /// The outward normal of colliderB at hitpointB in world space + /// + public float3 normalB; + /// + /// The distance between the colliders, negative if the colliders are penetrating + /// + public float distance; + /// + /// If colliderA is composed of multiple primitives, this is the index of the primitive that generated the result + /// + public int subColliderIndexA; + /// + /// If colliderB is composed of multiple primitives, this is the index of the primitive that generated the result + /// + public int subColliderIndexB; + } + + public struct RaycastResult + { + /// + /// Where the ray hit in world space + /// + public float3 position; + /// + /// The distance the ray traveled before hitting + /// + public float distance; + /// + /// The outward normal of the collider at position in world space + /// + public float3 normal; + /// + /// If the collider is composed of multiple primitives, this is the index of the primitive that generated the result + /// + public int subColliderIndex; + } + + public struct ColliderCastResult + { + /// + /// Where the hit occurred on the translated caster in world space. Should be the same as hitpointOnTarget + /// + public float3 hitpointOnCaster; + /// + /// Where the hit occurred on the stationary target in world space. Should be the same as hitpointOnCaster + /// + public float3 hitpointOnTarget; + /// + /// The outward normal of the caster at the hitpoint in world space + /// + public float3 normalOnCaster; + /// + /// The outward normal of the target at the hitpoint in world space + /// + public float3 normalOnTarget; + /// + /// The distance the caster traveled before hitting + /// + public float distance; + /// + /// If the caster is composed of multiple primitives, this is the index of the primitive that generated the result + /// + public int subColliderIndexOnCaster; + /// + /// If the target is composed of multiple primitives, this is the index of the primitive that generated the result + /// + public int subColliderIndexOnTarget; + } + + public struct LayerBodyInfo + { + /// + /// The body in the CollisionLayer that generated the corresponding result + /// + public ColliderBody body; + /// + /// The AABB that was stored alongside the body in the CollisionLayer that generated the corresponding result + /// + public Aabb aabb; + /// + /// The index in the CollisionLayer of the body that generated the corresponding result + /// + public int bodyIndex; + + /// + /// The entity in the CollisionLayer that generated the corresponding result + /// + public Entity entity => body.entity; + /// + /// The collider in the CollisionLayer that generated the corresponding result + /// + public Collider collider => body.collider; + /// + /// The transform in the CollisionLayer that generated the corresponding result + /// + public RigidTransform transform => body.transform; + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/QueryResults.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/QueryResults.cs.meta new file mode 100644 index 0000000..9946c0f --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/QueryResults.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aafd757a6ae274b409c57d3aa99541fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/RayPsyshock.cs b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/RayPsyshock.cs new file mode 100644 index 0000000..79fbcea --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/RayPsyshock.cs @@ -0,0 +1,107 @@ +using Unity.Mathematics; + +namespace Latios.Psyshock +{ + /// + /// A ray that is used for raycasts. It is better to use a Ray instance when + /// raycasting multiple elements using the same start and end points + /// + public struct Ray + { + public float3 start + { + get { return m_origin; } + set + { + float3 e = end; + m_origin = value; + m_displacement = e - value; + m_reciprocalDisplacement = math.select(math.rcp(m_displacement), math.sqrt(float.MaxValue), m_displacement == float3.zero); + } + } + + public float3 end + { + get { return m_origin + m_displacement; } + set + { + m_displacement = value - m_origin; + m_reciprocalDisplacement = math.select(math.rcp(m_displacement), math.sqrt(float.MaxValue), m_displacement == float3.zero); + } + } + + public float3 displacement => m_displacement; + public float3 reciprocalDisplacement => m_reciprocalDisplacement; + + private float3 m_origin; + private float3 m_displacement; + private float3 m_reciprocalDisplacement; + + public Ray(float3 start, float3 end) + { + m_origin = start; + m_displacement = end - start; + m_reciprocalDisplacement = math.select(math.rcp(m_displacement), math.sqrt(float.MaxValue), m_displacement == float3.zero); + } + + public Ray(float3 start, float3 direction, float length) : this(start, start + math.normalizesafe(direction) * length) + { + } + + public static Ray TransformRay(RigidTransform transform, Ray ray) + { + var disp = math.rotate(transform, ray.m_displacement); + Ray newRay = new Ray + { + m_origin = math.transform(transform, ray.m_origin), + m_displacement = disp, + m_reciprocalDisplacement = math.select(math.rcp(disp), math.sqrt(float.MaxValue), disp == float3.zero), + }; + return newRay; + } + } + + public struct Ray2d + { + public float2 start + { + get { return m_origin; } + set + { + float2 e = end; + m_origin = value; + m_displacement = e - value; + m_reciprocalDisplacement = math.select(math.rcp(m_displacement), math.sqrt(float.MaxValue), m_displacement == float2.zero); + } + } + + public float2 end + { + get { return m_origin + m_displacement; } + set + { + m_displacement = value - m_origin; + m_reciprocalDisplacement = math.select(math.rcp(m_displacement), math.sqrt(float.MaxValue), m_displacement == float2.zero); + } + } + + public float2 displacement => m_displacement; + public float2 reciprocalDisplacement => m_reciprocalDisplacement; + + private float2 m_origin; + private float2 m_displacement; + private float2 m_reciprocalDisplacement; + + public Ray2d(float2 start, float2 end) + { + m_origin = start; + m_displacement = end - start; + m_reciprocalDisplacement = math.select(math.rcp(m_displacement), math.sqrt(float.MaxValue), m_displacement == float2.zero); + } + + public Ray2d(float2 start, float2 direction, float length) : this(start, start + math.normalizesafe(direction) * length) + { + } + } +} + diff --git a/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/RayPsyshock.cs.meta b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/RayPsyshock.cs.meta new file mode 100644 index 0000000..3f20da5 --- /dev/null +++ b/Packages/com.latios.latios-framework/PsyshockPhysics/Physics/Types/RayPsyshock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f319b21352e8c5d4f8b8b50199fa8437 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.latios.latios-framework/package.json b/Packages/com.latios.latios-framework/package.json new file mode 100644 index 0000000..ef0f172 --- /dev/null +++ b/Packages/com.latios.latios-framework/package.json @@ -0,0 +1,22 @@ +{ + "name": "com.latios.latiosframework", + "displayName": "Latios Framework for DOTS", + "version": "0.6.0-preview", + "unity": "2020.3", + "description": "Latios Framework for DOTS is a collection of tools, algorithms, and API extensions developed by a hardcore hobbyist game developer.\n\nThis package includes all of the following packages:\n\u25aa Core\n\u25aa Physics\n\nThis version is the embedded version from a development project and may contain unstalbe features..", + "dependencies": { + "com.unity.entities": "0.50.1-preview.2", + "com.unity.audio.dspgraph": "0.1.0-preview.22", + "com.unity.rendering.hybrid": "0.50.0-preview.44" + }, + "unityRelease": "30f1", + "keywords": [ + "dots", + "latios", + "framework" + ], + "author": { + "name": "Dreaming I'm Latios" + }, + "hideInEditor": false +} \ No newline at end of file diff --git a/Packages/com.latios.latios-framework/package.json.meta b/Packages/com.latios.latios-framework/package.json.meta new file mode 100644 index 0000000..829016f --- /dev/null +++ b/Packages/com.latios.latios-framework/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a2e7cc2e6851cc54d9e3ea613db460b8 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json new file mode 100644 index 0000000..be83574 --- /dev/null +++ b/Packages/manifest.json @@ -0,0 +1,49 @@ +{ + "dependencies": { + "com.unity.collab-proxy": "1.17.1", + "com.unity.feature.development": "1.0.1", + "com.unity.ide.rider": "3.0.15", + "com.unity.ide.visualstudio": "2.0.16", + "com.unity.ide.vscode": "1.2.5", + "com.unity.inputsystem": "1.4.2", + "com.unity.recorder": "3.0.3", + "com.unity.render-pipelines.high-definition": "12.1.7", + "com.unity.rendering.hybrid": "0.51.1-preview.21", + "com.unity.test-framework": "1.1.31", + "com.unity.textmeshpro": "3.0.6", + "com.unity.timeline": "1.6.4", + "com.unity.ugui": "1.0.0", + "com.unity.visualscripting": "1.7.8", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json new file mode 100644 index 0000000..bf0d40e --- /dev/null +++ b/Packages/packages-lock.json @@ -0,0 +1,678 @@ +{ + "dependencies": { + "com.latios.latiosframework": { + "version": "file:com.latios.latios-framework", + "depth": 0, + "source": "embedded", + "dependencies": { + "com.unity.entities": "0.50.1-preview.2", + "com.unity.audio.dspgraph": "0.1.0-preview.22", + "com.unity.rendering.hybrid": "0.50.0-preview.44" + } + }, + "com.unity.audio.dspgraph": { + "version": "0.1.0-preview.22", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.6.4", + "com.unity.media.utilities": "0.1.0-preview.13", + "com.unity.test-framework": "1.1.27", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.video": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.burst": { + "version": "1.6.6", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collab-proxy": { + "version": "1.17.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.services.core": "1.0.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collections": { + "version": "1.4.0", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.6.6", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.test-framework": "1.1.31" + }, + "url": "https://packages.unity.com" + }, + "com.unity.editorcoroutines": { + "version": "1.0.0", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.entities": { + "version": "0.51.1-preview.21", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.6.6", + "com.unity.properties": "1.7.0-preview", + "com.unity.properties.ui": "1.7.0-preview", + "com.unity.serialization": "1.7.0-preview.1", + "com.unity.collections": "1.4.0", + "com.unity.jobs": "0.70.0-preview.7", + "com.unity.mathematics": "1.2.6", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.test-framework.performance": "2.8.0-preview", + "com.unity.nuget.mono-cecil": "1.10.1", + "com.unity.scriptablebuildpipeline": "1.19.2", + "com.unity.platforms": "0.51.1-preview.21", + "com.unity.roslyn": "0.2.2-preview", + "com.unity.profiling.core": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "1.0.6", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.feature.development": { + "version": "1.0.1", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ide.visualstudio": "2.0.16", + "com.unity.ide.rider": "3.0.15", + "com.unity.ide.vscode": "1.2.5", + "com.unity.editorcoroutines": "1.0.0", + "com.unity.performance.profile-analyzer": "1.1.1", + "com.unity.test-framework": "1.1.31", + "com.unity.testtools.codecoverage": "1.0.1" + } + }, + "com.unity.ide.rider": { + "version": "3.0.15", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.16", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.inputsystem": { + "version": "1.4.2", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.jobs": { + "version": "0.70.0-preview.7", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.collections": "1.4.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.mathematics": { + "version": "1.2.6", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.media.utilities": { + "version": "0.1.0-preview.13", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.burst": "1.5.3", + "com.unity.test-framework": "1.1.23" + }, + "url": "https://packages.unity.com" + }, + "com.unity.nuget.mono-cecil": { + "version": "1.11.4", + "depth": 3, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.0.2", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.performance.profile-analyzer": { + "version": "1.1.1", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.platforms": { + "version": "0.51.1-preview.21", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.properties": "1.7.0-preview", + "com.unity.properties.ui": "1.7.0-preview", + "com.unity.scriptablebuildpipeline": "1.19.2", + "com.unity.serialization": "1.7.0-preview.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.profiling.core": { + "version": "1.0.2", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.properties": { + "version": "1.7.0-preview", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.nuget.mono-cecil": "0.1.6-preview.2", + "com.unity.test-framework.performance": "2.3.1-preview" + }, + "url": "https://packages.unity.com" + }, + "com.unity.properties.ui": { + "version": "1.7.0-preview", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.properties": "1.7.0-preview", + "com.unity.serialization": "1.7.0-preview.1", + "com.unity.modules.uielements": "1.0.0", + "com.unity.test-framework.performance": "2.3.1-preview" + }, + "url": "https://packages.unity.com" + }, + "com.unity.recorder": { + "version": "3.0.3", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.timeline": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.render-pipelines.core": { + "version": "12.1.7", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.render-pipelines.high-definition": { + "version": "12.1.7", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.mathematics": "1.2.4", + "com.unity.burst": "1.6.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.render-pipelines.core": "12.1.7", + "com.unity.shadergraph": "12.1.7", + "com.unity.visualeffectgraph": "12.1.7", + "com.unity.render-pipelines.high-definition-config": "12.1.7" + } + }, + "com.unity.render-pipelines.high-definition-config": { + "version": "12.1.7", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "12.1.7" + } + }, + "com.unity.rendering.hybrid": { + "version": "0.51.1-preview.21", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.entities": "0.51.1-preview.21" + }, + "url": "https://packages.unity.com" + }, + "com.unity.roslyn": { + "version": "0.2.2-preview", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.scriptablebuildpipeline": { + "version": "1.20.1", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.searcher": { + "version": "4.9.1", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.serialization": { + "version": "1.7.0-preview.1", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.collections": "0.12.0-preview.13", + "com.unity.burst": "1.3.5", + "com.unity.jobs": "0.5.0-preview.14", + "com.unity.properties": "1.7.0-preview", + "com.unity.test-framework.performance": "2.3.1-preview" + }, + "url": "https://packages.unity.com" + }, + "com.unity.services.core": { + "version": "1.4.2", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.modules.androidjni": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.settings-manager": { + "version": "1.0.3", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "12.1.7", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "12.1.7", + "com.unity.searcher": "4.9.1" + } + }, + "com.unity.test-framework": { + "version": "1.1.31", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.test-framework.performance": { + "version": "2.8.0-preview", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.testtools.codecoverage": { + "version": "1.0.1", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.0.16", + "com.unity.settings-manager": "1.0.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.6.4", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualeffectgraph": { + "version": "12.1.7", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.shadergraph": "12.1.7", + "com.unity.render-pipelines.core": "12.1.7" + } + }, + "com.unity.visualscripting": { + "version": "1.7.8", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} diff --git a/ProjectSettings/AudioManager.asset b/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..27287fe --- /dev/null +++ b/ProjectSettings/AudioManager.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 0 diff --git a/ProjectSettings/ClusterInputManager.asset b/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..e7886b2 --- /dev/null +++ b/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..1596c42 --- /dev/null +++ b/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0.1 + m_ClothInterCollisionStiffness: 0.2 + m_ContactsGeneration: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 0 + m_ClothInterCollisionSettingsToggle: 0 + m_ClothGravity: {x: 0, y: -9.81, z: 0} + m_ContactPairsMode: 0 + m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 250, y: 250, z: 250} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_EnableUnifiedHeightmaps: 1 + m_SolverType: 0 + m_DefaultMaxAngularSpeed: 50 diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..a872089 --- /dev/null +++ b/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,11 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: + - enabled: 1 + path: Assets/Scenes/TitleAndMenu.unity + guid: 8124e5870f4fd4c779e7a5f994e84ad1 + m_configObjects: {} diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..231dcc6 --- /dev/null +++ b/ProjectSettings/EditorSettings.asset @@ -0,0 +1,43 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 2 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;asmref;rsp + m_ProjectGenerationRootNamespace: + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_AsyncShaderCompilation: 1 + m_CachingShaderPreprocessor: 1 + m_PrefabModeAllowAutoSave: 1 + m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptions: 3 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 + m_UseLegacyProbeSampleCount: 0 + m_SerializeInlineMappingsOnOneLine: 0 + m_DisableCookiesInLightmapper: 0 + m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 + m_CacheServerMode: 0 + m_CacheServerEndpoint: + m_CacheServerNamespacePrefix: default + m_CacheServerEnableDownload: 1 + m_CacheServerEnableUpload: 1 + m_CacheServerEnableAuth: 0 + m_CacheServerEnableTls: 0 + m_CacheServerValidationMode: 2 diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..bbff704 --- /dev/null +++ b/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,70 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 14 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_LegacyDeferred: + m_Mode: 1 + m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_VideoShadersIncludeMode: 2 + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_PreloadShadersBatchTimeLimit: -1 + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, + type: 0} + m_CustomRenderPipeline: {fileID: 11400000, guid: b9f3086da92434da0bc1518f19f0ce86, + type: 2} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_LightsUseLinearIntensity: 1 + m_LightsUseColorTemperature: 1 + m_DefaultRenderingLayerMask: 257 + m_LogWhenShaderIsCompiled: 0 + m_SRPDefaultSettings: + UnityEngine.Rendering.HighDefinition.HDRenderPipeline: {fileID: 11400000, guid: ac0316ca287ba459492b669ff1317a6f, + type: 2} diff --git a/ProjectSettings/HDRPProjectSettings.asset b/ProjectSettings/HDRPProjectSettings.asset new file mode 100644 index 0000000..31099c8 --- /dev/null +++ b/ProjectSettings/HDRPProjectSettings.asset @@ -0,0 +1,22 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 63a2978a97e4fc04cb9d905947216f3d, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ProjectSettingFolderPath: Settings/HDRPDefaultResources + m_LastMaterialVersion: 12 + m_WizardPopupAtStart: 1 + m_Version: 2 + m_ObsoleteWizardPopupAlreadyShownOnce: 1 + m_ObsoleteWizardActiveTab: 0 + m_ObsoleteWizardNeedRestartAfterChangingToDX12: 0 + m_ObsoleteWizardNeedToRunFixAllAgainAfterDomainReload: 0 diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..b16147e --- /dev/null +++ b/ProjectSettings/InputManager.asset @@ -0,0 +1,487 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: joystick button 8 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: backspace + altNegativeButton: + altPositiveButton: joystick button 9 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Reset + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Next + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page down + altNegativeButton: + altPositiveButton: joystick button 5 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Previous + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page up + altNegativeButton: + altPositiveButton: joystick button 4 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Validate + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Persistent + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: right shift + altNegativeButton: + altPositiveButton: joystick button 2 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Multiplier + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: joystick button 3 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 6 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 5 + joyNum: 0 diff --git a/ProjectSettings/MemorySettings.asset b/ProjectSettings/MemorySettings.asset new file mode 100644 index 0000000..5b5face --- /dev/null +++ b/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/ProjectSettings/NavMeshAreas.asset b/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..3b0b7c3 --- /dev/null +++ b/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,91 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset new file mode 100644 index 0000000..e549497 --- /dev/null +++ b/ProjectSettings/PackageManagerSettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreReleasePackages: 0 + m_EnablePackageDependencies: 1 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 0 + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_Modified: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -846 + m_OriginalInstanceId: -848 + m_LoadAssets: 0 diff --git a/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json b/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json new file mode 100644 index 0000000..ad11087 --- /dev/null +++ b/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json @@ -0,0 +1,7 @@ +{ + "m_Name": "Settings", + "m_Path": "ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json", + "m_Dictionary": { + "m_DictionaryValues": [] + } +} \ No newline at end of file diff --git a/ProjectSettings/Physics2DSettings.asset b/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..6c5cf8a --- /dev/null +++ b/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,56 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 0 + m_AutoSyncTransforms: 0 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/ProjectSettings/PresetManager.asset b/ProjectSettings/PresetManager.asset new file mode 100644 index 0000000..67a94da --- /dev/null +++ b/ProjectSettings/PresetManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..d1751d8 --- /dev/null +++ b/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,877 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 23 + productGUID: 3ce8430648220c349a8a620cfa56cc1b + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: Latios Framework Open Project + productName: ATAR + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + iosAllowHTTPDownload: 1 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidBlitType: 0 + androidResizableWindow: 0 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 0 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 0 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + stadiaPresentMode: 0 + stadiaTargetFramerate: 0 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 0 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + m_SupportedAspectRatios: + 4:3: 1 + 5:4: 1 + 16:10: 1 + 16:9: 1 + Others: 1 + bundleVersion: 0.1.0 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + useHDRDisplay: 0 + D3DHDRBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 + applicationIdentifier: + Standalone: com.UnityTechnologies.com.unity.template.hdrp-blank + buildNumber: + Standalone: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 1 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 22 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + APKExpansionFiles: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSTargetOSVersionString: 11.0 + tvOSSdkVersion: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 11.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: c71a6e77368cc6048998f34f4bbe2b86 + templatePackageId: com.unity.template.hdrp-blank@3.1.0 + templateDefaultScene: Assets/OutdoorsScene.unity + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 1 + AndroidTargetDevices: 0 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + chromeosInputEmulation: 1 + AndroidMinifyWithR8: 0 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 + m_BuildTargetGraphicsJobs: + - m_BuildTarget: MacStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: Switch + m_GraphicsJobs: 1 + - m_BuildTarget: MetroSupport + m_GraphicsJobs: 1 + - m_BuildTarget: AppleTVSupport + m_GraphicsJobs: 0 + - m_BuildTarget: BJMSupport + m_GraphicsJobs: 1 + - m_BuildTarget: LinuxStandaloneSupport + m_GraphicsJobs: 1 + - m_BuildTarget: PS4Player + m_GraphicsJobs: 1 + - m_BuildTarget: iOSSupport + m_GraphicsJobs: 0 + - m_BuildTarget: WindowsStandaloneSupport + m_GraphicsJobs: 1 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobs: 1 + - m_BuildTarget: LuminSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AndroidPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: WebGLSupport + m_GraphicsJobs: 0 + m_BuildTargetGraphicsJobMode: [] + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: LinuxStandaloneSupport + m_APIs: 15000000 + m_Automatic: 0 + - m_BuildTarget: MacStandaloneSupport + m_APIs: 10000000 + m_Automatic: 0 + - m_BuildTarget: WindowsStandaloneSupport + m_APIs: 02000000 + m_Automatic: 1 + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: AndroidPlayer + m_APIs: 0b00000008000000 + m_Automatic: 0 + m_BuildTargetVRSettings: [] + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - m_BuildTarget: Standalone + m_EncodingQuality: 2 + - m_BuildTarget: Android + m_EncodingQuality: 2 + - m_BuildTarget: Lumin + m_EncodingQuality: 2 + - m_BuildTarget: Windows Store Apps + m_EncodingQuality: 2 + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetNormalMapEncoding: [] + m_BuildTargetDefaultTextureCompressionFormat: [] + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchUseGOLDLinker: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchTouchScreenUsage: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchPlayerConnectionEnabled: 1 + switchUseNewStyleFilepaths: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 0 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: {} + il2cppCompilerConfiguration: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + enableRoslynAnalyzers: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 0 + assemblyVersionValidation: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: com.unity.template-starter-kit + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: com.unity.template-starter-kit + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, + a: 1} + metroSplashScreenUseBackgroundColor: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + apiCompatibilityLevel: 6 + activeInputHandler: 2 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: + organizationId: + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + playerDataPath: + forceSRGBBlit: 1 + virtualTexturingSupportEnabled: 0 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..85465a1 --- /dev/null +++ b/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 2021.3.8f1 +m_EditorVersionWithRevision: 2021.3.8f1 (b30333d56e81) diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..a89cc22 --- /dev/null +++ b/ProjectSettings/QualitySettings.asset @@ -0,0 +1,133 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 0 + m_QualitySettings: + - serializedVersion: 2 + name: High Fidelity + pixelLightCount: 0 + shadows: 2 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 255 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 36dd385e759c96147b6463dcd1149c11, + type: 2} + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Balanced + pixelLightCount: 0 + shadows: 2 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 255 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 3e2e6bfc59709614ab90c0cd7d755e48, + type: 2} + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Performant + pixelLightCount: 0 + shadows: 2 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 255 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 168a2336534e4e043b2a210b6f8d379a, + type: 2} + excludedTargetPlatforms: [] + m_PerPlatformDefaultQuality: + Android: 0 + CloudRendering: 0 + Lumin: 0 + Nintendo Switch: 0 + PS4: 0 + Server: 0 + Stadia: 0 + Standalone: 0 + WebGL: 0 + Windows Store Apps: 0 + XboxOne: 0 + iPhone: 0 + tvOS: 0 diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..6f3e60f --- /dev/null +++ b/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,167 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": false + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicMaterial", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": false + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "ignore": false, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/ProjectSettings/ShaderGraphSettings.asset b/ProjectSettings/ShaderGraphSettings.asset new file mode 100644 index 0000000..9b28428 --- /dev/null +++ b/ProjectSettings/ShaderGraphSettings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de02f9e1d18f588468e474319d09a723, type: 3} + m_Name: + m_EditorClassIdentifier: + customInterpolatorErrorThreshold: 32 + customInterpolatorWarningThreshold: 16 diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..1c92a78 --- /dev/null +++ b/ProjectSettings/TagManager.asset @@ -0,0 +1,43 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..558a017 --- /dev/null +++ b/ProjectSettings/TimeManager.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..6125b30 --- /dev/null +++ b/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + m_Enabled: 0 + m_TestMode: 0 + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + CrashReportingSettings: + m_EventUrl: https://perf-events.cloud.unity3d.com + m_Enabled: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/ProjectSettings/VFXManager.asset b/ProjectSettings/VFXManager.asset new file mode 100644 index 0000000..b004c5e --- /dev/null +++ b/ProjectSettings/VFXManager.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 7200000, guid: 84a17cfa13e40ae4082ef42714f0a81c, type: 3} + m_CopyBufferShader: {fileID: 7200000, guid: 23c51f21a3503f6428b527b01f8a2f4e, type: 3} + m_SortShader: {fileID: 7200000, guid: ea257ca3cfb12a642a5025e612af6b2a, type: 3} + m_StripUpdateShader: {fileID: 7200000, guid: 8fa6c4009fe2a4d4486c62736fc30ad8, type: 3} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 + m_CompiledVersion: 4 + m_RuntimeVersion: 22 + m_RuntimeResources: {fileID: 11400000, guid: bc10b42afe3813544bffd38ae2cd893d, type: 2} diff --git a/ProjectSettings/VersionControlSettings.asset b/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 0000000..dca2881 --- /dev/null +++ b/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/ProjectSettings/XRSettings.asset b/ProjectSettings/XRSettings.asset new file mode 100644 index 0000000..482590c --- /dev/null +++ b/ProjectSettings/XRSettings.asset @@ -0,0 +1,10 @@ +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} \ No newline at end of file diff --git a/ProjectSettings/boot.config b/ProjectSettings/boot.config new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7d8a20 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +# An Atypical Team With Atypical Responsibilities (ATAR) – Latios Framework Open Project \#2 + +This is the second open game project using Unity DOTS. It is used to develop and +validate new features and improvements in the Latios Framework, beginning with +version 0.6. A version of the framework is included as an embedded package. + +The Unity version is currently 2021.3.8f1 + +Feel free to clone, play with, and customize this game however you like! + +## Contributing + +Contributions are very much welcome! + +To contribute, fork the project. Make your changes, and make a pull request. Try +to avoid “merge commits” in your commit history. + +If this is a game you can imagine yourself or a friend enjoying, and if you have +the skills or are willing to learn, get involved and help make the experience +better! The game will remain free and open source. There’s no revenue, and +anyone can be involved as much or as little as they want, for as short or as +long as they want. + +## Concept, Gameplay, and Mechanics + +ATAR, which stands for *an Atypical Team with Atypical Responsibilities* is a +combination of tower defense and first-person shooter where a team of magical +girls and their robot army defend castle towers in the sky. Gameplay alternates +between fortification and combat phases. If any magical girl dies, player or +not, it is game over. + +Towers are indestructible. Resources for constructing towers are supplied at a +fixed rate for each wave cleared. There are different types of towers with +different accessories. Some have cover options, and some have special +interactions with specific magical girls. + +Friendly robot sentries can be destroyed during the combat phase, but are +reconstructed at the beginning of a fortification phase at no cost. Resources +for constructing sentries are based on the enemies killed in a wave. + +Magical girls can be recruited to help, but have specific requirements before +they can be recruited. Many require being provided a usable weapon, and some are +picky about the weapon. Other requirements include the number of waves already +defeated, or the existence of a specific type of tower. The UI for displaying +their requirements and abilities emulates a contact list on a cell phone. + +During fortification, towers, sentries, and magical girls are all placed in +rectangular playing field in a top-down view. Sentries and magical girls must be +placed on towers. + +Weapons require two parts to be usable. They require the weapon themselves, and +an Ammo Core that matches the weapon’s ammo type. Weapon drops from enemies are +fairly common. Ammo Core drops are rare. However, each wave guarantees at least +one matching pair of weapon and ammo core. + +Ammo is unlimited. Though most magical girls will get tired if they throw too +many grenades in succession, causing the distance of the grenades to get short +enough to catch the thrower in the blast. + +## Essential Technologies + +In order for this game to reach a functional minimal viable product state, the +following technologies in the Latios Framework will need to be developed: + +- A robust character controller synchronized with animation +- Advanced AI systems +- Inverse Kinematics +- Audio Mixing + +## Future Technologies + +The following features, while not strictly necessary, enhance the experience and +cover more future features of the Latios Framework + +- Storm Waves + - Clouds cover the playable area and are traversable, allowing magical + girls and some sentry types to leave towers. + - Requires Psyshock terrain colliders +- Death Ragdolls + - Requires Psyshock rigid-body physics simulation +- Player Character Customization + - Requires validation of Kinemation runtime bindings and authoring + workflow +- Hair and Cloth Simulation + - Requires implementation +- Character Intro Cutscenes + - Requires Kinemation Blend Shapes and Sequencing +- More? + +## Magical Girl Abilities + +New ideas and suggestions are welcome. Any character without a name is open to +name suggestions. + +### Player + +An esper who can teleport between different towers and has enough medical +training to revive fallen magical girl friends if she reaches them in time. + +### Experienced Esper + +A fellow esper who is more practiced with the craft. Not only can she teleport, +but she can also apply a homing effect to nearby friendly projectiles. She +requires a gun but is otherwise immediately unlockable. + +She has very pale skin, long dark curly hair, and speaks with a low voice. She +tends to be serious and straight-to-the-point with her words. + +### Baseball Fanatic + +She comes from a city named “Coudtop” which is home to the Stampede baseball +team. She requires a grenade type and grenade Ammo Core to be unlocked, but is +otherwise immediately unlockable. She always uses grenades as a primary weapon, +and will only resort to other weapons if she is low on health, sometimes. She +does not tire from throwing grenades. + +She is loud and enthusiastic, and wears baseball attire. + +### Clutch-Klutz + +She has a reputation for being clumsy, but when things get serious she becomes a +focused killing machine. She requires a sniper to be unlocked, but is otherwise +immediately unlockable. When she or a nearby magical girl fall below a health +threshold, she quick-scope crits 100% of her shots. + +She is normally social and sometimes bubbly, but when things are serious, she is +calm, collected, and tries to be encouraging. + +### Ghost + +She is dead and has low accuracy. She requires a gun, and cannot be unlocked +until several waves in as she is stuck in a Dead and Active Association (DA3) +meeting. + +She is spunky and a little sassy, but loyal to the group and seeks personal +vengeance against the enemy. + +### Witch + +She is well-versed in medicinal witch craft and combat broom flight. She +requires a Witchcraft Bunker Tower to be unlocked. She can fly to low-health or +downed teammates, bring them to the nearest bunker, and revive them back to full +health. If given a weapon, she can also be destructive in her flights. + +She tends to be very empathetic. Her witch outfit takes inspiration from medical +attire and symbolism. + +## Third Party Notices + +This project includes an embedded version of the Latios Framework which is +licensed under the Unity Companion License as a derivative work: +