using Microsoft.Xna.Framework;
using System;
using Terraria;
namespace SoulHarvest.Common;
/// <summary>
/// Shared client-side presentation for every Death-domain summoning portal.
/// The continuous tapered tear forms the opening, while restrained broken rims,
/// hairline fractures, and inward-flowing embers keep it readable as a portal
/// without covering the weapon that is passing through it.
/// </summary>
internal static class DeathDomainPortalRenderer
{
private const int RimSegments = 22;
internal static void Draw(Vector2 center, Vector2 travelDirection,
float scale, float aperture, int seed)
{
if (Main.dedServ || scale <= 0.05f || aperture <= 0.001f)
return;
aperture = Smooth01(aperture);
Vector2 travel = travelDirection.SafeNormalize(Vector2.UnitX);
Vector2 axis = travel.RotatedBy(MathHelper.PiOver2);
Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
float rotation = axis.ToRotation();
float pulse = 1f + (float)Math.Sin(Main.GlobalTimeWrappedHourly * 5.2f
+ seed * 0.173f) * 0.035f;
float length = (51f + Hash01(seed, 17) * 8f) * aperture * scale
* pulse;
float width = (15f + Hash01(seed, 31) * 3f) * aperture * scale;
const float opacity = 1f;
// A single continuous mask produces a clean tapered tear. It replaces
// the old chain of thick, sharply joined line segments.
DeathDomainPrimitiveTextureSystem.DrawDeathPortalRift(center, rotation,
length, width, opacity);
DrawBrokenRim(center, axis, normal, length, width, opacity, seed);
DrawFractures(center, axis, normal, length, width, opacity, seed);
DrawInwardEmbers(center, axis, normal, length, width, opacity, seed);
}
private static void DrawBrokenRim(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, float opacity, int seed)
{
Vector2 previous = GetRimPoint(center, axis, normal, length, width,
seed, 0f);
for (int segment = 0; segment < RimSegments; segment++)
{
float start = segment / (float)RimSegments;
float end = (segment + 1f) / RimSegments;
Vector2 current = GetRimPoint(center, axis, normal, length, width,
seed, end);
bool gap = Hash01(seed + segment * 97, 71) < 0.27f
|| segment is 5 or 16;
if (!gap)
{
float segmentOpacity = opacity * MathHelper.Lerp(0.52f, 0.88f,
Hash01(seed, segment * 43 + 103));
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous,
current, new Color(126, 0, 25, 0) * (segmentOpacity * 0.62f),
3.2f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous,
current, new Color(242, 27, 55, 205) * segmentOpacity,
1.25f);
if ((segment + seed & 3) == 0)
{
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous,
current, new Color(255, 214, 205, 215)
* (segmentOpacity * 0.52f), 0.48f);
}
}
previous = current;
}
}
private static Vector2 GetRimPoint(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, int seed, float progress)
{
float angle = progress * MathHelper.TwoPi;
float roughness = 1f
+ (float)Math.Sin(angle * 3f + seed * 0.37f) * 0.075f
+ (float)Math.Sin(angle * 7f - seed * 0.19f) * 0.035f;
return center + axis * ((float)Math.Cos(angle) * length * 0.57f
* roughness) + normal * ((float)Math.Sin(angle) * width * 0.88f
* roughness);
}
private static void DrawFractures(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, float opacity, int seed)
{
for (int branch = 0; branch < 4; branch++)
{
float side = (branch & 1) == 0 ? -1f : 1f;
float along = MathHelper.Lerp(-0.34f, 0.34f,
Hash01(seed, branch * 59 + 211));
Vector2 root = center + axis * length * along
+ normal * width * side * MathHelper.Lerp(0.32f, 0.48f,
Hash01(seed, branch * 61 + 229));
Vector2 elbow = root + axis * length
* (Hash01(seed, branch * 67 + 251) * 0.13f - 0.065f)
+ normal * width * side * 0.43f;
Vector2 tip = elbow + axis * length
* (Hash01(seed, branch * 71 + 277) * 0.17f - 0.085f)
+ normal * width * side * 0.34f;
float alpha = opacity * MathHelper.Lerp(0.34f, 0.62f,
Hash01(seed, branch * 73 + 293));
DrawHairline(root, elbow, alpha);
DrawHairline(elbow, tip, alpha * 0.72f);
}
}
private static void DrawHairline(Vector2 start, Vector2 end, float opacity)
{
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
new Color(100, 0, 22, 0) * (opacity * 0.55f), 2.1f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
new Color(235, 20, 50, 205) * opacity, 0.72f);
}
private static void DrawInwardEmbers(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, float opacity, int seed)
{
float time = Main.GlobalTimeWrappedHourly * 1.45f;
for (int ember = 0; ember < 7; ember++)
{
float flow = Frac(time + Hash01(seed, ember * 89 + 337));
float angle = Hash01(seed, ember * 101 + 359) * MathHelper.TwoPi;
Vector2 outer = axis * ((float)Math.Cos(angle) * length * 0.78f)
+ normal * ((float)Math.Sin(angle) * width * 1.65f);
Vector2 position = center + outer * (1f - Smooth01(flow));
Vector2 inward = (center - position).SafeNormalize(-normal);
float alpha = opacity * (float)Math.Sin(flow * MathHelper.Pi) * 0.64f;
float size = MathHelper.Lerp(1.55f, 0.45f, flow);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(position,
position - inward * (3.5f + 4f * (1f - flow)),
new Color(249, 37, 62, 0) * alpha, size);
DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch,
position - Main.screenPosition, size * 0.62f,
new Color(255, 221, 205, 220) * (alpha * 0.72f));
}
}
private static float Hash01(int a, int b)
{
uint value = unchecked((uint)a * 0x9E3779B9u
^ (uint)b * 0x85EBCA6Bu ^ 0xC2B2AE35u);
value ^= value >> 16;
value *= 0x7FEB352Du;
value ^= value >> 15;
value *= 0x846CA68Bu;
value ^= value >> 16;
return (value & 0x00FFFFFFu) / 16777215f;
}
private static float Frac(float value) => value - (float)Math.Floor(value);
private static float Smooth01(float value)
{
value = MathHelper.Clamp(value, 0f, 1f);
return value * value * (3f - 2f * value);
}
}
using Microsoft.Xna.Framework;
using System;
using Terraria;
namespace SoulHarvest.Common;
/// <summary>
/// Shared client-side presentation for every Death-domain summoning portal.
/// The continuous tapered tear forms the opening, while restrained broken rims,
/// hairline fractures, and inward-flowing embers keep it readable as a portal
/// without covering the weapon that is passing through it.
/// </summary>
internal static class DeathDomainPortalRenderer
{
private const int RimSegments = 22;
internal static void Draw(Vector2 center, Vector2 travelDirection,
float scale, float aperture, int seed)
{
if (Main.dedServ || scale <= 0.05f || aperture <= 0.001f)
return;
aperture = Smooth01(aperture);
Vector2 travel = travelDirection.SafeNormalize(Vector2.UnitX);
Vector2 axis = travel.RotatedBy(MathHelper.PiOver2);
Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
float rotation = axis.ToRotation();
float pulse = 1f + (float)Math.Sin(Main.GlobalTimeWrappedHourly * 5.2f
+ seed * 0.173f) * 0.035f;
float length = (51f + Hash01(seed, 17) * 8f) * aperture * scale
* pulse;
float width = (15f + Hash01(seed, 31) * 3f) * aperture * scale;
const float opacity = 1f;
// A single continuous mask produces a clean tapered tear. It replaces
// the old chain of thick, sharply joined line segments.
DeathDomainPrimitiveTextureSystem.DrawDeathPortalRift(center, rotation,
length, width, opacity);
DrawBrokenRim(center, axis, normal, length, width, opacity, seed);
DrawFractures(center, axis, normal, length, width, opacity, seed);
DrawInwardEmbers(center, axis, normal, length, width, opacity, seed);
}
private static void DrawBrokenRim(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, float opacity, int seed)
{
Vector2 previous = GetRimPoint(center, axis, normal, length, width,
seed, 0f);
for (int segment = 0; segment < RimSegments; segment++)
{
float start = segment / (float)RimSegments;
float end = (segment + 1f) / RimSegments;
Vector2 current = GetRimPoint(center, axis, normal, length, width,
seed, end);
bool gap = Hash01(seed + segment * 97, 71) < 0.27f
|| segment is 5 or 16;
if (!gap)
{
float segmentOpacity = opacity * MathHelper.Lerp(0.52f, 0.88f,
Hash01(seed, segment * 43 + 103));
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous,
current, new Color(126, 0, 25, 0) * (segmentOpacity * 0.62f),
3.2f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous,
current, new Color(242, 27, 55, 205) * segmentOpacity,
1.25f);
if ((segment + seed & 3) == 0)
{
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous,
current, new Color(255, 214, 205, 215)
* (segmentOpacity * 0.52f), 0.48f);
}
}
previous = current;
}
}
private static Vector2 GetRimPoint(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, int seed, float progress)
{
float angle = progress * MathHelper.TwoPi;
float roughness = 1f
+ (float)Math.Sin(angle * 3f + seed * 0.37f) * 0.075f
+ (float)Math.Sin(angle * 7f - seed * 0.19f) * 0.035f;
return center + axis * ((float)Math.Cos(angle) * length * 0.57f
* roughness) + normal * ((float)Math.Sin(angle) * width * 0.88f
* roughness);
}
private static void DrawFractures(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, float opacity, int seed)
{
for (int branch = 0; branch < 4; branch++)
{
float side = (branch & 1) == 0 ? -1f : 1f;
float along = MathHelper.Lerp(-0.34f, 0.34f,
Hash01(seed, branch * 59 + 211));
Vector2 root = center + axis * length * along
+ normal * width * side * MathHelper.Lerp(0.32f, 0.48f,
Hash01(seed, branch * 61 + 229));
Vector2 elbow = root + axis * length
* (Hash01(seed, branch * 67 + 251) * 0.13f - 0.065f)
+ normal * width * side * 0.43f;
Vector2 tip = elbow + axis * length
* (Hash01(seed, branch * 71 + 277) * 0.17f - 0.085f)
+ normal * width * side * 0.34f;
float alpha = opacity * MathHelper.Lerp(0.34f, 0.62f,
Hash01(seed, branch * 73 + 293));
DrawHairline(root, elbow, alpha);
DrawHairline(elbow, tip, alpha * 0.72f);
}
}
private static void DrawHairline(Vector2 start, Vector2 end, float opacity)
{
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
new Color(100, 0, 22, 0) * (opacity * 0.55f), 2.1f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
new Color(235, 20, 50, 205) * opacity, 0.72f);
}
private static void DrawInwardEmbers(Vector2 center, Vector2 axis,
Vector2 normal, float length, float width, float opacity, int seed)
{
float time = Main.GlobalTimeWrappedHourly * 1.45f;
for (int ember = 0; ember < 7; ember++)
{
float flow = Frac(time + Hash01(seed, ember * 89 + 337));
float angle = Hash01(seed, ember * 101 + 359) * MathHelper.TwoPi;
Vector2 outer = axis * ((float)Math.Cos(angle) * length * 0.78f)
+ normal * ((float)Math.Sin(angle) * width * 1.65f);
Vector2 position = center + outer * (1f - Smooth01(flow));
Vector2 inward = (center - position).SafeNormalize(-normal);
float alpha = opacity * (float)Math.Sin(flow * MathHelper.Pi) * 0.64f;
float size = MathHelper.Lerp(1.55f, 0.45f, flow);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(position,
position - inward * (3.5f + 4f * (1f - flow)),
new Color(249, 37, 62, 0) * alpha, size);
DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch,
position - Main.screenPosition, size * 0.62f,
new Color(255, 221, 205, 220) * (alpha * 0.72f));
}
}
private static float Hash01(int a, int b)
{
uint value = unchecked((uint)a * 0x9E3779B9u
^ (uint)b * 0x85EBCA6Bu ^ 0xC2B2AE35u);
value ^= value >> 16;
value *= 0x7FEB352Du;
value ^= value >> 15;
value *= 0x846CA68Bu;
value ^= value >> 16;
return (value & 0x00FFFFFFu) / 16777215f;
}
private static float Frac(float value) => value - (float)Math.Floor(value);
private static float Smooth01(float value)
{
value = MathHelper.Clamp(value, 0f, 1f);
return value * value * (3f - 2f * value);
}
}