Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,14 @@
using Unity.Entities;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
#if RUKHANKA_WITH_NETCODE
[GhostComponent(PrefabType = GhostPrefabType.Client)]
#endif
public struct CullAnimationsTag: IComponentData, IEnableableComponent { }
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8ea374b0d867d4d46af7d12fe9a9b617
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingComponents.cs
uploadId: 897522
@@ -0,0 +1,105 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
#if HDRP_10_0_0_OR_NEWER
using UnityEngine.Rendering.HighDefinition;
#elif URP_10_0_0_OR_NEWER
using UnityEngine.Rendering.Universal;
#endif
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public class AnimationCullingConfig: MonoBehaviour
{
[Tooltip("Add all cameras that should be used for visibility calculation")]
public Camera[] cullingCameras;
[Tooltip("Add all shadow casting lights for proper shadow occlusion calculation")]
public Light[] shadowCastingLights;
[Tooltip("Use editor scene view as culling camera")]
public bool addEditorSceneCamera = true;
#if HDRP_10_0_0_OR_NEWER
[HideInInspector]
public HDShadowSettings shadowSettings;
#elif URP_10_0_0_OR_NEWER
[HideInInspector]
public UniversalRenderPipelineAsset urpAsset;
#endif
public bool drawCullingVolumes;
public Color cullingVolumeColor = Color.blue;
public bool drawSceneBoundingBoxes;
public Color visibleChunkColor = Color.green;
public Color invisibleChunkColor = Color.red;
public Color visibleRendererColor = Color.white;
public Color invisibleRendererColor = Color.red;
public static AnimationCullingConfig Instance { get; private set; }
/////////////////////////////////////////////////////////////////////////////////
void Awake()
{
if (Instance != null)
throw new Exception($"There is more then single AnimationCullingConfig in scene!");
foreach (var l in shadowCastingLights)
{
if (l == null) continue;
if (l.shadows == LightShadows.None)
Debug.LogWarning($"Animation Culling Config: Light '{l.name}' does not casting shadows. It is meaningless to account it for culling calculation.");
}
#if HDRP_10_0_0_OR_NEWER
var volumes = FindObjectsByType<Volume>(FindObjectsSortMode.None);
foreach (var v in volumes)
{
if (!v.isGlobal) continue;
if (v.sharedProfile.TryGet(out shadowSettings))
{
break;
}
}
#elif URP_10_0_0_OR_NEWER
urpAsset = (UniversalRenderPipelineAsset)GraphicsSettings.currentRenderPipeline;
#endif
Instance = this;
}
/////////////////////////////////////////////////////////////////////////////////
void Update()
{
float shadowDistance = 0;
#if HDRP_10_0_0_OR_NEWER
if (shadowSettings == null)
return;
shadowDistance = shadowSettings.maxShadowDistance.value;
#elif URP_10_0_0_OR_NEWER
if (urpAsset == null)
return;
shadowDistance = urpAsset.shadowDistance;
#endif
foreach (var l in shadowCastingLights)
{
if (l == null || l.type != LightType.Directional) continue;
// Need to update shadow range according to shadow settings
l.range = shadowDistance;
}
}
/////////////////////////////////////////////////////////////////////////////////
void OnDestroy()
{
Instance = null;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2573637d14a0c34488e43cbfb7c74236
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingConfig.cs
uploadId: 897522
@@ -0,0 +1,79 @@
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimationCullingContext: IComponentData, IDisposable
{
public NativeList<float4> cullingPlanes;
public NativeList<int2> cullingVolumePlaneRanges;
public NativeList<LODGroupExtensions.LODParams> lodAffectors;
#if RUKHANKA_DEBUG_INFO
public bool drawCullingVolumes;
public uint cullingVolumeColor;
public bool drawSceneBoundingBoxes;
public uint visibleChunkColor;
public uint invisibleChunkColor;
public uint visibleRendererColor;
public uint invisibleRendererColor;
#endif
public void Dispose()
{
cullingPlanes.Dispose();
cullingVolumePlaneRanges.Dispose();
lodAffectors.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////
internal void AddCullingPlanes(NativeArray<float4> planes)
{
if (planes.Length == 0)
return;
var volumePlaneRanges = new int2(cullingPlanes.Length, planes.Length);
cullingVolumePlaneRanges.Add(volumePlaneRanges);
cullingPlanes.AddRange(planes);
}
/////////////////////////////////////////////////////////////////////////////////
internal void AddLODAffector(AnimationCullingContextUpdateSystem.CullingCameraData ccd, float lodBias)
{
var lp = new LODGroupExtensions.LODParams()
{
cameraPos = ccd.pos,
orthosize = ccd.orthographicSize,
distanceScale = CalculateDistanceScale(ccd.fieldOfView, lodBias, ccd.isOrthographic, ccd.orthographicSize),
isOrtho = ccd.isOrthographic
};
lodAffectors.Add(lp);
}
/////////////////////////////////////////////////////////////////////////////////
float CalculateDistanceScale(float fov, float globalLodBias, bool isOrtho, float orthoSize)
{
float rv;
if (isOrtho)
{
rv = 2.0f * orthoSize / globalLodBias;
}
else
{
var halfAngle = math.tan(math.radians(fov * 0.5f));
rv = 2.0f * halfAngle / globalLodBias;
}
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a28be9e97595a9149a02bba33792b217
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingContextSingleton.cs
uploadId: 897522
@@ -0,0 +1,170 @@
#if !RUKHANKA_NO_DEBUG_DRAWER
using Rukhanka.DebugDrawer;
#endif
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
using UnityEditor;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[UpdateAfter(typeof(FillAnimationsFromControllerSystem))]
public partial class AnimationCullingContextUpdateSystem: SystemBase
{
internal struct CullingCameraData
{
public float3 pos;
public float3 viewDir;
public float4x4 cullingMatrix;
public float fieldOfView;
public bool isOrthographic;
public float orthographicSize;
}
internal struct LightData
{
public float4 posOrDir;
public float range;
}
/////////////////////////////////////////////////////////////////////////////////
protected override void OnCreate()
{
#if RUKHANKA_WITH_NETCODE
bool isServer = World.IsServer();
if (isServer)
Enabled = false;
#endif
}
/////////////////////////////////////////////////////////////////////////////////
protected override void OnUpdate()
{
var acc = AnimationCullingConfig.Instance;
if (acc == null)
return;
if (!SystemAPI.TryGetSingletonRW<AnimationCullingContext>(out var actx))
{
var newContext = new AnimationCullingContext();
newContext.cullingPlanes = new (32, Allocator.Persistent);
newContext.cullingVolumePlaneRanges = new (32, Allocator.Persistent);
newContext.lodAffectors = new (32, Allocator.Persistent);
EntityManager.CreateSingleton(newContext, "Rukhanka.AnimationCullingContextSingleton");
actx = SystemAPI.GetSingletonRW<AnimationCullingContext>();
}
var cullingCameras = new NativeList<CullingCameraData>(acc.cullingCameras.Length, CheckedStateRef.WorldUpdateAllocator);
foreach (var cullingCam in acc.cullingCameras)
{
AddCullingCamera(cullingCam, ref cullingCameras);
}
var shadowCastingLights = new NativeList<LightData>(acc.shadowCastingLights.Length, CheckedStateRef.WorldUpdateAllocator);
foreach (var l in acc.shadowCastingLights)
{
AddShadowCastingLight(l, ref shadowCastingLights);
}
#if UNITY_EDITOR
if (acc.addEditorSceneCamera)
{
var sceneCam = SceneView.lastActiveSceneView?.camera;
AddCullingCamera(sceneCam, ref cullingCameras);
}
#endif
Dependency = BuildCullingContext(cullingCameras, shadowCastingLights, ref actx.ValueRW, acc, Dependency);
}
/////////////////////////////////////////////////////////////////////////////////
void AddShadowCastingLight(Light l, ref NativeList<LightData> shadowCastingLights)
{
if (l == null || l.shadows == LightShadows.None || !l.isActiveAndEnabled) return;
var lt = l.transform;
var ld = new LightData()
{
posOrDir = l.type == LightType.Directional ? new float4(-lt.forward, 0) : new float4(lt.position, 1),
range = l.range,
};
shadowCastingLights.Add(ld);
}
/////////////////////////////////////////////////////////////////////////////////
void AddCullingCamera(Camera cam, ref NativeList<CullingCameraData> cullingCameras)
{
if (cam == null) return;
var camTransform = cam.transform;
var ccd = new CullingCameraData()
{
pos = camTransform.position,
viewDir = camTransform.forward,
cullingMatrix = cam.cullingMatrix,
fieldOfView = cam.fieldOfView,
isOrthographic = cam.orthographic,
orthographicSize = cam.orthographicSize
};
cullingCameras.Add(ccd);
}
/////////////////////////////////////////////////////////////////////////////////
JobHandle BuildCullingContext(NativeList<CullingCameraData> cullingCameras, NativeList<LightData> shadowCastingLights, ref AnimationCullingContext actx, AnimationCullingConfig acc, JobHandle dependsOn)
{
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
SystemAPI.TryGetSingletonRW<Drawer>(out var dd);
#endif
var buildCullingContextJob = new BuildCullingContextJob()
{
cullingCameras = cullingCameras.AsArray(),
shadowCastingLights = shadowCastingLights.AsArray(),
lodBias = QualitySettings.lodBias,
actx = actx,
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
dd = dd.ValueRW,
#endif
};
var rv = buildCullingContextJob.Schedule(dependsOn);
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
actx.invisibleChunkColor = ColorTools.ToUint(acc.invisibleChunkColor);
actx.visibleChunkColor = ColorTools.ToUint(acc.visibleChunkColor);
actx.invisibleRendererColor = ColorTools.ToUint(acc.invisibleRendererColor);
actx.visibleRendererColor = ColorTools.ToUint(acc.visibleRendererColor);
actx.drawSceneBoundingBoxes = acc.drawSceneBoundingBoxes;
actx.drawCullingVolumes = acc.drawCullingVolumes;
actx.cullingVolumeColor = ColorTools.ToUint(acc.cullingVolumeColor);
#endif
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
protected override void OnDestroy()
{
if (SystemAPI.TryGetSingleton<AnimationCullingContext>(out var actx))
{
actx.Dispose();
EntityManager.DestroyEntity(SystemAPI.GetSingletonEntity<AnimationCullingContext>());
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b13e4736832aacd4b8bc21c586cb54c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingContextUpdateSystem.cs
uploadId: 897522
@@ -0,0 +1,199 @@
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
#if !RUKHANKA_NO_DEBUG_DRAWER
using Rukhanka.DebugDrawer;
#endif
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial class AnimationCullingContextUpdateSystem
{
[BurstCompile]
struct BuildCullingContextJob: IJob
{
[ReadOnly]
public NativeArray<CullingCameraData> cullingCameras;
[ReadOnly]
public NativeArray<LightData> shadowCastingLights;
public float lodBias;
public AnimationCullingContext actx;
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
public Drawer dd;
#endif
/////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
actx.cullingPlanes.Clear();
actx.cullingVolumePlaneRanges.Clear();
actx.lodAffectors.Clear();
foreach (var cm in cullingCameras)
{
var frustum = FrustumVolume.MakeCullingCameraFrustum(cm);
actx.AddLODAffector(cm, lodBias);
var i = 0;
for (; i < shadowCastingLights.Length; ++i)
{
var l = shadowCastingLights[i];
var cullingPlanes = MakeShadowVolume(frustum, l, cm.pos);
actx.AddCullingPlanes(cullingPlanes);
}
// In case of no lights add input frustum as culling planes
if (i == 0)
{
actx.AddCullingPlanes(frustum.planes);
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
if (actx.drawCullingVolumes)
frustum.DrawWireOutline(dd, actx.cullingVolumeColor);
#endif
}
}
}
/////////////////////////////////////////////////////////////////////////////////
NativeArray<float4> MakeShadowVolume(FrustumVolume frustum, LightData l, float3 cameraPos)
{
var rv = new NativeList<float4>(16, Allocator.Temp);
var lightDir = l.posOrDir.xyz;
var nearShadowPlane = float4.zero;
// For infinite (directional) lights
if (l.posOrDir.w == 0)
{
// Project hull points onto light vector and pick maximum distance
var shadowDist = l.range;
for (var i = 0; i < frustum.points.Length; ++i)
{
var fp = frustum.points[i];
var cameraRelativePos = fp - cameraPos;
var projPos = math.dot(cameraRelativePos, lightDir) * lightDir;
// Ignore points that lie behind camera
var d = math.dot(lightDir, cameraRelativePos);
if (d > 0)
shadowDist = math.max(math.length(projPos), shadowDist);
}
// Make "near" shadow plane
var nearShadowPlanePoint = cameraPos + lightDir * shadowDist;
nearShadowPlane = MathUtils.BuildPlaneFromNormalAndPoint(-lightDir, nearShadowPlanePoint);
rv.Add(nearShadowPlane);
}
else
{
// Skip entire calculation if light sphere is not visible or light inside view frustum
var distanceToLight = frustum.GetMinimumDistanceToPoint(l.posOrDir.xyz);
if (distanceToLight < -l.range || distanceToLight >= 0)
{
#if (RUKHANKA_DEBUG_INFO && ! RUKHANKA_NO_DEBUG_DRAWER)
if (actx.drawCullingVolumes)
frustum.DrawWireOutline(dd, actx.cullingVolumeColor);
#endif
return frustum.planes;
}
}
for (var i = 0; i < FrustumVolume.frustumEdgeIndices.Length; ++i)
{
var fep = FrustumVolume.frustumEdgeIndices[i];
var ep = fep.zw;
var pts = fep.xy;
var p0 = frustum.planes[ep.x];
var p1 = frustum.planes[ep.y];
var d0 = math.dot(p0, l.posOrDir);
var d1 = math.dot(p1, l.posOrDir);
// This is a silhouette edge
if (d0 * d1 < 0)
{
var pt0 = frustum.points[pts.x];
var pt1 = frustum.points[pts.y];
var planeNormal = math.cross(l.posOrDir.xyz - pt0 * l.posOrDir.w, pt1 - pt0);
var m = math.lengthsq(planeNormal);
if (m > math.EPSILON)
{
planeNormal *= math.rcp(math.sqrt(m));
if (math.dot(frustum.middlePoint - pt0, planeNormal) < 0)
planeNormal *= -1;
var d = -math.dot(planeNormal, pt0);
var plane = new float4(planeNormal, d);
rv.Add(plane);
}
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
if (actx.drawCullingVolumes)
{
if (l.posOrDir.w == 0)
{
float3 ip0 = 0;
MathUtils.PlaneLineIntersection(pt0, lightDir, nearShadowPlane, ref ip0);
float3 ip1 = 0;
MathUtils.PlaneLineIntersection(pt1, lightDir, nearShadowPlane, ref ip1);
dd.DrawLine(ip0, pt0, actx.cullingVolumeColor);
dd.DrawLine(pt0, pt1, actx.cullingVolumeColor);
dd.DrawLine(pt1, ip1, actx.cullingVolumeColor);
dd.DrawLine(ip1, ip0, actx.cullingVolumeColor);
}
else
{
dd.DrawLine(pt0, pt1, actx.cullingVolumeColor);
dd.DrawLine(l.posOrDir.xyz, pt1, actx.cullingVolumeColor);
dd.DrawLine(l.posOrDir.xyz, pt0, actx.cullingVolumeColor);
}
}
#endif
}
#if (RUKHANKA_DEBUG_INFO && ! RUKHANKA_NO_DEBUG_DRAWER)
else if (d0 > 0 && actx.drawCullingVolumes)
{
// Draw edge with light facing planes
var pt0 = frustum.points[pts.x];
var pt1 = frustum.points[pts.y];
dd.DrawLine(pt0, pt1, actx.cullingVolumeColor);
}
#endif
}
AddExistingLightFacingEdges(l.posOrDir, frustum.planes, ref rv);
return rv.AsArray();
}
/////////////////////////////////////////////////////////////////////////////////
void AddExistingLightFacingEdges(float4 lightPos, NativeArray<float4> planes, ref NativeList<float4> outPlanes)
{
for (var i = 0; i < planes.Length; ++i)
{
var pln = planes[i];
if (math.dot(pln, lightPos) > 0)
{
outPlanes.Add(pln);
}
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 19986fdaa7bc0ef4b8683d0b63b19ff6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingContextUpdateSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,93 @@
#if !RUKHANKA_NO_DEBUG_DRAWER
using Rukhanka.DebugDrawer;
#endif
using Unity.Burst;
using Unity.Entities;
using Unity.Rendering;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[UpdateAfter(typeof(AnimationCullingContextUpdateSystem))]
partial struct AnimationCullingSystem: ISystem
{
EntityQuery cullAnimationTagQuery;
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
cullAnimationTagQuery = SystemAPI.QueryBuilder()
.WithAll<CullAnimationsTag>()
.WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)
.Build();
ss.RequireForUpdate(cullAnimationTagQuery);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
if (!SystemAPI.TryGetSingleton<AnimationCullingContext>(out var actx))
{
if (!cullAnimationTagQuery.IsEmptyIgnoreFilter)
{
Debug.LogError($"Animation culling config is absent on scene, but some entities request animation culling functionality. <a href=\"https://docs.rukhanka.com/Optimizing%20Rukhanka/animation_frustum_culling#animation-culling-environment-setup\">Please properly configure the culling environment!</a>");
}
return;
}
var resetCullingStateJob = new ResetAnimationCullingJob()
{
cullAnimationsTypeHandle = SystemAPI.GetComponentTypeHandle<CullAnimationsTag>()
};
var resetCullingStateJH = resetCullingStateJob.ScheduleParallel(cullAnimationTagQuery, ss.Dependency);
var wrbTypeHandle = SystemAPI.GetComponentTypeHandle<WorldRenderBounds>(true);
var chunkWrbTypeHandle = SystemAPI.GetComponentTypeHandle<ChunkWorldRenderBounds>(true);
var cullAnimationsTagLookup = SystemAPI.GetComponentLookup<CullAnimationsTag>();
var skinnedMeshTypeHandle = SystemAPI.GetComponentTypeHandle<SkinnedMeshRendererComponent>(true);
var cullAnimationsJob = new CullAnimationsJob()
{
actx = actx,
wrbTypeHandle = wrbTypeHandle,
chunkWrbTypeHandle = chunkWrbTypeHandle,
cullAnimationsTagLookup = cullAnimationsTagLookup,
asmTypeHandle = skinnedMeshTypeHandle
};
var cullQuery = SystemAPI.QueryBuilder()
.WithAllChunkComponent<ChunkWorldRenderBounds>()
.WithAll<WorldRenderBounds, SkinnedMeshRendererComponent>()
.Build();
ss.Dependency = cullAnimationsJob.ScheduleParallel(cullQuery, resetCullingStateJH);
#if (RUKHANKA_DEBUG_INFO && !RUKHANKA_NO_DEBUG_DRAWER)
SystemAPI.TryGetSingletonRW<Drawer>(out var dd);
var debugDrawJob = new DebugDrawRendererBoundingBoxes()
{
dd = dd.ValueRW,
actx = actx,
wrbTypeHandle = wrbTypeHandle,
chunkWrbTypeHandle = chunkWrbTypeHandle,
cullAnimationsTagLookup = cullAnimationsTagLookup,
asmTypeHandle = skinnedMeshTypeHandle
};
ss.Dependency = debugDrawJob.ScheduleParallel(cullQuery, ss.Dependency);
#endif
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e7c6913d44171114eaffae78da19800c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingSystem.cs
uploadId: 897522
@@ -0,0 +1,165 @@
#if !RUKHANKA_NO_DEBUG_DRAWER
using Rukhanka.DebugDrawer;
#endif
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Collections;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
partial struct AnimationCullingSystem
{
[BurstCompile]
struct CullAnimationsJob: IJobChunk
{
[ReadOnly]
public AnimationCullingContext actx;
[ReadOnly]
public ComponentTypeHandle<ChunkWorldRenderBounds> chunkWrbTypeHandle;
[ReadOnly]
public ComponentTypeHandle<WorldRenderBounds> wrbTypeHandle;
[ReadOnly]
public ComponentTypeHandle<SkinnedMeshRendererComponent> asmTypeHandle;
[NativeDisableParallelForRestriction]
public ComponentLookup<CullAnimationsTag> cullAnimationsTagLookup;
/////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
var chunkWRB = chunk.GetChunkComponentData(ref chunkWrbTypeHandle);
var isChunkVisible = IsAxisAlignedBoxVisible(chunkWRB.Value, actx);
var wrbArr = chunk.GetNativeArray(ref wrbTypeHandle);
var asmArr = chunk.GetNativeArray(ref asmTypeHandle);
while (cee.NextEntityIndex(out var i) && isChunkVisible)
{
var arEntity = asmArr[i].animatedRigEntity;
if (arEntity == Entity.Null || !cullAnimationsTagLookup.HasComponent(arEntity))
continue;
var cullAnimsAlreadyDisabled = !cullAnimationsTagLookup.IsComponentEnabled(arEntity);
if (cullAnimsAlreadyDisabled)
{
continue;
}
var wrb = wrbArr[i].Value;
var isVisible = IsAxisAlignedBoxVisible(wrb, actx);
cullAnimationsTagLookup.SetComponentEnabled(arEntity, !isVisible);
}
}
/////////////////////////////////////////////////////////////////////////////////
internal static bool IsAxisAlignedBoxVisible(in AABB wrb, in AnimationCullingContext actx)
{
var isVisible = false;
for (var j = 0; j < actx.cullingVolumePlaneRanges.Length && !isVisible; ++j)
{
var planeRange = actx.cullingVolumePlaneRanges[j];
var isVisibleForPlaneRange = true;
for (var i = 0; i < planeRange.y && isVisibleForPlaneRange; ++i)
{
var pln = actx.cullingPlanes[i + planeRange.x];
var rg0 = pln.xyz * wrb.Extents;
var rg = math.dot(math.abs(rg0), 1);
var distance = math.dot(pln, new float4(wrb.Center, 1));
isVisibleForPlaneRange = distance > -rg;
}
isVisible |= isVisibleForPlaneRange;
}
return isVisible;
}
}
//=================================================================================================================//
#if !RUKHANKA_NO_DEBUG_DRAWER
[BurstCompile]
struct DebugDrawRendererBoundingBoxes: IJobChunk
{
public Drawer dd;
[ReadOnly]
public AnimationCullingContext actx;
[ReadOnly]
public ComponentTypeHandle<ChunkWorldRenderBounds> chunkWrbTypeHandle;
[ReadOnly]
public ComponentTypeHandle<WorldRenderBounds> wrbTypeHandle;
[ReadOnly]
public ComponentTypeHandle<SkinnedMeshRendererComponent> asmTypeHandle;
[NativeDisableParallelForRestriction]
public ComponentLookup<CullAnimationsTag> cullAnimationsTagLookup;
/////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
#if RUKHANKA_DEBUG_INFO
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
var chunkWRB = chunk.GetChunkComponentData(ref chunkWrbTypeHandle);
var isChunkVisible = CullAnimationsJob.IsAxisAlignedBoxVisible(chunkWRB.Value, actx);
if (actx.drawSceneBoundingBoxes)
{
var chunkBBColor = isChunkVisible ? actx.visibleChunkColor : actx.invisibleChunkColor;
var xformChunk = new RigidTransform(quaternion.identity, chunkWRB.Value.Center);
dd.DrawWireCuboid(chunkWRB.Value.Size, chunkBBColor, xformChunk);
}
var wrbArr = chunk.GetNativeArray(ref wrbTypeHandle);
var asmArr = chunk.GetNativeArray(ref asmTypeHandle);
while (cee.NextEntityIndex(out var i) && isChunkVisible)
{
var arEntity = asmArr[i].animatedRigEntity;
if (arEntity == Entity.Null || !cullAnimationsTagLookup.HasComponent(arEntity))
continue;
var isEntityVisible = !cullAnimationsTagLookup.IsComponentEnabled(arEntity);
var wrb = wrbArr[i].Value;
if (actx.drawSceneBoundingBoxes)
{
var cl = isEntityVisible ? actx.visibleRendererColor : actx.invisibleRendererColor;
var xform = new RigidTransform(quaternion.identity, wrb.Center);
dd.DrawWireCuboid(wrb.Size, cl, xform);
}
}
#endif
}
}
#endif
//=================================================================================================================//
[BurstCompile]
struct ResetAnimationCullingJob: IJobChunk
{
public ComponentTypeHandle<CullAnimationsTag> cullAnimationsTypeHandle;
/////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
chunk.SetComponentEnabledForAll(ref cullAnimationsTypeHandle, true);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 725330bc4d248af4ca096c867a7fb86d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/AnimationCullingSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,153 @@
#if !RUKHANKA_NO_DEBUG_DRAWER
using Rukhanka.DebugDrawer;
#endif
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
internal struct FrustumVolume
{
public NativeArray<float4> planes;
public NativeArray<float3> points;
public float3 middlePoint;
/////////////////////////////////////////////////////////////////////////////////
// Frustum corners in NDC
static readonly float4[] frustumCorners =
{
// Near plane
new float4(-1, -1, -1, 1),
new float4(-1, +1, -1, 1),
new float4(+1, +1, -1, 1),
new float4(+1, -1, -1, 1),
// Far plane
new float4(-1, -1, +1, 1),
new float4(-1, +1, +1, 1),
new float4(+1, +1, +1, 1),
new float4(+1, -1, +1, 1),
};
// Point (xy) and plane (zw) indices used to form edges
public static readonly int4[] frustumEdgeIndices =
{
// Sides
new int4(2, 6, 0, 2),
new int4(1, 5, 1, 2),
new int4(0, 4, 1, 3),
new int4(3, 7, 0, 3),
// Near plane edges
new int4(1, 2, 5, 2),
new int4(2, 3, 5, 0),
new int4(0, 3, 5, 3),
new int4(0, 1, 5, 1),
// Far plane edges
new int4(5, 6, 4, 2),
new int4(6, 7, 4, 0),
new int4(4, 7, 4, 3),
new int4(4, 5, 4, 1),
};
/////////////////////////////////////////////////////////////////////////////////
public static FrustumVolume Allocate()
{
var rv = new FrustumVolume()
{
points = new (frustumCorners.Length, Allocator.Temp),
planes = new (6, Allocator.Temp),
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
#if !RUKHANKA_NO_DEBUG_DRAWER
public void DrawWireOutline(Drawer dd, uint color)
{
for (var j = 0; j < frustumEdgeIndices.Length; ++j)
{
var e = frustumEdgeIndices[j].xy;
var p0 = points[e.x];
var p1 = points[e.y];
dd.DrawLine(p0, p1, color);
}
}
#endif
/////////////////////////////////////////////////////////////////////////////////
public float GetMinimumDistanceToPoint(float3 pt)
{
var distance = float.MaxValue;
var pt4 = new float4(pt, 1);
foreach (var pln in planes)
{
var d = math.dot(pln, pt4);
distance = math.min(distance, d);
}
return distance;
}
/////////////////////////////////////////////////////////////////////////////////
public static FrustumVolume MakeCullingCameraFrustum(AnimationCullingContextUpdateSystem.CullingCameraData ccd)
{
var rv = Allocate();
var cullingMatrix = math.inverse(ccd.cullingMatrix);
// Build point list
for (var i = 0; i < frustumCorners.Length; ++i)
{
var vtx = math.mul(cullingMatrix, frustumCorners[i]);
vtx /= vtx.w;
rv.points[i] = vtx.xyz;
}
var mp = math.mul(cullingMatrix, new float4(0, 0, 0.1f, 1));
rv.middlePoint = mp.xyz / mp.w;
// Build planes
if (ccd.isOrthographic)
{
// +X
rv.planes[0] = MathUtils.BuildPlaneFromThreePoints(rv.points[3], rv.points[7], rv.points[2]);
// -X
rv.planes[1] = MathUtils.BuildPlaneFromThreePoints(rv.points[1], rv.points[5], rv.points[0]);
// +Y
rv.planes[2] = MathUtils.BuildPlaneFromThreePoints(rv.points[2], rv.points[6], rv.points[1]);
// -Y
rv.planes[3] = MathUtils.BuildPlaneFromThreePoints(rv.points[0], rv.points[4], rv.points[3]);
// +Z
rv.planes[4] = MathUtils.BuildPlaneFromThreePoints(rv.points[5], rv.points[6], rv.points[4]);
// -Z
rv.planes[5] = MathUtils.BuildPlaneFromThreePoints(rv.points[0], rv.points[2], rv.points[1]);
}
else
{
// +X
rv.planes[0] = MathUtils.BuildPlaneFromThreePoints(ccd.pos, rv.points[7], rv.points[6]);
// -X
rv.planes[1] = MathUtils.BuildPlaneFromThreePoints(ccd.pos, rv.points[5], rv.points[4]);
// +Y
rv.planes[2] = MathUtils.BuildPlaneFromThreePoints(ccd.pos, rv.points[6], rv.points[5]);
// -Y
rv.planes[3] = MathUtils.BuildPlaneFromThreePoints(ccd.pos, rv.points[4], rv.points[7]);
// +Z
rv.planes[4] = MathUtils.BuildPlaneFromNormalAndPoint(-ccd.viewDir, rv.points[5]);
// -Z
rv.planes[5] = MathUtils.BuildPlaneFromNormalAndPoint(ccd.viewDir, rv.points[0]);
}
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bd54353fe71e7634782b86662178035a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Culling/FrustumVolume.cs
uploadId: 897522