From 977e10e7524324ac177fe66c40af72a7f20d54f2 Mon Sep 17 00:00:00 2001 From: John Ludlow Date: Wed, 10 Sep 2025 00:51:39 +0100 Subject: [PATCH 1/4] OcclusionTest --- .../App.config | 3 + .../Program.cs | 3 +- .../Program.cs | 139 ++++++++++++++++++ .../StrideExamples.Local.OcclusionTest.csproj | 16 ++ StrideExamples.slnx | 3 + 5 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 Community/StrideExamples.Community.CubeClicker/App.config create mode 100644 Local/StrideExamples.Local.OcclusionTest/Program.cs create mode 100644 Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj diff --git a/Community/StrideExamples.Community.CubeClicker/App.config b/Community/StrideExamples.Community.CubeClicker/App.config new file mode 100644 index 0000000..49cc43e --- /dev/null +++ b/Community/StrideExamples.Community.CubeClicker/App.config @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Community/StrideExamples.Community.MeshOutlineShader/Program.cs b/Community/StrideExamples.Community.MeshOutlineShader/Program.cs index 29285ff..cfc91a0 100644 --- a/Community/StrideExamples.Community.MeshOutlineShader/Program.cs +++ b/Community/StrideExamples.Community.MeshOutlineShader/Program.cs @@ -39,8 +39,7 @@ void CreateOutlinePrimitive(Scene rootScene, PrimitiveModelType modelType, Color var entity = game.Create3DPrimitive(modelType, options: new() { RenderGroup = RenderGroup.Group5, - } - ); + }); entity.Transform.Position = position; entity.Add( diff --git a/Local/StrideExamples.Local.OcclusionTest/Program.cs b/Local/StrideExamples.Local.OcclusionTest/Program.cs new file mode 100644 index 0000000..dec78ad --- /dev/null +++ b/Local/StrideExamples.Local.OcclusionTest/Program.cs @@ -0,0 +1,139 @@ +using Stride.CommunityToolkit.Bepu; +using Stride.CommunityToolkit.Engine; +using Stride.CommunityToolkit.Games; +using Stride.CommunityToolkit.Rendering.ProceduralModels; +using Stride.Core.Mathematics; +using Stride.Engine; +using Stride.Games; +using Stride.Physics; + + +Console.WriteLine("Hello, World!"); + +using var game = new Game(); +var visibility = new MultiRaycastVisibility(); +var gameStart = TimeSpan.FromMilliseconds(0); +game.Run(start: Start, update: Update); + +void Start(Scene rootScene) +{ + game.SetupBase3DScene(); + game.AddProfiler(); + + var greenCube = game.Create3DPrimitive(PrimitiveModelType.Cube, new Primitive3DEntityOptions { Material = game.CreateMaterial(Color.Green), Size = new(2, 2, 2) }); + greenCube.Name = "GreenCube"; + greenCube.Transform.Position = new(-5, 1, 0); + greenCube.Scene = rootScene; + + var blueCube = game.Create3DPrimitive(PrimitiveModelType.Cube, new Primitive3DEntityOptions { Material = game.CreateMaterial(Color.Blue), Size = new(3) }); + blueCube.Name = "BlueCube"; + blueCube.Transform.Position = new(+5, 1, 0); + blueCube.Scene = rootScene; +} + +void Update(Scene rootScene, GameTime gameTime) +{ + if ((gameTime.Elapsed - gameStart).TotalSeconds > 5) return; + gameStart = gameTime.Elapsed; + + var camera = rootScene.Entities.First(e => e.Get() != null).Get(); + + var greenCube = rootScene.Entities.First(e => e.Name == "GreenCube"); + var greenCubeModel = greenCube.Get(); + Console.WriteLine($"Green cube: {visibility.CheckVisibility(greenCube, camera, camera.Entity.GetSimulation())}"); + + var blueCube = rootScene.Entities.First(e => e.Name == "BlueCube"); + var blueCubeModel = blueCube.Get(); + Console.WriteLine($"Blue cube: {visibility.CheckVisibility(blueCube, camera, camera.Entity.GetSimulation())}"); +} + +public class MultiRaycastVisibility +{ + public record struct VisibilityResult + { + public bool IsVisible; + public float VisibilityPercentage; // 0.0 to 1.0 + public int RaysHit; + public int TotalRays; + } + + public VisibilityResult CheckVisibility(Entity target, CameraComponent camera, Stride.BepuPhysics.BepuSimulation simulation) + { + var cameraPos = camera.Entity.Transform.WorldMatrix.TranslationVector; + + // Get target bounding box points + var targetTransform = target.Transform; + var targetModel = target.Get(); + var worldMatrix = targetTransform.WorldMatrix; + var localBoundingBox = targetModel.Model.BoundingBox; + BoundingBox.Transform(localBoundingBox, worldMatrix, out var worldBoundingBox); + + var testPoints = GenerateTestPoints(worldBoundingBox); + var visibleRays = 0; + + foreach (var point in testPoints) + { + var direction = point - cameraPos; + + // Use Bepu's raycast method + var hit = simulation.RayCast( + cameraPos, + Vector3.Normalize(direction), + float.MaxValue, + out var hitInfo + ); + + if (hit && hitInfo.Collidable?.Entity is not null) + { + if (hitInfo.Collidable.Entity.Name == "Ground") continue; + + Console.WriteLine($"Ray hit entity: {hitInfo.Collidable.Entity.Name}"); + + if (hit && hitInfo.Collidable?.Entity == target) + { + visibleRays++; // Ray hit the target + } + } + + } + + var visibilityPercentage = (float)visibleRays / testPoints.Length; + + return new VisibilityResult + { + IsVisible = visibilityPercentage > 0.0f, + VisibilityPercentage = visibilityPercentage, + RaysHit = visibleRays, + TotalRays = testPoints.Length + }; + } + + private Vector3[] GenerateTestPoints(BoundingBox boundingBox) + { + var min = boundingBox.Minimum; + var max = boundingBox.Maximum; + var center = (min + max) * 0.5f; + + return + [ + // 8 corners of the bounding box + new(min.X, min.Y, min.Z), // 0: min corner + new(max.X, min.Y, min.Z), // 1: +X + new(min.X, max.Y, min.Z), // 2: +Y + new(min.X, min.Y, max.Z), // 3: +Z + new(max.X, max.Y, min.Z), // 4: +XY + new(max.X, min.Y, max.Z), // 5: +XZ + new(min.X, max.Y, max.Z), // 6: +YZ + new(max.X, max.Y, max.Z), // 7: max corner + + // Face centers + center, // Center + new(center.X, center.Y, min.Z), // Front face center + new(center.X, center.Y, max.Z), // Back face center + new(min.X, center.Y, center.Z), // Left face center + new(max.X, center.Y, center.Z), // Right face center + new(center.X, min.Y, center.Z), // Bottom face center + new(center.X, max.Y, center.Z), // Top face center + ]; + } +} \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj b/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj new file mode 100644 index 0000000..3f0df4f --- /dev/null +++ b/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + diff --git a/StrideExamples.slnx b/StrideExamples.slnx index 41f304c..599b5f9 100644 --- a/StrideExamples.slnx +++ b/StrideExamples.slnx @@ -16,4 +16,7 @@ + + + From 10f2361ad04842af1a1ec77548492e07c4da9bc5 Mon Sep 17 00:00:00 2001 From: John Ludlow Date: Thu, 11 Sep 2025 20:22:38 +0100 Subject: [PATCH 2/4] Basic occlusion test with no UI --- .../Core/ClickData.cs | 2 +- .../Core/CubeData.cs | 3 +- .../Core/DataSaver.cs | 2 +- .../Core/IClickable.cs | 2 +- .../Core/LeftMouseButtonCounter.cs | 2 +- .../Core/RightMouseButtonCounter.cs | 2 +- .../Core/SimpleVector.cs | 2 +- .../Managers/ClickDataManager.cs | 4 +- .../Managers/CubeDataManager.cs | 5 +- .../Managers/GameManager.cs | 2 +- .../Managers/UIManager.cs | 5 +- .../Program.cs | 12 +- .../Scripts/ClickHandlerComponent.cs | 6 +- .../Scripts/CubeGrower.cs | 2 +- .../Scripts/CubeVanisher.cs | 2 +- .../Managers/GameManager.cs | 16 +++ .../Managers/UIManager.cs | 20 +++ .../MultiRaycastVisibility.cs | 86 ++++++++++++ .../Program.cs | 132 +++++------------- .../StrideExamples.Local.OcclusionTest.csproj | 12 +- nuget.config | 7 + 21 files changed, 193 insertions(+), 133 deletions(-) create mode 100644 Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs create mode 100644 Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs create mode 100644 Local/StrideExamples.Local.OcclusionTest/MultiRaycastVisibility.cs create mode 100644 nuget.config diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/ClickData.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/ClickData.cs index ecec679..9da5249 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/ClickData.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/ClickData.cs @@ -1,6 +1,6 @@ using Stride.Core; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; [DataContract] public sealed class ClickData diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/CubeData.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/CubeData.cs index c01e703..b8c861b 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/CubeData.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/CubeData.cs @@ -1,8 +1,7 @@ -using BepuUtilities.Collections; using Stride.Core; using Stride.Core.Mathematics; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; [DataContract] public sealed class CubeData diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/DataSaver.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/DataSaver.cs index 8b4bd8c..141c6a8 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/DataSaver.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/DataSaver.cs @@ -1,7 +1,7 @@ using NexVYaml; using Stride.Core.IO; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; public class DataSaver { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/IClickable.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/IClickable.cs index 4cfe28f..f53ef7d 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/IClickable.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/IClickable.cs @@ -1,6 +1,6 @@ using Stride.Input; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; public interface IClickable { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/LeftMouseButtonCounter.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/LeftMouseButtonCounter.cs index a11d055..1a54e5f 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/LeftMouseButtonCounter.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/LeftMouseButtonCounter.cs @@ -1,7 +1,7 @@ using Stride.Core; using Stride.Input; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; /// /// Has to be directly [DataContract] tagged, else it won't detect it. diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/RightMouseButtonCounter.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/RightMouseButtonCounter.cs index 371ca0a..a14ca54 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/RightMouseButtonCounter.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/RightMouseButtonCounter.cs @@ -1,7 +1,7 @@ using Stride.Core; using Stride.Input; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; [DataContract] diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Core/SimpleVector.cs b/Community/StrideExamples.Community.StrideUI.Grid/Core/SimpleVector.cs index 1a016f9..90a455d 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Core/SimpleVector.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Core/SimpleVector.cs @@ -1,6 +1,6 @@ using Stride.Core; -namespace StrideExamples.StrideUI.Grid.Core; +namespace StrideExamples.Community.StrideUI.Grid.Core; /// /// Stride.Core.Vector isn't generated as the generator can't reach the core without being in it diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Managers/ClickDataManager.cs b/Community/StrideExamples.Community.StrideUI.Grid/Managers/ClickDataManager.cs index cf3ee34..06b83a4 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Managers/ClickDataManager.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Managers/ClickDataManager.cs @@ -1,6 +1,6 @@ -using StrideExamples.StrideUI.Grid.Core; +using StrideExamples.Community.StrideUI.Grid.Core; -namespace StrideExamples.StrideUI.Grid.Managers; +namespace StrideExamples.Community.StrideUI.Grid.Managers; public class ClickDataManager { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Managers/CubeDataManager.cs b/Community/StrideExamples.Community.StrideUI.Grid/Managers/CubeDataManager.cs index f604e36..7596710 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Managers/CubeDataManager.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Managers/CubeDataManager.cs @@ -1,8 +1,7 @@ using Stride.Core.Mathematics; -using StrideExamples.StrideUI.Grid.Core; -using System.Runtime.Serialization; +using StrideExamples.Community.StrideUI.Grid.Core; -namespace StrideExamples.StrideUI.Grid.Managers; +namespace StrideExamples.Community.StrideUI.Grid.Managers; public class CubeDataManager { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Managers/GameManager.cs b/Community/StrideExamples.Community.StrideUI.Grid/Managers/GameManager.cs index fb40d68..dfbd0ed 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Managers/GameManager.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Managers/GameManager.cs @@ -6,7 +6,7 @@ using System.Globalization; using System.Text; -namespace StrideExamples.StrideUI.Grid.Managers; +namespace StrideExamples.Community.StrideUI.Grid.Managers; public class GameManager { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Managers/UIManager.cs b/Community/StrideExamples.Community.StrideUI.Grid/Managers/UIManager.cs index 46e2772..14412ee 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Managers/UIManager.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Managers/UIManager.cs @@ -6,10 +6,9 @@ using Stride.UI; using Stride.UI.Controls; using Stride.UI.Events; -using Stride.UI.Panels; -using StrideExamples.StrideUI.Grid.Core; +using StrideExamples.Community.StrideUI.Grid.Core; -namespace StrideExamples.StrideUI.Grid.Managers; +namespace StrideExamples.Community.StrideUI.Grid.Managers; public class UIManager { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Program.cs b/Community/StrideExamples.Community.StrideUI.Grid/Program.cs index 1057f17..3ddfaf6 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Program.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Program.cs @@ -1,14 +1,12 @@ -using Stride.CommunityToolkit.Bepu; +using NexVYaml; +using Stride.CommunityToolkit.Bepu; using Stride.CommunityToolkit.Engine; +using Stride.CommunityToolkit.Renderers; using Stride.CommunityToolkit.Skyboxes; using Stride.Engine; using Stride.Graphics; - -using NexVYaml; - -using StrideExamples.StrideUI.Grid.Managers; -using StrideExamples.StrideUI.Grid.Scripts; -using Stride.CommunityToolkit.Renderers; +using StrideExamples.Community.StrideUI.Grid.Managers; +using StrideExamples.Community.StrideUI.Grid.Scripts; using var game = new Game(); diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Scripts/ClickHandlerComponent.cs b/Community/StrideExamples.Community.StrideUI.Grid/Scripts/ClickHandlerComponent.cs index 3cb3aa2..87035d6 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Scripts/ClickHandlerComponent.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Scripts/ClickHandlerComponent.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Stride.CommunityToolkit.Bepu; using Stride.CommunityToolkit.Engine; using Stride.CommunityToolkit.Rendering.ProceduralModels; @@ -5,10 +6,9 @@ using Stride.Engine; using Stride.Input; using Stride.Rendering; -using StrideExamples.StrideUI.Grid.Managers; -using System.Diagnostics; +using StrideExamples.Community.StrideUI.Grid.Managers; -namespace StrideExamples.StrideUI.Grid.Scripts; +namespace StrideExamples.Community.StrideUI.Grid.Scripts; public class ClickHandlerComponent : AsyncScript { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeGrower.cs b/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeGrower.cs index f77886e..3679009 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeGrower.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeGrower.cs @@ -3,7 +3,7 @@ using Stride.Engine; using Stride.Physics; -namespace StrideExamples.StrideUI.Grid.Scripts; +namespace StrideExamples.Community.StrideUI.Grid.Scripts; public class CubeGrower : AsyncScript { diff --git a/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeVanisher.cs b/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeVanisher.cs index fed2598..1d1ba60 100644 --- a/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeVanisher.cs +++ b/Community/StrideExamples.Community.StrideUI.Grid/Scripts/CubeVanisher.cs @@ -3,7 +3,7 @@ using Stride.Engine; using Stride.Physics; -namespace StrideExamples.StrideUI.Grid.Scripts; +namespace StrideExamples.Community.StrideUI.Grid.Scripts; public class CubeVanisher : AsyncScript { diff --git a/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs b/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs new file mode 100644 index 0000000..b02801e --- /dev/null +++ b/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs @@ -0,0 +1,16 @@ +using Stride.Engine; +using Stride.Graphics; + +namespace StrideExamples.Local.OcclusionTest.Managers; + +public class GameManager +{ + private readonly UIManager _uiManager; + + public GameManager(SpriteFont font) + { + _uiManager = new UIManager(); + } + + internal Entity CreateUI() => _uiManager.CreateUI(); +} \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs b/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs new file mode 100644 index 0000000..c09e66a --- /dev/null +++ b/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs @@ -0,0 +1,20 @@ +using Stride.Engine; + +namespace StrideExamples.Local.OcclusionTest.Managers; + +public class UIManager +{ + public Entity CreateUI() + { + var entity = new Entity("UI") + { + new UIComponent + { + Page = new UIPage { RootElement = new Stride.UI.Controls.TextBlock { Text = "Occlusion Test" } }, + RenderGroup = Stride.Rendering.RenderGroup.Group31 + } + }; + + return entity; + } +} \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/MultiRaycastVisibility.cs b/Local/StrideExamples.Local.OcclusionTest/MultiRaycastVisibility.cs new file mode 100644 index 0000000..aa2fbb3 --- /dev/null +++ b/Local/StrideExamples.Local.OcclusionTest/MultiRaycastVisibility.cs @@ -0,0 +1,86 @@ +using Stride.Core.Mathematics; +using Stride.Engine; + +public class MultiRaycastVisibility +{ + public record struct VisibilityResult + { + public bool IsVisible; + public float VisibilityPercentage; // 0.0 to 1.0 + public int RaysHit; + public int TotalRays; + } + + public VisibilityResult CheckVisibility(Game game, Entity target, CameraComponent camera, Stride.BepuPhysics.BepuSimulation simulation) + { + var cameraPos = camera.Entity.Transform.WorldMatrix.TranslationVector; + + // Get target bounding box points + var targetTransform = target.Transform; + var targetModel = target.Get(); + var worldMatrix = targetTransform.WorldMatrix; + var localBoundingBox = targetModel.Model.BoundingBox; + BoundingBox.Transform(ref localBoundingBox, ref worldMatrix, out var worldBoundingBox); + + var testPoints = GenerateTestPoints(worldBoundingBox); + var visibleRays = 0; + + foreach (var point in testPoints) + { + var direction = point - cameraPos; + direction.Normalize(); + + // Use Bepu's raycast method + var hit = simulation.RayCast( + cameraPos, + direction, + float.MaxValue, + out var hitInfo + ); + + if (hit && hitInfo.Collidable?.Entity == target) + { + visibleRays++; // Ray hit the target + } + } + + var visibilityPercentage = (float)visibleRays / testPoints.Length; + + return new VisibilityResult + { + IsVisible = visibilityPercentage > 0.0f, + VisibilityPercentage = visibilityPercentage, + RaysHit = visibleRays, + TotalRays = testPoints.Length + }; + } + + private Vector3[] GenerateTestPoints(BoundingBox boundingBox) + { + var min = boundingBox.Minimum; + var max = boundingBox.Maximum; + var center = (min + max) * 0.5f; + + return + [ + // 8 corners of the bounding box + new(min.X, min.Y, min.Z), // 0: min corner + new(max.X, min.Y, min.Z), // 1: +X + new(min.X, max.Y, min.Z), // 2: +Y + new(min.X, min.Y, max.Z), // 3: +Z + new(max.X, max.Y, min.Z), // 4: +XY + new(max.X, min.Y, max.Z), // 5: +XZ + new(min.X, max.Y, max.Z), // 6: +YZ + new(max.X, max.Y, max.Z), // 7: max corner + + // Face centers + center, // Center + new(center.X, center.Y, min.Z), // Front face center + new(center.X, center.Y, max.Z), // Back face center + new(min.X, center.Y, center.Z), // Left face center + new(max.X, center.Y, center.Z), // Right face center + new(center.X, min.Y, center.Z), // Bottom face center + new(center.X, max.Y, center.Z), // Top face center + ]; + } +} diff --git a/Local/StrideExamples.Local.OcclusionTest/Program.cs b/Local/StrideExamples.Local.OcclusionTest/Program.cs index dec78ad..8dcb1ac 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Program.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Program.cs @@ -5,135 +5,67 @@ using Stride.Core.Mathematics; using Stride.Engine; using Stride.Games; -using Stride.Physics; +using Stride.Graphics; +using StrideExamples.Local.OcclusionTest.Managers; Console.WriteLine("Hello, World!"); using var game = new Game(); var visibility = new MultiRaycastVisibility(); -var gameStart = TimeSpan.FromMilliseconds(0); game.Run(start: Start, update: Update); void Start(Scene rootScene) { game.SetupBase3DScene(); + game.AddGroundGizmo(); + game.Add3DGround(); game.AddProfiler(); + // game.AddEntityDebugSceneRenderer(); - var greenCube = game.Create3DPrimitive(PrimitiveModelType.Cube, new Primitive3DEntityOptions { Material = game.CreateMaterial(Color.Green), Size = new(2, 2, 2) }); - greenCube.Name = "GreenCube"; - greenCube.Transform.Position = new(-5, 1, 0); - greenCube.Scene = rootScene; + var font = game.Content.Load("StrideDefaultFont"); + var gameManager = new GameManager(font); + game.Services.AddService(gameManager); - var blueCube = game.Create3DPrimitive(PrimitiveModelType.Cube, new Primitive3DEntityOptions { Material = game.CreateMaterial(Color.Blue), Size = new(3) }); - blueCube.Name = "BlueCube"; - blueCube.Transform.Position = new(+5, 1, 0); - blueCube.Scene = rootScene; + var uiEntity = gameManager.CreateUI(); + uiEntity.Scene = rootScene; + + CreateCube(rootScene, game, "GreenCube", new(-5, 1, 0), Color.Green); + CreateCube(rootScene, game, "BlueCube", new(5, 1, 0), Color.Blue); } void Update(Scene rootScene, GameTime gameTime) { - if ((gameTime.Elapsed - gameStart).TotalSeconds > 5) return; - gameStart = gameTime.Elapsed; + if (!game.Input.IsKeyPressed(Stride.Input.Keys.Space)) + { + return; + } var camera = rootScene.Entities.First(e => e.Get() != null).Get(); var greenCube = rootScene.Entities.First(e => e.Name == "GreenCube"); - var greenCubeModel = greenCube.Get(); - Console.WriteLine($"Green cube: {visibility.CheckVisibility(greenCube, camera, camera.Entity.GetSimulation())}"); + var greenVisibility = visibility.CheckVisibility(game, greenCube, camera, camera.Entity.GetSimulation()); + Console.WriteLine($"Green cube: {visibility.CheckVisibility(game, greenCube, camera, camera.Entity.GetSimulation())}"); var blueCube = rootScene.Entities.First(e => e.Name == "BlueCube"); - var blueCubeModel = blueCube.Get(); - Console.WriteLine($"Blue cube: {visibility.CheckVisibility(blueCube, camera, camera.Entity.GetSimulation())}"); + var blueVisibility = visibility.CheckVisibility(game, blueCube, camera, camera.Entity.GetSimulation()); + Console.WriteLine($"Blue cube: {visibility.CheckVisibility(game, blueCube, camera, camera.Entity.GetSimulation())}"); } -public class MultiRaycastVisibility +static Entity CreateCube(Scene rootScene, Game game, string name, Vector3 position, Color color) { - public record struct VisibilityResult - { - public bool IsVisible; - public float VisibilityPercentage; // 0.0 to 1.0 - public int RaysHit; - public int TotalRays; - } - - public VisibilityResult CheckVisibility(Entity target, CameraComponent camera, Stride.BepuPhysics.BepuSimulation simulation) - { - var cameraPos = camera.Entity.Transform.WorldMatrix.TranslationVector; - - // Get target bounding box points - var targetTransform = target.Transform; - var targetModel = target.Get(); - var worldMatrix = targetTransform.WorldMatrix; - var localBoundingBox = targetModel.Model.BoundingBox; - BoundingBox.Transform(localBoundingBox, worldMatrix, out var worldBoundingBox); - - var testPoints = GenerateTestPoints(worldBoundingBox); - var visibleRays = 0; - - foreach (var point in testPoints) + var cube = game.Create3DPrimitive( + PrimitiveModelType.Cube, + new Bepu3DPhysicsOptions { - var direction = point - cameraPos; - - // Use Bepu's raycast method - var hit = simulation.RayCast( - cameraPos, - Vector3.Normalize(direction), - float.MaxValue, - out var hitInfo - ); - - if (hit && hitInfo.Collidable?.Entity is not null) - { - if (hitInfo.Collidable.Entity.Name == "Ground") continue; - - Console.WriteLine($"Ray hit entity: {hitInfo.Collidable.Entity.Name}"); - - if (hit && hitInfo.Collidable?.Entity == target) - { - visibleRays++; // Ray hit the target - } - } - + Material = game.CreateMaterial(color), + Size = new(2) } - - var visibilityPercentage = (float)visibleRays / testPoints.Length; - - return new VisibilityResult - { - IsVisible = visibilityPercentage > 0.0f, - VisibilityPercentage = visibilityPercentage, - RaysHit = visibleRays, - TotalRays = testPoints.Length - }; - } + ); - private Vector3[] GenerateTestPoints(BoundingBox boundingBox) - { - var min = boundingBox.Minimum; - var max = boundingBox.Maximum; - var center = (min + max) * 0.5f; + cube.Name = name; + cube.Transform.Position = position; + cube.Scene = rootScene; - return - [ - // 8 corners of the bounding box - new(min.X, min.Y, min.Z), // 0: min corner - new(max.X, min.Y, min.Z), // 1: +X - new(min.X, max.Y, min.Z), // 2: +Y - new(min.X, min.Y, max.Z), // 3: +Z - new(max.X, max.Y, min.Z), // 4: +XY - new(max.X, min.Y, max.Z), // 5: +XZ - new(min.X, max.Y, max.Z), // 6: +YZ - new(max.X, max.Y, max.Z), // 7: max corner - - // Face centers - center, // Center - new(center.X, center.Y, min.Z), // Front face center - new(center.X, center.Y, max.Z), // Back face center - new(min.X, center.Y, center.Z), // Left face center - new(max.X, center.Y, center.Z), // Right face center - new(center.X, min.Y, center.Z), // Bottom face center - new(center.X, max.Y, center.Z), // Top face center - ]; - } + return cube; } \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj b/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj index 3f0df4f..4d5fccc 100644 --- a/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj +++ b/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj @@ -8,9 +8,13 @@ - - - - + + + + + + + + diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..3a9f6b3 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 157104d29818b59a23527bd39c5e14d20da1b062 Mon Sep 17 00:00:00 2001 From: John Ludlow Date: Sat, 13 Sep 2025 20:24:11 +0100 Subject: [PATCH 3/4] User interface --- .../Components/MultiRaycastVisibility.cs | 110 ++++++++++++++ .../MultiRaycastVisibilityComponent.cs} | 0 .../Managers/GameManager.cs | 12 +- .../Managers/UIManager.cs | 134 +++++++++++++++++- .../Program.cs | 62 +++++--- .../StrideExamples.Local.OcclusionTest.csproj | 1 + 6 files changed, 289 insertions(+), 30 deletions(-) create mode 100644 Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs rename Local/StrideExamples.Local.OcclusionTest/{MultiRaycastVisibility.cs => Components/MultiRaycastVisibilityComponent.cs} (100%) diff --git a/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs b/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs new file mode 100644 index 0000000..58eedc0 --- /dev/null +++ b/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs @@ -0,0 +1,110 @@ +using Stride.Core; +using Stride.Core.Mathematics; +using Stride.Engine; + +namespace StrideExamples.Local.OcclusionTest.Components; + +public class MultiRaycastVisibilityComponent : SyncScript +{ + public required Game Game { get; init; } + public required Entity Target { get; init; } + public required CameraComponent Camera { get; init; } + public required Stride.BepuPhysics.BepuSimulation Simulation { get; init; } + + public VisibilityResult LastVisibilityResult { get; private set; } + + [DataMemberIgnore] + private Stride.CommunityToolkit.DebugShapes.Code.ImmediateDebugRenderSystem? _debugDraw; + + public record struct VisibilityResult + { + public bool IsVisible; + public float VisibilityPercentage; // 0.0 to 1.0 + public int RaysHit; + public int TotalRays; + } + + public VisibilityResult CheckVisibility(Game game, Entity target, CameraComponent camera, Stride.BepuPhysics.BepuSimulation simulation) + { + _debugDraw ??= game.Services.GetService(); + _debugDraw.Enabled = true; + _debugDraw.Visible = true; + + var cameraPos = camera.Entity.Transform.WorldMatrix.TranslationVector; + + // Get target bounding box points + var targetTransform = target.Transform; + var targetModel = target.Get(); + var worldMatrix = targetTransform.WorldMatrix; + var localBoundingBox = targetModel.Model.BoundingBox; + BoundingBox.Transform(ref localBoundingBox, ref worldMatrix, out var worldBoundingBox); + + var testPoints = GenerateTestPoints(worldBoundingBox); + var visibleRays = 0; + + foreach (var point in testPoints) + { + var direction = point - cameraPos; + + direction.Normalize(); + _debugDraw.DrawRay(cameraPos, direction*100, Color.Yellow, 0, false); + + // Use Bepu's raycast method + var hit = simulation.RayCast( + cameraPos, + direction, + float.MaxValue, + out var hitInfo + ); + + if (hit && hitInfo.Collidable?.Entity == target) + { + visibleRays++; // Ray hit the target + } + } + + var visibilityPercentage = (float)visibleRays / testPoints.Length; + + return new VisibilityResult + { + IsVisible = visibilityPercentage > 0.0f, + VisibilityPercentage = visibilityPercentage, + RaysHit = visibleRays, + TotalRays = testPoints.Length + }; + } + + private Vector3[] GenerateTestPoints(BoundingBox boundingBox) + { + var min = boundingBox.Minimum; + var max = boundingBox.Maximum; + var center = (min + max) * 0.5f; + + return + [ + // 8 corners of the bounding box + new(min.X, min.Y, min.Z), // 0: min corner + new(max.X, min.Y, min.Z), // 1: +X + new(min.X, max.Y, min.Z), // 2: +Y + new(min.X, min.Y, max.Z), // 3: +Z + new(max.X, max.Y, min.Z), // 4: +XY + new(max.X, min.Y, max.Z), // 5: +XZ + new(min.X, max.Y, max.Z), // 6: +YZ + new(max.X, max.Y, max.Z), // 7: max corner + + // Face centers + center, // Center + new(center.X, center.Y, min.Z), // Front face center + new(center.X, center.Y, max.Z), // Back face center + new(min.X, center.Y, center.Z), // Left face center + new(max.X, center.Y, center.Z), // Right face center + new(center.X, min.Y, center.Z), // Bottom face center + new(center.X, max.Y, center.Z), // Top face center + ]; + } + + public override void Update() + { + LastVisibilityResult = CheckVisibility(Game, Target, Camera, Simulation); + } +} \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/MultiRaycastVisibility.cs b/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibilityComponent.cs similarity index 100% rename from Local/StrideExamples.Local.OcclusionTest/MultiRaycastVisibility.cs rename to Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibilityComponent.cs diff --git a/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs b/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs index b02801e..51c9245 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Managers/GameManager.cs @@ -1,16 +1,18 @@ using Stride.Engine; +using Stride.Games; using Stride.Graphics; namespace StrideExamples.Local.OcclusionTest.Managers; public class GameManager { - private readonly UIManager _uiManager; + private readonly MultiRaycastVisibility _visibility; - public GameManager(SpriteFont font) + public UIManager UIManager { get; } + + public GameManager(Scene rootScene, SpriteFont font) { - _uiManager = new UIManager(); + UIManager = new UIManager(rootScene, font); + _visibility = new MultiRaycastVisibility(); } - - internal Entity CreateUI() => _uiManager.CreateUI(); } \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs b/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs index c09e66a..9566f40 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs @@ -1,20 +1,142 @@ +using Stride.Core.Mathematics; using Stride.Engine; +using Stride.Graphics; +using Stride.Input; +using Stride.Rendering; +using Stride.UI; +using Stride.UI.Controls; +using Stride.UI.Events; +using Stride.UI.Panels; +using StrideExamples.Local.OcclusionTest.Components; namespace StrideExamples.Local.OcclusionTest.Managers; public class UIManager { - public Entity CreateUI() + private const string EntityName = "GameUI"; + private readonly SpriteFont _font; + private readonly Color _gridBackgroundColor = Color.LightGray; + private readonly Grid _grid; + + private readonly StackPanel _panel; + + private List _monitoredEntities = []; + + public UIManager(Scene rootScene, SpriteFont spriteFont) + { + _font = spriteFont; + _grid = CreateGrid(); + _panel = CreatePanel(_grid); + + var ui = CreateUI(_grid); + ui.Scene = rootScene; + } + + internal Entity CreateUI(Grid grid) => new(EntityName) + { + new UIComponent + { + Page = new UIPage { RootElement = grid }, + RenderGroup = RenderGroup.Group31 + } + }; + + internal void UpdateUI() { - var entity = new Entity("UI") + _panel.Children.Clear(); + foreach (var entity in _monitoredEntities) { - new UIComponent + var visibility = entity.Get(); + + if (visibility is not null) + { + _panel.Children.Add(CreateTextBlock($"Entity: {entity.Name} ({visibility.LastVisibilityResult.VisibilityPercentage:P}%, {visibility.LastVisibilityResult.RaysHit} / {visibility.LastVisibilityResult.TotalRays})", 12, TextAlignment.Left)); + } + } + } + + public void MonitorEntity(Entity entity) + { + if (!_monitoredEntities.Contains(entity)) + { + _monitoredEntities.Add(entity); + } + } + + private StackPanel CreatePanel(Grid grid) + { + var panel = CreateVisibilityPanel(); + panel.SetGridColumn(2); + panel.SetGridRow(1); + grid.Children.Add(panel); + + return panel; + } + + private static Grid CreateGrid() => new() + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Stretch, + RowDefinitions = { + new StripDefinition() { Type = StripType.Auto }, + new StripDefinition() { Type = StripType.Auto }, + new StripDefinition() { Type = StripType.Auto }, + new StripDefinition() { Type = StripType.Auto } + }, + ColumnDefinitions = { - Page = new UIPage { RootElement = new Stride.UI.Controls.TextBlock { Text = "Occlusion Test" } }, - RenderGroup = Stride.Rendering.RenderGroup.Group31 + new StripDefinition(StripType.Star, 1), + new StripDefinition(StripType.Star, 1), + new StripDefinition(StripType.Star, 1) } + }; + + private StackPanel CreateVisibilityPanel() + { + var panel = new StackPanel + { + Orientation = Orientation.Vertical, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + BackgroundColor = _gridBackgroundColor, + Margin = new Thickness(0, 3, 0, 3), + }; + + panel.Children.Add(CreateTextBlock("Visibility:", 14, TextAlignment.Center)); + panel.Children.Add(CreateSlider()); + + return panel; + } + + + private Slider CreateSlider() + { + var slider = new Slider + { + Minimum = 0, + Maximum = 100, + Value = 50, + Width = 200, + Margin = new Thickness(3, 3, 3, 3), + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + BackgroundColor = Color.Black, + MinimumWidth = 100, + Name = "VisibilitySlider" }; - return entity; + return slider; } + + private TextBlock CreateTextBlock(string? text = null, float textSize = 20, TextAlignment textAlignment = TextAlignment.Left) + => new() + { + Text = text, + TextColor = Color.Black, + Margin = new Thickness(3, 0, 3, 0), + TextSize = textSize, + Font = _font, + TextAlignment = textAlignment, + VerticalAlignment = VerticalAlignment.Center + }; } \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/Program.cs b/Local/StrideExamples.Local.OcclusionTest/Program.cs index 8dcb1ac..bbf22e5 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Program.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Program.cs @@ -1,18 +1,23 @@ using Stride.CommunityToolkit.Bepu; +using Stride.CommunityToolkit.DebugShapes.Code; using Stride.CommunityToolkit.Engine; using Stride.CommunityToolkit.Games; +using Stride.CommunityToolkit.Renderers; using Stride.CommunityToolkit.Rendering.ProceduralModels; using Stride.Core.Mathematics; using Stride.Engine; using Stride.Games; using Stride.Graphics; +using Stride.Rendering; +using Stride.Rendering.Materials; +using Stride.Rendering.Materials.ComputeColors; +using StrideExamples.Local.OcclusionTest.Components; using StrideExamples.Local.OcclusionTest.Managers; Console.WriteLine("Hello, World!"); using var game = new Game(); -var visibility = new MultiRaycastVisibility(); game.Run(start: Start, update: Update); void Start(Scene rootScene) @@ -21,35 +26,46 @@ void Start(Scene rootScene) game.AddGroundGizmo(); game.Add3DGround(); game.AddProfiler(); - // game.AddEntityDebugSceneRenderer(); + game.AddAllDirectionLighting(); + game.AddEntityDebugSceneRenderer(); + game.AddDebugShapes(); var font = game.Content.Load("StrideDefaultFont"); - var gameManager = new GameManager(font); + var gameManager = new GameManager(rootScene, font); game.Services.AddService(gameManager); - var uiEntity = gameManager.CreateUI(); - uiEntity.Scene = rootScene; + var greenCube = CreateCube(rootScene, game, "GreenCube", new(-5, 1, 0), Color.Green); + gameManager.UIManager.MonitorEntity(greenCube); - CreateCube(rootScene, game, "GreenCube", new(-5, 1, 0), Color.Green); - CreateCube(rootScene, game, "BlueCube", new(5, 1, 0), Color.Blue); + var blueCube = CreateCube(rootScene, game, "BlueCube", new(5, 1, 0), Color.Blue); + gameManager.UIManager.MonitorEntity(blueCube); } void Update(Scene rootScene, GameTime gameTime) { - if (!game.Input.IsKeyPressed(Stride.Input.Keys.Space)) - { - return; - } - - var camera = rootScene.Entities.First(e => e.Get() != null).Get(); + var gameManager = game.Services.GetService(); + gameManager?.UIManager.UpdateUI(); +} - var greenCube = rootScene.Entities.First(e => e.Name == "GreenCube"); - var greenVisibility = visibility.CheckVisibility(game, greenCube, camera, camera.Entity.GetSimulation()); - Console.WriteLine($"Green cube: {visibility.CheckVisibility(game, greenCube, camera, camera.Entity.GetSimulation())}"); +static Material CreateMaterial(Game game, Color? color = null, float specular = 1.0f, float microSurface = 0.65f) +{ + var lightmapMaterial = new MaterialDescriptor + { + Attributes = + { + Diffuse = new MaterialDiffuseMapFeature(new ComputeColor(color ?? GameDefaults.DefaultMaterialColor)), + // DiffuseModel = new MaterialLightmapModelFeature() + // { + // Intensity = 20, + // LightMap = new ComputeColor(color ?? GameDefaults.DefaultMaterialColor) + // }, + Specular = new MaterialMetalnessMapFeature(new ComputeFloat(specular)), + SpecularModel = new MaterialSpecularMicrofacetModelFeature(), + MicroSurface = new MaterialGlossinessMapFeature(new ComputeFloat(microSurface)) + } + }; - var blueCube = rootScene.Entities.First(e => e.Name == "BlueCube"); - var blueVisibility = visibility.CheckVisibility(game, blueCube, camera, camera.Entity.GetSimulation()); - Console.WriteLine($"Blue cube: {visibility.CheckVisibility(game, blueCube, camera, camera.Entity.GetSimulation())}"); + return Material.New(game.GraphicsDevice, lightmapMaterial); } static Entity CreateCube(Scene rootScene, Game game, string name, Vector3 position, Color color) @@ -67,5 +83,13 @@ static Entity CreateCube(Scene rootScene, Game game, string name, Vector3 positi cube.Transform.Position = position; cube.Scene = rootScene; + cube.Add(new MultiRaycastVisibilityComponent + { + Game = game, + Target = cube, + Camera = rootScene.GetCamera() ?? throw new InvalidOperationException("No camera found in scene"), + Simulation = cube.GetSimulation() + }); + return cube; } \ No newline at end of file diff --git a/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj b/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj index 4d5fccc..f5136ef 100644 --- a/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj +++ b/Local/StrideExamples.Local.OcclusionTest/StrideExamples.Local.OcclusionTest.csproj @@ -12,6 +12,7 @@ + From b39259a24bd66abf27f1c307ab1537552311de15 Mon Sep 17 00:00:00 2001 From: John Ludlow Date: Sat, 13 Sep 2025 20:27:22 +0100 Subject: [PATCH 4/4] User interface --- .../Components/MultiRaycastVisibility.cs | 9 ++++++--- .../Managers/UIManager.cs | 2 +- Local/StrideExamples.Local.OcclusionTest/Program.cs | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs b/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs index 58eedc0..cc21238 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Components/MultiRaycastVisibility.cs @@ -27,8 +27,11 @@ public record struct VisibilityResult public VisibilityResult CheckVisibility(Game game, Entity target, CameraComponent camera, Stride.BepuPhysics.BepuSimulation simulation) { _debugDraw ??= game.Services.GetService(); - _debugDraw.Enabled = true; - _debugDraw.Visible = true; + if (_debugDraw is not null) + { + _debugDraw.Enabled = true; + _debugDraw.Visible = true; + } var cameraPos = camera.Entity.Transform.WorldMatrix.TranslationVector; @@ -47,7 +50,7 @@ public VisibilityResult CheckVisibility(Game game, Entity target, CameraComponen var direction = point - cameraPos; direction.Normalize(); - _debugDraw.DrawRay(cameraPos, direction*100, Color.Yellow, 0, false); + _debugDraw?.DrawRay(cameraPos, direction*100, Color.Yellow, 0, false); // Use Bepu's raycast method var hit = simulation.RayCast( diff --git a/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs b/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs index 9566f40..9b285ee 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Managers/UIManager.cs @@ -50,7 +50,7 @@ internal void UpdateUI() if (visibility is not null) { - _panel.Children.Add(CreateTextBlock($"Entity: {entity.Name} ({visibility.LastVisibilityResult.VisibilityPercentage:P}%, {visibility.LastVisibilityResult.RaysHit} / {visibility.LastVisibilityResult.TotalRays})", 12, TextAlignment.Left)); + _panel.Children.Add(CreateTextBlock($"Entity: {entity.Name} ({visibility.LastVisibilityResult.VisibilityPercentage:P}% visible, {visibility.LastVisibilityResult.RaysHit} / {visibility.LastVisibilityResult.TotalRays} rays hit)", 12, TextAlignment.Left)); } } } diff --git a/Local/StrideExamples.Local.OcclusionTest/Program.cs b/Local/StrideExamples.Local.OcclusionTest/Program.cs index bbf22e5..48fe14d 100644 --- a/Local/StrideExamples.Local.OcclusionTest/Program.cs +++ b/Local/StrideExamples.Local.OcclusionTest/Program.cs @@ -27,8 +27,8 @@ void Start(Scene rootScene) game.Add3DGround(); game.AddProfiler(); game.AddAllDirectionLighting(); - game.AddEntityDebugSceneRenderer(); - game.AddDebugShapes(); + // game.AddEntityDebugSceneRenderer(); + // game.AddDebugShapes(); var font = game.Content.Load("StrideDefaultFont"); var gameManager = new GameManager(rootScene, font);