diff --git a/ACViewer/Config/MapViewerOptions.cs b/ACViewer/Config/MapViewerOptions.cs index 6daf1a9..ed40d08 100644 --- a/ACViewer/Config/MapViewerOptions.cs +++ b/ACViewer/Config/MapViewerOptions.cs @@ -5,6 +5,9 @@ namespace ACViewer.Config public class MapViewerOptions { public MapViewerMode Mode { get; set; } + public bool EnableZSlicing { get; set; } = false; + public int CurrentZLevel { get; set; } = 1; + public float LevelHeight { get; set; } = 10.0f; public MapViewerOptions() { diff --git a/ACViewer/Extensions/CommandHandler.cs b/ACViewer/Extensions/CommandHandler.cs index 2ab31e4..3ef602e 100644 --- a/ACViewer/Extensions/CommandHandler.cs +++ b/ACViewer/Extensions/CommandHandler.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Input; +using ACViewer.Config; namespace ACViewer.Extensions { diff --git a/ACViewer/Extensions/Vector3Extensions.cs b/ACViewer/Extensions/Vector3Extensions.cs index 808c1f2..de74dc0 100644 --- a/ACViewer/Extensions/Vector3Extensions.cs +++ b/ACViewer/Extensions/Vector3Extensions.cs @@ -1,11 +1,16 @@ using System; +using Microsoft.Xna.Framework; + +using System.Numerics; + using ACE.Server.Physics; namespace ACViewer { public static class Vector3Extensions { + public static System.Numerics.Vector3 ToNumerics(this Microsoft.Xna.Framework.Vector3 v) { return new System.Numerics.Vector3(v.X, v.Y, v.Z); diff --git a/ACViewer/Model/VertexInstance.cs b/ACViewer/Model/VertexInstance.cs index cbfa891..8c3a6d2 100644 --- a/ACViewer/Model/VertexInstance.cs +++ b/ACViewer/Model/VertexInstance.cs @@ -5,9 +5,10 @@ namespace ACViewer.Model { public struct VertexInstance : IVertexType { - public Vector3 Position; + public Vector3 Position { get; set; } public Vector4 Orientation; public Vector3 Scale; + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration ( diff --git a/ACViewer/Render/Buffer.cs b/ACViewer/Render/Buffer.cs index 1afad3f..36b524a 100644 --- a/ACViewer/Render/Buffer.cs +++ b/ACViewer/Render/Buffer.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using ACE.DatLoader.Entity; using ACE.Server.Physics; - +using ACViewer.Config; +using ACViewer.Extensions; using ACViewer.Enum; using ACViewer.Model; @@ -86,6 +88,95 @@ public void Init() AnimatedTextureAtlasChains = new Dictionary<TextureFormat, TextureAtlasChain>(); } + + private bool IsInCurrentZLevel(Vector3 position) + { + if (!ConfigManager.Config.MapViewer.EnableZSlicing) + return true; + + var config = ConfigManager.Config.MapViewer; + float levelBottom = (config.CurrentZLevel - 1) * config.LevelHeight; + float levelTop = levelBottom + config.LevelHeight; + + return position.Z >= levelBottom && position.Z < levelTop; + } + + public void DrawWithZSlicing() + { + Effect.Parameters["xWorld"].SetValue(Matrix.Identity); + Effect.Parameters["xLightDirection"].SetValue(-Vector3.UnitZ); + Effect.Parameters["xAmbient"].SetValue(0.5f); + + Effect_Clamp.Parameters["xWorld"].SetValue(Matrix.Identity); + Effect_Clamp.Parameters["xLightDirection"].SetValue(-Vector3.UnitZ); + Effect_Clamp.Parameters["xAmbient"].SetValue(0.5f); + + PerfTimer.Start(ProfilerSection.Draw); + + if (drawTerrain) + { + SetRasterizerState(); + TerrainBatch.DrawWithZFiltering(IsInCurrentZLevel); + } + + if (drawEnvCells) + DrawBufferWithZSlicing(RB_EnvCell, true); + + if (drawStaticObjs) + DrawBufferWithZSlicing(RB_StaticObjs); + + if (drawBuildings) + DrawBufferWithZSlicing(RB_Buildings); + + if (drawScenery) + DrawBufferWithZSlicing(RB_Scenery); + + if (drawInstances && Server.InstancesLoaded) + DrawBufferWithZSlicing(RB_Instances); + + if (drawEncounters && Server.EncountersLoaded) + DrawBufferWithZSlicing(RB_Encounters); + + DrawBufferWithZSlicing(RB_Animated); + + if (Picker.HitVertices != null) + Picker.DrawHitPoly(); + + PerfTimer.Stop(ProfilerSection.Draw); + } + + private void DrawBufferWithZSlicing(Dictionary<uint, GfxObjInstance_Shared> batches) + { + SetRasterizerState(CullMode.None); + + foreach (var batch in batches.Values) + batch.DrawFiltered(IsInCurrentZLevel); + } + + private void DrawBufferWithZSlicing(Dictionary<GfxObjTexturePalette, GfxObjInstance_Shared> batches) + { + SetRasterizerState(CullMode.None); + + foreach (var batch in batches.Values) + batch.DrawFiltered(IsInCurrentZLevel); + } + + private void DrawBufferWithZSlicing(Dictionary<TextureSet, InstanceBatch> batches, bool culling = false) + { + var cullMode = WorldViewer.Instance.DungeonMode || culling ? + CullMode.CullClockwiseFace : CullMode.None; + + SetRasterizerState(cullMode); + + Effect.CurrentTechnique = Effect.Techniques["TexturedInstanceEnv"]; + Effect_Clamp.CurrentTechnique = Effect_Clamp.Techniques["TexturedInstanceEnv"]; + + foreach (var batch in batches.Values) + { + batch.DrawFiltered(IsInCurrentZLevel); + } + } + public void ClearBuffer() { diff --git a/ACViewer/Render/Camera.cs b/ACViewer/Render/Camera.cs index e758253..e2a72d8 100644 --- a/ACViewer/Render/Camera.cs +++ b/ACViewer/Render/Camera.cs @@ -295,6 +295,31 @@ public void Update(GameTime gameTime) Position -= Vector3.Cross(Up, Dir) * Speed; if (keyboardState.IsKeyDown(Keys.Space)) Position += Up * Speed; + // Shift key control for downward movement + if (keyboardState.IsKeyDown(Keys.LeftShift) || keyboardState.IsKeyDown(Keys.RightShift)) + Position -= Up * Speed; + + // Z-level controls + if (keyboardState.IsKeyDown(Keys.F3) && LastKeyboardState != null && !LastKeyboardState.IsKeyDown(Keys.F3)) + { + ConfigManager.Config.MapViewer.EnableZSlicing = !ConfigManager.Config.MapViewer.EnableZSlicing; + ConfigManager.Config.MapViewer.CurrentZLevel = 1; + } + + // Z-level adjustment + if (ConfigManager.Config.MapViewer.EnableZSlicing) + { + if ((keyboardState.IsKeyDown(Keys.LeftAlt) || keyboardState.IsKeyDown(Keys.RightAlt))) + { + var config = ConfigManager.Config.MapViewer; + if (keyboardState.IsKeyDown(Keys.OemPlus) && !LastKeyboardState.IsKeyDown(Keys.OemPlus)) + config.CurrentZLevel = Math.Min(config.CurrentZLevel + 1, 20); + if (keyboardState.IsKeyDown(Keys.OemMinus) && !LastKeyboardState.IsKeyDown(Keys.OemMinus)) + config.CurrentZLevel--; + } + } + + LastKeyboardState = keyboardState; // camera speed control if (mouseState.ScrollWheelValue != PrevMouseState.ScrollWheelValue) @@ -368,6 +393,7 @@ public void Update(GameTime gameTime) //Console.WriteLine("Camera dir: " + GameView.Instance.Render.Camera.Dir); } + private KeyboardState LastKeyboardState; public int centerX => GameView.GraphicsDevice.Viewport.Width / 2; public int centerY => GameView.GraphicsDevice.Viewport.Height / 2; diff --git a/ACViewer/Render/GfxObjInstance_Shared.cs b/ACViewer/Render/GfxObjInstance_Shared.cs index da16e5e..175a00e 100644 --- a/ACViewer/Render/GfxObjInstance_Shared.cs +++ b/ACViewer/Render/GfxObjInstance_Shared.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; - +using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -25,9 +26,8 @@ public class GfxObjInstance_Shared public Dictionary<TextureFormatChain, GfxObjInstance_TextureFormat> BaseFormats_Alpha { get; set; } public List<VertexPositionNormalTextures> Vertices { get; set; } - + public List<VertexInstance> Instances { get; set; } - public VertexInstance[] Instances_ { get; set; } public VertexBuffer Shared_VB { get; set; } @@ -39,9 +39,7 @@ public class GfxObjInstance_Shared public GfxObjInstance_Shared(GfxObj gfxObj, Dictionary<TextureFormat, TextureAtlasChain> textureAtlasChains, Dictionary<uint, uint> textureChanges = null, PaletteChanges paletteChanges = null) { GfxObj = gfxObj; - BuildStatic(gfxObj, textureAtlasChains, textureChanges, paletteChanges); - Instances = new List<VertexInstance>(); } @@ -185,6 +183,51 @@ public void Draw() foreach (var baseFormat in BaseFormats_Alpha.Values) baseFormat.Draw(Instances.Count); } + + public void DrawFiltered(Func<Vector3, bool> filter) + { + if (Bindings == null) return; + + if (isDirty) + { + Instances_VB.SetData(Instances_); + isDirty = false; + } + + // Store original instances + var originalInstances = Instances_.ToArray(); + + // Filter instances + var filteredInstances = Instances.Where(instance => filter(instance.Position)).ToArray(); + + if (filteredInstances.Length > 0) + { + // Update vertex buffer with filtered instances + Instances_ = filteredInstances; + Instances_VB.SetData(filteredInstances); + + GraphicsDevice.SetVertexBuffers(Bindings); + + Effect.CurrentTechnique = Effect.Techniques["TexturedInstance"]; + Effect_Clamp.CurrentTechnique = Effect_Clamp.Techniques["TexturedInstance"]; + + foreach (var baseFormat in BaseFormats_Solid.Values) + baseFormat.Draw(filteredInstances.Length); + + if (Buffer.drawAlpha) + { + Effect.CurrentTechnique = Effect.Techniques["TexturedInstanceAlpha"]; + Effect_Clamp.CurrentTechnique = Effect_Clamp.Techniques["TexturedInstanceAlpha"]; + } + + foreach (var baseFormat in BaseFormats_Alpha.Values) + baseFormat.Draw(filteredInstances.Length); + } + + // Restore original instances + Instances_ = originalInstances; + Instances_VB.SetData(originalInstances); + } public void Dispose() { diff --git a/ACViewer/Render/InstanceBatch.cs b/ACViewer/Render/InstanceBatch.cs index 10c413b..b7d363c 100644 --- a/ACViewer/Render/InstanceBatch.cs +++ b/ACViewer/Render/InstanceBatch.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; - +using System; +using System.Collections.Generic; +using System.Linq; using ACE.Entity.Enum; - +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace ACViewer.Render @@ -17,6 +18,28 @@ public class InstanceBatch public VertexBuffer InstanceBuffer { get; set; } public R_Environment R_Environment { get; set; } + + public void DrawFiltered(Func<Vector3, bool> filter) + { + // Store original instances + var originalInstances = new List<VertexInstanceEnv>(Instances_Env); + + // Filter instances based on Z position + Instances_Env = Instances_Env.Where(instance => filter(instance.Position)).ToList(); + + if (Instances_Env.Count > 0) + { + // Rebuild instance buffer with filtered instances + BuildInstanceBuffer(); + BuildBindings(); + Draw(); + } + + // Restore original instances + Instances_Env = originalInstances; + BuildInstanceBuffer(); + BuildBindings(); + } public InstanceBatch(R_EnvCell envCell) { diff --git a/ACViewer/Render/Render.cs b/ACViewer/Render/Render.cs index 110a17a..56d44d2 100644 --- a/ACViewer/Render/Render.cs +++ b/ACViewer/Render/Render.cs @@ -25,6 +25,9 @@ public class Render // multiple SamplerStates in the same .fx file apparently don't work public static Effect Effect_Clamp { get; set; } + + // Add to existing properties + private MapViewerOptions Config => ConfigManager.Config.MapViewer; public Camera Camera { @@ -67,21 +70,23 @@ public void SetRasterizerState(bool wireframe = true) GraphicsDevice.RasterizerState = rs; } - + public void Draw() { GraphicsDevice.Clear(ConfigManager.Config.BackgroundColors.WorldViewer); SetRasterizerState(false); - + Effect.Parameters["xView"].SetValue(Camera.ViewMatrix); Effect_Clamp.Parameters["xView"].SetValue(Camera.ViewMatrix); - //landblock.Draw(); - Buffer.Draw(); + if (ConfigManager.Config.MapViewer.EnableZSlicing) + Buffer.DrawWithZSlicing(); + else + Buffer.Draw(); - //DrawEmitters_Naive(); DrawEmitters_Batch(); + DrawHUD(); } public bool ParticlesInitted { get; set; } @@ -186,14 +191,29 @@ public void DestroyEmitters() private static readonly Vector2 TextPos = new Vector2(10, 10); + // DrawHUD to show Z-slice information public void DrawHUD() { - var cameraPos = GameView.Camera.GetPosition(); + var text = ""; + + if (ConfigManager.Config.MapViewer.EnableZSlicing) + { + var config = ConfigManager.Config.MapViewer; + string levelPrefix = config.CurrentZLevel < 0 ? "B" : ""; // Add "B" prefix for basement levels + int displayLevel = config.CurrentZLevel < 0 ? -config.CurrentZLevel : config.CurrentZLevel; + + text += $"Current Z-Level: {levelPrefix}{displayLevel}\n"; // Shows B1, B2, etc. for basement levels + text += $"Height Range: {(config.CurrentZLevel - 1) * config.LevelHeight:F1}m - {config.CurrentZLevel * config.LevelHeight:F1}m\n"; + } + var cameraPos = Camera.GetPosition(); if (cameraPos != null) + text += $"Location: {cameraPos}"; + + if (!string.IsNullOrEmpty(text)) { SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.LinearClamp); - SpriteBatch.DrawString(Font, $"Location: {cameraPos}", TextPos, Color.White); + SpriteBatch.DrawString(Font, text, TextPos, Color.White); SpriteBatch.End(); } } diff --git a/ACViewer/Render/TerrainBatch.cs b/ACViewer/Render/TerrainBatch.cs index 5615084..7c6b764 100644 --- a/ACViewer/Render/TerrainBatch.cs +++ b/ACViewer/Render/TerrainBatch.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; - +using System.Linq; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace ACViewer.Render @@ -56,6 +57,33 @@ public void Draw() foreach (var batch in Batches) batch.Draw(); } + + public void DrawWithZFiltering(Func<Vector3, bool> filter) + { + Effect.CurrentTechnique = Effect.Techniques["LandscapeSinglePass"]; + + if (OverlayAtlasChain.TextureAtlases.Count > 0) + Effect.Parameters["xOverlays"].SetValue(OverlayAtlasChain.TextureAtlases[0]._Textures); + if (AlphaAtlasChain.TextureAtlases.Count > 0) + Effect.Parameters["xAlphas"].SetValue(AlphaAtlasChain.TextureAtlases[0]._Textures); + + foreach (var batch in Batches) + { + if (batch.Vertices.Count == 0) continue; + + var originalVertices = new List<LandVertex>(batch.Vertices); + batch.Vertices = batch.Vertices.Where(v => filter(v.Position)).ToList(); + + if (batch.Vertices.Count > 0) + { + batch.OnCompleted(); + batch.Draw(); + } + + batch.Vertices = originalVertices; + batch.OnCompleted(); + } + } public void Dispose() {