using Godot; using System; using Rokojori.Extensions; namespace Rokojori { /** One blit a layer contributes - where it goes, and how the shader should read its content there. A plain layer produces exactly one of these. A layer with TextureStackStamps produces one per stamp, plus the extra copies edge wrapping needs. Layers fill in rect unclipped and leave uvRect alone; TextureStackCompositor then clips the rect against the output and derives uvRect from the two. That split is what lets a stamp hang off the output's edge: the blit shrinks to the visible part, but uvRect tells the shader which slice of the stamp that part is, so the content and the masks stay aligned with the whole stamp instead of being squeezed into the sliver. Without it, UV being rect-local (spike gate G6) would silently scale every clipped placement. */ public struct TextureStackPlacement { /** Identity uvRect: this blit covers the whole placement. */ public static readonly Vector4 fullUVRect = new Vector4( 0, 0, 1, 1 ); /** Image space. Set by the layer unclipped, replaced by the compositor with the part that actually lies inside the output. */ public Rect2I rect; /** What rect was before clipping. Layers that generate content at the placement's resolution - a nested stack - have to work at this size and then show the uvRect slice of it, or their own full-texture layers would fill the visible sliver instead of the whole placement. */ public Rect2I unclippedRect; /** offset.xy / scale.zw - the slice of the placement that rect covers. Filled in by the compositor. */ public Vector4 uvRect; /** Extra content rotation in radians, on top of the layer's own. Non-zero only for scattered stamps. Note this rotates the content *inside* an axis-aligned rect, so a non-square stamp gets cropped at its corners - keep scattered stamps square, or leave rotation variation at 0. */ public float rotation; /** Which stamp this is, 0 for a plain layer. Wrapped copies keep the index of the stamp they belong to, so they draw identically. */ public int index; public static TextureStackPlacement At( Rect2I rect, int index = 0, float rotation = 0 ) { var placement = new TextureStackPlacement(); placement.rect = rect; placement.unclippedRect = rect; placement.uvRect = fullUVRect; placement.rotation = rotation; placement.index = index; return placement; } /** Clips against the output and works out which slice of the original rect survived. Returns false when nothing of it is visible. */ public bool ClipTo( Rect2I bounds ) { var unclipped = rect; unclippedRect = unclipped; if ( unclipped.Size.X <= 0 || unclipped.Size.Y <= 0 ) { return false; } var clipped = unclipped.Intersection( bounds ); if ( clipped.Size.X <= 0 || clipped.Size.Y <= 0 ) { return false; } rect = clipped; uvRect = new Vector4( ( clipped.Position.X - unclipped.Position.X ) / (float) unclipped.Size.X, ( clipped.Position.Y - unclipped.Position.Y ) / (float) unclipped.Size.Y, clipped.Size.X / (float) unclipped.Size.X, clipped.Size.Y / (float) unclipped.Size.Y ); return true; } } }