mirror of
https://github.com/MaddyThorson/StrawberryBF.git
synced 2024-11-25 16:18:56 +08:00
Some aseprite loading work. Still need to solve decompression
This commit is contained in:
parent
524bb14919
commit
aed77272ac
|
@ -1,8 +1,7 @@
|
|||
FileVersion = 1
|
||||
Dependencies = {corlib = "*", SDL2 = "*"}
|
||||
Dependencies = {corlib = "*", SDL2 = "*", MiniZ = "*"}
|
||||
|
||||
[Project]
|
||||
Name = "Strawberry"
|
||||
TargetType = "BeefLib"
|
||||
StartupObject = "Program"
|
||||
DefaultNamespace = "Strawberry"
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace Strawberry
|
|||
|
||||
public class Game
|
||||
{
|
||||
public readonly Dictionary<String, Sprite> Sprites;
|
||||
public readonly List<VirtualInput> VirtualInputs;
|
||||
public readonly String Title;
|
||||
public readonly int Width;
|
||||
|
@ -42,7 +43,6 @@ namespace Strawberry
|
|||
: base()
|
||||
{
|
||||
Game = this;
|
||||
VirtualInputs = new List<VirtualInput>();
|
||||
|
||||
Title = windowTitle;
|
||||
Width = width;
|
||||
|
@ -81,8 +81,13 @@ namespace Strawberry
|
|||
SDLMixer.OpenAudio(44100, SDLMixer.MIX_DEFAULT_FORMAT, 2, 4096);
|
||||
SDLTTF.Init();
|
||||
|
||||
BuildTypeLists();
|
||||
VirtualInputs = new List<VirtualInput>();
|
||||
Input.[Friend]Init(gamepadLimit);
|
||||
|
||||
Sprites = new Dictionary<String, Sprite>();
|
||||
//LoadSprites();
|
||||
|
||||
BuildTypeLists();
|
||||
}
|
||||
|
||||
public ~this()
|
||||
|
@ -104,6 +109,8 @@ namespace Strawberry
|
|||
|
||||
DisposeTypeLists();
|
||||
Input.[Friend]Dispose();
|
||||
DisposeSprites();
|
||||
Sprite.[Friend]Dispose();
|
||||
Game = null;
|
||||
}
|
||||
|
||||
|
@ -184,7 +191,7 @@ namespace Strawberry
|
|||
}
|
||||
}
|
||||
|
||||
public void Render()
|
||||
private void Render()
|
||||
{
|
||||
SDL.SetRenderDrawColor(Renderer, ClearColor.R, ClearColor.G, ClearColor.B, ClearColor.A);
|
||||
SDL.RenderClear(Renderer);
|
||||
|
@ -214,6 +221,51 @@ namespace Strawberry
|
|||
}
|
||||
}
|
||||
|
||||
// Load Images
|
||||
|
||||
private void LoadSprites()
|
||||
{
|
||||
let root = scope String(ContentRoot);
|
||||
root.Append(Path.DirectorySeparatorChar);
|
||||
root.Append("Sprites");
|
||||
if (Directory.Exists(root))
|
||||
LoadSpritesDir(root);
|
||||
else
|
||||
Console.WriteLine("Content/Sprites folder does not exist!");
|
||||
}
|
||||
|
||||
private void LoadSpritesDir(String directory)
|
||||
{
|
||||
for (let dir in Directory.EnumerateDirectories(directory))
|
||||
{
|
||||
let path = scope String();
|
||||
dir.GetFilePath(path);
|
||||
LoadSpritesDir(path);
|
||||
}
|
||||
|
||||
for (let file in Directory.EnumerateFiles(directory, "*.ase"))
|
||||
{
|
||||
let path = scope String();
|
||||
file.GetFilePath(path);
|
||||
|
||||
let sprite = new [Friend]Sprite(path);
|
||||
|
||||
path.Remove(0, ContentRoot.Length + 8);
|
||||
Sprites.Add(new String(path), sprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeSprites()
|
||||
{
|
||||
for (let kv in Sprites)
|
||||
{
|
||||
delete kv.key;
|
||||
delete kv.value;
|
||||
}
|
||||
|
||||
delete Sprites;
|
||||
}
|
||||
|
||||
// Type assignable caching
|
||||
|
||||
private void BuildTypeLists()
|
||||
|
|
604
src/Core/Sprite.bf
Normal file
604
src/Core/Sprite.bf
Normal file
|
@ -0,0 +1,604 @@
|
|||
using System;
|
||||
using SDL2;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using MiniZ;
|
||||
|
||||
namespace Strawberry
|
||||
{
|
||||
public class Sprite
|
||||
{
|
||||
private enum Modes
|
||||
{
|
||||
Indexed = 1,
|
||||
Grayscale = 2,
|
||||
RGBA = 4,
|
||||
}
|
||||
|
||||
private enum Chunks
|
||||
{
|
||||
OldPaletteA = 0x0004,
|
||||
OldPaletteB = 0x0011,
|
||||
Layer = 0x2004,
|
||||
Cel = 0x2005,
|
||||
CelExtra = 0x2006,
|
||||
Mask = 0x2016,
|
||||
Path = 0x2017,
|
||||
FrameTags = 0x2018,
|
||||
Palette = 0x2019,
|
||||
UserData = 0x2020,
|
||||
Slice = 0x2022
|
||||
}
|
||||
|
||||
public readonly String Path;
|
||||
|
||||
private Frame[] frames;
|
||||
private List<Layer> layers;
|
||||
private List<Tag> tags;
|
||||
private List<Slice> slices;
|
||||
private int width;
|
||||
private int height;
|
||||
private Modes mode;
|
||||
|
||||
private this(String path)
|
||||
{
|
||||
Path = new String(path);
|
||||
Load();
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
delete Path;
|
||||
Unload();
|
||||
}
|
||||
|
||||
private void Unload()
|
||||
{
|
||||
for (let f in frames)
|
||||
delete f;
|
||||
delete frames;
|
||||
|
||||
for (let l in layers)
|
||||
delete l;
|
||||
delete layers;
|
||||
|
||||
for (let t in tags)
|
||||
delete t;
|
||||
delete tags;
|
||||
|
||||
for (let s in slices)
|
||||
delete s;
|
||||
delete slices;
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
Unload();
|
||||
Load();
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
/*
|
||||
Aseprite file loading based on code from Noel Berry's Foster Framework here:
|
||||
https://github.com/NoelFB/Foster/blob/master/Framework/Graphics/Images/Aseprite.cs
|
||||
*/
|
||||
|
||||
let stream = scope FileStream();
|
||||
stream.Open(Path, .Read, .Read);
|
||||
|
||||
//Helpers to match ASE file format spec
|
||||
uint8 BYTE() => stream.Read<uint8>();
|
||||
uint16 WORD() => stream.Read<uint16>();
|
||||
int16 SHORT() => stream.Read<int16>();
|
||||
uint32 DWORD() => stream.Read<uint32>();
|
||||
int32 LONG() => stream.Read<int32>();
|
||||
void SEEK(int bytes) => stream.Position += bytes;
|
||||
void BYTES(uint8[] into, int bytes)
|
||||
{
|
||||
for (let i < bytes)
|
||||
into[i] = BYTE();
|
||||
}
|
||||
String STRING(String into)
|
||||
{
|
||||
let len = WORD();
|
||||
let arr = scope uint8[len];
|
||||
for (let i < len)
|
||||
arr[i] = BYTE();
|
||||
|
||||
Encoding.UTF8.DecodeToUTF8(arr, into);
|
||||
return into;
|
||||
}
|
||||
|
||||
//Parse
|
||||
{
|
||||
//Header
|
||||
{
|
||||
//File Size
|
||||
DWORD();
|
||||
|
||||
// Magic number
|
||||
var magic = WORD();
|
||||
if (magic != 0xA5E0)
|
||||
Runtime.FatalError("File is not in .ase format");
|
||||
|
||||
// Frame Count / Width / Height / Color Mode
|
||||
frames = new Frame[WORD()];
|
||||
width = WORD();
|
||||
height = WORD();
|
||||
mode = (Modes)(WORD() / 8);
|
||||
|
||||
// Other Info, Ignored
|
||||
DWORD(); // Flags
|
||||
WORD(); // Speed (deprecated)
|
||||
DWORD(); // Set be 0
|
||||
DWORD(); // Set be 0
|
||||
BYTE(); // Palette entry
|
||||
SEEK(3); // Ignore these bytes
|
||||
WORD(); // Number of colors (0 means 256 for old sprites)
|
||||
BYTE(); // Pixel width
|
||||
BYTE(); // Pixel height
|
||||
SEEK(92); // For Future
|
||||
}
|
||||
|
||||
layers = new List<Layer>();
|
||||
tags = new List<Tag>();
|
||||
slices = new List<Slice>();
|
||||
|
||||
// Body
|
||||
{
|
||||
var temp = scope:: uint8[width * height * (int)mode];
|
||||
let palette = scope:: Color[256];
|
||||
HasUserData last = null;
|
||||
|
||||
for (int i = 0; i < frames.Count; i++)
|
||||
{
|
||||
let frame = new Frame(width, height);
|
||||
frames[i] = frame;
|
||||
|
||||
int64 frameStart, frameEnd;
|
||||
int chunkCount;
|
||||
|
||||
// frame header
|
||||
{
|
||||
frameStart = stream.Position;
|
||||
frameEnd = frameStart + DWORD();
|
||||
WORD(); // Magic number (always 0xF1FA)
|
||||
chunkCount = WORD(); // Number of "chunks" in this frame
|
||||
frame.Duration = WORD(); // Frame duration (in milliseconds)
|
||||
SEEK(6); // For future (set to zero)
|
||||
}
|
||||
|
||||
// chunks
|
||||
for (int j = 0; j < chunkCount; j++)
|
||||
{
|
||||
int64 chunkStart, chunkEnd;
|
||||
Chunks chunkType;
|
||||
|
||||
// chunk header
|
||||
{
|
||||
chunkStart = stream.Position;
|
||||
chunkEnd = chunkStart + DWORD();
|
||||
chunkType = (Chunks)WORD();
|
||||
}
|
||||
|
||||
// LAYER CHUNK
|
||||
if (chunkType == Chunks.Layer)
|
||||
{
|
||||
// create layer
|
||||
var layer = new Layer();
|
||||
|
||||
// get layer data
|
||||
layer.Flag = (Layer.Flags)WORD();
|
||||
layer.Type = (Layer.Types)WORD();
|
||||
layer.ChildLevel = WORD();
|
||||
WORD(); // width (unused)
|
||||
WORD(); // height (unused)
|
||||
layer.BlendMode = WORD();
|
||||
layer.Alpha = (BYTE() / 255f);
|
||||
SEEK(3); // for future
|
||||
STRING(layer.Name);
|
||||
|
||||
layers.Add(layer);
|
||||
}
|
||||
// CEL CHUNK
|
||||
else if (chunkType == Chunks.Cel) Cel:
|
||||
{
|
||||
var layer = layers[WORD()];
|
||||
var x = SHORT();
|
||||
var y = SHORT();
|
||||
var alpha = BYTE() / 255f;
|
||||
var celType = WORD();
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
Color[] pixels = null;
|
||||
Cel link = null;
|
||||
|
||||
SEEK(7);
|
||||
|
||||
// RAW or DEFLATE
|
||||
if (celType == 0 || celType == 2)
|
||||
{
|
||||
width = WORD();
|
||||
height = WORD();
|
||||
|
||||
var count = width * height * (int)mode;
|
||||
if (count > temp.Count)
|
||||
temp = scope:: uint8[count];
|
||||
|
||||
// RAW
|
||||
if (celType == 0)
|
||||
{
|
||||
BYTES(temp, count);
|
||||
}
|
||||
// DEFLATE
|
||||
else
|
||||
{
|
||||
//TODO: Figure this out to enable aseprite loading
|
||||
|
||||
Runtime.FatalError("Decompression not yet implemented.");
|
||||
|
||||
/*
|
||||
Noel's C#:
|
||||
SEEK(2);
|
||||
|
||||
using var deflate = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true);
|
||||
deflate.Read(temp, 0, count);
|
||||
*/
|
||||
}
|
||||
|
||||
// get pixel data
|
||||
pixels = scope:Cel Color[width * height];
|
||||
BytesToPixels(temp, pixels, mode, palette);
|
||||
}
|
||||
// REFERENCE
|
||||
else if (celType == 1)
|
||||
{
|
||||
var linkFrame = frames[WORD()];
|
||||
var linkCel = linkFrame.Cels[frame.Cels.Count];
|
||||
|
||||
width = linkCel.Width;
|
||||
height = linkCel.Height;
|
||||
pixels = linkCel.Pixels;
|
||||
link = linkCel;
|
||||
}
|
||||
else
|
||||
Runtime.FatalError("Cel type not yet implemented");
|
||||
|
||||
var cel = new Cel(layer, pixels);
|
||||
cel.X = x;
|
||||
cel.Y = y;
|
||||
cel.Width = width;
|
||||
cel.Height = height;
|
||||
cel.Alpha = alpha;
|
||||
cel.Link = link;
|
||||
|
||||
// draw to frame if visible
|
||||
if (cel.Layer.Visible)
|
||||
CelToFrame(frame, cel);
|
||||
|
||||
frame.Cels.Add(cel);
|
||||
}
|
||||
// PALETTE CHUNK
|
||||
else if (chunkType == Chunks.Palette)
|
||||
{
|
||||
DWORD(); //size (unused)
|
||||
var start = DWORD();
|
||||
var end = DWORD();
|
||||
SEEK(8); // for future
|
||||
|
||||
for (int p = 0; p < (end - start) + 1; p++)
|
||||
{
|
||||
var hasName = WORD();
|
||||
palette[start + p] = Color(BYTE(), BYTE(), BYTE(), BYTE());
|
||||
|
||||
if (Calc.BitCheck(hasName, 0))
|
||||
STRING(scope String());
|
||||
}
|
||||
}
|
||||
// USERDATA
|
||||
else if (chunkType == Chunks.UserData)
|
||||
{
|
||||
if (last != null)
|
||||
{
|
||||
var flags = (int)DWORD();
|
||||
|
||||
// has text
|
||||
if (Calc.BitCheck(flags, 0))
|
||||
STRING(last.UserDataText);
|
||||
|
||||
// has color
|
||||
if (Calc.BitCheck(flags, 1))
|
||||
last.UserDataColor = Color(BYTE(), BYTE(), BYTE(), BYTE());
|
||||
}
|
||||
}
|
||||
// TAG
|
||||
else if (chunkType == Chunks.FrameTags)
|
||||
{
|
||||
var count = WORD();
|
||||
SEEK(8);
|
||||
|
||||
for (int t = 0; t < count; t++)
|
||||
{
|
||||
var tag = new Tag();
|
||||
tag.From = WORD();
|
||||
tag.To = WORD();
|
||||
tag.LoopDirection = (Tag.LoopDirections)BYTE();
|
||||
SEEK(8);
|
||||
tag.Color = Color(BYTE(), BYTE(), BYTE(), (uint8)255);
|
||||
SEEK(1);
|
||||
STRING(tag.Name);
|
||||
tags.Add(tag);
|
||||
}
|
||||
}
|
||||
// SLICE
|
||||
else if (chunkType == Chunks.Slice)
|
||||
{
|
||||
var count = DWORD();
|
||||
var flags = (int)DWORD();
|
||||
DWORD(); // reserved
|
||||
var name = STRING(scope String());
|
||||
|
||||
for (int s = 0; s < count; s++)
|
||||
{
|
||||
var slice = new Slice();
|
||||
slice.Name.Set(name);
|
||||
slice.Frame = (int)DWORD();
|
||||
slice.OriginX = (int)LONG();
|
||||
slice.OriginY = (int)LONG();
|
||||
slice.Width = (int)DWORD();
|
||||
slice.Height = (int)DWORD();
|
||||
|
||||
// 9 slice (ignored atm)
|
||||
if (Calc.BitCheck(flags, 0))
|
||||
{
|
||||
slice.NineSlice = Rect(
|
||||
(int)LONG(),
|
||||
(int)LONG(),
|
||||
(int)DWORD(),
|
||||
(int)DWORD());
|
||||
}
|
||||
|
||||
// pivot point
|
||||
if (Calc.BitCheck(flags, 1))
|
||||
slice.Pivot = Point((int)DWORD(), (int)DWORD());
|
||||
|
||||
slices.Add(slice);
|
||||
}
|
||||
}
|
||||
|
||||
stream.Position = chunkEnd;
|
||||
}
|
||||
|
||||
stream.Position = frameEnd;
|
||||
frame.FinishLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
private void BytesToPixels(uint8[] bytes, Color[] pixels, Modes mode, Color[] palette)
|
||||
{
|
||||
int len = pixels.Count;
|
||||
if (mode == Modes.RGBA)
|
||||
{
|
||||
for (int p = 0, int b = 0; p < len; p++, b += 4)
|
||||
{
|
||||
pixels[p].R = (uint8)(bytes[b + 0] * bytes[b + 3] / 255);
|
||||
pixels[p].G = (uint8)(bytes[b + 1] * bytes[b + 3] / 255);
|
||||
pixels[p].B = (uint8)(bytes[b + 2] * bytes[b + 3] / 255);
|
||||
pixels[p].A = bytes[b + 3];
|
||||
}
|
||||
}
|
||||
else if (mode == Modes.Grayscale)
|
||||
{
|
||||
for (int p = 0, int b = 0; p < len; p++, b += 2)
|
||||
{
|
||||
pixels[p].R = pixels[p].G = pixels[p].B = (uint8)(bytes[b + 0] * bytes[b + 1] / 255);
|
||||
pixels[p].A = bytes[b + 1];
|
||||
}
|
||||
}
|
||||
else if (mode == Modes.Indexed)
|
||||
{
|
||||
for (int p = 0; p < len; p++)
|
||||
pixels[p] = palette[bytes[p]];
|
||||
}
|
||||
}
|
||||
|
||||
private void CelToFrame(Frame frame, Cel cel)
|
||||
{
|
||||
let opacity = (uint8)((cel.Alpha * cel.Layer.Alpha) * 255);
|
||||
var pxLen = frame.PixelsLength;
|
||||
|
||||
var blend = BlendModes[0];
|
||||
if (cel.Layer.BlendMode < BlendModes.Count)
|
||||
blend = BlendModes[cel.Layer.BlendMode];
|
||||
|
||||
for (int sx = Math.Max(0, -cel.X), int right = Math.Min(cel.Width, width - cel.X); sx < right; sx++)
|
||||
{
|
||||
int dx = cel.X + sx;
|
||||
int dy = cel.Y * width;
|
||||
|
||||
for (int sy = Math.Max(0, -cel.Y), int bottom = Math.Min(cel.Height, height - cel.Y); sy < bottom; sy++, dy += width)
|
||||
{
|
||||
if (dx + dy >= 0 && dx + dy < pxLen)
|
||||
blend(frame.Pixels, (dx + dy) * 4, cel.Pixels[sx + sy * cel.Width], opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data
|
||||
|
||||
private class Frame
|
||||
{
|
||||
public SDL.Texture* Texture;
|
||||
public float Duration;
|
||||
public List<Cel> Cels = new List<Cel>() ~ delete _;
|
||||
public uint8* Pixels;
|
||||
public int32 PixelsLength;
|
||||
|
||||
public this(int w, int h)
|
||||
{
|
||||
Texture = SDL.CreateTexture(Game.Renderer, (uint32)SDL.PIXELFORMAT_ARGB8888, (int32)SDL.TextureAccess.Static, (int32)w, (int32)h);
|
||||
|
||||
void* ptr;
|
||||
SDL.LockTexture(Texture, null, out ptr, out PixelsLength);
|
||||
Pixels = (uint8*)ptr;
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
SDL.DestroyTexture(Texture);
|
||||
}
|
||||
|
||||
public void FinishLoad()
|
||||
{
|
||||
SDL.UnlockTexture(Texture);
|
||||
Pixels = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Tag
|
||||
{
|
||||
public enum LoopDirections
|
||||
{
|
||||
Forward = 0,
|
||||
Reverse = 1,
|
||||
PingPong = 2
|
||||
}
|
||||
|
||||
public String Name = new String() ~ delete _;
|
||||
public LoopDirections LoopDirection;
|
||||
public int From;
|
||||
public int To;
|
||||
public Color Color;
|
||||
}
|
||||
|
||||
public class HasUserData
|
||||
{
|
||||
public String UserDataText = new String("") ~ delete _;
|
||||
public Color UserDataColor;
|
||||
}
|
||||
|
||||
private class Slice : HasUserData
|
||||
{
|
||||
public int Frame;
|
||||
public String Name = new String() ~ delete _;
|
||||
public int OriginX;
|
||||
public int OriginY;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public Point? Pivot;
|
||||
public Rect? NineSlice;
|
||||
}
|
||||
|
||||
private class Cel : HasUserData
|
||||
{
|
||||
public Layer Layer;
|
||||
public Color[] Pixels;
|
||||
public Cel Link;
|
||||
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public float Alpha;
|
||||
|
||||
public this(Layer layer, Color[] pixels)
|
||||
{
|
||||
Layer = layer;
|
||||
Pixels = pixels;
|
||||
}
|
||||
}
|
||||
|
||||
private class Layer : HasUserData
|
||||
{
|
||||
public enum Flags
|
||||
{
|
||||
Visible = 1,
|
||||
Editable = 2,
|
||||
LockMovement = 4,
|
||||
Background = 8,
|
||||
PreferLinkedCels = 16,
|
||||
Collapsed = 32,
|
||||
Reference = 64
|
||||
}
|
||||
|
||||
public enum Types
|
||||
{
|
||||
Normal = 0,
|
||||
Group = 1
|
||||
}
|
||||
|
||||
public Flags Flag;
|
||||
public Types Type;
|
||||
public String Name = new String() ~ delete _;
|
||||
public int ChildLevel;
|
||||
public int BlendMode;
|
||||
public float Alpha;
|
||||
public bool Visible { get { return Flag.HasFlag(Flags.Visible); } }
|
||||
}
|
||||
|
||||
// Blend Modes
|
||||
|
||||
// More or less copied from Aseprite's source code:
|
||||
// https://github.com/aseprite/aseprite/blob/master/src/doc/blend_funcs.cpp
|
||||
|
||||
private delegate void Blend(uint8* dest, int index, Color src, uint8 opacity);
|
||||
|
||||
private static void Dispose()
|
||||
{
|
||||
for (let b in BlendModes)
|
||||
delete b;
|
||||
delete BlendModes;
|
||||
}
|
||||
|
||||
private static readonly Blend[] BlendModes = new Blend[]
|
||||
{
|
||||
// 0 - NORMAL
|
||||
new (dest, index, src, opacity) =>
|
||||
{
|
||||
if (src.A != 0)
|
||||
{
|
||||
uint8 a = dest[index];
|
||||
uint8 r = dest[index + 1];
|
||||
uint8 g = dest[index + 2];
|
||||
uint8 b = dest[index + 3];
|
||||
|
||||
if (dest[index] == 0)
|
||||
{
|
||||
a = src.A;
|
||||
r = src.R;
|
||||
g = src.G;
|
||||
b = src.B;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sa = MUL_UN8(src.A, opacity);
|
||||
var ra = a + sa - MUL_UN8(a, sa);
|
||||
|
||||
a = (uint8)ra;
|
||||
r = (uint8)(r + (src.R - r) * sa / ra);
|
||||
g = (uint8)(g + (src.G - g) * sa / ra);
|
||||
b = (uint8)(b + (src.B - b) * sa / ra);
|
||||
}
|
||||
|
||||
dest[index] = a;
|
||||
dest[index + 1] = r;
|
||||
dest[index + 2] = g;
|
||||
dest[index + 3] = b;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[Inline]
|
||||
private static int MUL_UN8(int a, int b)
|
||||
{
|
||||
var t = (a * b) + 0x80;
|
||||
return (((t >> 8) + t) >> 8);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,12 @@ namespace Strawberry
|
|||
{
|
||||
static public class Calc
|
||||
{
|
||||
[Inline]
|
||||
static public bool BitCheck(int bits, int pos)
|
||||
{
|
||||
return (bits & (1 << pos)) != 0;
|
||||
}
|
||||
|
||||
//Move toward a target value without crossing it
|
||||
[Inline]
|
||||
static public float Approach(float value, float target, float maxDelta)
|
||||
|
|
Loading…
Reference in New Issue
Block a user