From 5da2f1ba8a27d3773796cb202b3418faa31f2f12 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 28 Mar 2025 16:26:27 +0000 Subject: [PATCH] Implement sonic blast rendering effect. --- .../Graphics/SonicBlastRenderable.cs | 65 +++++++++++++ OpenRA.Mods.D2k/Projectiles/SonicBlast.cs | 18 ++-- .../Traits/World/SonicBlastRenderer.cs | 96 +++++++++++++++++++ glsl/postprocess_textured_sonic.frag | 18 ++++ mods/d2k/rules/world.yaml | 1 + mods/d2k/weapons/other.yaml | 1 - 6 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 OpenRA.Mods.D2k/Graphics/SonicBlastRenderable.cs create mode 100644 OpenRA.Mods.D2k/Traits/World/SonicBlastRenderer.cs create mode 100644 glsl/postprocess_textured_sonic.frag diff --git a/OpenRA.Mods.D2k/Graphics/SonicBlastRenderable.cs b/OpenRA.Mods.D2k/Graphics/SonicBlastRenderable.cs new file mode 100644 index 0000000000..406f700b63 --- /dev/null +++ b/OpenRA.Mods.D2k/Graphics/SonicBlastRenderable.cs @@ -0,0 +1,65 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Mods.D2k.Traits; +using OpenRA.Primitives; + +namespace OpenRA.Mods.D2k.Graphics +{ + public class SonicBlastRenderable : IRenderable, IFinalizedRenderable + { + public static readonly IEnumerable None = Array.Empty(); + readonly SonicBlastRenderer renderer; + readonly float3 r; + + public WPos Pos { get; } + public int ZOffset => 0; + + public SonicBlastRenderable(SonicBlastRenderer renderer, WPos pos) + { + this.renderer = renderer; + Pos = pos; + r = renderer.Info.Size * new float3(0.5f, 0.5f, 0); + } + + public bool IsDecoration => false; + + public IRenderable WithZOffset(int newOffset) => this; + public IRenderable OffsetBy(in WVec offset) => this; + public IRenderable AsDecoration() => this; + + public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } + + public void Render(WorldRenderer wr) + { + renderer.Draw(wr.Screen3DPxPosition(Pos)); + } + + public void RenderDebugGeometry(WorldRenderer wr) + { + var pos = wr.Screen3DPxPosition(Pos); + var tl = wr.Viewport.WorldToViewPx(pos - r); + var br = wr.Viewport.WorldToViewPx(pos + r); + Game.Renderer.RgbaColorRenderer.DrawRect(tl, br, 1, Color.Red); + } + + public Rectangle ScreenBounds(WorldRenderer wr) + { + var pos = wr.Screen3DPxPosition(Pos); + var tl = wr.Viewport.WorldToViewPx(pos - r); + var br = wr.Viewport.WorldToViewPx(pos + r); + return new Rectangle(tl.X, tl.Y, br.X - tl.X, br.Y - tl.Y); + } + } +} diff --git a/OpenRA.Mods.D2k/Projectiles/SonicBlast.cs b/OpenRA.Mods.D2k/Projectiles/SonicBlast.cs index 63c2cf6438..2d32591f8e 100644 --- a/OpenRA.Mods.D2k/Projectiles/SonicBlast.cs +++ b/OpenRA.Mods.D2k/Projectiles/SonicBlast.cs @@ -14,9 +14,9 @@ using System.Linq; using OpenRA.GameRules; using OpenRA.Graphics; using OpenRA.Mods.Common; -using OpenRA.Mods.Common.Graphics; using OpenRA.Mods.Common.Traits; -using OpenRA.Primitives; +using OpenRA.Mods.D2k.Graphics; +using OpenRA.Mods.D2k.Traits; using OpenRA.Traits; namespace OpenRA.Mods.D2k.Projectiles @@ -30,9 +30,6 @@ namespace OpenRA.Mods.D2k.Projectiles [Desc("The number of ticks between the blast causing warhead impacts in its area of effect.")] public readonly int DamageInterval = 1; - [Desc("Equivalent to sequence ZOffset. Controls Z sorting.")] - public readonly int ZOffset = 0; - [Desc("The minimum distance the blast travels.")] public readonly WDist MinDistance = WDist.Zero; @@ -57,9 +54,6 @@ namespace OpenRA.Mods.D2k.Projectiles [Desc("Can this projectile be blocked when hitting actors with an nameof(BlocksProjectiles) trait.")] public readonly bool Blockable = false; - [Desc("Color of the blast.")] - public readonly Color Color = Color.SkyBlue; - public IProjectile Create(ProjectileArgs args) { return new SonicBlast(this, args); @@ -72,6 +66,7 @@ namespace OpenRA.Mods.D2k.Projectiles readonly ProjectileArgs args; readonly WDist speed; + readonly SonicBlastRenderer renderer; [Sync] WPos pos, lastPos; @@ -85,6 +80,8 @@ namespace OpenRA.Mods.D2k.Projectiles this.info = info; this.args = args; var world = args.SourceActor.World; + renderer = world.WorldActor.Trait(); + if (info.Speed.Length > 1) speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length)); else @@ -151,10 +148,7 @@ namespace OpenRA.Mods.D2k.Projectiles public IEnumerable Render(WorldRenderer wr) { if (!wr.World.FogObscures(pos)) - { - var blastRender = new BeamRenderable(pos, info.ZOffset, lastPos - pos, BeamRenderableShape.Cylindrical, info.Width, info.Color); - return new[] { (IRenderable)blastRender }; - } + return new[] { (IRenderable)new SonicBlastRenderable(renderer, pos) }; return SpriteRenderable.None; } diff --git a/OpenRA.Mods.D2k/Traits/World/SonicBlastRenderer.cs b/OpenRA.Mods.D2k/Traits/World/SonicBlastRenderer.cs new file mode 100644 index 0000000000..23514ea043 --- /dev/null +++ b/OpenRA.Mods.D2k/Traits/World/SonicBlastRenderer.cs @@ -0,0 +1,96 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Traits +{ + [TraitLocation(SystemActors.World | SystemActors.EditorWorld)] + [Desc("Renders sonic blasts")] + public class SonicBlastRendererInfo : TraitInfo + { + [Desc("Diameter of the sonic effect circle.")] + public readonly int Size = 16; + + [Desc("Amount to scale the visuals within the effect circle.")] + public readonly float Zoom = 2.5f; + + public override object Create(ActorInitializer init) { return new SonicBlastRenderer(this); } + } + + public sealed class SonicBlastRenderer : IRenderPostProcessPass, INotifyActorDisposing + { + public readonly SonicBlastRendererInfo Info; + + readonly Renderer renderer; + readonly IShader shader; + readonly IVertexBuffer buffer; + readonly List positions = new(); + + public SonicBlastRenderer(SonicBlastRendererInfo info) + { + Info = info; + renderer = Game.Renderer; + shader = renderer.CreateShader(new RenderPostProcessPassTexturedShaderBindings("sonic")); + buffer = renderer.CreateVertexBuffer(6); + + var r = 0.5f * info.Size; + shader.SetVec("Scale", r * (1f / info.Zoom - 1)); + var vertices = new RenderPostProcessPassTexturedVertex[] + { + new(-r, -r, -1, -1), + new(r, -r, 1, -1), + new(r, r, 1, 1), + new(r, r, 1, 1), + new(-r, r, -1, 1), + new(-r, -r, -1, -1) + }; + + buffer.SetData(ref vertices, 6); + } + + public void Draw(float3 pos) + { + positions.Add(pos); + } + + PostProcessPassType IRenderPostProcessPass.Type => PostProcessPassType.AfterWorld; + bool IRenderPostProcessPass.Enabled => positions.Count > 0; + + void IRenderPostProcessPass.Draw(WorldRenderer wr) + { + var scroll = wr.Viewport.TopLeft; + var size = renderer.WorldFrameBufferSize; + var width = 2f / (renderer.WorldDownscaleFactor * size.Width); + var height = 2f / (renderer.WorldDownscaleFactor * size.Height); + + shader.SetVec("Scroll", scroll.X, scroll.Y); + shader.SetVec("p1", width, height); + shader.SetVec("p2", -1, -1); + shader.SetTexture("WorldTexture", Game.Renderer.WorldBufferSnapshot()); + shader.PrepareRender(); + foreach (var pos in positions) + { + shader.SetVec("Pos", pos.X, pos.Y); + renderer.DrawBatch(buffer, shader, 0, 6, PrimitiveType.TriangleList); + } + + positions.Clear(); + } + + void INotifyActorDisposing.Disposing(Actor self) + { + buffer.Dispose(); + } + } +} diff --git a/glsl/postprocess_textured_sonic.frag b/glsl/postprocess_textured_sonic.frag new file mode 100644 index 0000000000..2c553d34e4 --- /dev/null +++ b/glsl/postprocess_textured_sonic.frag @@ -0,0 +1,18 @@ +#version {VERSION} +#ifdef GL_ES +precision mediump float; +#endif + +uniform sampler2D WorldTexture; +uniform float Scale; + +in vec2 vTexCoord; +out vec4 fragColor; + +void main() +{ + if (dot(vTexCoord, vTexCoord) >= 1.0) + discard; + + fragColor = texelFetch(WorldTexture, ivec2(gl_FragCoord.xy + Scale * vTexCoord), 0); +} diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index 8095bc8573..26109fc8dd 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -11,6 +11,7 @@ TerrainGeometryOverlay: DebugVisualizations: TerrainRenderer: + SonicBlastRenderer: ShroudRenderer: ShroudVariants: shrouda, shroudb, shroudc, shroudd FogVariants: foga, fogb, fogc, fogd diff --git a/mods/d2k/weapons/other.yaml b/mods/d2k/weapons/other.yaml index e4d3d4278f..547a279e0f 100644 --- a/mods/d2k/weapons/other.yaml +++ b/mods/d2k/weapons/other.yaml @@ -10,7 +10,6 @@ Sound: Falloff: 0, 0, 100, 0 Range: 0, 0c512, 4c0, 6c0 MinDistance: 5c0 - Color: 00FFFFC8 Warhead@1Dam: SpreadDamage Falloff: 100, 0 Spread: 1c756