CrtView.cs
namespace PixelPusher;
[Library( "crtview" )]
public sealed class CrtView : Panel
{
public const int VW = 640;
public const int VH = 360;
Texture _tex;
Color32[] _buf;
int _tw;
int _th;
bool _held;
bool _heldRight;
bool _panning;
int _hx;
int _hy;
int _panX;
int _panY;
public static bool PointerOn { get; private set; }
public static int HoverX { get; private set; }
public static int HoverY { get; private set; }
public CrtView()
{
AddClass( "crtview" );
Style.Width = Length.Fraction( 1f );
Style.Height = Length.Fraction( 1f );
}
public override void Tick()
{
base.Tick();
Ensure( VW, VH );
if ( _buf is null )
return;
Clear( new Color32( 4, 19, 28, 255 ) );
var screen = GameFlow.Screen;
if ( screen == GameScreen.Pixel )
DrawPixel();
else if ( IsIso( screen ) )
DrawIso( IsPlaying3D( screen ) );
else if ( screen is GameScreen.Stage or GameScreen.Play2D or GameScreen.Cleared or GameScreen.Pause )
DrawWorld( IsPlaying2D( screen ) );
else if ( screen == GameScreen.Chip )
DrawChip();
else if ( screen == GameScreen.Library )
DrawWorld( false );
PointerOn = HasHovered || _held || _panning;
if ( MapMouse( MousePosition, out var hx, out var hy ) )
{
HoverX = hx;
HoverY = hy;
}
if ( _held )
PumpHeld();
DrawCursorBox();
try
{
_tex.Update( _buf, 0, 0, _tw, _th );
Style.BackgroundImage = _tex;
}
catch
{
// ignore a missed frame
}
}
protected override void OnMouseDown( MousePanelEvent e )
{
base.OnMouseDown( e );
if ( !MapMouse( e.LocalPosition, out var mx, out var my ) )
return;
var right = e.MouseButton == MouseButtons.Right;
var left = e.MouseButton == MouseButtons.Left;
var middle = e.MouseButton == MouseButtons.Middle;
if ( middle )
{
_panning = true;
_panX = mx;
_panY = my;
e.StopPropagation();
return;
}
if ( !left && !right )
return;
_held = true;
_heldRight = right;
_hx = mx;
_hy = my;
e.StopPropagation();
Dispatch( mx, my, left, right, true, false );
if ( GameFlow.Screen == GameScreen.Pixel )
Coach.OnPaint();
if ( GameFlow.Screen == GameScreen.Stage )
Coach.OnStagePaint();
}
protected override void OnMouseMove( MousePanelEvent e )
{
base.OnMouseMove( e );
if ( !MapMouse( e.LocalPosition, out var mx, out var my ) )
return;
HoverX = mx;
HoverY = my;
if ( _panning )
{
StudioMouse.Pan( mx - _panX, my - _panY );
_panX = mx;
_panY = my;
return;
}
if ( !_held )
return;
_hx = mx;
_hy = my;
Dispatch( mx, my, !_heldRight, _heldRight, false, false );
}
protected override void OnMouseUp( MousePanelEvent e )
{
base.OnMouseUp( e );
if ( _panning )
{
_panning = false;
e.StopPropagation();
return;
}
if ( !_held )
return;
MapMouse( e.LocalPosition, out var mx, out var my );
Dispatch( mx, my, false, false, false, true );
_held = false;
e.StopPropagation();
}
public override void OnMouseWheel( Vector2 delta )
{
var dir = delta.y != 0 ? (delta.y > 0 ? 1 : -1) : (delta.x > 0 ? 1 : -1);
StudioMouse.Wheel( dir, HoverX, HoverY );
}
protected override void OnBack( PanelEvent e )
{
StudioMouse.Back();
e.StopPropagation();
}
protected override void OnForward( PanelEvent e )
{
StudioMouse.Forward();
e.StopPropagation();
}
void PumpHeld()
{
if ( !MapMouse( MousePosition, out var mx, out var my ) )
return;
_hx = mx;
_hy = my;
Dispatch( mx, my, !_heldRight, _heldRight, false, false );
}
bool MapMouse( Vector2 local, out int mx, out int my )
{
mx = 0;
my = 0;
var rect = Box.Rect;
if ( rect.Width < 2 || rect.Height < 2 )
return false;
var scale = MathF.Min( rect.Width / VW, rect.Height / VH );
if ( scale < 0.01f )
return false;
var ox = (rect.Width - VW * scale) * 0.5f;
var oy = (rect.Height - VH * scale) * 0.5f;
mx = (int)MathF.Floor( (local.x - ox) / scale );
my = (int)MathF.Floor( (local.y - oy) / scale );
return (uint)mx < VW && (uint)my < VH;
}
void Dispatch( int mx, int my, bool left, bool right, bool down, bool released )
{
if ( GameFlow.Screen == GameScreen.Pixel )
{
MapPixel( mx, my, out var px, out var py );
PixelStudio.Pointer( px, py, left, right, down, released );
return;
}
if ( GameFlow.Screen == GameScreen.Stage )
{
MapStage( mx, my, out var tx, out var ty );
StageStudio.Pointer( tx, ty, left, right, down, released );
return;
}
if ( GameFlow.Screen == GameScreen.Chip )
{
if ( down )
ChipStudio.Hit( mx, my, right );
else if ( left || right )
ChipStudio.DragPaint( mx, my, right );
}
}
void MapPixel( int mx, int my, out int px, out int py )
{
var doc = Desk.Sprite;
var zoom = Math.Max( 1, PixelStudio.Zoom );
var cw = doc?.Width ?? 16;
var ch = doc?.Height ?? 16;
var ox = (VW - cw * zoom) / 2 - PixelStudio.PanX;
var oy = (VH - ch * zoom) / 2 - PixelStudio.PanY;
px = (mx - ox) / zoom;
py = (my - oy) / zoom;
}
void MapStage( int mx, int my, out int tx, out int ty )
{
var stage = Desk.Stage;
var ts = stage?.TileSize ?? 16;
var zoom = Math.Max( 1, StageStudio.Zoom );
var wx = StageStudio.CamX + mx / (float)zoom;
var wy = StageStudio.CamY + my / (float)zoom;
tx = (int)MathF.Floor( wx / ts );
ty = (int)MathF.Floor( wy / ts );
}
void DrawPixel()
{
var doc = Desk.Sprite;
if ( doc is null )
return;
var zoom = Math.Max( 1, PixelStudio.Zoom );
var ox = (VW - doc.Width * zoom) / 2 - PixelStudio.PanX;
var oy = (VH - doc.Height * zoom) / 2 - PixelStudio.PanY;
Checker( ox, oy, doc.Width * zoom, doc.Height * zoom );
if ( PixelRef.Visible && PixelRef.HasImage )
{
var fade = PixelRef.Opacity / 100f;
for ( var y = 0; y < doc.Height; y++ )
for ( var x = 0; x < doc.Width; x++ )
{
var ghost = PixelRef.Sample( x, y, doc.Width, doc.Height );
Color ga = ghost;
if ( ga.a < 0.04f )
continue;
BlendRect( ox + x * zoom, oy + y * zoom, zoom, zoom, ghost, fade );
}
}
if ( PixelStudio.Onion && doc.FrameCount > 1 )
{
for ( var y = 0; y < doc.Height; y++ )
for ( var x = 0; x < doc.Width; x++ )
{
var ghost = doc.OnionAt( x, y );
Color ga = ghost;
if ( ga.a < 0.04f )
continue;
FillRect( ox + x * zoom, oy + y * zoom, zoom, zoom, ghost );
}
}
for ( var y = 0; y < doc.Height; y++ )
for ( var x = 0; x < doc.Width; x++ )
{
Color32 c = doc.ColorAt( x, y );
Color cc = c;
if ( cc.a < 0.04f )
continue;
FillRect( ox + x * zoom, oy + y * zoom, zoom, zoom, c );
}
if ( PixelStudio.Grid && zoom >= 4 )
{
var grid = new Color32( 255, 255, 255, 28 );
for ( var x = 0; x <= doc.Width; x++ )
VLine( ox + x * zoom, oy, doc.Height * zoom, grid );
for ( var y = 0; y <= doc.Height; y++ )
HLine( ox, oy + y * zoom, doc.Width * zoom, grid );
}
if ( PixelStudio.Preview( out var x0, out var y0, out var x1, out var y1 ) )
{
Color src = Palettes.Default.Colors[6];
var preview = new Color32( (byte)(src.r * 255f), (byte)(src.g * 255f), (byte)(src.b * 255f), 180 );
var rx0 = Math.Min( x0, x1 );
var ry0 = Math.Min( y0, y1 );
var rx1 = Math.Max( x0, x1 );
var ry1 = Math.Max( y0, y1 );
Rect( ox + rx0 * zoom, oy + ry0 * zoom, (rx1 - rx0 + 1) * zoom, (ry1 - ry0 + 1) * zoom, preview );
}
if ( PixelStudio.PadAim || Input.UsingController )
{
var cx = ox + PixelStudio.CursorX * zoom;
var cy = oy + PixelStudio.CursorY * zoom;
Rect( cx, cy, zoom, zoom, new Color32( 245, 248, 254, 255 ) );
}
else
{
MapPixel( HoverX, HoverY, out var hx, out var hy );
PixelStudio.HoverPx = hx;
PixelStudio.HoverPy = hy;
if ( (uint)hx < (uint)doc.Width && (uint)hy < (uint)doc.Height && zoom >= 2 )
{
Color fc = doc.Palette[Math.Clamp( PixelStudio.Fg, 0, doc.Palette.Length - 1 )];
var ghost = new Color32(
(byte)MathF.Round( fc.r * 255f ),
(byte)MathF.Round( fc.g * 255f ),
(byte)MathF.Round( fc.b * 255f ),
90 );
var r = Math.Max( 1, PixelStudio.Brush );
for ( var dy = -r + 1; dy < r; dy++ )
for ( var dx = -r + 1; dx < r; dx++ )
{
if ( PixelStudio.Circle && dx * dx + dy * dy >= r * r )
continue;
var gx = hx + dx;
var gy = hy + dy;
if ( (uint)gx >= (uint)doc.Width || (uint)gy >= (uint)doc.Height )
continue;
FillRect( ox + gx * zoom, oy + gy * zoom, zoom, zoom, ghost );
if ( PixelStudio.Mirror )
{
var mx = doc.Width - 1 - gx;
FillRect( ox + mx * zoom, oy + gy * zoom, zoom, zoom, ghost );
}
}
}
}
}
void DrawWorld( bool playing )
{
var stage = Desk.Stage;
var tiles = Desk.Tileset;
if ( stage is null )
return;
var ts = stage.TileSize;
var zoom = playing ? 2 : Math.Max( 1, StageStudio.Zoom );
var camX = playing ? PlaySim.CamX : StageStudio.CamX;
var camY = playing ? PlaySim.CamY : StageStudio.CamY;
var worldW = stage.Width * ts;
var worldH = stage.Height * ts;
var x0 = Math.Max( 0, (int)MathF.Floor( camX / ts ) - 1 );
var y0 = Math.Max( 0, (int)MathF.Floor( camY / ts ) - 1 );
var x1 = Math.Min( stage.Width - 1, (int)MathF.Floor( (camX + VW / (float)zoom) / ts ) + 1 );
var y1 = Math.Min( stage.Height - 1, (int)MathF.Floor( (camY + VH / (float)zoom) / ts ) + 1 );
for ( var ty = y0; ty <= y1; ty++ )
for ( var tx = x0; tx <= x1; tx++ )
{
var id = stage.TileAt( tx, ty );
if ( id == 0 )
continue;
var sx = (int)MathF.Round( (tx * ts - camX) * zoom );
var sy = (int)MathF.Round( (ty * ts - camY) * zoom );
BlitTile( tiles, id, sx, sy, zoom );
}
foreach ( var e in stage.Entities )
{
var sx = (int)MathF.Round( (e.Tx * ts - camX) * zoom );
var sy = (int)MathF.Round( (e.Ty * ts - camY) * zoom );
if ( e.Kind == "coin" )
{
if ( playing )
continue;
StarterPack.BlitCoin( _buf, _tw, _th, sx, sy, Time.Now );
}
else if ( e.Kind == "start" && !playing )
Rect( sx + 2, sy + 2, ts * zoom - 4, ts * zoom - 4, new Color32( 77, 255, 200, 200 ) );
else if ( e.Kind == "exit" )
FillRect( sx + 3, sy, ts * zoom - 6, ts * zoom, new Color32( 255, 77, 141, 220 ) );
else if ( e.Kind == "spring" && !playing )
FillRect( sx + 2, sy + ts * zoom / 2, ts * zoom - 4, ts * zoom / 2, new Color32( 255, 209, 102, 220 ) );
else if ( e.Kind == "check" && !playing )
FillRect( sx + 5, sy + 2, 6 * zoom, ts * zoom - 4, new Color32( 77, 255, 200, 220 ) );
else if ( e.Kind == "sign" && !playing )
{
FillRect( sx + 4, sy + 2, 8 * zoom, 10 * zoom, new Color32( 255, 209, 102, 230 ) );
FillRect( sx + 7, sy + 12 * zoom, 2 * zoom, 4 * zoom, new Color32( 192, 96, 64, 255 ) );
}
}
if ( playing )
{
foreach ( var e in stage.Entities )
{
if ( e.Kind != "coin" || PlaySim.Collected( e.Tx, e.Ty ) )
continue;
var sx = (int)MathF.Round( (e.Tx * ts - camX) * zoom );
var sy = (int)MathF.Round( (e.Ty * ts - camY) * zoom );
StarterPack.BlitCoin( _buf, _tw, _th, sx, sy, PlaySim.TimePlayed );
}
var px = (int)MathF.Round( (PlaySim.Px - camX) * zoom ) - 2;
var py = (int)MathF.Round( (PlaySim.Py - camY) * zoom ) - 1;
var frame = PlaySim.Grounded && MathF.Abs( PlaySim.Vx ) > 20f && ((int)(PlaySim.Anim * 8f) & 1) == 1 ? 1 : 0;
if ( PlaySim.Dead )
FillRect( px, py, 16, 16, new Color32( 255, 77, 141, 180 ) );
else
StarterPack.BlitTerry( _buf, _tw, _th, px, py, PlaySim.FaceLeft, frame );
}
else if ( StageStudio.Grid && zoom >= 2 )
{
var grid = new Color32( 255, 255, 255, 22 );
var gx0 = (int)MathF.Round( (x0 * ts - camX) * zoom );
var gy0 = (int)MathF.Round( (y0 * ts - camY) * zoom );
for ( var tx = x0; tx <= x1 + 1; tx++ )
VLine( (int)MathF.Round( (tx * ts - camX) * zoom ), gy0, (y1 - y0 + 2) * ts * zoom, grid );
for ( var ty = y0; ty <= y1 + 1; ty++ )
HLine( gx0, (int)MathF.Round( (ty * ts - camY) * zoom ), (x1 - x0 + 2) * ts * zoom, grid );
}
if ( !playing && StageStudio.Preview( out var ax, out var ay, out var bx, out var by ) )
{
var px0 = Math.Min( ax, bx );
var py0 = Math.Min( ay, by );
var px1 = Math.Max( ax, bx );
var py1 = Math.Max( ay, by );
var sx = (int)MathF.Round( (px0 * ts - camX) * zoom );
var sy = (int)MathF.Round( (py0 * ts - camY) * zoom );
Rect( sx, sy, (px1 - px0 + 1) * ts * zoom, (py1 - py0 + 1) * ts * zoom, new Color32( 50, 115, 235, 200 ) );
}
if ( !playing && (StageStudio.PadAim || Input.UsingController) )
{
var sx = (int)MathF.Round( (StageStudio.CursorTx * ts - camX) * zoom );
var sy = (int)MathF.Round( (StageStudio.CursorTy * ts - camY) * zoom );
Rect( sx, sy, ts * zoom, ts * zoom, new Color32( 50, 115, 235, 255 ) );
}
_ = worldW;
_ = worldH;
}
static bool IsPlaying2D( GameScreen screen )
=> screen == GameScreen.Play2D
|| (screen == GameScreen.Pause && GameFlow.ResumeTo == GameScreen.Play2D)
|| (screen == GameScreen.Cleared && !PlaySim.Walk3D);
static bool IsPlaying3D( GameScreen screen )
=> screen == GameScreen.Play3D
|| (screen == GameScreen.Pause && GameFlow.ResumeTo == GameScreen.Play3D)
|| (screen == GameScreen.Cleared && PlaySim.Walk3D);
static bool IsIso( GameScreen screen )
=> screen == GameScreen.World
|| screen == GameScreen.Play3D
|| (screen == GameScreen.Pause && GameFlow.ResumeTo is GameScreen.World or GameScreen.Play3D)
|| (screen == GameScreen.Cleared && PlaySim.Walk3D);
void DrawIso( bool playing )
{
var stage = Desk.Stage;
var tiles = Desk.Tileset;
if ( stage is null )
return;
var ts = stage.TileSize;
var scale = playing ? 2f : Math.Max( 1, WorldStudio.Zoom );
var camX = playing ? PlaySim.CamX : WorldStudio.CamX;
var camY = playing ? PlaySim.CamY : WorldStudio.CamY;
var slab = WorldStudio.SlabDepth;
for ( var ty = 0; ty < stage.Height; ty++ )
for ( var tx = 0; tx < stage.Width; tx++ )
{
var id = stage.TileAt( tx, ty );
if ( id == 0 )
continue;
var wx = tx * ts;
var wy = ty * ts;
var solid = tiles is not null && (tiles.IsSolid( id ) || tiles.IsOneWay( id ) || tiles.IsHazard( id ) || tiles.IsSpring( id ));
DrawSlab( tiles, id, wx, wy, solid ? slab : slab * 0.35f, scale, camX, camY, solid );
}
foreach ( var e in stage.Entities )
{
if ( playing && e.Kind == "coin" && PlaySim.Collected( e.Tx, e.Ty ) )
continue;
var wx = e.Tx * ts;
var wy = e.Ty * ts;
WorldStudio.Project( wx, wy, slab * 0.45f, out var sx, out var sy, scale, camX, camY );
if ( e.Kind == "coin" )
StarterPack.BlitCoin( _buf, _tw, _th, sx, sy, playing ? PlaySim.TimePlayed : Time.Now );
else if ( e.Kind == "exit" )
FillRect( sx + 3, sy, ts - 6, ts, new Color32( 255, 77, 141, 220 ) );
else if ( e.Kind == "spring" )
FillRect( sx + 2, sy + ts / 2, ts - 4, ts / 2, new Color32( 255, 209, 102, 220 ) );
else if ( e.Kind == "start" && !playing )
Rect( sx + 2, sy + 2, ts - 4, ts - 4, new Color32( 77, 255, 200, 200 ) );
else if ( e.Kind == "check" )
FillRect( sx + 5, sy + 2, 6, ts - 4, new Color32( 77, 255, 200, 220 ) );
else if ( e.Kind == "sign" )
{
FillRect( sx + 4, sy + 2, 8, 10, new Color32( 255, 209, 102, 230 ) );
FillRect( sx + 7, sy + 12, 2, 4, new Color32( 192, 96, 64, 255 ) );
}
}
if ( playing )
{
WorldStudio.Project( PlaySim.Px - 2, PlaySim.Py - 1, PlaySim.Pz, out var px, out var py, scale, camX, camY );
var frame = PlaySim.Grounded && MathF.Abs( PlaySim.Vx ) > 20f && ((int)(PlaySim.Anim * 8f) & 1) == 1 ? 1 : 0;
if ( PlaySim.Dead )
FillRect( px, py, 16, 16, new Color32( 255, 77, 141, 180 ) );
else
StarterPack.BlitTerry( _buf, _tw, _th, px, py, PlaySim.FaceLeft, frame );
}
PixFont.Draw( _buf, _tw, _th, 4, 4, playing ? "WALK" : "WORLD STUDIO", new Color32( 77, 255, 200, 220 ), 1 );
}
void DrawSlab( TilesetDoc tiles, int id, float wx, float wy, float depth, float scale, float camX, float camY, bool solid )
{
WorldStudio.Project( wx, wy, 0, out var fx, out var fy, scale, camX, camY );
WorldStudio.Project( wx, wy, depth, out var bx, out var by, scale, camX, camY );
var ts = (int)MathF.Round( (Desk.Stage?.TileSize ?? 16) * scale );
if ( ts < 4 )
ts = 4;
var avg = AverageTile( tiles, id );
var top = Mul( avg, 1.25f );
var side = Mul( avg, 0.55f );
if ( solid )
{
FillParallelogram( fx + ts, fy, bx + ts, by, ts, side );
FillParallelogram( fx, fy, bx, by, ts, top );
}
if ( tiles?.Sheet is not null )
BlitTile( tiles, id, fx, fy, Math.Max( 1, (int)scale ) );
else
FillRect( fx, fy, ts, ts, avg );
}
void DrawChip()
{
var doc = Desk.Chip;
FillRect( 0, 0, VW, VH, new Color32( 17, 20, 29, 255 ) );
PixFont.Draw( _buf, _tw, _th, 16, 14, "CHIP STUDIO", new Color32( 50, 115, 235, 255 ), 3 );
PixFont.Draw( _buf, _tw, _th, 16, 42, doc is null ? "NO CHIP" : $"{doc.Bpm} BPM SPACE PLAY CLICK NOTE RMB PITCH", new Color32( 198, 212, 238, 255 ), 1 );
if ( doc is null )
return;
var gx = ChipStudio.GridX;
var gy = ChipStudio.GridY;
var cw = ChipStudio.CellW;
var chh = ChipStudio.CellH;
var head = ChipStudio.Playing ? ChipStudio.Head : -1;
var blue = new Color32( 50, 115, 235, 255 );
var dim = new Color32( 22, 28, 40, 255 );
var beat = new Color32( 28, 36, 52, 255 );
var ink = new Color32( 245, 248, 254, 255 );
var mute = new Color32( 255, 93, 106, 255 );
for ( var step = 0; step < ChipDoc.Steps; step++ )
{
var x = gx + step * cw;
if ( step % 4 == 0 )
PixFont.Draw( _buf, _tw, _th, x + 6, gy - 14, (step + 1).ToString(), new Color32( 143, 154, 174, 255 ), 1 );
if ( step == head )
FillRect( x - 1, gy - 4, cw, ChipDoc.Channels * chh + 4, new Color32( 50, 115, 235, 40 ) );
}
for ( var ch = 0; ch < ChipDoc.Channels; ch++ )
{
var rowY = gy + ch * chh;
var name = ChipStudio.ChannelNames[ch];
var muted = ChipStudio.Mute[ch];
PixFont.Draw( _buf, _tw, _th, 10, rowY + 22, name, muted ? mute : ink, 1 );
if ( muted )
PixFont.Draw( _buf, _tw, _th, 10, rowY + 36, "MUTE", mute, 1 );
for ( var step = 0; step < ChipDoc.Steps; step++ )
{
var x = gx + step * cw;
var note = doc.Get( ch, step );
var on = note > 0;
var cursor = ChipStudio.CursorCh == ch && ChipStudio.CursorStep == step;
var bg = step % 4 == 0 ? beat : dim;
FillRect( x + 1, rowY + 2, cw - 3, chh - 6, bg );
if ( on )
{
var h = Math.Max( 8, note * (chh - 16) / 12 );
var y = rowY + (chh - 8) - h;
FillRect( x + 4, y, cw - 9, h, muted ? mute : blue );
var label = note < ChipStudio.NoteNames.Length ? ChipStudio.NoteNames[note] : note.ToString();
PixFont.Draw( _buf, _tw, _th, x + 6, rowY + 6, label, ink, 1 );
}
if ( cursor )
Rect( x + 1, rowY + 2, cw - 3, chh - 6, ink );
}
}
}
void FillParallelogram( int x0, int y0, int x1, int y1, int h, Color32 c )
{
var steps = Math.Max( 1, Math.Abs( x1 - x0 ) );
for ( var i = 0; i <= steps; i++ )
{
var t = i / (float)steps;
var x = (int)MathF.Round( x0 + (x1 - x0) * t );
var y = (int)MathF.Round( y0 + (y1 - y0) * t );
VLine( x, y, h, c );
}
}
Color32 AverageTile( TilesetDoc tiles, int id )
{
if ( tiles?.Sheet is null || id <= 0 )
return new Color32( 61, 77, 138, 255 );
tiles.TileOrigin( id, out var ox, out var oy );
var r = 0f;
var g = 0f;
var b = 0f;
var n = 0;
var ts = tiles.TileSize;
for ( var y = 0; y < ts; y += 2 )
for ( var x = 0; x < ts; x += 2 )
{
Color c = tiles.Sheet.ColorAt( ox + x, oy + y );
if ( c.a < 0.04f )
continue;
r += c.r;
g += c.g;
b += c.b;
n++;
}
if ( n == 0 )
return new Color32( 61, 77, 138, 255 );
return new Color32(
(byte)MathF.Round( r / n * 255f ),
(byte)MathF.Round( g / n * 255f ),
(byte)MathF.Round( b / n * 255f ),
255 );
}
static Color32 Mul( Color32 c, float m )
{
Color col = c;
return new Color32(
(byte)MathF.Round( Math.Clamp( col.r * m, 0f, 1f ) * 255f ),
(byte)MathF.Round( Math.Clamp( col.g * m, 0f, 1f ) * 255f ),
(byte)MathF.Round( Math.Clamp( col.b * m, 0f, 1f ) * 255f ),
255 );
}
void DrawCursorBox()
{
if ( !HasHovered )
return;
if ( GameFlow.Screen is not (GameScreen.Pixel or GameScreen.Stage) )
return;
Color src = Palettes.Default.Colors[8];
Rect( _hx, _hy, 1, 1, new Color32( (byte)(src.r * 255f), (byte)(src.g * 255f), (byte)(src.b * 255f), 220 ) );
}
void BlitTile( TilesetDoc tiles, int id, int dx, int dy, int zoom )
{
if ( tiles?.Sheet is null || id <= 0 )
return;
tiles.TileOrigin( id, out var ox, out var oy );
var ts = tiles.TileSize;
var sheet = tiles.Sheet;
for ( var y = 0; y < ts; y++ )
for ( var x = 0; x < ts; x++ )
{
var col = sheet.ColorAt( ox + x, oy + y );
Color cc = col;
if ( cc.a < 0.04f )
continue;
if ( zoom <= 1 )
{
Put( dx + x, dy + y, col );
}
else
{
FillRect( dx + x * zoom, dy + y * zoom, zoom, zoom, col );
}
}
}
void Ensure( int w, int h )
{
if ( _tex is not null && _tw == w && _th == h )
return;
_tw = w;
_th = h;
_buf = new Color32[w * h];
try
{
_tex = Texture.Create( w, h )
.WithName( "pp_crt" )
.WithDynamicUsage()
.Finish();
}
catch
{
_tex = Texture.Create( w, h ).WithName( "pp_crt" ).Finish();
}
}
void Clear( Color32 c )
{
for ( var i = 0; i < _buf.Length; i++ )
_buf[i] = c;
}
void Checker( int x, int y, int w, int h )
{
var a = new Color32( 22, 28, 48, 255 );
var b = new Color32( 16, 20, 36, 255 );
for ( var py = 0; py < h; py++ )
for ( var px = 0; px < w; px++ )
Put( x + px, y + py, ((px / 4 + py / 4) & 1) == 0 ? a : b );
}
void FillRect( int x, int y, int w, int h, Color32 c )
{
for ( var py = 0; py < h; py++ )
for ( var px = 0; px < w; px++ )
Put( x + px, y + py, c );
}
void Rect( int x, int y, int w, int h, Color32 c )
{
HLine( x, y, w, c );
HLine( x, y + h - 1, w, c );
VLine( x, y, h, c );
VLine( x + w - 1, y, h, c );
}
void HLine( int x, int y, int w, Color32 c )
{
for ( var i = 0; i < w; i++ )
Put( x + i, y, c );
}
void VLine( int x, int y, int h, Color32 c )
{
for ( var i = 0; i < h; i++ )
Put( x, y + i, c );
}
void Put( int x, int y, Color32 c )
{
if ( (uint)x >= (uint)_tw || (uint)y >= (uint)_th )
return;
_buf[y * _tw + x] = c;
}
void BlendRect( int x, int y, int w, int h, Color32 src, float a )
{
a = Math.Clamp( a, 0f, 1f );
Color s = src;
for ( var py = 0; py < h; py++ )
for ( var px = 0; px < w; px++ )
{
var gx = x + px;
var gy = y + py;
if ( (uint)gx >= (uint)_tw || (uint)gy >= (uint)_th )
continue;
var i = gy * _tw + gx;
Color d = _buf[i];
_buf[i] = new Color32(
(byte)MathF.Round( (d.r * (1f - a) + s.r * a) * 255f ),
(byte)MathF.Round( (d.g * (1f - a) + s.g * a) * 255f ),
(byte)MathF.Round( (d.b * (1f - a) + s.b * a) * 255f ),
255 );
}
}
}