using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace OpenVIII.Fields
{
///
/// Background Tiles for field
///
///
///
///
///
///
///
public partial class Background : IDisposable
{
#region Fields
private const int BytesPerPalette = 2 * ColorsPerPalette;
private const int ColorsPerPalette = 256;
///
/// 4 bit has 2 columns per every byte so it expands to twice the width.
///
private const int FourBitTexturePageWidth = 2 * TexturePageWidth;
///
/// Standard texture page width.
///
private const int TexturePageWidth = 128;
private Dictionary> _animations;
private AlphaTestEffect _ate;
private Vector3 _camPosition;
private Vector3 _camTarget;
///
/// Palettes/Color Lookup Tables
///
private Cluts _cluts;
public float Degrees;
private bool _disposedValue;
private BasicEffect _effect;
private FPS_Camera _fpsCamera;
private Rectangle _outputDims;
private Matrix _projectionMatrix, _viewMatrix, _worldMatrix;
private List _quads;
private Dictionary _textureIDs;
private Dictionary _textureIDsPalettes;
private ConcurrentDictionary>>>>>
_textures;
private BackgroundTextureType _textureType;
#endregion Fields
#region Destructors
// To detect redundant calls
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
~Background()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
#endregion Destructors
#region Properties
public TimeSpan CurrentTime { get; set; }
public Tiles GetTiles { get; private set; }
public bool HasSpriteBatchTexturesLoaded => GetTexturesReadyToDrawClassicSpriteBatch()?.Count > 0;
public int Height { get => _outputDims.Height; private set => _outputDims.Height = value; }
/*
public bool Is4Bit => GetTiles?.Any(x => x.Is4Bit) ?? false;
public bool IsAddBlendMode => GetTiles?.Any(x => x.BlendMode == BlendMode.add) ?? false;
public bool IsHalfBlendMode => GetTiles?.Any(x => x.BlendMode == BlendMode.halfadd) ?? false;
public bool IsQuarterBlendMode => GetTiles?.Any(x => x.BlendMode == BlendMode.quarteradd) ?? false;
public bool IsSubtractBlendMode => GetTiles?.Any(x => x.BlendMode == BlendMode.subtract) ?? false;
*/
public Vector3 MouseLocation { get; set; }
public TimeSpan TotalTime { get; set; }
public int Width { get => _outputDims.Width; private set => _outputDims.Width = value; }
#endregion Properties
#region Methods
public static Background Load(byte[] mim, byte[] map)
{
if (mim == null || map == null)
return null;
var r = new Background
{
_textureType = BackgroundTextureType.GetTextureType(mim),
_camTarget = Vector3.Zero,
_camPosition = new Vector3(0f, 0f, -10f),
_fpsCamera = new FPS_Camera(),
Degrees = 90f
};
if (Memory.Graphics != null)
{
r._ate = new AlphaTestEffect(Memory.Graphics.GraphicsDevice);
r._effect = new BasicEffect(Memory.Graphics.GraphicsDevice);
}
r._worldMatrix = Matrix.CreateWorld(r._camPosition, Vector3.
Forward, Vector3.Up);
r._viewMatrix = Matrix.CreateLookAt(r._camPosition, r._camTarget,
Vector3.Up);
r.LoadTiles(map);
r.LoadPalettes(mim);
r.DumpRawTexture(mim);
var watch = Stopwatch.StartNew();
try
{
if (!r.ParseBackgroundQuads(mim))
{
return null;
}
}
finally
{
watch.Stop();
Debug.WriteLine($"{nameof(ParseBackgroundQuads)} took {watch.ElapsedMilliseconds / 1000f} seconds.");
}
try
{
if (!r.ParseBackgroundClassicSpriteBatch(mim))
{
return null;
}
}
finally
{
watch.Stop();
Debug.WriteLine($"{nameof(ParseBackgroundClassicSpriteBatch)} took {watch.ElapsedMilliseconds / 1000f} seconds.");
}
return r;
}
public void Deswizzle()
{
using (var mask = new Texture2D(Memory.Graphics.GraphicsDevice, 4, 4))
{
mask.SetData(Enumerable.Range(0, 16).Select(x => Color.White).ToArray());
var fieldName = Module.GetFieldName();
var folder = Module.GetFolder(fieldName, "deswizzle");
var scale = _quads[0].Texture.ScaleFactor;
var tilesWidth = (int)(GetTiles.Width * scale.X);
var tilesHeight = (int)(GetTiles.Height * scale.Y);
//Matrix backup = projectionMatrix;
//projectionMatrix = Matrix.CreateOrthographic(tiles.Width, tiles.Height, 0f, 100f);
GetTiles.UniquePupuIDs();// make sure each layer has it's own ID.
foreach (var pupuGroup in _quads.GroupBy(x => x.GetTile.PupuID)
) //group the quads by their pupu ID.
{
using (var outTex = new RenderTarget2D(Memory.Graphics.GraphicsDevice, tilesWidth, tilesHeight))
{
//start drawing
Memory.Graphics.GraphicsDevice.SetRenderTarget(outTex);
Memory.Graphics.GraphicsDevice.Clear(Color.TransparentBlack);
Memory.SpriteBatchStartAlpha();
foreach (var quad in pupuGroup)
{
var tile = (Tile)quad;
//DrawBackgroundQuadsStart();
//DrawBackgroundQuad(quad, true);
var dst = tile.GetRectangle;
dst.Offset(Math.Abs(GetTiles.TopLeft.X), Math.Abs(GetTiles.TopLeft.Y));
var src = tile.Source;
//src = src.Scale(scale);
dst = dst.Scale(scale);
quad.Texture.Draw(dst, src, Color.White);
}
Memory.SpriteBatchEnd();
//end drawing
Memory.Graphics.GraphicsDevice.SetRenderTarget(null);
//set path
var path = Path.Combine(folder,
$"{fieldName}_{pupuGroup.Key:X8}.png");
//save image.
Extended.Save_As_PNG(outTex, path, tilesWidth, tilesHeight);
}
using (var outTex = new RenderTarget2D(Memory.Graphics.GraphicsDevice, tilesWidth, tilesHeight))
{
//start drawing
Memory.Graphics.GraphicsDevice.SetRenderTarget(outTex);
Memory.Graphics.GraphicsDevice.Clear(Color.Black);
Memory.SpriteBatchStartAlpha();
var src = new Rectangle(0, 0, 4, 4);
foreach (var quad in pupuGroup)
{
var tile = (Tile)quad;
//DrawBackgroundQuadsStart();
//DrawBackgroundQuad(quad, true);
var dst = tile.GetRectangle;
dst.Offset(Math.Abs(GetTiles.TopLeft.X), Math.Abs(GetTiles.TopLeft.Y));
//src = src.Scale(scale);
dst = dst.Scale(scale);
Memory.SpriteBatch.Draw(mask, dst, src, Color.White);
}
Memory.SpriteBatchEnd();
//end drawing
Memory.Graphics.GraphicsDevice.SetRenderTarget(null);
//set path
var path = Path.Combine(folder,
$"{fieldName}_{pupuGroup.Key:X8}_MASK.png");
//save image.
Extended.Save_As_PNG(outTex, path, tilesWidth, tilesHeight);
}
}
Process.Start(folder);
//projectionMatrix = backup;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose() =>
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
public void Draw()
{
//Memory.spriteBatch.GraphicsDevice.Clear(Color.Black);
DrawBackgroundQuads();
DrawWalkMesh();
DrawSpriteBatch();
}
public void Reswizzle()
{
GetTiles.UniquePupuIDs();// make sure each layer has it's own ID.
var fieldName = Module.GetFieldName();
var folder = Module.GetFolder(fieldName, "deswizzle"); //goes from deswizzle folder
if (!Directory.Exists(folder)) return;
IEnumerable files = Directory.EnumerateFiles(folder, "*.png").ToArray();
folder = Module.GetFolder(fieldName, "reswizzle");
var overlap = GetTiles.Select(x => x.TextureID).Distinct().ToDictionary(x => x, x => new HashSet());
var texIDs = new ConcurrentDictionary();
var texIDsPalette = new ConcurrentDictionary();
var width = 0; var height = 0;
//Vector2 origin = tiles.Origin;
var lowest = GetTiles.TopLeft;
var size = new Vector2(GetTiles.Width, GetTiles.Height);//new Point(Math.Abs(lowest.X) + highest.X + Tile.size, Math.Abs(lowest.Y) + highest.Y + Tile.size);
var re = new Regex(@".+_([0-9A-F]{8}).png", RegexOptions.IgnoreCase | RegexOptions.Compiled);
process();
if (overlap.Any(x => x.Value.Count > 1))
process(true);
void process(bool doOverLap = false)
{
foreach (var file in files)
{
//Point highest = new Point(tiles.Max(x => x.X), tiles.Max(x => x.Y));
var match = re.Match(file);
if (match.Groups.Count > 1 && uint.TryParse(match.Groups[1].Value, NumberStyles.HexNumber,
CultureInfo.InvariantCulture, out var pupuID))
{
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var tex = Texture2D.FromStream(Memory.Graphics.GraphicsDevice, fs))
{
var scale = new Vector2(tex.Width, tex.Height) / size;
width = (int)(256 * scale.X);
height = (int)(256 * scale.Y);
var inTex = new TextureBuffer(tex.Width, tex.Height);
inTex.GetData(tex);
foreach (var quad in _quads.Where(x =>
x.GetTile.PupuID == pupuID &&
(!doOverLap || overlap[x.GetTile.TextureID].Contains(x.GetTile.PaletteID))))
{
var tile = (Tile)quad;
texIDs.TryAdd(tile.TextureID, new TextureBuffer(width, height, false));
var src = (new Vector2(Math.Abs(lowest.X) + tile.X, Math.Abs(lowest.Y) + tile.Y) * scale).ToPoint();
var dst = (new Vector2(tile.SourceX, tile.SourceY) * scale).ToPoint();
if (!doOverLap)
{
foreach (var p in from x in Enumerable.Range(0, (int)(Tile.Size * scale.X))
from y in Enumerable.Range(0, (int)(Tile.Size * scale.Y))
orderby y, x
select new Point(x, y))
{
var input = inTex[src.X + p.X, src.Y + p.Y];
var current = texIDs[tile.TextureID][dst.X + p.X, dst.Y + p.Y];
var unscaledLocation = tile.Source.Location;
unscaledLocation.Offset(p.ToVector2() / scale);
var output = ChangeColor(current, input, unscaledLocation, tile.TextureID, overlap);
if (!output.HasValue) break;
if (output.Value.A != 0)
texIDs[tile.TextureID][dst.X + p.X, dst.Y + p.Y] = output.Value;
}
}
else if (overlap[tile.TextureID].Count > 1)
{
var key = new TextureIDPaletteID { PaletteID = tile.PaletteID, TextureID = tile.TextureID };
texIDsPalette.TryAdd(key, new TextureBuffer(width, height));
foreach (var p in from x in Enumerable.Range(0, (int)(Tile.Size * scale.X))
from y in Enumerable.Range(0, (int)(Tile.Size * scale.Y))
select new Point(x, y))
{
var input = inTex[src.X + p.X, src.Y + p.Y];
if (input.A != 0)
{
texIDsPalette[key][dst.X + p.X, dst.Y + p.Y] = inTex[src.X + p.X, src.Y + p.Y];
}
}
}
}
}
}
}
}
//save new reswizzles
foreach (var tid in texIDs)
{
var path = Path.Combine(folder,
$"{fieldName}_{tid.Key}.png");
//save image.
using (var outTex = (Texture2D)tid.Value)
Extended.Save_As_PNG(outTex, path, width, height);
}
foreach (var tid in texIDsPalette)
{
var path = Path.Combine(folder,
$"{fieldName}_{tid.Key.TextureID}_{tid.Key.PaletteID}.png");
//save image.
using (var outTex = (Texture2D)tid.Value)
Extended.Save_As_PNG(outTex, path, width, height);
}
Process.Start(folder);
}
public Tiles TilesUnderMouse() => new Tiles(GetTiles.Where(x =>
x.X < MouseLocation.X && x.X + 16 > MouseLocation.X &&
x.Y < MouseLocation.Y && x.Y + 16 > MouseLocation.Y).ToList());
public void Update()
{
if ((CurrentTime += Memory.ElapsedGameTime) > TotalTime)
{
CurrentTime = TimeSpan.Zero;
foreach (var a in _animations)
{
int i = a.Value.FirstOrDefault(x => x.Enabled)?.AnimationState ?? 0;
int max = a.Value.Max(k => k.AnimationState);
var i1 = i;
a.Value.Where(x => x.AnimationState == i1).ForEach(x => x.Hide());
if (++i >= max)
i = 0;
a.Value.Where(x => x.AnimationState == i).ForEach(x => x.Show());
}
}
float tilesWidth = GetTiles.Width;
float tilesHeight = GetTiles.Height;
if (Module.Toggles.HasFlag(Toggles.Perspective)) //perspective mode shows gabs in the tiles.
{
//finds the min zoom out to fit the entire image in frame.
var half = new Vector2(tilesWidth / 2f, tilesHeight / 2f);
var fieldOfView = MathHelper.ToRadians(70);
float getOppositeSide(float side, float angle)
{
return (float)(Math.Tan(angle) * side);
}
half.X = getOppositeSide(half.X, MathHelper.ToRadians(45));
half.Y = getOppositeSide(half.Y, MathHelper.ToRadians(45));
var minDistanceFromBG = -Math.Max(half.X, half.Y);
if (_camPosition.Z > minDistanceFromBG)
_camPosition.Z = minDistanceFromBG;
_projectionMatrix = Matrix.CreatePerspectiveFieldOfView(fieldOfView, Memory.Graphics.GraphicsDevice.Viewport.AspectRatio, float.Epsilon, 1000f);
_viewMatrix = !Module.Toggles.HasFlag(Toggles.Menu)
? _fpsCamera.Update(ref _camPosition, ref _camTarget, ref Degrees)
: Matrix.CreateLookAt(_camPosition, _camTarget, Vector3.Up);
}
else
{
var vp = Memory.Graphics.GraphicsDevice.Viewport;
var scale = Memory.Scale(tilesWidth, tilesHeight, ScaleMode.FitBoth);
_projectionMatrix = Matrix.CreateOrthographic(vp.Width / scale.X, vp.Height / scale.Y, 0f, 100f);
_viewMatrix = Matrix.CreateLookAt(Vector3.Forward * 10f, Vector3.Zero, Vector3.Up);
}
var ml = InputMouse.Location.ToVector2();
var ml3d = Memory.Graphics.GraphicsDevice.Viewport.Unproject(ml.ToVector3(), _projectionMatrix, _viewMatrix, _worldMatrix);
ml3d.Y *= -1;
MouseLocation = ml3d;
}
protected virtual void Dispose(bool disposing)
{
if (_disposedValue) return;
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
_textureIDs?.ForEach(x => x.Value?.Dispose());
_textureIDsPalettes?.ForEach(x => x.Value?.Dispose());
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposedValue = true;
}
private static Color Blend1(Color baseColor, Color color)
{//ClassicSpriteBatch
var r = new Color
{
R = (byte) MathHelper.Clamp(baseColor.R + color.R, 0, 255),
G = (byte) MathHelper.Clamp(baseColor.G + color.G, 0, 255),
B = (byte) MathHelper.Clamp(baseColor.B + color.B, 0, 255),
A = 0xFF
};
return r;
}
private static Color Blend2(Color baseColor, Color color)
{//ClassicSpriteBatch
var r = new Color
{
R = (byte) MathHelper.Clamp(baseColor.R - color.R, 0, 255),
G = (byte) MathHelper.Clamp(baseColor.G - color.G, 0, 255),
B = (byte) MathHelper.Clamp(baseColor.B - color.B, 0, 255),
A = 0xFF
};
return r;
}
private Color? ChangeColor(Color current, Color input, Point p, byte textureID, IReadOnlyDictionary> overlap)
{
if (input.A == 0) return Color.TransparentBlack;
if (current.A == 0 || current == input)
return input;
var o = (from tile in GetTiles
// ReSharper disable once ImplicitlyCapturedClosure
where tile.TextureID.Equals(textureID) && tile.Source.Contains(p)
select tile.PaletteID).Distinct().ToArray();
if (o.Count() <= 1) return input; // two tiles same palette is drawing to same place
o.ForEach(x => overlap[textureID].Add(x));
return null;
}
private void DrawBackgroundQuad(TileQuadTexture quad, bool forceBlendModeNone = false, Vector2 scale2 = default)
{
VertexPositionTexture[] temp = quad;
var tile = (Tile)quad;
_ate.Texture = quad;
if (scale2 != default)
{
temp = (VertexPositionTexture[])temp.Clone();
var scale = Matrix.CreateScale(scale2.ToVector3());
for (var i = 0; i < temp.Length; i++)
{
var t = temp[i];
t.Position = Vector3.Transform(temp[i].Position, scale);
temp[i] = t;
}
}
DrawBackgroundQuadsSetBendMode(forceBlendModeNone ? BlendMode.None : tile.BlendMode);
foreach (var pass in _ate.CurrentTechnique.Passes)
{
pass.Apply();
Memory.Graphics.GraphicsDevice.DrawUserPrimitives(primitiveType: PrimitiveType.TriangleList,
vertexData: temp, vertexOffset: 0, primitiveCount: 2);
}
}
private void DrawBackgroundQuads()
{
if (!Module.Toggles.HasFlag(Toggles.Quad)) return;
DrawBackgroundQuadsStart();
foreach (var quad in _quads.Where(x => x.Enabled))
{
DrawBackgroundQuad(quad);
}
}
private void DrawBackgroundQuadsSetBendMode(BlendMode bm)
{
_ate.Alpha = 1f;
var half = new Color(.5f, .5f, .5f, 1f);
var quarter = new Color(.25f, .25f, .25f, 1f);
var full = Color.White;
switch (bm)
{
//If we deswizzled and merged the (BlendModes != BlendMode.none) tiles
// we can change SamplerState to Anisotropic.
//But swizzled textures are a Texture Atlas so it will draw bad pixels from near by.
default:
Memory.Graphics.GraphicsDevice.BlendFactor = full;
Memory.Graphics.GraphicsDevice.BlendState = BlendState.AlphaBlend;
//Memory.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
break;
case BlendMode.Add:
Memory.Graphics.GraphicsDevice.BlendFactor = full;
Memory.Graphics.GraphicsDevice.BlendState = Memory.BlendStateAdd;
//Memory.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
break;
case BlendMode.Subtract:
Memory.Graphics.GraphicsDevice.BlendFactor = full;
Memory.Graphics.GraphicsDevice.BlendState = Memory.BlendStateSubtract;
_ate.Alpha = .85f; //doesn't darken so much.
//Memory.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
break;
case BlendMode.HalfAdd:
Memory.Graphics.GraphicsDevice.BlendFactor = half;
Memory.Graphics.GraphicsDevice.BlendState = Memory.BlendStateAddBlendFactor;
//Memory.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
break;
case BlendMode.QuarterAdd:
Memory.Graphics.GraphicsDevice.BlendFactor = quarter;
Memory.Graphics.GraphicsDevice.BlendState = Memory.BlendStateAddBlendFactor;
//Memory.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
break;
}
}
private void DrawBackgroundQuadsStart()
{
Memory.Graphics.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
Memory.Graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
_ate.Projection = _projectionMatrix; _ate.View = _viewMatrix; _ate.World = _worldMatrix;
_effect.Projection = _projectionMatrix; _effect.View = _viewMatrix; _effect.World = _worldMatrix;
Memory.Graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
_effect.TextureEnabled = true;
_ate.VertexColorEnabled = false;
_effect.VertexColorEnabled = false;
}
private void DrawSpriteBatch()
{
if (!Module.Toggles.HasFlag(Toggles.ClassicSpriteBatch)) return;
var drawTextures = GetTexturesReadyToDrawClassicSpriteBatch();
var open = false;
var lastBlendMode = BlendMode.None;
var alpha = 1f;
if (drawTextures != null)
foreach (var kvp in drawTextures)
{
if (!open || lastBlendMode != kvp.Key)
{
if (open)
Memory.SpriteBatchEnd();
open = true;
alpha = 1f;
switch (kvp.Key)
{
default:
Memory.SpriteBatchStartAlpha();
break;
case BlendMode.HalfAdd:
Memory.SpriteBatchStart(bs: Memory.BlendStateAdd, ss: SamplerState.AnisotropicClamp);
break;
case BlendMode.QuarterAdd:
Memory.SpriteBatchStart(bs: Memory.BlendStateAdd, ss: SamplerState.AnisotropicClamp);
break;
case BlendMode.Add:
Memory.SpriteBatchStart(bs: Memory.BlendStateAdd, ss: SamplerState.AnisotropicClamp);
break;
case BlendMode.Subtract:
alpha = .9f;
Memory.SpriteBatchStart(bs: Memory.BlendStateSubtract, ss: SamplerState.AnisotropicClamp);
break;
}
lastBlendMode = kvp.Key;
}
var tex = kvp.Value;
var src = new Rectangle(0, 0, tex.Width, tex.Height);
var dst = src;
dst.Size = (dst.Size.ToVector2() * Memory.Scale(tex.Width, tex.Height, ScaleMode.FitBoth)).ToPoint();
//In game I think we'd keep the field from leaving the screen edge but would center on the Squall and the party when it can.
//I setup scaling after noticing the field didn't size with the screen. I set it to center on screen.
dst.Offset(Memory.Center.X - dst.Center.X, Memory.Center.Y - dst.Center.Y);
Memory.SpriteBatch.Draw(tex, dst, src, Color.White * alpha);
//new Microsoft.Xna.Framework.Rectangle(0, 0, 1280 + (width - 320), 720 + (height - 224)),
//new Microsoft.Xna.Framework.Rectangle(0, 0, tex.Width, tex.Height)
}
if (open)
Memory.SpriteBatchEnd();
}
private void DrawWalkMesh()
{//todo move into walk mesh class. was only because at the time I thought i'd need the background data.
if (!Module.Toggles.HasFlag(Toggles.WalkMesh)) return;
_effect.TextureEnabled = false;
Memory.Graphics.GraphicsDevice.BlendFactor = Color.White;
Memory.Graphics.GraphicsDevice.BlendState = BlendState.Opaque;
Memory.Graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
//using (DepthStencilState depthStencilState = new DepthStencilState() { DepthBufferEnable = true })
using (var rasterizerState = new RasterizerState() { CullMode = CullMode.None })
{
Memory.Graphics.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;//depthStencilState;
Memory.Graphics.GraphicsDevice.RasterizerState = rasterizerState;
_ate.Texture = null;
_ate.VertexColorEnabled = true;
_effect.VertexColorEnabled = true;
//camPosition = Module.Cameras[0].Position;
_effect.World = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up);// Module.Cameras[0].CreateWorld();
var fieldOfView = MathHelper.ToRadians(70);
//effect.View = //Module.Cameras[0].CreateLookAt();
//effect.Projection = Module.Cameras[0].CreateProjection();
_effect.Projection = Matrix.CreatePerspectiveFieldOfView(fieldOfView,
Memory.Graphics.GraphicsDevice.Viewport.AspectRatio, float.Epsilon, 1000f);
_effect.View = !Module.Toggles.HasFlag(Toggles.Menu)
? _fpsCamera.Update(ref _camPosition, ref _camTarget, ref Degrees)
: Matrix.CreateLookAt(_camPosition, _camTarget, Vector3.Up);
foreach (var pass in _effect.CurrentTechnique.Passes)
{
pass.Apply();
Memory.Graphics.GraphicsDevice.DrawUserPrimitives(primitiveType: PrimitiveType.TriangleList,
vertexData: Module.WalkMesh.Vertices.ToArray(), vertexOffset: 0, primitiveCount: Module.WalkMesh.Count);
}
}
}
private void DumpRawTexture(byte[] mim)
{
if (!Memory.EnableDumpingData && !Module.Toggles.HasFlag(Toggles.DumpingData)) return;
MemoryStream ms;
var path = Path.Combine(Module.GetFolder(),
$"{Module.GetFieldName()}_raw_{{0}}bit_{{1}}.png");
using (var br = new BinaryReader(ms = new MemoryStream(mim)))
{
long startPixel = _textureType.PaletteSectionSize;
process(8);
process(4);
process(16);
process(24);
process(24,true);
void process(byte bit, bool alt = false)
{
var adj = bit / 8f;
var textureTypeWidth = bit == 24? _textureType.Width :(int)(_textureType.Width / adj);
var height = checked((int)Math.Ceiling(((float)mim.Length - _textureType.PaletteSectionSize) / _textureType.Width / (bit == 24 ? adj : 1f)));
if (bit == 24 && alt)
{
textureTypeWidth = (int)Math.Ceiling(_textureType.Width / adj);
height *= (int)adj;
}
var buffer = new TextureBuffer(textureTypeWidth, height, false);
foreach (var clut in _cluts)
{
ms.Seek(startPixel, SeekOrigin.Begin);
var i = 0;
byte colorKey = 0;
var lastRow = 0;
while (ms.Position + Math.Ceiling(adj) < ms.Length)
{
var row = i / textureTypeWidth;
Color input;
if (bit == 24) //just to see if anything is there. don't think there is a real usage of 24 bit.
{
if (alt && lastRow != row && row % 3 ==0) i++;
//i += 1;
input = new Color
{
B = br.ReadByte(),
G = br.ReadByte(),
R = br.ReadByte(),
A = 0xFF,
};
}
else if (bit == 16)
{
//i += 1;
input = Texture_Base.ABGR1555toRGBA32bit(br.ReadUInt16());
}
else if (bit == 8)
{
//i = checked((int)(ms.Position - startPixel));
colorKey = br.ReadByte();
input = clut.Value[colorKey];
}
else if (bit == 4)
{
//i++;
if (i % 2 == 0)
{
colorKey = br.ReadByte();
input = clut.Value[colorKey & 0xf];
}
else
{
input = clut.Value[(colorKey & 0xf0) >> 4];
}
}
else throw new ArgumentException($"{nameof(bit)} is {bit}, it may only be 4 or 8.");
if (i < buffer.Count)
buffer[i] = input;
else break;
i++;
lastRow = row;
}
using (var tex = (Texture2D)buffer)
Extended.Save_As_PNG(tex, string.Format(path, bit, $"{clut.Key}{(alt ? "a" : "")}"), textureTypeWidth, height);
if (bit > 8) break;
}
}
}
}
private void FindOverlappingTilesClassicSpriteBatch() => (from t1 in GetTiles
from t2 in GetTiles
where t1.TileID < t2.TileID
where t1.BlendMode == BlendMode.None
where t1.Intersect(t2)
orderby t1.TileID, t2.TileID ascending
select new[] { t1, t2 }
).ForEach(x => x[1].OverLapID = checked((byte)(x[0].OverLapID + 1)));
private static byte GetColorKeyClassicSpriteBatch(IReadOnlyList mim, int textureWidth, int startPixel, int x, int y, bool is8Bit)
{
if (is8Bit)
return mim[startPixel + x + y * textureWidth];
var tempKey = mim[startPixel + x / 2 + y * textureWidth];
if (x % 2 == 1)
return checked((byte)((tempKey & 0xf0) >> 4));
return checked((byte)(tempKey & 0xf));
}
private List> GetTexturesReadyToDrawClassicSpriteBatch() =>
_textures?.OrderByDescending(kvpZ => kvpZ.Key)
.SelectMany(kvpLayerID => kvpLayerID.Value.OrderBy(x => kvpLayerID.Key)
.SelectMany(kvpAnimationID => kvpAnimationID.Value.OrderBy(x => kvpAnimationID.Key))
.SelectMany(kvpAnimationState => kvpAnimationState.Value.OrderBy(x => kvpAnimationState.Key))
.SelectMany(kvpOverlapID => kvpOverlapID.Value.OrderBy(x => kvpOverlapID.Key))
.SelectMany(kvpBlendMode => kvpBlendMode.Value)).ToList();
///
/// Gets the TextureHandler used by the tile.
///
///
/// Texture handler used by tile
private TextureHandler GetTextureUsedByTile(Tile tile)
{
var textureIDsPalette = _textureIDsPalettes
?.FirstOrDefault(x => x.Key.PaletteID == tile.PaletteID && x.Key.TextureID == tile.TextureID).Value;
if (textureIDsPalette != null)
return textureIDsPalette;
var tid = _textureIDs?.FirstOrDefault(x => x.Key == tile.TextureID).Value;
return tid;
}
private void LoadPalettes(byte[] mim)
{
var offset = /*Memory.FieldHolder.FieldID == 76 ? 0 :*/ _textureType?.BytesSkippedPalettes ?? 0;
var cluts = GetTiles != null
? new Cluts(
GetTiles.Select(x => x.PaletteID).Distinct().ToDictionary(x => x, x => new Color[ColorsPerPalette]),
false)
:
new Cluts(
Enumerable.Range(0, 16).Select(x => (byte) x)
.ToDictionary(x => x, x => new Color[ColorsPerPalette]), false);
using (var br = new BinaryReader(new MemoryStream(mim)))
foreach (var clut in cluts)
{
var palettePointer = offset + clut.Key * BytesPerPalette;
br.BaseStream.Seek(palettePointer, SeekOrigin.Begin);
for (var i = 0; i < ColorsPerPalette; i++)
clut.Value[i] = Texture_Base.ABGR1555toRGBA32bit(br.ReadUInt16());
}
_cluts = cluts;
SaveCluts();
}
private void LoadTiles(byte[] map) => GetTiles = map == null ? default : Tiles.Load(map, _textureType.Type);
private int LoadUpscaleBackgrounds(string path)
{
if (Directory.Exists(path))
{
var files = Directory.EnumerateFiles(path, $"*{Module.GetFieldName()}*.png", SearchOption.AllDirectories).OrderBy(x => x.Length).ThenBy(x => x, StringComparer.OrdinalIgnoreCase).ToList();
if (files.Count > 0)
{
_textureIDs = new Dictionary();
var escapedName = Regex.Escape(Module.GetFieldName());
var regex = new Regex(@".+" + escapedName + @"_(\d{1,2})\.png", RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var file in files.Select(x => regex.Match(x)))
{
if (file.Groups.Count <= 1 || !byte.TryParse(file.Groups[1].Value, out var b)) continue;
if (b >= 13) b -= 13;
if (_textureIDs.ContainsKey(b)) continue;
var alt = $"{Module.GetFieldName()}_{b + 13}.png";
_textureIDs.Add(b, TextureHandler.CreateFromPng(File.Exists(alt) ? alt : file.Value, 256, 256, 0, true, true));
}
SaveSwizzled(_textureIDs.ToDictionary(x => x.Key, x => (Texture2D)x.Value));
_textureIDsPalettes = new Dictionary();
var regex2 = new Regex(@".+" + escapedName + @"_(\d{1,2})_(\d{1,2})\.png", RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var file in files.Select(x => regex2.Match(x)))
{
if (file.Groups.Count > 1 && byte.TryParse(file.Groups[1].Value, out var b) && byte.TryParse(file.Groups[2].Value, out var b2))
{
if (b >= 13) b -= 13;
TextureIDPaletteID textureIDPaletteID;
if (!_textureIDsPalettes.ContainsKey(textureIDPaletteID = new TextureIDPaletteID { PaletteID = b2, TextureID = b }))
{
var alt = $"{Module.GetFieldName()}_{b + 13}_{b2}.png";
_textureIDsPalettes.Add(textureIDPaletteID, TextureHandler.CreateFromPng(File.Exists(alt) ? alt : file.Value, 256, 256, b2, true, true));
}
}
foreach (var groups in _textureIDsPalettes.Where(x => _textureIDsPalettes.Count(y => y.Key.TextureID == x.Key.TextureID) > 1).GroupBy(x => x.Key.PaletteID))
foreach (var kvpGroup in groups)
{
var textureIDs =
groups.ToDictionary(x => x.Key.TextureID, x => (Texture2D) x.Value);
SaveSwizzled(textureIDs, $"_{kvpGroup.Key.PaletteID}");
break;
}
}
}
}
var count = (_textureIDs?.Count ?? 0) + (_textureIDsPalettes?.Count ?? 0);
return count;
}
//private void SaveSwizzled(string suf = "") => SaveSwizzled(TextureIDs, suf);
private void OldSaveDeswizzled()
{
if (!Memory.EnableDumpingData && (!Module.Toggles.HasFlag(Toggles.DumpingData) ||
!Module.Toggles.HasFlag(Toggles.ClassicSpriteBatch))) return;
var fieldName = Module.GetFieldName();
var folder = Module.GetFolder(fieldName);
foreach (var kvpZ in _textures)
foreach (var kvpLayer in kvpZ.Value)
foreach (var kvpAnimationID in kvpLayer.Value)
foreach (var kvpAnimationState in kvpAnimationID.Value)
foreach (var kvpOverlapID in kvpAnimationState.Value)
foreach (var kvp in kvpOverlapID.Value)
{
var path = Path.Combine(folder,$"{fieldName}_{kvpZ.Key:D4}.{kvpLayer.Key}.{kvpAnimationID.Key}.{kvpAnimationState.Key}.{kvpOverlapID.Key}.{(int) kvp.Key}.png");
Extended.Save_As_PNG(kvp.Value, path, kvp.Value.Width, kvp.Value.Height);
}
}
private bool ParseBackgroundClassicSpriteBatch(byte[] mim)
{
if (!Module.Toggles.HasFlag(Toggles.ClassicSpriteBatch)) return true;
if (mim == null || (GetTiles?.Count ?? 0) == 0)
return false;
FindOverlappingTilesClassicSpriteBatch();
//FindSameXYTilesSource();
var lowest = new Point(GetTiles.Min(x => x.X), GetTiles.Min(x => x.Y));
var maximum = new Point(GetTiles.Max(x => x.X), GetTiles.Max(x => x.Y));
Height = Math.Abs(lowest.Y) + maximum.Y + Tile.Size; //224
Width = Math.Abs(lowest.X) + maximum.X + Tile.Size; //320
//Color[] finalImage = new Color[height * width]; //ARGB;
//Color[] finalOverlapImage = new Color[height * width];
//tex = new Texture2D(Memory.graphics.GraphicsDevice, width, height);
//texOverlap = new Texture2D(Memory.graphics.GraphicsDevice, width, height);
IEnumerable layers = GetTiles.Select(x => x.LayerID).Distinct().OrderBy(x => x).ToArray();
Debug.WriteLine($"FieldID: {Memory.FieldHolder.FieldID}, Layers: {layers.Count()}, ({string.Join(",", layers)}) ");
var sortedTiles = GetTiles.OrderBy(x => x.OverLapID).ThenByDescending(x => x.Z).ThenBy(x => x.LayerID).ThenBy(x => x.AnimationID).ThenBy(x => x.AnimationState).ThenBy(x => x.BlendMode).ToList();
if (_textures != null)
{
foreach (var tex in GetTexturesReadyToDrawClassicSpriteBatch().Select(x => x.Value))
tex.Dispose();
}
_textures = new ConcurrentDictionary>>>>>();
const ushort z = 0;
byte layerID = 0;
byte animationID = 0;
byte animationState = 0;
byte overlapID = 0;
var blendMode = BlendMode.None;
TextureBuffer textureBuffer = null;
var hasColor = false;
var dictLayerID = _textures.GetOrAdd(z, new ConcurrentDictionary>>>>());
void convertColorToTexture2d()
{
if (!hasColor || textureBuffer == null) return;
hasColor = false;
var dictAnimationID =
dictLayerID.GetOrAdd(layerID,
new ConcurrentDictionary>>>());
var
dictAnimationState = dictAnimationID.GetOrAdd(animationID,
new ConcurrentDictionary>>());
var dictOverlapID =
dictAnimationState.GetOrAdd(animationState,
new ConcurrentDictionary>());
var dictBlend =
dictOverlapID.GetOrAdd(overlapID, new ConcurrentDictionary());
var tex = dictBlend.GetOrAdd(blendMode, new Texture2D(Memory.Graphics.GraphicsDevice, Width, Height));
textureBuffer.SetData(tex);
}
for (var i = 0; i < sortedTiles.Count; i++)
{
var previousTile = i > 0 ? sortedTiles[i - 1] : null;
var tile = sortedTiles[i];
if (textureBuffer == null || previousTile == null || previousTile.Z != tile.Z ||
previousTile.LayerID != tile.LayerID || previousTile.BlendMode != tile.BlendMode ||
previousTile.AnimationID != tile.AnimationID ||
previousTile.AnimationState != tile.AnimationState || previousTile.OverLapID != tile.OverLapID)
{
convertColorToTexture2d();
textureBuffer = new TextureBuffer(Width, Height, false);
layerID = tile.LayerID;
blendMode = tile.BlendMode;
animationID = tile.AnimationID;
animationState = tile.AnimationState;
overlapID = tile.OverLapID;
}
var palettePointer = _textureType.BytesSkippedPalettes + tile.PaletteID * BytesPerPalette;
var sourceImagePointer = BytesPerPalette * _textureType.Palettes;
const int textureWidth = 128;
var startPixel = sourceImagePointer + tile.SourceX + textureWidth * tile.TextureID + _textureType.Width * tile.SourceY;
var real = new Point(Math.Abs(lowest.X) + tile.X, Math.Abs(lowest.Y) + tile.Y);
var realDestinationPixel = real.Y * Width + real.X;
if (tile.Is4Bit)
{
startPixel -= tile.SourceX / 2;
}
for (var y = 0; y < Tile.Size; y++)
for (var x = 0; x < Tile.Size; x++)
{
var colorKey = GetColorKeyClassicSpriteBatch(mim, _textureType.Width, startPixel, x, y, tile.Is8Bit);
var color16Bit = BitConverter.ToUInt16(mim, 2 * colorKey + palettePointer);
if (color16Bit == 0) // 0 is Color.TransparentBlack So we skip it.
continue;
var color = Texture_Base.ABGR1555toRGBA32bit(color16Bit);
var pos = realDestinationPixel + x + y * Width;
var bufferedColor = textureBuffer[pos];
if (blendMode < BlendMode.None)
{
if (color == Color.Black)
continue;
if (blendMode == BlendMode.Subtract)
{
if (bufferedColor.A != 0)
color = Blend2(bufferedColor, color);
}
else
{
switch (blendMode)
{
case BlendMode.QuarterAdd:
color = Color.Multiply(color, .25f);
break;
case BlendMode.HalfAdd:
color = Color.Multiply(color, .5f);
break;
case BlendMode.Add:
break;
case BlendMode.Subtract:
break;
case BlendMode.None:
break;
default:
throw new ArgumentOutOfRangeException();
}
if (bufferedColor.A != 0)
color = Blend1(bufferedColor, color);
}
}
else if (bufferedColor.A != 0)
{
throw new Exception("Color is already set something may be wrong.");
}
color.A = 0xFF;
textureBuffer[pos] = color;
hasColor = true;
}
}
convertColorToTexture2d(); // gets leftover colors from last batch and makes a texture.
OldSaveDeswizzled();
return true;
}
///
/// Create a swizzeled Textures with one palette.
/// Few exceptions where tiles conflict and need separate files.
///
/// Image Data .mim file
///
private bool ParseBackgroundQuads(byte[] mim)
{
if (mim == null || (GetTiles?.Count ?? 0) == 0)
return false;
var path = Path.Combine(Memory.FF8Dir, "textures");
var count = LoadUpscaleBackgrounds(path);
if (count <= 0)
{
var uniqueSetOfTileData = GetTiles.Where(x => x.Draw).Select(x => new
{
x.TextureID, x.BlendMode, loc = new Point(x.SourceX, x.SourceY), x.Depth, x.PaletteID, x.AnimationID
}).Distinct().ToList().AsReadOnly();
Width = uniqueSetOfTileData.Max(x => x.loc.X) + Tile.Size;
Height = uniqueSetOfTileData.Max(x => x.loc.Y) + Tile.Size;
IReadOnlyDictionary textureIDs = uniqueSetOfTileData.Select(x => x.TextureID)
.Distinct()
.ToDictionary(x => x,
x => Memory.Graphics != null ? new Texture2D(Memory.Graphics.GraphicsDevice, 256, 256) : null);
IReadOnlyDictionary> overlap = GetTiles.Select(x => x.TextureID).Distinct()
.ToDictionary(x => x, x => new HashSet());
using (var br = new BinaryReader(new MemoryStream(mim)))
{
foreach (var kvp in textureIDs)
{
GenTexture(kvp.Key, kvp.Value);
}
SaveSwizzled(textureIDs);
var fieldName = Module.GetFieldName();
_textureIDs = textureIDs.ToDictionary(x => x.Key,
x => TextureHandler.Create($"{fieldName}_{x.Key}", new Texture2DWrapper(x.Value),
ushort.MaxValue));
if (overlap.Any(x => x.Value.Count > 1))
{
IReadOnlyDictionary textureIDsPalettes = uniqueSetOfTileData
.Where(x => overlap[x.TextureID].Contains(x.PaletteID))
.Select(x => new TextureIDPaletteID {TextureID = x.TextureID, PaletteID = x.PaletteID})
.Distinct().ToDictionary(x => x,
x => new Texture2D(Memory.Graphics.GraphicsDevice, 256, 256));
_textureIDsPalettes = textureIDsPalettes.ToDictionary(x => x.Key,
x => TextureHandler.Create($"{fieldName}_{x.Key.TextureID}", new Texture2DWrapper(x.Value),
x.Key.PaletteID));
foreach (var kvp in textureIDsPalettes)
{
GenTexture(kvp.Key.TextureID, kvp.Value, kvp.Key.PaletteID);
}
foreach (var groups in
textureIDsPalettes
.Where(x => textureIDsPalettes.Count(y => y.Key.TextureID == x.Key.TextureID) > 1)
.GroupBy(x => x.Key.PaletteID))
foreach (var kvpGroup in groups)
{
textureIDs =
groups.ToDictionary(x => x.Key.TextureID, x => x.Value);
SaveSwizzled(textureIDs, $"_{kvpGroup.Key.PaletteID}");
break;
}
}
void GenTexture(byte texID, Texture2D tex2d, byte? inPaletteID = null)
{
if (tex2d == null) return;
var tex = new TextureBuffer(tex2d.Width, tex2d.Height, false);
foreach (var tile in uniqueSetOfTileData.Where(x =>
x.TextureID == texID && (!inPaletteID.HasValue || inPaletteID.Value == x.PaletteID)))
{
var is4Bit = Tile.Test4Bit(tile.Depth);
var is8Bit = Tile.Test8Bit(tile.Depth);
var is16Bit = Tile.Test16Bit(tile.Depth);
byte colorKey = 0;
foreach (var p in from x in Enumerable.Range(0, Tile.Size)
from y in Enumerable.Range(0, Tile.Size)
orderby y, x
select new Point(x, y))
{
(var trueX, var trueY) = (tile.loc.X+p.X, tile.loc.Y + p.Y);
var offsetX = is16Bit
? trueX * 2
: trueX / (is4Bit ? 2 : 1);
var texturePageOffset = TexturePageWidth * tile.TextureID;
var offsetY = _textureType.Width * trueY ;
var offset = _textureType.PaletteSectionSize +
offsetX +
texturePageOffset +
offsetY;
br.BaseStream.Seek(offset, SeekOrigin.Begin);
var point = new Point(p.X + tile.loc.X, p.Y + tile.loc.Y);
var paletteID = tile.PaletteID;
Color input = default;
if (is8Bit)
{
input = _cluts[paletteID][br.ReadByte()];
}
else if (is16Bit)
{
input = Texture_Base.ABGR1555toRGBA32bit(br.ReadUInt16());
}
else if (is4Bit)
{
if (p.X % 2 == 0)
{
colorKey = br.ReadByte();
input = _cluts[paletteID][colorKey & 0xf];
}
else
{
input = _cluts[paletteID][(colorKey & 0xf0) >> 4];
}
}
if (!inPaletteID.HasValue)
// forcing a palette happens post overlap test. So shouldn't need to rerun test.
{
var current = tex[point.X, point.Y];
var output = ChangeColor(current, input, point, tile.TextureID, overlap);
if (!output.HasValue) break;
if (output.Value.A != 0)
tex[point.X, point.Y] = output.Value;
}
else
{
if (input.A != 0)
tex[point.X, point.Y] = input;
}
}
}
tex.SetData(tex2d);
}
}
}
else
{
Debug.WriteLine($"Loaded {count} Textures from {path}");
}
//the sort here should be the default draw order. May need changed.
_quads = GetTiles.Select(x => new TileQuadTexture(x, GetTextureUsedByTile(x), 1f)).Where(x => x.Enabled)
.OrderByDescending(x => x.GetTile.Z)
.ThenByDescending(x => x.GetTile.TileID)
.ThenBy(x => x.GetTile.LayerID)
.ThenBy(x => x.GetTile.AnimationID)
.ThenBy(x => x.GetTile.AnimationState)
.ThenByDescending(x => x.GetTile.BlendMode).ToList();
_animations = _quads.Where(x => x.AnimationID != 0xFF).Select(x => x.AnimationID).Distinct().ToDictionary(x => x, x => _quads.Where(y => y.AnimationID == x).OrderBy(y => y.AnimationState).ToList());
_animations.ForEach(x => x.Value.Where(y => y.AnimationState != 0).ForEach(w => w.Hide()));
TotalTime = TimeSpan.FromMilliseconds(1000f / 10f);
CurrentTime = TimeSpan.Zero;
return true;
}
private void SaveCluts()
{
if (Memory.EnableDumpingData || Module.Toggles.HasFlag(Toggles.DumpingData))
{
var path = Path.Combine(Module.GetFolder(),
$"{Module.GetFieldName()}_Clut.png");
_cluts.Save(path);
}
}
private static void SaveSwizzled(IReadOnlyDictionary textureIDs, string suf = "")
{
if (!Memory.EnableDumpingData && !Module.Toggles.HasFlag(Toggles.DumpingData)) return;
var fieldName = Module.GetFieldName();
var folder = Module.GetFolder(fieldName);
foreach (var kvp in textureIDs)
{
var path = Path.Combine(folder,
$"{fieldName}_{kvp.Key}{suf}.png");
if (File.Exists(path))
continue;
Extended.Save_As_PNG(kvp.Value, path, kvp.Value.Width, kvp.Value.Height);
}
}
#endregion Methods
// TODO: uncomment the following line if the finalizer is overridden above.// GC.SuppressFinalize(this);
}
}