From 9f9d5066e90619ab3600d786f514c199c26c0ce9 Mon Sep 17 00:00:00 2001 From: Rampastring Date: Wed, 25 Sep 2024 23:30:24 +0300 Subject: [PATCH] Implement batched rendering for objects --- src/TSMapEditor/Constants.cs | 3 +- src/TSMapEditor/Content/Content.mgcb | 6 + .../Content/Shaders/CombineWithDepth.fx | 92 +++++ .../Content/Shaders/PalettedColorDraw.fx | 9 +- src/TSMapEditor/Initialization/MapLoader.cs | 2 +- src/TSMapEditor/Models/Animation.cs | 2 + src/TSMapEditor/Models/BridgeType.cs | 16 +- src/TSMapEditor/Models/GameObject.cs | 2 + src/TSMapEditor/Models/Overlay.cs | 2 + src/TSMapEditor/Models/OverlayType.cs | 3 + src/TSMapEditor/Models/Smudge.cs | 2 + src/TSMapEditor/Models/Techno.cs | 2 + src/TSMapEditor/Models/TerrainObject.cs | 2 + .../Mutations/Classes/PlaceBridgeMutation.cs | 36 +- src/TSMapEditor/Rendering/MapView.cs | 358 ++++++++++-------- .../ObjectRenderers/AircraftRenderer.cs | 4 +- .../Rendering/ObjectRenderers/AnimRenderer.cs | 24 +- .../ObjectRenderers/BuildingRenderer.cs | 80 ++-- .../ObjectRenderers/InfantryRenderer.cs | 19 +- .../ObjectRenderers/ObjectRenderer.cs | 221 ++++++----- .../ObjectRenderers/OverlayRenderer.cs | 43 +-- .../ObjectRenderers/RenderDependencies.cs | 22 +- .../ObjectRenderers/SmudgeRenderer.cs | 41 +- .../ObjectRenderers/TerrainRenderer.cs | 6 +- .../Rendering/ObjectRenderers/UnitRenderer.cs | 40 +- .../Rendering/ObjectSpriteRecord.cs | 164 ++++++++ src/TSMapEditor/UI/SettingsPanel.cs | 20 +- 27 files changed, 836 insertions(+), 385 deletions(-) create mode 100644 src/TSMapEditor/Content/Shaders/CombineWithDepth.fx create mode 100644 src/TSMapEditor/Rendering/ObjectSpriteRecord.cs diff --git a/src/TSMapEditor/Constants.cs b/src/TSMapEditor/Constants.cs index 8dede4334..73a494c13 100644 --- a/src/TSMapEditor/Constants.cs +++ b/src/TSMapEditor/Constants.cs @@ -95,7 +95,8 @@ public static class Constants public const string NoneValue2 = "None"; public const float RemapBrightenFactor = 1.25f; - public const float DepthRenderStep = 1 / 32f; + public const float DownwardsDepthRenderSpace = 0.3f; + public static readonly float DepthRenderStep = 0.5f / MaxMapHeightLevel; public const bool DrawShadows = true; diff --git a/src/TSMapEditor/Content/Content.mgcb b/src/TSMapEditor/Content/Content.mgcb index a26893acb..46db20383 100644 --- a/src/TSMapEditor/Content/Content.mgcb +++ b/src/TSMapEditor/Content/Content.mgcb @@ -19,6 +19,12 @@ /processorParam:DebugMode=Debug /build:Shaders/ColorDraw.fx +#begin Shaders/CombineWithDepth.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Debug +/build:Shaders/CombineWithDepth.fx + #begin Shaders/DepthApply.fx /importer:EffectImporter /processor:EffectProcessor diff --git a/src/TSMapEditor/Content/Shaders/CombineWithDepth.fx b/src/TSMapEditor/Content/Shaders/CombineWithDepth.fx new file mode 100644 index 000000000..8848b4ce1 --- /dev/null +++ b/src/TSMapEditor/Content/Shaders/CombineWithDepth.fx @@ -0,0 +1,92 @@ +#pragma enable_d3d11_debug_symbols + +#if OPENGL +#define SV_POSITION POSITION +#define VS_SHADERMODEL vs_3_0 +#define PS_SHADERMODEL ps_3_0 +#else +#define VS_SHADERMODEL vs_4_0 +#define PS_SHADERMODEL ps_4_0 +#endif + +// Shader for combining the terrain and object render layers, +// taking depth of each into account in separate textures. + +sampler2D SpriteTextureSampler : register(s0) +{ + Texture = (SpriteTexture); // this is set by spritebatch + MipFilter = Point; + MinFilter = Point; + MagFilter = Point; +}; + +Texture2D TerrainDepthTexture; +sampler2D TerrainDepthTextureSampler +{ + Texture = ; + AddressU = clamp; + AddressV = clamp; + MipFilter = Point; + MinFilter = Point; + MagFilter = Point; +}; + +// We use SpriteTextureSampler in place of this, because it has to be supplied anyway +// Texture2D ObjectsTexture; +// sampler2D ObjectsTextureSampler +// { +// Texture = ; +// AddressU = clamp; +// AddressV = clamp; +// MipFilter = Point; +// MinFilter = Point; +// MagFilter = Point; +// }; + +Texture2D ObjectsDepthTexture; +sampler2D ObjectsDepthTextureSampler +{ + Texture = ; + AddressU = clamp; + AddressV = clamp; + MipFilter = Point; + MinFilter = Point; + MagFilter = Point; +}; + +struct VertexShaderOutput +{ + float4 Position : SV_POSITION; + float4 Color : COLOR0; + float2 TextureCoordinates : TEXCOORD0; +}; + +float4 MainPS(VertexShaderOutput input) : COLOR +{ + // We need to read from the main texture first, + // otherwise the output will be black! + float4 objectsTex = tex2D(SpriteTextureSampler, input.TextureCoordinates); + float terrainDepth = tex2D(TerrainDepthTextureSampler, input.TextureCoordinates).x; + float objectsDepth = tex2D(ObjectsDepthTextureSampler, input.TextureCoordinates).x; + + if (objectsTex.a == 0) + { + discard; + } + + if (objectsDepth < terrainDepth) + { + discard; + } + + return objectsTex; +} + +technique SpriteDrawing +{ + pass P0 + { + AlphaBlendEnable = TRUE; + PixelShader = compile PS_SHADERMODEL MainPS(); + } +}; \ No newline at end of file diff --git a/src/TSMapEditor/Content/Shaders/PalettedColorDraw.fx b/src/TSMapEditor/Content/Shaders/PalettedColorDraw.fx index 8a9676004..1d579871b 100644 --- a/src/TSMapEditor/Content/Shaders/PalettedColorDraw.fx +++ b/src/TSMapEditor/Content/Shaders/PalettedColorDraw.fx @@ -46,8 +46,9 @@ struct VertexShaderOutput struct PixelShaderOutput { - float4 color : SV_Target; - float depth : SV_Depth; + float4 color : SV_Target0; + float depthTarget : SV_Target1; + float depthEmbedded : SV_Depth; }; PixelShaderOutput MainPS(VertexShaderOutput input) @@ -65,7 +66,8 @@ PixelShaderOutput MainPS(VertexShaderOutput input) // This is because when doing batched rendering, shader parameters cannot be changed // within one batch, meaning we cannot introduce a shader parameter for depth // without sacrificing performance. And we need a unique depth value for each sprite. - output.depth = input.Color.a; + output.depthTarget = input.Color.a; + output.depthEmbedded = input.Color.a; if (IsShadow) { @@ -115,7 +117,6 @@ technique SpriteDrawing { pass P0 { - AlphaBlendEnable = TRUE; PixelShader = compile PS_SHADERMODEL MainPS(); } }; \ No newline at end of file diff --git a/src/TSMapEditor/Initialization/MapLoader.cs b/src/TSMapEditor/Initialization/MapLoader.cs index a0dfaf4e9..7ec82ec7e 100644 --- a/src/TSMapEditor/Initialization/MapLoader.cs +++ b/src/TSMapEditor/Initialization/MapLoader.cs @@ -1149,7 +1149,7 @@ public static void ReadHouses(IMap map, IniFile mapIni) if (houseType == null) { - houseType = map.StandardHouseTypes[0]; + houseType = map.GetHouseTypes()[0]; AddMapLoadError($"Nonexistent Country= or no Country= specified for House {houseName}. This makes it default to the first standard Country ({houseType.ININame})."); } } diff --git a/src/TSMapEditor/Models/Animation.cs b/src/TSMapEditor/Models/Animation.cs index e2b114e28..3436c72c7 100644 --- a/src/TSMapEditor/Models/Animation.cs +++ b/src/TSMapEditor/Models/Animation.cs @@ -17,6 +17,8 @@ public Animation(AnimType animType, Point2D position) : this(animType) public override RTTIType WhatAmI() => RTTIType.Anim; + public override GameObjectType GetObjectType() => AnimType; + public AnimType AnimType { get; private set; } public House Owner { get; set; } public byte Facing { get; set; } diff --git a/src/TSMapEditor/Models/BridgeType.cs b/src/TSMapEditor/Models/BridgeType.cs index 308e7283f..7879fd31d 100644 --- a/src/TSMapEditor/Models/BridgeType.cs +++ b/src/TSMapEditor/Models/BridgeType.cs @@ -19,6 +19,7 @@ public enum BridgeKind public enum BridgeDirection { + None, EastWest, NorthSouth } @@ -35,17 +36,17 @@ public BridgeConfig(IniSection iniSection, BridgeDirection direction, BridgeType if (bridgeStart == null) throw new BridgeLoadException($"Low bridge {bridgeType.Name} has no start overlay!"); - Start = rules.FindOverlayType(bridgeStart)?.Index ?? + Start = rules.FindOverlayType(bridgeStart) ?? throw new BridgeLoadException($"Low bridge {bridgeType.Name} has an invalid start overlay {bridgeStart}!"); string bridgeEnd = iniSection.GetStringValue($"BridgeEnd.{suffix}", null); if (bridgeEnd == null) throw new BridgeLoadException($"Low bridge {bridgeType.Name} has no end overlay!"); - End = rules.FindOverlayType(bridgeEnd)?.Index ?? + End = rules.FindOverlayType(bridgeEnd) ?? throw new BridgeLoadException($"Low bridge {bridgeType.Name} has an invalid end overlay {bridgeEnd}!"); - Pieces = iniSection.GetListValue($"BridgePieces.{suffix}", ',', (overlayName) => rules.FindOverlayType(overlayName)?.Index ?? + Pieces = iniSection.GetListValue($"BridgePieces.{suffix}", ',', (overlayName) => rules.FindOverlayType(overlayName) ?? throw new BridgeLoadException($"Low bridge {bridgeType.Name} has an invalid bridge piece {overlayName}!")); if (Pieces.Count == 0) @@ -57,16 +58,17 @@ public BridgeConfig(IniSection iniSection, BridgeDirection direction, BridgeType if (piece == null) throw new BridgeLoadException($"High bridge {bridgeType.Name} has no bridge piece!"); - int bridgePiece = rules.FindOverlayType(piece)?.Index ?? + OverlayType bridgePiece = rules.FindOverlayType(piece) ?? throw new BridgeLoadException($"High bridge {bridgeType.Name} has an invalid bridge piece {piece}!"); Pieces.Add(bridgePiece); + Pieces.ForEach(p => p.HighBridgeDirection = direction); } } - public int Start; - public int End; - public List Pieces = new List(); + public OverlayType Start; + public OverlayType End; + public List Pieces = new List(); } public class BridgeType diff --git a/src/TSMapEditor/Models/GameObject.cs b/src/TSMapEditor/Models/GameObject.cs index 34d02e232..32632dd5b 100644 --- a/src/TSMapEditor/Models/GameObject.cs +++ b/src/TSMapEditor/Models/GameObject.cs @@ -20,6 +20,8 @@ public abstract class GameObject : AbstractObject, IMovable public ulong LastRefreshIndex; + public abstract GameObjectType GetObjectType(); + public virtual int GetYDrawOffset() { return 0; diff --git a/src/TSMapEditor/Models/Overlay.cs b/src/TSMapEditor/Models/Overlay.cs index 1bc7c3755..caf9519b9 100644 --- a/src/TSMapEditor/Models/Overlay.cs +++ b/src/TSMapEditor/Models/Overlay.cs @@ -2,6 +2,8 @@ { public class Overlay : GameObject { + public override GameObjectType GetObjectType() => OverlayType; + public override RTTIType WhatAmI() => RTTIType.Overlay; public OverlayType OverlayType { get; set; } diff --git a/src/TSMapEditor/Models/OverlayType.cs b/src/TSMapEditor/Models/OverlayType.cs index 599e5d3dc..5a2afc0d6 100644 --- a/src/TSMapEditor/Models/OverlayType.cs +++ b/src/TSMapEditor/Models/OverlayType.cs @@ -29,5 +29,8 @@ public OverlayType(string iniName) : base(iniName) public bool IsVeins { get; set; } public bool IsVeinholeMonster { get; set; } public TiberiumType TiberiumType { get; set; } + + [INI(false)] + public BridgeDirection HighBridgeDirection { get; set; } } } diff --git a/src/TSMapEditor/Models/Smudge.cs b/src/TSMapEditor/Models/Smudge.cs index d011f6b7e..3bb9a2cbe 100644 --- a/src/TSMapEditor/Models/Smudge.cs +++ b/src/TSMapEditor/Models/Smudge.cs @@ -4,6 +4,8 @@ public class Smudge : GameObject { public override RTTIType WhatAmI() => RTTIType.Smudge; + public override GameObjectType GetObjectType() => SmudgeType; + public SmudgeType SmudgeType { get; set; } public override int GetYDrawOffset() diff --git a/src/TSMapEditor/Models/Techno.cs b/src/TSMapEditor/Models/Techno.cs index 2f436c9e9..1b963e99f 100644 --- a/src/TSMapEditor/Models/Techno.cs +++ b/src/TSMapEditor/Models/Techno.cs @@ -9,6 +9,8 @@ public Techno(T objectType) ObjectType = objectType; } + public override GameObjectType GetObjectType() => ObjectType; + public override double GetWeaponRange() => ObjectType.GetWeaponRange(); public override double GetGuardRange() diff --git a/src/TSMapEditor/Models/TerrainObject.cs b/src/TSMapEditor/Models/TerrainObject.cs index ddc206702..3103384bf 100644 --- a/src/TSMapEditor/Models/TerrainObject.cs +++ b/src/TSMapEditor/Models/TerrainObject.cs @@ -17,6 +17,8 @@ public TerrainObject(TerrainType terrainType, Point2D position) : this(terrainTy Position = position; } + public override GameObjectType GetObjectType() => TerrainType; + public override RTTIType WhatAmI() => RTTIType.Terrain; public TerrainType TerrainType { get; private set; } diff --git a/src/TSMapEditor/Mutations/Classes/PlaceBridgeMutation.cs b/src/TSMapEditor/Mutations/Classes/PlaceBridgeMutation.cs index 17ad687d7..cf9bdf76f 100644 --- a/src/TSMapEditor/Mutations/Classes/PlaceBridgeMutation.cs +++ b/src/TSMapEditor/Mutations/Classes/PlaceBridgeMutation.cs @@ -95,32 +95,32 @@ public override void Perform() MutationTarget.InvalidateMap(); } - private void PlaceLowBridge(int bridgeStartOverlayIndex, int bridgeEndOverlayIndex, List bridgePieces, int beginCoord, int endCoord, Action piecePlacementFunction) + private void PlaceLowBridge(OverlayType bridgeStartOverlayType, OverlayType bridgeEndOverlayType, List bridgePieces, int beginCoord, int endCoord, Action piecePlacementFunction) { - piecePlacementFunction(bridgeStartOverlayIndex, beginCoord); + piecePlacementFunction(bridgeStartOverlayType, beginCoord); for (int c = beginCoord + 1; c < endCoord; c++) { - int overlayIndex = bridgePieces[MutationTarget.Randomizer.GetRandomNumber(0, bridgePieces.Count - 1)]; - piecePlacementFunction(overlayIndex, c); + OverlayType overlayType = bridgePieces[MutationTarget.Randomizer.GetRandomNumber(0, bridgePieces.Count - 1)]; + piecePlacementFunction(overlayType, c); } - piecePlacementFunction(bridgeEndOverlayIndex, endCoord); + piecePlacementFunction(bridgeEndOverlayType, endCoord); } - private void PlaceEastWestDirectionLowBridgePiece(int overlayIndex, int x) + private void PlaceEastWestDirectionLowBridgePiece(OverlayType overlayType, int x) { - PlaceLowBridgePiece(overlayIndex, x, + PlaceLowBridgePiece(overlayType, x, (fixedCoord, variableCoord) => new Point2D(fixedCoord, startPoint.Y + variableCoord)); } - private void PlaceNorthSouthDirectionLowBridgePiece(int overlayIndex, int y) + private void PlaceNorthSouthDirectionLowBridgePiece(OverlayType overlayType, int y) { - PlaceLowBridgePiece(overlayIndex, y, + PlaceLowBridgePiece(overlayType, y, (fixedCoord, variableCoord) => new Point2D(startPoint.X + variableCoord, fixedCoord)); } - private void PlaceLowBridgePiece(int overlayIndex, int fixedCoordinate, Func coordGenerator) + private void PlaceLowBridgePiece(OverlayType overlayType, int fixedCoordinate, Func coordGenerator) { for (int variableCoordinateOffset = -1; variableCoordinateOffset <= 1; variableCoordinateOffset++) { @@ -137,13 +137,13 @@ private void PlaceLowBridgePiece(int overlayIndex, int fixedCoordinate, Func piecePlacementFunction) + private void PlaceHighBridge(OverlayType bridgePiece, int beginCoord, int endCoord, Action piecePlacementFunction) { var startCell = MutationTarget.Map.GetTile(startPoint); if (startCell == null) @@ -155,25 +155,25 @@ private void PlaceHighBridge(int bridgePiece, int beginCoord, int endCoord, Acti } } - private void PlaceEastWestDirectionHighBridgePiece(int overlayIndex, int x, int startingHeight) + private void PlaceEastWestDirectionHighBridgePiece(OverlayType overlayType, int x, int startingHeight) { const int xStartFrame = 0; const int xEndFrame = 3; int frameIndex = MutationTarget.Randomizer.GetRandomNumber(xStartFrame, xEndFrame); - PlaceHighBridgePiece(overlayIndex, frameIndex, new Point2D(x, startPoint.Y), startingHeight); + PlaceHighBridgePiece(overlayType, frameIndex, new Point2D(x, startPoint.Y), startingHeight); } - private void PlaceNorthSouthDirectionHighBridgePiece(int overlayIndex, int y, int startingHeight) + private void PlaceNorthSouthDirectionHighBridgePiece(OverlayType overlayType, int y, int startingHeight) { const int yStartFrame = 9; const int yEndFrame = 12; int frameIndex = MutationTarget.Randomizer.GetRandomNumber(yStartFrame, yEndFrame); - PlaceHighBridgePiece(overlayIndex, frameIndex, new Point2D(startPoint.X, y), startingHeight); + PlaceHighBridgePiece(overlayType, frameIndex, new Point2D(startPoint.X, y), startingHeight); } - private void PlaceHighBridgePiece(int overlayIndex, int frameIndex, Point2D cellCoords, int startingHeight) + private void PlaceHighBridgePiece(OverlayType overlayType, int frameIndex, Point2D cellCoords, int startingHeight) { var mapCell = MutationTarget.Map.GetTile(cellCoords); @@ -190,7 +190,7 @@ private void PlaceHighBridgePiece(int overlayIndex, int frameIndex, Point2D cell mapCell.Overlay = new Overlay() { - OverlayType = MutationTarget.Map.Rules.OverlayTypes[overlayIndex], + OverlayType = overlayType, FrameIndex = frameIndex, Position = cellCoords }; diff --git a/src/TSMapEditor/Rendering/MapView.cs b/src/TSMapEditor/Rendering/MapView.cs index b913f5620..e15a6b690 100644 --- a/src/TSMapEditor/Rendering/MapView.cs +++ b/src/TSMapEditor/Rendering/MapView.cs @@ -8,6 +8,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text; +using TSMapEditor.CCEngine; using TSMapEditor.GameMath; using TSMapEditor.Misc; using TSMapEditor.Models; @@ -68,7 +69,6 @@ public interface ICursorActionTarget : IMapView TechnoBase TechnoUnderCursor { get; set; } } - public class MapView : XNAControl, ICursorActionTarget, IMutationTarget, IMapView { private const float RightClickScrollRateDivisor = 64f; @@ -104,7 +104,11 @@ public MapView(WindowManager windowManager, Map map, TheaterGraphics theaterGrap this.windowController = windowController; Camera = new Camera(WindowManager, Map); - Camera.CameraUpdated += (s, e) => cameraMoved = true; + Camera.CameraUpdated += (s, e) => + { + cameraMoved = true; + if (UserSettings.Instance.GraphicsLevel > 0) InvalidateMap(); + }; SetControlSize(); } @@ -150,15 +154,17 @@ public CursorAction CursorAction set => EditorState.CursorAction = value; } - private RenderTarget2D mapRenderTarget; // Render target for terrain and objects + private RenderTarget2D mapRenderTarget; // Render target for terrain + private RenderTarget2D mapDepthRenderTarget; + private RenderTarget2D objectsRenderTarget; // Render target for objects + private RenderTarget2D objectsDepthRenderTarget; private RenderTarget2D transparencyRenderTarget; // Render target for map UI elements (celltags etc.) that are only refreshed if something in the map changes (due to performance reasons) private RenderTarget2D transparencyPerFrameRenderTarget; // Render target for map UI elements that are redrawn each frame private RenderTarget2D compositeRenderTarget; // Render target where all the above is combined private RenderTarget2D minimapRenderTarget; // For minimap and megamap rendering - private Effect colorDrawEffect; // Effect for rendering RGBA textures with depth testing - private Effect palettedColorDrawEffect; // Effect for rendering paletted textures with depth testing - private Effect depthApplyEffect; // Effect for rendering to depth buffer + private Effect palettedColorDrawEffect; // Effect for rendering textures, both paletted and RGBA, with or without remap, with depth assignation to a separate render target + private Effect combineDrawEffect; // Effect for combining map and object render targets into one, taking both of their depth buffers into account private MapTile tileUnderCursor; private MapTile lastTileUnderCursor; @@ -178,15 +184,14 @@ public CursorAction CursorAction private Point lastClickedPoint; - private List gameObjectsToRender = new List(); + private List gameObjectsToRender = new List(); private List smudgesToRender = new List(); + private ObjectSpriteRecord objectSpriteRecord = new ObjectSpriteRecord(); private Stopwatch refreshStopwatch; private ulong refreshIndex; - private bool isRenderingDepth; - private bool debugRenderDepthBuffer = false; private AircraftRenderer aircraftRenderer; @@ -204,8 +209,6 @@ public CursorAction CursorAction private DepthStencilState depthRenderStencilState; private DepthStencilState shadowRenderStencilState; - private bool isDrawingShadows; - public void AddRefreshPoint(Point2D point, int size = 1) { InvalidateMap(); @@ -342,9 +345,8 @@ private void ViewMegamap_Triggered(object sender, EventArgs e) private void LoadShaders() { - colorDrawEffect = AssetLoader.LoadEffect("Shaders/ColorDraw"); palettedColorDrawEffect = AssetLoader.LoadEffect("Shaders/PalettedColorDraw"); - depthApplyEffect = AssetLoader.LoadEffect("Shaders/DepthApply"); + combineDrawEffect = AssetLoader.LoadEffect("Shaders/CombineWithDepth"); } private void RotateUnitOneStep_Triggered(object sender, EventArgs e) @@ -415,6 +417,9 @@ private void Map_LightingColorsRefreshed() private void ClearRenderTargets() { mapRenderTarget?.Dispose(); + mapDepthRenderTarget?.Dispose(); + objectsRenderTarget?.Dispose(); + objectsDepthRenderTarget?.Dispose(); transparencyRenderTarget?.Dispose(); transparencyPerFrameRenderTarget?.Dispose(); compositeRenderTarget?.Dispose(); @@ -428,7 +433,10 @@ private void RefreshRenderTargets() { ClearRenderTargets(); - mapRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Color, DepthFormat.Depth24Stencil8); + mapRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Color, DepthFormat.Depth24); + mapDepthRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Single); + objectsRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Color, DepthFormat.Depth24); + objectsDepthRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Single); transparencyRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Color); transparencyPerFrameRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Color); compositeRenderTarget = CreateFullMapRenderTarget(SurfaceFormat.Color); @@ -437,7 +445,7 @@ private void RefreshRenderTargets() private RenderDependencies CreateRenderDependencies() { - return new RenderDependencies(Map, TheaterGraphics, EditorState, GraphicsDevice, colorDrawEffect, palettedColorDrawEffect, Camera, GetCameraRightXCoord, GetCameraBottomYCoord); + return new RenderDependencies(Map, TheaterGraphics, EditorState, GraphicsDevice, objectSpriteRecord, palettedColorDrawEffect, Camera, GetCameraRightXCoord, GetCameraBottomYCoord); } private void InitRenderers() @@ -501,20 +509,18 @@ public void DrawVisibleMapPortion() Renderer.PushRenderTarget(mapRenderTarget); + GraphicsDevice.SetRenderTargets(mapRenderTarget, mapDepthRenderTarget); + if (mapInvalidated) { - GraphicsDevice.SetRenderTarget(mapRenderTarget); - GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer | ClearOptions.Stencil, Color.Black, 0f, 0); + GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 0f, 0); + objectSpriteRecord.Clear(); } if (depthRenderStencilState == null) { depthRenderStencilState = new DepthStencilState() { - StencilEnable = true, - StencilPass = StencilOperation.Replace, - StencilFunction = CompareFunction.Always, - ReferenceStencil = 0, DepthBufferEnable = true, DepthBufferWriteEnable = true, DepthBufferFunction = CompareFunction.GreaterEqual, @@ -531,39 +537,14 @@ public void DrawVisibleMapPortion() DoForVisibleCells(DrawTerrainTileAndRegisterObjects); Renderer.PopSettings(); - // Unfortunately, object drawing is way too complex with variable shader settings used depending on each object, and so it can't be batched. - var colorDrawSettings = new SpriteBatchSettings(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, depthRenderStencilState, null, palettedColorDrawEffect); - Renderer.PushSettings(colorDrawSettings); - DrawSmudges(); - DrawObjects(); - Renderer.PopSettings(); + // Render objects + GraphicsDevice.SetRenderTargets(objectsRenderTarget, objectsDepthRenderTarget); - // Render shadows. We use the stencil buffer to avoid drawing them multiple times on the same scene. - // Reserved for higher graphics modes due to the required extra processing. - if (UserSettings.Instance.GraphicsLevel > 0) - { - if (shadowRenderStencilState == null) - { - shadowRenderStencilState = new DepthStencilState() - { - StencilEnable = true, - StencilPass = StencilOperation.Replace, - StencilFail = StencilOperation.Keep, - StencilFunction = CompareFunction.Greater, - ReferenceStencil = 1, - DepthBufferEnable = true, - DepthBufferWriteEnable = false, - DepthBufferFunction = CompareFunction.Greater, - }; - } + if (mapInvalidated) + GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Transparent, 0f, 0); - var shadowDrawSettings = new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, shadowRenderStencilState, null, palettedColorDrawEffect); - Renderer.PushSettings(shadowDrawSettings); - isDrawingShadows = true; - gameObjectsToRender.ForEach(DrawObject); - isDrawingShadows = false; - Renderer.PopSettings(); - } + DrawSmudges(); + DrawGameObjects(); // Then draw on-map UI elements DrawMapUIElements(); @@ -603,7 +584,7 @@ private void DrawMapUIElements() Renderer.PopRenderTarget(); } - private void SetPaletteEffectParams(Effect effect, Texture2D paletteTexture, bool usePalette, bool useRemap, float opacity) + private void SetPaletteEffectParams(Effect effect, Texture2D paletteTexture, bool usePalette, bool useRemap, float opacity, bool isShadow = false) { if (paletteTexture != null) { @@ -611,7 +592,7 @@ private void SetPaletteEffectParams(Effect effect, Texture2D paletteTexture, boo GraphicsDevice.Textures[2] = paletteTexture; } - effect.Parameters["IsShadow"].SetValue(false); + effect.Parameters["IsShadow"].SetValue(isShadow); effect.Parameters["UsePalette"].SetValue(usePalette); effect.Parameters["UseRemap"].SetValue(useRemap); effect.Parameters["Opacity"].SetValue(opacity); @@ -715,28 +696,36 @@ public void DrawTerrainTileAndRegisterObjects(MapTile tile) smudgesToRender.Add(tile.Smudge); if ((EditorState.RenderObjectFlags & RenderObjectFlags.Overlay) == RenderObjectFlags.Overlay && tile.Overlay != null && tile.Overlay.OverlayType != null) - gameObjectsToRender.Add(tile.Overlay); + AddGameObjectToRender(tile.Overlay); if ((EditorState.RenderObjectFlags & RenderObjectFlags.Structures) == RenderObjectFlags.Structures) { tile.DoForAllBuildings(structure => { if (structure.Position == tile.CoordsToPoint()) - gameObjectsToRender.Add(structure); + AddGameObjectToRender(structure); }); } if ((EditorState.RenderObjectFlags & RenderObjectFlags.Infantry) == RenderObjectFlags.Infantry) - tile.DoForAllInfantry(gameObjectsToRender.Add); + tile.DoForAllInfantry(AddGameObjectToRender); if ((EditorState.RenderObjectFlags & RenderObjectFlags.Aircraft) == RenderObjectFlags.Aircraft) - tile.DoForAllAircraft(gameObjectsToRender.Add); + tile.DoForAllAircraft(AddGameObjectToRender); if ((EditorState.RenderObjectFlags & RenderObjectFlags.Vehicles) == RenderObjectFlags.Vehicles) - tile.DoForAllVehicles(gameObjectsToRender.Add); + tile.DoForAllVehicles(AddGameObjectToRender); if ((EditorState.RenderObjectFlags & RenderObjectFlags.TerrainObjects) == RenderObjectFlags.TerrainObjects && tile.TerrainObject != null) - gameObjectsToRender.Add(tile.TerrainObject); + AddGameObjectToRender(tile.TerrainObject); + } + + private void AddGameObjectToRender(GameObject gameObject) + { + if (objectSpriteRecord.ProcessedObjects.Contains(gameObject)) + return; + + gameObjectsToRender.Add(gameObject); } public void DrawTerrainTile(MapTile tile) @@ -744,20 +733,13 @@ public void DrawTerrainTile(MapTile tile) if (tile.LastRefreshIndex == refreshIndex) return; + tile.LastRefreshIndex = refreshIndex; + if (tile.TileIndex >= TheaterGraphics.TileCount) return; Point2D drawPoint = CellMath.CellTopLeftPointFromCellCoords(new Point2D(tile.X, tile.Y), Map); - if (!minimapNeedsRefresh) - { - if (drawPoint.X + Constants.CellSizeX < Camera.TopLeftPoint.X || drawPoint.X > GetCameraRightXCoord()) - return; - - if (drawPoint.Y + Constants.CellSizeY < Camera.TopLeftPoint.Y) - return; - } - if (tile.TileImage == null) tile.TileImage = TheaterGraphics.GetTileGraphics(tile.TileIndex); @@ -782,9 +764,6 @@ public void DrawTerrainTile(MapTile tile) tileImage = TheaterGraphics.GetMarbleMadnessTileGraphics(tileImage.TileID); if (subTileIndex >= tileImage.TMPImages.Length) - subTileIndex = 0; - - if (tileImage.TMPImages.Length == 0) return; MGTMPImage tmpImage = tileImage.TMPImages[subTileIndex]; @@ -792,24 +771,8 @@ public void DrawTerrainTile(MapTile tile) if (tmpImage.TmpImage == null) return; - int extraDrawY = drawPoint.Y + tmpImage.TmpImage.YExtra - tmpImage.TmpImage.Y; - - if (!minimapNeedsRefresh && drawPoint.Y - (level * Constants.CellHeight) > GetCameraBottomYCoord()) - { - if (tmpImage.ExtraTexture == null) - return; - - // If we have extra graphics, need to check whether they are on screen - if (extraDrawY - (level * Constants.CellHeight) > GetCameraBottomYCoord()) - return; - } - - // We know we're going to render this tile - update refresh index - if (!isRenderingDepth) - tile.LastRefreshIndex = refreshIndex; - - if (!EditorState.Is2DMode) - drawPoint -= new Point2D(0, (Constants.CellSizeY / 2) * level); + int drawX = drawPoint.X; + int drawY = drawPoint.Y; if (subTileIndex >= tileImage.TMPImages.Length) { @@ -817,50 +780,49 @@ public void DrawTerrainTile(MapTile tile) return; } - Vector4 lightingColor = new Vector4((float)tile.CellLighting.R, (float)tile.CellLighting.G, (float)tile.CellLighting.B, 1.0f); + if (!EditorState.Is2DMode) + drawY -= (Constants.CellSizeY / 2) * level; + + float depth = ((drawPoint.Y / (float)mapRenderTarget.Height) * Constants.DownwardsDepthRenderSpace) + (level * Constants.DepthRenderStep); + + // Divide the color by 2f. This is done because unlike map lighting which can exceed 1.0 and go up to 2.0, + // the Color instance values are capped at 1.0. + // We lose a bit of precision from doing this, but we'll have to accept that. + Color color = new Color((float)tile.CellLighting.R / 2f, (float)tile.CellLighting.G / 2f, (float)tile.CellLighting.B / 2f, depth); if (tmpImage.Texture != null) { - float depth = level * Constants.DepthRenderStep; - - var textureToDraw = tmpImage.Texture; - - // Divide the color by 2f. This is done because unlike map lighting which can exceed 1.0 and go up to 2.0, - // the Color instance values are capped at 1.0. - // We lose a bit of precision from doing this, but we'll have to accept that. - Color color = new Color(lightingColor.X / 2f, lightingColor.Y / 2f, lightingColor.Z / 2f, depth); + Texture2D textureToDraw = tmpImage.Texture; // Replace terrain lacking MM graphics with colored cells to denote height if we are in marble madness mode - if (!Constants.IsFlatWorld && - EditorState.IsMarbleMadness && - !TheaterGraphics.HasSeparateMarbleMadnessTileGraphics(tileImage.TileID)) + if (EditorState.IsMarbleMadness && !Constants.IsFlatWorld) { - textureToDraw = EditorGraphics.GenericTileWithBorderTexture; - color = MarbleMadnessTileHeightLevelColors[level]; - palettedColorDrawEffect.Parameters["UsePalette"].SetValue(false); - SetPaletteEffectParams(palettedColorDrawEffect, tmpImage.GetPaletteTexture(), false, false, depth); + if (!TheaterGraphics.HasSeparateMarbleMadnessTileGraphics(tileImage.TileID)) + { + textureToDraw = EditorGraphics.GenericTileWithBorderTexture; + color = MarbleMadnessTileHeightLevelColors[level]; + color = new Color(color.R, color.G, color.B, depth); + SetPaletteEffectParams(palettedColorDrawEffect, null, false, false, 1.0f, false); + } + else + { + SetPaletteEffectParams(palettedColorDrawEffect, tmpImage.GetPaletteTexture(), true, false, 1.0f, false); + } } - DrawTexture(textureToDraw, new Rectangle(drawPoint.X, drawPoint.Y, + DrawTexture(textureToDraw, new Rectangle(drawX, drawY, Constants.CellSizeX, Constants.CellSizeY), null, color, 0f, Vector2.Zero, SpriteEffects.None, depth); } if (tmpImage.ExtraTexture != null && !EditorState.Is2DMode) { - int exDrawPointX = drawPoint.X + tmpImage.TmpImage.XExtra - tmpImage.TmpImage.X; - int exDrawPointY = drawPoint.Y + tmpImage.TmpImage.YExtra - tmpImage.TmpImage.Y; - - // Reduce base sub-tile height from depth. This allows - // objects like buildings and trees to be drawn over cliff sides. - float depth = (level - tmpImage.TmpImage.Height) * Constants.DepthRenderStep; - - Color color = new Color(lightingColor.X / 2f, lightingColor.Y / 2f, lightingColor.Z / 2f, depth); + drawX = drawX + tmpImage.TmpImage.XExtra - tmpImage.TmpImage.X; + drawY = drawY + tmpImage.TmpImage.YExtra - tmpImage.TmpImage.Y; if (EditorState.IsMarbleMadness) SetPaletteEffectParams(palettedColorDrawEffect, tmpImage.GetPaletteTexture(), true, false, 1.0f); - var exDrawRectangle = new Rectangle(exDrawPointX, - exDrawPointY, + var exDrawRectangle = new Rectangle(drawX, drawY, tmpImage.ExtraTexture.Width, tmpImage.ExtraTexture.Height); @@ -870,12 +832,6 @@ public void DrawTerrainTile(MapTile tile) color, 0f, Vector2.Zero, SpriteEffects.None, depth); - - var cameraRectangle = GetCameraRectangle(); - - // If this tile was only rendered partially, then we need to redraw it properly later - if (!cameraRectangle.Contains(exDrawRectangle)) - tile.LastRefreshIndex = 0; } } @@ -893,14 +849,6 @@ private void DrawCellTags() }); } - private Point2D GetBuildingCenterPoint(Structure structure) - { - Point2D topPoint = CellMath.CellCenterPointFromCellCoords(structure.Position, Map); - var foundation = structure.ObjectType.ArtConfig.Foundation; - Point2D bottomPoint = CellMath.CellCenterPointFromCellCoords(structure.Position + new Point2D(foundation.Width - 1, foundation.Height - 1), Map); - return topPoint + new Point2D((bottomPoint.X - topPoint.X) / 2, (bottomPoint.Y - topPoint.Y) / 2); - } - private int CompareGameObjectsForRendering(GameObject obj1, GameObject obj2) { // Use pixel coords for sorting. Objects closer to the top are rendered first. @@ -926,9 +874,9 @@ private Point2D GetObjectCoordsForComparison(GameObject obj) { return obj.WhatAmI() switch { - RTTIType.Building => GetBuildingCenterPoint((Structure)obj), + RTTIType.Building => buildingRenderer.GetBuildingCenterPoint((Structure)obj), RTTIType.Anim => ((Animation)obj).IsBuildingAnim ? - GetBuildingCenterPoint(((Animation)obj).ParentBuilding) : + buildingRenderer.GetBuildingCenterPoint(((Animation)obj).ParentBuilding) : CellMath.CellCenterPointFromCellCoords(obj.Position, Map), _ => CellMath.CellCenterPointFromCellCoords(obj.Position, Map) }; @@ -937,13 +885,29 @@ private Point2D GetObjectCoordsForComparison(GameObject obj) private void DrawSmudges() { smudgesToRender.Sort(CompareGameObjectsForRendering); + + var colorDrawSettings = new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, depthRenderStencilState, null, palettedColorDrawEffect); + SetPaletteEffectParams(palettedColorDrawEffect, TheaterGraphics.TheaterPalette.GetTexture(), true, false, 1.0f); + Renderer.PushSettings(colorDrawSettings); + for (int i = 0; i < smudgesToRender.Count; i++) + { + smudgeRenderer.DrawNonRemap(smudgesToRender[i], smudgeRenderer.GetDrawPoint(smudgesToRender[i])); + } smudgesToRender.ForEach(DrawObject); + Renderer.PopSettings(); } - private void DrawObjects() + private void DrawGameObjects() { gameObjectsToRender.Sort(CompareGameObjectsForRendering); - gameObjectsToRender.ForEach(DrawObject); + + for (int i = 0; i < gameObjectsToRender.Count; i++) + { + DrawObject(gameObjectsToRender[i]); + objectSpriteRecord.ProcessedObjects.Add(gameObjectsToRender[i]); + } + + ProcessObjectSpriteRecord(); } private void DrawObject(GameObject gameObject) @@ -951,39 +915,117 @@ private void DrawObject(GameObject gameObject) if (!EditorState.RenderInvisibleInGameObjects && gameObject.IsInvisibleInGame()) return; - bool drawShadow = isDrawingShadows; - switch (gameObject.WhatAmI()) { case RTTIType.Aircraft: - aircraftRenderer.Draw(gameObject as Aircraft, !minimapNeedsRefresh, drawShadow); + aircraftRenderer.Draw(gameObject as Aircraft, false); return; case RTTIType.Anim: - animRenderer.Draw(gameObject as Animation, !minimapNeedsRefresh, drawShadow); + animRenderer.Draw(gameObject as Animation, false); return; case RTTIType.Building: - buildingRenderer.Draw(gameObject as Structure, !minimapNeedsRefresh, drawShadow); + buildingRenderer.Draw(gameObject as Structure, false); return; case RTTIType.Infantry: - infantryRenderer.Draw(gameObject as Infantry, !minimapNeedsRefresh, drawShadow); + infantryRenderer.Draw(gameObject as Infantry, false); return; case RTTIType.Overlay: - overlayRenderer.Draw(gameObject as Overlay, !minimapNeedsRefresh, drawShadow); + overlayRenderer.Draw(gameObject as Overlay, false); return; case RTTIType.Smudge: - smudgeRenderer.Draw(gameObject as Smudge, !minimapNeedsRefresh, drawShadow); + smudgeRenderer.Draw(gameObject as Smudge, false); return; case RTTIType.Terrain: - terrainRenderer.Draw(gameObject as TerrainObject, !minimapNeedsRefresh, drawShadow); + terrainRenderer.Draw(gameObject as TerrainObject, false); return; case RTTIType.Unit: - unitRenderer.Draw(gameObject as Unit, !minimapNeedsRefresh, drawShadow); + unitRenderer.Draw(gameObject as Unit, false); return; default: throw new NotImplementedException("No renderer implemented for type " + gameObject.WhatAmI()); } } + private void ProcessObjectSpriteRecord() + { + if (objectSpriteRecord.LineEntries.Count > 0) + { + SetPaletteEffectParams(palettedColorDrawEffect, null, false, false, 1.0f, false); + Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.Opaque, null, depthRenderStencilState, null, palettedColorDrawEffect)); + + for (int i = 0; i < objectSpriteRecord.LineEntries.Count; i++) + { + var lineEntry = objectSpriteRecord.LineEntries[i]; + Renderer.DrawLine(lineEntry.Source, lineEntry.Destination, + new Color(lineEntry.Color.R / 255.0f, lineEntry.Color.G / 255.0f, lineEntry.Color.B / 255.0f, lineEntry.Depth), + lineEntry.Thickness, lineEntry.Depth); + } + + Renderer.PopSettings(); + } + + foreach (var kvp in objectSpriteRecord.SpriteEntries) + { + Texture2D paletteTexture = kvp.Key.Item1; + bool isRemap = kvp.Key.Item2; + + SetPaletteEffectParams(palettedColorDrawEffect, paletteTexture, true, isRemap, 1.0f, false); + Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, depthRenderStencilState, null, palettedColorDrawEffect)); + + for (int i = 0; i < kvp.Value.Count; i++) + { + var spriteEntry = kvp.Value[i]; + Renderer.DrawTexture(spriteEntry.Texture, spriteEntry.DrawingBounds, null, spriteEntry.Color, 0f, Vector2.Zero, SpriteEffects.None, spriteEntry.Depth); + } + + Renderer.PopSettings(); + } + + if (objectSpriteRecord.NonPalettedSpriteEntries.Count > 0) + { + SetPaletteEffectParams(palettedColorDrawEffect, null, false, false, 1.0f, false); + Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, depthRenderStencilState, null, palettedColorDrawEffect)); + + for (int i = 0; i < objectSpriteRecord.NonPalettedSpriteEntries.Count; i++) + { + var spriteEntry = objectSpriteRecord.NonPalettedSpriteEntries[i]; + Renderer.DrawTexture(spriteEntry.Texture, spriteEntry.DrawingBounds, null, spriteEntry.Color, 0f, Vector2.Zero, SpriteEffects.None, spriteEntry.Depth); + } + + Renderer.PopSettings(); + } + + if (objectSpriteRecord.ShadowEntries.Count > 0) + { + SetPaletteEffectParams(palettedColorDrawEffect, null, false, false, 1.0f, true); + Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, depthRenderStencilState, null, palettedColorDrawEffect)); + + for (int i = 0; i < objectSpriteRecord.ShadowEntries.Count; i++) + { + var spriteEntry = objectSpriteRecord.ShadowEntries[i]; + + // It doesn't really matter what we give as color to the shadow + Renderer.DrawTexture(spriteEntry.Texture, spriteEntry.DrawingBounds, null, new Color(1.0f, 1.0f, 1.0f, spriteEntry.Depth), 0f, Vector2.Zero, SpriteEffects.None, spriteEntry.Depth); + } + + Renderer.PopSettings(); + } + + if (objectSpriteRecord.TextEntries.Count > 0) + { + SetPaletteEffectParams(palettedColorDrawEffect, null, false, false, 1.0f, false); + Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.Opaque, null, depthRenderStencilState, null, palettedColorDrawEffect)); + + for (int i = 0; i < objectSpriteRecord.TextEntries.Count; i++) + { + var textEntry = objectSpriteRecord.TextEntries[i]; + Renderer.DrawStringWithShadow(textEntry.Text, Constants.UIBoldFont, textEntry.DrawPoint.ToXNAVector(), textEntry.Color, 1f, 1f, 1f); + } + + Renderer.PopSettings(); + } + } + private void DrawBaseNodes() { Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Immediate, BlendState.Opaque, null, null, null, palettedColorDrawEffect)); @@ -1856,18 +1898,32 @@ private void DrawWorld() DepthBufferFunction = CompareFunction.GreaterEqual, }; - Renderer.PushRenderTarget(compositeRenderTarget, new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, dss, null, null)); - - GraphicsDevice.Clear(Color.Black); - Rectangle sourceRectangle = new Rectangle(0, 0, mapRenderTarget.Width, mapRenderTarget.Height); Rectangle destinationRectangle = sourceRectangle; + combineDrawEffect.Parameters["TerrainDepthTexture"].SetValue(mapDepthRenderTarget); + combineDrawEffect.Parameters["ObjectsDepthTexture"].SetValue(objectsDepthRenderTarget); + + GraphicsDevice.SetRenderTarget(compositeRenderTarget); + + GraphicsDevice.Clear(Color.Black); + + // First, draw the map to the composite render target as a base. DrawTexture(mapRenderTarget, sourceRectangle, destinationRectangle, Color.White); + // Then draw objects to the composite render target, making use of our custom shader. + Renderer.PushRenderTarget(compositeRenderTarget, new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, dss, null, combineDrawEffect)); + DrawTexture(objectsRenderTarget, + sourceRectangle, + destinationRectangle, + Color.White); + + // Then draw transparency layers, without using a custom shader. + Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null)); + DrawTexture(transparencyRenderTarget, sourceRectangle, destinationRectangle, @@ -1878,9 +1934,11 @@ private void DrawWorld() destinationRectangle, Color.White); + Renderer.PopSettings(); + Renderer.PopRenderTarget(); - // Draw the composite render target directly to the screen + // Last, draw the composite render target directly to the screen. Renderer.PushSettings(new SpriteBatchSettings(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null)); diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/AircraftRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/AircraftRenderer.cs index 7764c66cd..9bf3cdab7 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/AircraftRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/AircraftRenderer.cs @@ -22,11 +22,11 @@ protected override CommonDrawParams GetDrawParams(Aircraft gameObject) }; } - protected override void Render(Aircraft gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + protected override void Render(Aircraft gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { DrawVoxelModel(gameObject, drawParams.MainVoxel, gameObject.Facing, RampType.None, Color.White, true, gameObject.GetRemapColor(), - Constants.VoxelsAffectedByLighting, drawPoint, heightOffset); + Constants.VoxelsAffectedByLighting, drawPoint); } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/AnimRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/AnimRenderer.cs index 773a52a3d..6dce20945 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/AnimRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/AnimRenderer.cs @@ -27,7 +27,7 @@ protected override bool ShouldRenderReplacementText(Animation gameObject) return false; } - protected override void Render(Animation gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + protected override void Render(Animation gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { if (drawParams.ShapeImage == null) return; @@ -61,18 +61,21 @@ protected override void Render(Animation gameObject, int heightOffset, Point2D d bool affectedByLighting = RenderDependencies.EditorState.IsLighting; bool affectedByAmbient = !drawParams.ShapeImage.SubjectToLighting; - // DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + DrawShadowDirect(gameObject); DrawShapeImage(gameObject, drawParams.ShapeImage, - frameIndex, Color.White * alpha, false, + frameIndex, Color.White * alpha, gameObject.IsBuildingAnim, gameObject.GetRemapColor() * alpha, - affectedByLighting, affectedByAmbient, drawPoint, heightOffset); + affectedByLighting, affectedByAmbient, drawPoint); } - protected override void DrawShadow(Animation gameObject, in CommonDrawParams drawParams, Point2D drawPoint, int heightOffset) + public override void DrawShadowDirect(Animation gameObject) { if (!Constants.DrawBuildingAnimationShadows && gameObject.IsBuildingAnim) return; + var drawParams = GetDrawParams(gameObject); + var drawPoint = GetDrawPoint(gameObject); + int shadowFrameIndex = gameObject.GetShadowFrameIndex(drawParams.ShapeImage.GetFrameCount()); if (gameObject.IsTurretAnim) @@ -84,9 +87,14 @@ protected override void DrawShadow(Animation gameObject, in CommonDrawParams dra if (shadowFrameIndex > 0 && shadowFrameIndex < drawParams.ShapeImage.GetFrameCount()) { - DrawShapeImage(gameObject, drawParams.ShapeImage, shadowFrameIndex, - new Color(0, 0, 0, 128), true, false, Color.White, - false, false, drawPoint, heightOffset); + var frame = drawParams.ShapeImage.GetFrame(shadowFrameIndex); + if (frame != null && frame.Texture != null) + { + Rectangle drawingBounds = GetTextureDrawCoords(gameObject, frame, drawPoint); + float depth = GetDepth(gameObject, drawPoint.Y + drawingBounds.Height); + + RenderDependencies.ObjectSpriteRecord.AddGraphicsEntry(new ObjectSpriteEntry(null, frame.Texture, drawingBounds, Color.White, false, true, depth)); + } } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/BuildingRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/BuildingRenderer.cs index e6c853080..57242f95f 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/BuildingRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/BuildingRenderer.cs @@ -17,7 +17,15 @@ public BuildingRenderer(RenderDependencies renderDependencies) : base(renderDepe private AnimRenderer buildingAnimRenderer; - private void DrawFoundationLines(Structure gameObject) + public Point2D GetBuildingCenterPoint(Structure structure) + { + Point2D topPoint = CellMath.CellCenterPointFromCellCoords(structure.Position, Map); + var foundation = structure.ObjectType.ArtConfig.Foundation; + Point2D bottomPoint = CellMath.CellCenterPointFromCellCoords(structure.Position + new Point2D(foundation.Width - 1, foundation.Height - 1), Map); + return topPoint + new Point2D((bottomPoint.X - topPoint.X) / 2, (bottomPoint.Y - topPoint.Y) / 2); + } + + public void DrawFoundationLines(Structure gameObject) { int foundationX = gameObject.ObjectType.ArtConfig.Foundation.Width; int foundationY = gameObject.ObjectType.ArtConfig.Foundation.Height; @@ -37,29 +45,34 @@ private void DrawFoundationLines(Structure gameObject) if (cell != null && !RenderDependencies.EditorState.Is2DMode) heightOffset = cell.Level * Constants.CellHeight; - float depth = cell.Level * Constants.DepthRenderStep; - - foundationLineColor = new Color((foundationLineColor.R / 255.0f) * (float)cell.CellLighting.R, - (foundationLineColor.G / 255.0f) * (float)cell.CellLighting.G, - (foundationLineColor.B / 255.0f) * (float)cell.CellLighting.B, - depth); - - SetEffectParams_RGBADraw(false); + // Cell lighting ranges from 0.0 to 2.0, XNA colors from 0.0 to 1.0. Thus division by 2 + foundationLineColor = new Color((foundationLineColor.R / 255.0f) * (float)cell.CellLighting.R / 2.0f, + (foundationLineColor.G / 255.0f) * (float)cell.CellLighting.G / 2.0f, + (foundationLineColor.B / 255.0f) * (float)cell.CellLighting.B / 2.0f, + 1.0f); foreach (var edge in gameObject.ObjectType.ArtConfig.Foundation.Edges) { // Translate edge vertices from cell coordinate space to world coordinate space. var start = CellMath.CellTopLeftPointFromCellCoords(gameObject.Position + edge[0], map); var end = CellMath.CellTopLeftPointFromCellCoords(gameObject.Position + edge[1], map); + + float depth = GetFoundationLineDepth(gameObject, start, end); // Height is an illusion, just move everything up or down. // Also offset X to match the top corner of an iso tile. start += new Point2D(Constants.CellSizeX / 2, -heightOffset); end += new Point2D(Constants.CellSizeX / 2, -heightOffset); // Draw edge. - DrawLine(start.ToXNAVector(), end.ToXNAVector(), foundationLineColor, 1); + RenderDependencies.ObjectSpriteRecord.AddLineEntry(new LineEntry(start.ToXNAVector(), end.ToXNAVector(), foundationLineColor, 1, depth)); } } + private float GetFoundationLineDepth(Structure gameObject, Point2D startPoint, Point2D endPoint) + { + Point2D lowerPoint = startPoint.Y > endPoint.Y ? startPoint : endPoint; + return base.GetDepth(gameObject, lowerPoint.Y) - (Constants.DepthRenderStep); + } + protected override CommonDrawParams GetDrawParams(Structure gameObject) { string iniName = gameObject.ObjectType.ININame; @@ -89,13 +102,20 @@ protected override bool ShouldRenderReplacementText(Structure gameObject) return base.ShouldRenderReplacementText(gameObject); } - private void DrawBibGraphics(Structure gameObject, ShapeImage bibGraphics, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams, bool affectedByLighting) + protected override float GetDepth(Structure gameObject, int referenceDrawPointY) + { + // The 100.0 divisor is just an arbitrary number here. It appeared to give me the best result on SOV01UMD.MAP. + // Before we implement a shader-based replacement for BUILDINGZ.SHP, we probably can't do better. + return base.GetDepth(gameObject, referenceDrawPointY) + ((gameObject.ObjectType.ArtConfig.Height * Constants.DepthRenderStep) / 100.0f); + } + + private void DrawBibGraphics(Structure gameObject, ShapeImage bibGraphics, Point2D drawPoint, in CommonDrawParams drawParams, bool affectedByLighting) { - DrawShapeImage(gameObject, bibGraphics, 0, Color.White, false, true, gameObject.GetRemapColor(), - affectedByLighting, !drawParams.ShapeImage.SubjectToLighting, drawPoint, heightOffset); + DrawShapeImage(gameObject, bibGraphics, 0, Color.White, true, gameObject.GetRemapColor(), + affectedByLighting, !drawParams.ShapeImage.SubjectToLighting, drawPoint); } - protected override void Render(Structure gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + protected override void Render(Structure gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { if (RenderDependencies.EditorState.RenderInvisibleInGameObjects) DrawFoundationLines(gameObject); @@ -105,7 +125,7 @@ protected override void Render(Structure gameObject, int heightOffset, Point2D d // Bib is on the ground, gets grawn first var bibGraphics = RenderDependencies.TheaterGraphics.BuildingBibTextures[gameObject.ObjectType.Index]; if (bibGraphics != null) - DrawBibGraphics(gameObject, bibGraphics, heightOffset, drawPoint, drawParams, affectedByLighting); + DrawBibGraphics(gameObject, bibGraphics, drawPoint, drawParams, affectedByLighting); Color nonRemapColor = gameObject.IsBaseNodeDummy ? new Color(150, 150, 255) * 0.5f : Color.White; @@ -121,25 +141,25 @@ protected override void Render(Structure gameObject, int heightOffset, Point2D d // The building itself has an offset of 0, so first draw all anims with sort values < 0 foreach (var anim in animsList.Where(a => a.BuildingAnimDrawConfig.SortValue < 0)) - buildingAnimRenderer.Draw(anim, false, false); + buildingAnimRenderer.Draw(anim, false); // Then the building itself - // if (!gameObject.ObjectType.NoShadow && drawShadow) - // DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + if (!gameObject.ObjectType.NoShadow) + DrawShadowDirect(gameObject); int frameCount = drawParams.ShapeImage == null ? 0 : drawParams.ShapeImage.GetFrameCount(); bool affectedByAmbient = !affectedByLighting; DrawShapeImage(gameObject, drawParams.ShapeImage, gameObject.GetFrameIndex(frameCount), - nonRemapColor, false, true, gameObject.GetRemapColor(), - affectedByLighting, affectedByAmbient, drawPoint, heightOffset); + nonRemapColor, true, gameObject.GetRemapColor(), + affectedByLighting, affectedByAmbient, drawPoint); // Then draw all anims with sort values >= 0 foreach (var anim in animsList.Where(a => a.BuildingAnimDrawConfig.SortValue >= 0)) - buildingAnimRenderer.Draw(anim, false, false); + buildingAnimRenderer.Draw(anim, false); - DrawVoxelTurret(gameObject, heightOffset, drawPoint, drawParams, nonRemapColor, affectedByLighting); + DrawVoxelTurret(gameObject, drawPoint, drawParams, nonRemapColor, affectedByLighting); if (gameObject.ObjectType.HasSpotlight) { @@ -151,7 +171,7 @@ protected override void Render(Structure gameObject, int heightOffset, Point2D d } } - private void DrawVoxelTurret(Structure gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams, Color nonRemapColor, bool affectedByLighting) + private void DrawVoxelTurret(Structure gameObject, Point2D drawPoint, in CommonDrawParams drawParams, Color nonRemapColor, bool affectedByLighting) { if (gameObject.ObjectType.Turret && gameObject.ObjectType.TurretAnimIsVoxel) { @@ -165,21 +185,21 @@ private void DrawVoxelTurret(Structure gameObject, int heightOffset, Point2D dra { DrawVoxelModel(gameObject, drawParams.TurretVoxel, gameObject.Facing, RampType.None, nonRemapColor, true, gameObject.GetRemapColor(), - affectedByLighting, turretDrawPoint, heightOffset); + affectedByLighting, turretDrawPoint); DrawVoxelModel(gameObject, drawParams.BarrelVoxel, gameObject.Facing, RampType.None, nonRemapColor, true, gameObject.GetRemapColor(), - affectedByLighting, turretDrawPoint, heightOffset); + affectedByLighting, turretDrawPoint); } else { DrawVoxelModel(gameObject, drawParams.BarrelVoxel, gameObject.Facing, RampType.None, nonRemapColor, true, gameObject.GetRemapColor(), - affectedByLighting, turretDrawPoint, heightOffset); + affectedByLighting, turretDrawPoint); DrawVoxelModel(gameObject, drawParams.TurretVoxel, gameObject.Facing, RampType.None, nonRemapColor, true, gameObject.GetRemapColor(), - affectedByLighting, turretDrawPoint, heightOffset); + affectedByLighting, turretDrawPoint); } } else if (gameObject.ObjectType.Turret && !gameObject.ObjectType.TurretAnimIsVoxel && @@ -187,15 +207,15 @@ private void DrawVoxelTurret(Structure gameObject, int heightOffset, Point2D dra { DrawVoxelModel(gameObject, drawParams.BarrelVoxel, gameObject.Facing, RampType.None, nonRemapColor, true, gameObject.GetRemapColor(), - affectedByLighting, drawPoint, heightOffset); + affectedByLighting, drawPoint); } } - protected override void DrawObjectReplacementText(Structure gameObject, in CommonDrawParams drawParams, Point2D drawPoint) + protected override void DrawObjectReplacementText(Structure gameObject, string text, Point2D drawPoint) { DrawFoundationLines(gameObject); - base.DrawObjectReplacementText(gameObject, drawParams, drawPoint); + base.DrawObjectReplacementText(gameObject, text, drawPoint); } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/InfantryRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/InfantryRenderer.cs index f44711797..56d028f6f 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/InfantryRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/InfantryRenderer.cs @@ -18,11 +18,13 @@ protected override CommonDrawParams GetDrawParams(Infantry gameObject) { IniName = gameObject.ObjectType.ININame, ShapeImage = TheaterGraphics.InfantryTextures[gameObject.ObjectType.Index] - }; + }; } - protected override void Render(Infantry gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + public override Point2D GetDrawPoint(Infantry gameObject) { + Point2D drawPoint = base.GetDrawPoint(gameObject); + switch (gameObject.SubCell) { case SubCell.Top: @@ -42,13 +44,18 @@ protected override void Render(Infantry gameObject, int heightOffset, Point2D dr break; } - // if (!gameObject.ObjectType.NoShadow) - // DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + return drawPoint; + } + + protected override void Render(Infantry gameObject, Point2D drawPoint, in CommonDrawParams drawParams) + { + if (!gameObject.ObjectType.NoShadow) + DrawShadowDirect(gameObject); DrawShapeImage(gameObject, drawParams.ShapeImage, gameObject.GetFrameIndex(drawParams.ShapeImage.GetFrameCount()), - Color.White, false, true, gameObject.GetRemapColor(), - false, true, drawPoint, heightOffset); + Color.White, true, gameObject.GetRemapColor(), + false, true, drawPoint); } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/ObjectRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/ObjectRenderer.cs index 0fa8f0d6b..6e44c43bf 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/ObjectRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/ObjectRenderer.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Rampastring.XNAUI; +using SharpDX.Mathematics.Interop; using System; using TSMapEditor.CCEngine; using TSMapEditor.GameMath; @@ -8,11 +9,16 @@ namespace TSMapEditor.Rendering.ObjectRenderers { + public interface IObjectRenderer + { + void DrawShadow(GameObject gameObject); + } + /// /// Base class for all object renderers. /// /// The type of game object to render. - public abstract class ObjectRenderer where T : GameObject + public abstract class ObjectRenderer : IObjectRenderer where T : GameObject { protected ObjectRenderer(RenderDependencies renderDependencies) { @@ -30,13 +36,38 @@ protected ObjectRenderer(RenderDependencies renderDependencies) /// The entry point for rendering an object. /// /// Checks whether the object is within the visible screen space. If yes, - /// draws the graphics of the object, or the object's replacement text in - /// case it has no loaded graphics. + /// draws the graphics of the object. /// /// The game object to render. /// Whether the object's presence within the camera should be checked. - /// Whether a shadow should also be drawn for this object. - public void Draw(T gameObject, bool checkInCamera, bool drawShadow) + public void Draw(T gameObject, bool checkInCamera) + { + Point2D drawPoint = GetDrawPoint(gameObject); + + CommonDrawParams drawParams = GetDrawParams(gameObject); + + PositionedTexture frame = GetFrameTexture(gameObject, drawParams, RenderDependencies.EditorState.IsLighting); + + if (checkInCamera) + { + Rectangle drawingBounds = GetTextureDrawCoords(gameObject, frame, drawPoint); + + // If the object is not in view, skip + if (!IsObjectInCamera(drawingBounds)) + return; + } + + if (frame == null && ShouldRenderReplacementText(gameObject)) + { + DrawText(gameObject, false); + } + else if (frame != null) + { + Render(gameObject, drawPoint, drawParams); + } + } + + public bool IsWithinCamera(T gameObject) { Point2D drawPointWithoutCellHeight = CellMath.CellTopLeftPointFromCellCoords(gameObject.Position, RenderDependencies.Map); @@ -51,24 +82,59 @@ public void Draw(T gameObject, bool checkInCamera, bool drawShadow) Rectangle drawingBounds = GetTextureDrawCoords(gameObject, frame, drawPoint); // If the object is not in view, skip - if (checkInCamera && !IsObjectInCamera(drawingBounds)) - return; + if (!IsObjectInCamera(drawingBounds)) + return false; - if (drawShadow) - { - if (gameObject.HasShadow()) - DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + return true; + } - return; - } + public virtual Point2D GetDrawPoint(T gameObject) + { + Point2D drawPointWithoutCellHeight = CellMath.CellTopLeftPointFromCellCoords(gameObject.Position, RenderDependencies.Map); - if (frame == null && ShouldRenderReplacementText(gameObject)) - { - DrawObjectReplacementText(gameObject, drawParams, drawPoint); - } - else + var mapCell = RenderDependencies.Map.GetTile(gameObject.Position); + int heightOffset = RenderDependencies.EditorState.Is2DMode ? 0 : mapCell.Level * Constants.CellHeight; + Point2D drawPoint = new Point2D(drawPointWithoutCellHeight.X, drawPointWithoutCellHeight.Y - heightOffset); + + return drawPoint; + } + + public virtual void DrawNonRemap(T gameObject, Point2D drawPoint) + { + // Do nothing by default + } + + public virtual void DrawRemap(T gameObject, Point2D drawPoint) + { + // Do nothing by default + } + + /// + /// Draws a textual representation of the object. + /// + /// Usually used as a fallback rendering method for an object that has no loaded graphics. + /// + /// The game object for which to render a textual representation. + /// Whether the object's presence within the camera should be checked. + public void DrawText(T gameObject, bool checkInCamera) + { + if (ShouldRenderReplacementText(gameObject)) { - Render(gameObject, heightOffset, drawPoint, drawParams); + Point2D drawPointWithoutCellHeight = CellMath.CellTopLeftPointFromCellCoords(gameObject.Position, RenderDependencies.Map); + + var mapCell = RenderDependencies.Map.GetTile(gameObject.Position); + int heightOffset = RenderDependencies.EditorState.Is2DMode ? 0 : mapCell.Level * Constants.CellHeight; + Point2D drawPoint = new Point2D(drawPointWithoutCellHeight.X, drawPointWithoutCellHeight.Y - heightOffset); + + if (checkInCamera) + { + Rectangle drawingBounds = new Rectangle(drawPoint.X, drawPoint.Y, 1, 1); + if (!IsObjectInCamera(drawingBounds)) + return; + } + + // DrawObjectReplacementText(gameObject, gameObject.GetObjectType().ININame, drawPoint); + RenderDependencies.ObjectSpriteRecord.AddTextEntry(new TextEntry(gameObject.GetObjectType().ININame, ReplacementColor, drawPoint)); } } @@ -98,19 +164,17 @@ protected virtual bool ShouldRenderReplacementText(T gameObject) /// The Y-axis draw offset from cell height. /// The draw point of the object, with cell height taken into account. /// Draw parameters. - protected abstract void Render(T gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams); + protected virtual void Render(T gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { } /// /// Renders the replacement text of an object, displayed when no graphics for an object have been loaded. /// Override in derived classes to implement and customize the rendering process. /// /// The game object for which to draw a replacement text. - /// Draw parameters. + /// The string to draw. /// The draw point of the object, with cell height taken into account. - protected virtual void DrawObjectReplacementText(T gameObject, in CommonDrawParams drawParams, Point2D drawPoint) + protected virtual void DrawObjectReplacementText(T gameObject, string text, Point2D drawPoint) { - SetEffectParams_RGBADraw(false); - // If the object is a techno, draw an arrow that displays its facing if (gameObject.IsTechno()) { @@ -118,7 +182,7 @@ protected virtual void DrawObjectReplacementText(T gameObject, in CommonDrawPara DrawObjectFacingArrow(techno.Facing, drawPoint); } - Renderer.DrawString(drawParams.IniName, 1, drawPoint.ToXNAVector(), ReplacementColor, 1.0f); + Renderer.DrawString(text, 1, drawPoint.ToXNAVector(), ReplacementColor, 1.0f); } protected void DrawObjectFacingArrow(byte facing, Point2D drawPoint) @@ -174,7 +238,7 @@ private PositionedTexture GetFrameTexture(T gameObject, in CommonDrawParams draw return null; } - private Rectangle GetTextureDrawCoords(T gameObject, + protected Rectangle GetTextureDrawCoords(T gameObject, PositionedTexture frame, Point2D initialDrawPoint) { @@ -197,55 +261,48 @@ private Rectangle GetTextureDrawCoords(T gameObject, } return new Rectangle(finalDrawPointX, finalDrawPointY, - finalDrawPointX + frame?.Texture.Width ?? 0, finalDrawPointY + frame?.Texture.Height ?? 0); + frame?.Texture.Width ?? 1, frame?.Texture.Height ?? 1); } - protected void SetEffectParams_PalettedDraw(bool isShadow, Texture2D paletteTexture) - => SetEffectParams(RenderDependencies.PalettedColorDrawEffect, isShadow, paletteTexture, true); + public void DrawShadow(GameObject gameObject) => DrawShadowDirect(gameObject as T); - protected void SetEffectParams_RGBADraw(bool isShadow) - => SetEffectParams(RenderDependencies.PalettedColorDrawEffect, isShadow, null, false); - - protected void SetEffectParams(Effect effect, bool isShadow, Texture2D paletteTexture, bool usePalette) + public virtual void DrawShadowDirect(T gameObject) { - effect.Parameters["IsShadow"].SetValue(isShadow); - RenderDependencies.GraphicsDevice.SamplerStates[1] = SamplerState.LinearClamp; - - if (paletteTexture != null) - { - effect.Parameters["PaletteTexture"].SetValue(paletteTexture); - RenderDependencies.GraphicsDevice.Textures[2] = paletteTexture; - } - - effect.Parameters["UsePalette"].SetValue(usePalette); - RenderDependencies.PalettedColorDrawEffect.Parameters["UseRemap"].SetValue(false); // Disable remap by default - } + Point2D drawPoint = GetDrawPoint(gameObject); + CommonDrawParams drawParams = GetDrawParams(gameObject); - protected virtual void DrawShadow(T gameObject, in CommonDrawParams drawParams, Point2D drawPoint, int heightOffset) - { if (drawParams.ShapeImage == null) return; int shadowFrameIndex = gameObject.GetShadowFrameIndex(drawParams.ShapeImage.GetFrameCount()); - if (shadowFrameIndex > 0 && shadowFrameIndex < drawParams.ShapeImage.GetFrameCount()) - { - DrawShapeImage(gameObject, drawParams.ShapeImage, shadowFrameIndex, - new Color(0, 0, 0, 128), true, false, Color.White, false, false, drawPoint, heightOffset); - } + if (shadowFrameIndex < 0 && shadowFrameIndex >= drawParams.ShapeImage.GetFrameCount()) + return; + + PositionedTexture frame = drawParams.ShapeImage.GetFrame(shadowFrameIndex); + + if (frame == null) + return; + + float depth = GetDepth(gameObject, drawPoint.Y); + + Texture2D texture = frame.Texture; + + Rectangle drawingBounds = GetTextureDrawCoords(gameObject, frame, drawPoint); + + RenderDependencies.ObjectSpriteRecord.AddGraphicsEntry(new ObjectSpriteEntry(null, texture, drawingBounds, Color.White, false, true, depth)); + + // For the shadow it doesn't matter what we input as color + // Renderer.DrawTexture(texture, drawingBounds, null, Color.White, 0f, Vector2.Zero, SpriteEffects.None, depth); } - protected virtual float GetDepth(T gameObject, Texture2D texture) + protected virtual float GetDepth(T gameObject, int referenceDrawPointY) { var tile = Map.GetTile(gameObject.Position); - // int textureHeightInCells = texture.Height / Constants.CellHeight; - // if (textureHeightInCells == 0) - // textureHeightInCells++; - - return (tile.Level + 1) * Constants.DepthRenderStep; + return (((float)referenceDrawPointY / RenderDependencies.Map.HeightInPixelsWithCellHeight) * Constants.DownwardsDepthRenderSpace) + ((tile.Level + 2) * Constants.DepthRenderStep); } protected void DrawShapeImage(T gameObject, ShapeImage image, int frameIndex, Color color, - bool isShadow, bool drawRemap, Color remapColor, bool affectedByLighting, bool affectedByAmbient, Point2D drawPoint, int heightOffset) + bool drawRemap, Color remapColor, bool affectedByLighting, bool affectedByAmbient, Point2D drawPoint) { if (image == null) return; @@ -291,14 +348,14 @@ protected void DrawShapeImage(T gameObject, ShapeImage image, int frameIndex, Co } } - float depth = GetDepth(gameObject, frame.Texture); + float depth = GetDepth(gameObject, drawPoint.Y); - RenderFrame(frame, remapFrame, color, drawRemap, remapColor, isShadow, - drawingBounds.X, drawingBounds.Y, image.GetPaletteTexture(), lighting, depth); + RenderFrame(frame, remapFrame, color, drawRemap, remapColor, + drawingBounds, image.GetPaletteTexture(), lighting, depth); } protected void DrawVoxelModel(T gameObject, VoxelModel model, byte facing, RampType ramp, - Color color, bool drawRemap, Color remapColor, bool affectedByLighting, Point2D drawPoint, int heightOffset) + Color color, bool drawRemap, Color remapColor, bool affectedByLighting, Point2D drawPoint) { if (model == null) return; @@ -307,8 +364,6 @@ protected void DrawVoxelModel(T gameObject, VoxelModel model, byte facing, RampT if (frame == null || frame.Texture == null) return; - float depth = GetDepth(gameObject, frame.Texture); - PositionedTexture remapFrame = null; if (drawRemap) remapFrame = model.GetRemapFrame(facing, ramp, false); @@ -319,9 +374,6 @@ protected void DrawVoxelModel(T gameObject, VoxelModel model, byte facing, RampT case RTTIType.Unit: extraLight = Map.Rules.ExtraUnitLight; break; - case RTTIType.Infantry: - extraLight = Map.Rules.ExtraInfantryLight; - break; case RTTIType.Aircraft: extraLight = Map.Rules.ExtraAircraftLight; break; @@ -342,28 +394,32 @@ protected void DrawVoxelModel(T gameObject, VoxelModel model, byte facing, RampT } } + float depth = GetDepth(gameObject, drawPoint.Y + frame.Texture.Height); + remapColor = ScaleColorToAmbient(remapColor, mapCell.CellLighting); Rectangle drawingBounds = GetTextureDrawCoords(gameObject, frame, drawPoint); - RenderFrame(frame, remapFrame, color, drawRemap, remapColor, false, - drawingBounds.X, drawingBounds.Y, null, lighting, depth); + RenderFrame(frame, remapFrame, color, drawRemap, remapColor, + drawingBounds, null, lighting, depth); } private void RenderFrame(PositionedTexture frame, PositionedTexture remapFrame, Color color, bool drawRemap, Color remapColor, - bool isShadow, int finalDrawPointX, int finalDrawPointY, Texture2D paletteTexture, Vector4 lightingColor, float depth) + Rectangle drawingBounds, Texture2D paletteTexture, Vector4 lightingColor, float depth) { Texture2D texture = frame.Texture; - ApplyShaderEffectValues(isShadow, paletteTexture); + if (depth > 1.0f) + depth = 1.0f; color = new Color((color.R / 255.0f) * lightingColor.X / 2f, (color.B / 255.0f) * lightingColor.Y / 2f, (color.B / 255.0f) * lightingColor.Z / 2f, depth); - Renderer.DrawTexture(texture, - new Rectangle(finalDrawPointX, finalDrawPointY, texture.Width, texture.Height), - null, color, 0f, Vector2.Zero, SpriteEffects.None, depth); + RenderDependencies.ObjectSpriteRecord.AddGraphicsEntry(new ObjectSpriteEntry(paletteTexture, texture, drawingBounds, color, false, false, depth)); + + // Renderer.DrawTexture(texture, drawingBounds, + // null, color, 0f, Vector2.Zero, SpriteEffects.None, depth); if (drawRemap && remapFrame != null) { @@ -373,27 +429,20 @@ private void RenderFrame(PositionedTexture frame, PositionedTexture remapFrame, (remapColor.B / 255.0f), depth); - RenderDependencies.PalettedColorDrawEffect.Parameters["UseRemap"].SetValue(true); + // RenderDependencies.PalettedColorDrawEffect.Parameters["UseRemap"].SetValue(true); + RenderDependencies.ObjectSpriteRecord.AddGraphicsEntry(new ObjectSpriteEntry(paletteTexture, remapFrame.Texture, drawingBounds, remapColor, true, false, depth)); Renderer.DrawTexture(remapFrame.Texture, - new Rectangle(finalDrawPointX, finalDrawPointY, texture.Width, texture.Height), + drawingBounds, null, remapColor, 0f, Vector2.Zero, SpriteEffects.None, - 0f); + depth); } } - private void ApplyShaderEffectValues(bool isShadow, Texture2D paletteTexture) - { - if (paletteTexture == null) - SetEffectParams_RGBADraw(isShadow); - else - SetEffectParams_PalettedDraw(isShadow, paletteTexture); - } - protected void DrawLine(Vector2 start, Vector2 end, Color color, int thickness = 1, float depth = 0f) => Renderer.DrawLine(start, end, color, thickness, depth); diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/OverlayRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/OverlayRenderer.cs index 736ef48c1..2d14be2dc 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/OverlayRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/OverlayRenderer.cs @@ -1,5 +1,4 @@ using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using TSMapEditor.GameMath; using TSMapEditor.Models; @@ -22,13 +21,18 @@ protected override CommonDrawParams GetDrawParams(Overlay gameObject) }; } - protected override float GetDepth(Overlay gameObject, Texture2D texture) + protected override float GetDepth(Overlay gameObject, int referenceDrawPointY) { - // Overlay belong to the same layer with tiles themselves - return base.GetDepth(gameObject, texture) - Constants.DepthRenderStep; + if (gameObject.OverlayType.HighBridgeDirection == BridgeDirection.None) + { + return base.GetDepth(gameObject, referenceDrawPointY) - (Constants.DepthRenderStep / 3f); + } + + var tile = Map.GetTile(gameObject.Position); + return (((float)referenceDrawPointY / RenderDependencies.Map.HeightInPixelsWithCellHeight) * Constants.DownwardsDepthRenderSpace) + ((tile.Level + 4) * Constants.DepthRenderStep); } - protected override void Render(Overlay gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + protected override void Render(Overlay gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { Color remapColor = Color.White; if (gameObject.OverlayType.TiberiumType != null) @@ -36,35 +40,24 @@ protected override void Render(Overlay gameObject, int heightOffset, Point2D dra int overlayIndex = gameObject.OverlayType.Index; - if (!RenderDependencies.EditorState.Is2DMode) + if (!RenderDependencies.EditorState.Is2DMode && gameObject.OverlayType.HighBridgeDirection != BridgeDirection.None) { - foreach (var bridge in Map.EditorConfig.Bridges) + if (gameObject.OverlayType.HighBridgeDirection == BridgeDirection.EastWest) { - if (bridge.Kind == BridgeKind.High) - { - if (bridge.EastWest.Pieces.Contains(overlayIndex)) - { - drawPoint.Y -= Constants.CellHeight + 1; - heightOffset += Constants.CellHeight + 1; - break; - } - - if (bridge.NorthSouth.Pieces.Contains(overlayIndex)) - { - drawPoint.Y -= Constants.CellHeight * 2 + 1; - heightOffset += Constants.CellHeight * 2 + 1; - break; - } - } + drawPoint.Y -= Constants.CellHeight + 1; + } + else + { + drawPoint.Y -= Constants.CellHeight * 2 + 1; } } bool affectedByLighting = drawParams.ShapeImage.SubjectToLighting; bool affectedByAmbient = !gameObject.OverlayType.Tiberium && !affectedByLighting; - // DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + DrawShadowDirect(gameObject); DrawShapeImage(gameObject, drawParams.ShapeImage, gameObject.FrameIndex, Color.White, - false, true, remapColor, affectedByLighting, affectedByAmbient, drawPoint, heightOffset); + true, remapColor, affectedByLighting, affectedByAmbient, drawPoint); } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/RenderDependencies.cs b/src/TSMapEditor/Rendering/ObjectRenderers/RenderDependencies.cs index 6af54a069..0c85ef7e6 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/RenderDependencies.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/RenderDependencies.cs @@ -6,22 +6,22 @@ namespace TSMapEditor.Rendering.ObjectRenderers { public struct RenderDependencies { - public Map Map; - public TheaterGraphics TheaterGraphics; - public EditorState EditorState; - public GraphicsDevice GraphicsDevice; - public Effect ColorDrawEffect; - public Effect PalettedColorDrawEffect; - public Camera Camera; - public Func GetCameraRightXCoord; - public Func GetCameraBottomYCoord; + public readonly Map Map; + public readonly TheaterGraphics TheaterGraphics; + public readonly EditorState EditorState; + public readonly GraphicsDevice GraphicsDevice; + public readonly ObjectSpriteRecord ObjectSpriteRecord; + public readonly Effect PalettedColorDrawEffect; + public readonly Camera Camera; + public readonly Func GetCameraRightXCoord; + public readonly Func GetCameraBottomYCoord; public RenderDependencies(Map map, TheaterGraphics theaterGraphics, EditorState editorState, GraphicsDevice graphicsDevice, - Effect colorDrawEffect, + ObjectSpriteRecord objectSpriteRecord, Effect palettedColorDrawEffect, Camera camera, Func getCameraRightXCoord, @@ -31,7 +31,7 @@ public RenderDependencies(Map map, TheaterGraphics = theaterGraphics; EditorState = editorState; GraphicsDevice = graphicsDevice; - ColorDrawEffect = colorDrawEffect; + ObjectSpriteRecord = objectSpriteRecord; PalettedColorDrawEffect = palettedColorDrawEffect; Camera = camera; GetCameraRightXCoord = getCameraRightXCoord; diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/SmudgeRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/SmudgeRenderer.cs index ff20d4872..bdd03fb01 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/SmudgeRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/SmudgeRenderer.cs @@ -1,4 +1,6 @@ using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Rampastring.XNAUI; using TSMapEditor.GameMath; using TSMapEditor.Models; @@ -21,10 +23,43 @@ protected override CommonDrawParams GetDrawParams(Smudge gameObject) }; } - protected override void Render(Smudge gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + public override void DrawNonRemap(Smudge gameObject, Point2D drawPoint) { - DrawShapeImage(gameObject, drawParams.ShapeImage, 0, Color.White, false, false, Color.White, - RenderDependencies.EditorState.IsLighting, !drawParams.ShapeImage.SubjectToLighting, drawPoint, heightOffset); + var drawParams = GetDrawParams(gameObject); + + if (drawParams.ShapeImage == null) + return; + + PositionedTexture frame = drawParams.ShapeImage.GetFrame(0); + if (frame == null || frame.Texture == null) + return; + + Rectangle drawingBounds = GetTextureDrawCoords(gameObject, frame, drawPoint); + + Vector4 lighting = Vector4.One; + var mapCell = Map.GetTile(gameObject.Position); + + if (RenderDependencies.EditorState.IsLighting && mapCell != null) + { + if (RenderDependencies.EditorState.IsLighting && drawParams.ShapeImage.SubjectToLighting) + { + lighting = mapCell.CellLighting.ToXNAVector4(0); + } + else if (!drawParams.ShapeImage.SubjectToLighting) + { + lighting = mapCell.CellLighting.ToXNAVector4Ambient(0); + } + } + + float depth = GetDepth(gameObject, drawPoint.Y); + + Texture2D texture = frame.Texture; + + Color color = new Color(lighting.X / 2f, + lighting.Y / 2f, + lighting.Z / 2f, depth); + + Renderer.DrawTexture(texture, drawingBounds, null, color, 0f, Vector2.Zero, SpriteEffects.None, depth); } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/TerrainRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/TerrainRenderer.cs index 9ecebfeed..812518077 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/TerrainRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/TerrainRenderer.cs @@ -21,13 +21,13 @@ protected override CommonDrawParams GetDrawParams(TerrainObject gameObject) }; } - protected override void Render(TerrainObject gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + protected override void Render(TerrainObject gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { bool affectedByLighting = RenderDependencies.EditorState.IsLighting; - // DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + DrawShadowDirect(gameObject); DrawShapeImage(gameObject, drawParams.ShapeImage, 0, - Color.White, false, false, Color.White, affectedByLighting, !drawParams.ShapeImage.SubjectToLighting, drawPoint, heightOffset); + Color.White, false, Color.White, affectedByLighting, !drawParams.ShapeImage.SubjectToLighting, drawPoint); } } } diff --git a/src/TSMapEditor/Rendering/ObjectRenderers/UnitRenderer.cs b/src/TSMapEditor/Rendering/ObjectRenderers/UnitRenderer.cs index 50b84e901..39cdaa843 100644 --- a/src/TSMapEditor/Rendering/ObjectRenderers/UnitRenderer.cs +++ b/src/TSMapEditor/Rendering/ObjectRenderers/UnitRenderer.cs @@ -26,17 +26,17 @@ protected override CommonDrawParams GetDrawParams(Unit gameObject) }; } - protected override void Render(Unit gameObject, int heightOffset, Point2D drawPoint, in CommonDrawParams drawParams) + protected override void Render(Unit gameObject, Point2D drawPoint, in CommonDrawParams drawParams) { bool affectedByLighting = RenderDependencies.EditorState.IsLighting; if (gameObject.UnitType.ArtConfig.Voxel) { - RenderVoxelModel(gameObject, heightOffset, drawPoint, drawParams, drawParams.MainVoxel, affectedByLighting); + RenderVoxelModel(gameObject, drawPoint, drawParams, drawParams.MainVoxel, affectedByLighting); } else { - RenderMainShape(gameObject, heightOffset, drawPoint, drawParams); + RenderMainShape(gameObject, drawPoint, drawParams); } if (gameObject.UnitType.Turret) @@ -59,37 +59,37 @@ protected override void Render(Unit gameObject, int heightOffset, Point2D drawPo if (gameObject.Facing is > facingStartDrawAbove and <= facingEndDrawAbove) { if (gameObject.UnitType.ArtConfig.Voxel) - RenderVoxelModel(gameObject, heightOffset, drawPoint + turretOffset, drawParams, drawParams.TurretVoxel, affectedByLighting); + RenderVoxelModel(gameObject, drawPoint + turretOffset, drawParams, drawParams.TurretVoxel, affectedByLighting); else - RenderTurretShape(gameObject, heightOffset, drawPoint, drawParams, affectedByLighting); + RenderTurretShape(gameObject, drawPoint, drawParams); - RenderVoxelModel(gameObject, heightOffset, drawPoint + turretOffset, drawParams, drawParams.BarrelVoxel, affectedByLighting); + RenderVoxelModel(gameObject, drawPoint + turretOffset, drawParams, drawParams.BarrelVoxel, affectedByLighting); } else { - RenderVoxelModel(gameObject, heightOffset, drawPoint + turretOffset, drawParams, drawParams.BarrelVoxel, affectedByLighting); + RenderVoxelModel(gameObject, drawPoint + turretOffset, drawParams, drawParams.BarrelVoxel, affectedByLighting); if (gameObject.UnitType.ArtConfig.Voxel) - RenderVoxelModel(gameObject, heightOffset, drawPoint + turretOffset, drawParams, drawParams.TurretVoxel, affectedByLighting); + RenderVoxelModel(gameObject, drawPoint + turretOffset, drawParams, drawParams.TurretVoxel, affectedByLighting); else - RenderTurretShape(gameObject, heightOffset, drawPoint, drawParams, affectedByLighting); + RenderTurretShape(gameObject, drawPoint, drawParams); } } } - private void RenderMainShape(Unit gameObject, int heightOffset, Point2D drawPoint, CommonDrawParams drawParams) + private void RenderMainShape(Unit gameObject, Point2D drawPoint, CommonDrawParams drawParams) { - // if (!gameObject.ObjectType.NoShadow) - // DrawShadow(gameObject, drawParams, drawPoint, heightOffset); + if (!gameObject.ObjectType.NoShadow) + DrawShadowDirect(gameObject); DrawShapeImage(gameObject, drawParams.ShapeImage, gameObject.GetFrameIndex(drawParams.ShapeImage.GetFrameCount()), - Color.White, false, true, gameObject.GetRemapColor(), - false, true, drawPoint, heightOffset); + Color.White, true, gameObject.GetRemapColor(), + false, true, drawPoint); } - private void RenderTurretShape(Unit gameObject, int heightOffset, Point2D drawPoint, - CommonDrawParams drawParams, bool affectedByLighting) + private void RenderTurretShape(Unit gameObject, Point2D drawPoint, + CommonDrawParams drawParams) { int turretFrameIndex = gameObject.GetTurretFrameIndex(); @@ -101,12 +101,12 @@ private void RenderTurretShape(Unit gameObject, int heightOffset, Point2D drawPo return; DrawShapeImage(gameObject, drawParams.ShapeImage, - turretFrameIndex, Color.White, false, true, gameObject.GetRemapColor(), - false, true, drawPoint, heightOffset); + turretFrameIndex, Color.White, true, gameObject.GetRemapColor(), + false, true, drawPoint); } } - private void RenderVoxelModel(Unit gameObject, int heightOffset, Point2D drawPoint, + private void RenderVoxelModel(Unit gameObject, Point2D drawPoint, in CommonDrawParams drawParams, VoxelModel model, bool affectedByLighting) { var unitTile = RenderDependencies.Map.GetTile(gameObject.Position.X, gameObject.Position.Y); @@ -120,7 +120,7 @@ private void RenderVoxelModel(Unit gameObject, int heightOffset, Point2D drawPoi DrawVoxelModel(gameObject, model, gameObject.Facing, ramp, Color.White, true, gameObject.GetRemapColor(), - affectedByLighting, drawPoint, heightOffset); + affectedByLighting, drawPoint); } } } diff --git a/src/TSMapEditor/Rendering/ObjectSpriteRecord.cs b/src/TSMapEditor/Rendering/ObjectSpriteRecord.cs new file mode 100644 index 000000000..0b04176ec --- /dev/null +++ b/src/TSMapEditor/Rendering/ObjectSpriteRecord.cs @@ -0,0 +1,164 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using TSMapEditor.GameMath; +using TSMapEditor.Models; + +namespace TSMapEditor.Rendering +{ + public struct ObjectSpriteEntry + { + public Texture2D PaletteTexture; // 8 bytes + public Texture2D Texture; // 16 bytes + public Rectangle DrawingBounds; // 24 bytes + public Color Color; // 28 bytes + public bool UseRemap; // 29 bytes + public bool UseShadow; // 30 bytes + public float Depth; // 34 bytes + + public ObjectSpriteEntry(Texture2D paletteTexture, Texture2D texture, Rectangle drawingBounds, Color color, bool useRemap, bool useShadow, float depth) + { + PaletteTexture = paletteTexture; + Texture = texture; + DrawingBounds = drawingBounds; + Color = color; + UseRemap = useRemap; + UseShadow = useShadow; + Depth = depth; + } + } + + public struct ObjectDetailEntry + { + public Texture2D Texture; + public Rectangle DrawingBounds; + public Color Color; + public float Depth; + + public ObjectDetailEntry(Texture2D texture, Rectangle drawingBounds, Color color, float depth) + { + Texture = texture; + DrawingBounds = drawingBounds; + Color = color; + Depth = depth; + } + } + + public struct ShadowEntry + { + public Texture2D Texture; + public Rectangle DrawingBounds; + public float Depth; + + public ShadowEntry(Texture2D texture, Rectangle drawingBounds, float depth) + { + Texture = texture; + DrawingBounds = drawingBounds; + Depth = depth; + } + } + + public struct TextEntry + { + public string Text; + public Color Color; + public Point2D DrawPoint; + + public TextEntry(string text, Color color, Point2D drawPoint) + { + Text = text; + Color = color; + DrawPoint = drawPoint; + } + } + + public struct LineEntry + { + public Vector2 Source; + public Vector2 Destination; + public Color Color; + public int Thickness; + public float Depth; + + public LineEntry(Vector2 source, Vector2 destination, Color color, int thickness, float depth) + { + Source = source; + Destination = destination; + Color = color; + Thickness = thickness; + Depth = depth; + } + } + + /// + /// Makes it possible to batch sprites that are originally + /// processed in any order, with any kinds of required shader settings. + /// + /// Keeps track of sprites and other graphics that should be drawn as the + /// renderer processes objects. Finally, the renderer can process the lists + /// of this class to draw the objects. + /// + public class ObjectSpriteRecord + { + public Dictionary<(Texture2D, bool), List> SpriteEntries = new Dictionary<(Texture2D, bool), List>(); + public List NonPalettedSpriteEntries = new List(); + public List ShadowEntries = new List(); + public List TextEntries = new List(); + public List LineEntries = new List(); + public HashSet ProcessedObjects = new HashSet(); + + public void AddGraphicsEntry(in ObjectSpriteEntry entry) + { + if (entry.Texture == null) + throw new ArgumentNullException(nameof(entry)); + + // Shadows are handled separately + if (entry.UseShadow) + { + ShadowEntries.Add(new ShadowEntry(entry.Texture, entry.DrawingBounds, entry.Depth)); + return; + } + + // If the entry has no palette, we can store it separately + if (entry.PaletteTexture == null) + { + NonPalettedSpriteEntries.Add(new ObjectDetailEntry(entry.Texture, entry.DrawingBounds, new Color(entry.Color.R / 255.0f, entry.Color.G / 255.0f, entry.Color.B / 255.0f, entry.Depth), entry.Depth)); + return; + } + + // Paletted entries, with or without remap + var key = (entry.PaletteTexture, entry.UseRemap); + bool success = SpriteEntries.TryGetValue(key, out var list); + if (!success) + { + list = new List(); + SpriteEntries.Add(key, list); + } + + list.Add(new ObjectDetailEntry(entry.Texture, entry.DrawingBounds, entry.Color, entry.Depth)); + } + + public void AddTextEntry(TextEntry textEntry) => TextEntries.Add(textEntry); + + public void AddLineEntry(LineEntry lineEntry) => LineEntries.Add(lineEntry); + + public void Clear() + { + foreach (var list in SpriteEntries.Values) + { + list.Clear(); + } + + NonPalettedSpriteEntries.Clear(); + + ShadowEntries.Clear(); + + TextEntries.Clear(); + + LineEntries.Clear(); + + ProcessedObjects.Clear(); + } + } +} diff --git a/src/TSMapEditor/UI/SettingsPanel.cs b/src/TSMapEditor/UI/SettingsPanel.cs index 925ed352a..49ab973eb 100644 --- a/src/TSMapEditor/UI/SettingsPanel.cs +++ b/src/TSMapEditor/UI/SettingsPanel.cs @@ -78,7 +78,7 @@ public SettingsPanel(WindowManager windowManager) : base(windowManager) private XNADropDown ddTheme; private XNADropDown ddScrollRate; private XNACheckBox chkUseBoldFont; - private XNACheckBox chkDrawShadows; + private XNACheckBox chkGraphicsLevel; private EditorTextBox tbTextEditorPath; public override void Initialize() @@ -171,18 +171,18 @@ public override void Initialize() chkUseBoldFont.Text = "Use Bold Font"; AddChild(chkUseBoldFont); - chkDrawShadows = new XNACheckBox(WindowManager); - chkDrawShadows.Name = nameof(chkDrawShadows); - chkDrawShadows.X = Constants.UIEmptySideSpace; - chkDrawShadows.Y = chkUseBoldFont.Bottom + Constants.UIVerticalSpacing; - chkDrawShadows.Text = "Draw Shadows (disable on slow PCs)"; - AddChild(chkDrawShadows); + chkGraphicsLevel = new XNACheckBox(WindowManager); + chkGraphicsLevel.Name = nameof(chkGraphicsLevel); + chkGraphicsLevel.X = Constants.UIEmptySideSpace; + chkGraphicsLevel.Y = chkUseBoldFont.Bottom + Constants.UIVerticalSpacing; + chkGraphicsLevel.Text = "Enhanced Graphical Quality"; + AddChild(chkGraphicsLevel); var lblTextEditorPath = new XNALabel(WindowManager); lblTextEditorPath.Name = nameof(lblTextEditorPath); lblTextEditorPath.Text = "Text Editor Path:"; lblTextEditorPath.X = Constants.UIEmptySideSpace; - lblTextEditorPath.Y = chkDrawShadows.Bottom + Constants.UIVerticalSpacing * 2; + lblTextEditorPath.Y = chkGraphicsLevel.Bottom + Constants.UIVerticalSpacing * 2; AddChild(lblTextEditorPath); tbTextEditorPath = new EditorTextBox(WindowManager); @@ -212,7 +212,7 @@ private void LoadSettings() chkBorderless.Checked = userSettings.Borderless; chkUseBoldFont.Checked = userSettings.UseBoldFont; - chkDrawShadows.Checked = userSettings.GraphicsLevel > 0; + chkGraphicsLevel.Checked = userSettings.GraphicsLevel > 0; tbTextEditorPath.Text = userSettings.TextEditorPath; } @@ -222,7 +222,7 @@ public void ApplySettings() var userSettings = UserSettings.Instance; userSettings.UseBoldFont.UserDefinedValue = chkUseBoldFont.Checked; - userSettings.GraphicsLevel.UserDefinedValue = chkDrawShadows.Checked ? 1 : 0; + userSettings.GraphicsLevel.UserDefinedValue = chkGraphicsLevel.Checked ? 1 : 0; userSettings.Theme.UserDefinedValue = ddTheme.SelectedItem.Text; if (ddScrollRate.SelectedItem != null)