'use strict';

//-----------------------------------------------------------------------------
/**
 * Serve como objeto base para todas as funções disponíveis aqui.
 * Esta classe não deve ser instanciada.
 *
 * @class Ion
 */
class Ion {
	
	/**
	 * Atualização principal.
	 *
	 * @method Update
	 */
    static Update() {
        Ion.Input.Update();
        for (let i = 0; i < this.Main.invokeList.length; i++) {
            if (this.Main.invokeList[i].isDone()) {
                this.Main.invokeList[i] = null;
                this.Main.invokeIdentifier[i] = null;
                this.Main.invokeList = Ion.Array.Compact(this.Main.invokeList);
                this.Main.invokeIdentifier = Ion.Array.Compact(this.Main.invokeIdentifier);
            } else {
                this.Main.invokeList[i].Update();
            }
        }
        for (let i = 0; i < this.Main.invokeRepeatingList.length; i++) {
            this.Main.invokeRepeatingList[i].Update();
        }
        if (Ion.Input.GetButtonDown("RightArrow")) {
        	console.log("Hey there!");
        }
    }
	
	/**
	 * Invoca a função desejada em determinados segundos.
	 *
	 * @method Ion.Invoke
	 * @param {Object} mainObject O objeto que possui a função.
	 * @param {String} identifier String usada para identificar a função.
	 * @param {Function} func A função que será chamada.
	 * @param {Number} time O tempo em segundos que levará para chamar a função.
	 * @param {Array} args Os argumentos necessários da função.
	 */
    static Invoke(mainObject, identifier, func, time, args) {
        this.Main.invokeIdentifier.push(identifier);
        this.Main.invokeList.push(new Ion.InvokeClass(mainObject, func, time, args));
    }
	
	/**
	 * Invoca a função desejada em determinados segundos, e
	 * então continua a invocando após cada tempo de repetição.
	 *
	 * @method Ion.InvokeRepeating
	 * @param {Object} mainObject O objeto que possui a função.
	 * @param {String} identifier String usada para identificar a função.
	 * @param {Function} func A função que será chamada.
	 * @param {Number} time O tempo em segundos que levará para chamar a função.
	 * @param {Number} repeat O tempo em segundos entre cada repetição.
	 * @param {Array} args Os argumentos necessários da função.
	 */
    static InvokeRepeating(mainObject, identifier, func, time, repeat, args) {
        this.Main.invokeRepeatingIdentifier.push(identifier);
        this.Main.invokeRepeatingList.push(new Ion.InvokeRepeatingClass(mainObject, func, time, repeat, args));
    }

    /**
	 * Cancela a invocação de uma função.
	 *
	 * @method Ion.CancelInvoke
	 * @param {String} identifier A string que foi usada como identificador da função.
	 */
    static CancelInvoke(identifier) {
        let ind = this.Main.invokeIdentifier.indexOf(identifier);
        if (ind === null || ind === undefined) return;
        this.Main.invokeList[ind] = null;
        this.Main.invokeIdentifier[ind] = null;
        this.Main.invokeList = Ion.Array.Compact(this.Main.invokeList);
        this.Main.invokeIdentifier = Ion.Array.Compact(this.Main.invokeIdentifier);
    }

    /**
	 * Cancela a invocação de uma função que está sendo invocada repetidamente.
	 *
	 * @method Ion.CancelInvoking
	 * @param {String} identifier A string que foi usada como identificador da função.
	 */
    static CancelInvoking(identifier) {
        let ind = this.Main.invokeRepeatingIdentifier.indexOf(identifier);
        if (ind === null || ind === undefined) return;
        this.Main.invokeRepeatingList[ind] = null;
        this.Main.invokeRepeatingIdentifier[ind] = null;
        this.Main.invokeRepeatingList = Ion.Array.Compact(this.Main.invokeRepeatingList);
        this.Main.invokeRepeatingIdentifier = Ion.Array.Compact(this.Main.invokeRepeatingIdentifier);
    }

    /**
	 * Cancela todas as invocações.
	 *
	 * @method Ion.CancelInvokeAll
	 */
    static CancelInvokeAll() {
        this.Main.invokeList = [];
        this.Main.invokeIdentifier = [];
        this.Main.invokeRepeatingList = [];
        this.Main.invokeRepeatingIdentifier = [];
    }

}

//-----------------------------------------------------------------------------
/**
 * Esta classe serve para guardar funções úteis para arrays.
 * Esta classe não deve ser instanciada.
 *
 * @class Ion.Array
 */
Ion.Array = class {
	
	/**
	 * Obtém uma cópia da array com todos os elementos null ou
	 * undefined removidos.
	 *
	 * @method Ion.Array.Compact
	 * @param {Array} array A array requerida.
	 * @return {Array} A array modificada.
	 */
    static Compact(array) {
        let res = [];
        for (let i = 0; i < array.length; i++) {
            if (array[i] === null || array[i] === undefined) { continue }
            res.push(array[i]);
        }
        return res;
    }

    /**
	 * Obtém uma array com apenas os elementos de um
	 * determinado range de outra array.
	 *
	 * @method Ion.Array.Range
	 * @param {Array} array A array requerida.
	 * @param {Number} s O index inicial.
	 * @param {Number} e O index final.
	 * @return {Array} A array com os elementos.
	 */
    static Range(array, s, e) {
        let res = [];
        for (let i = s; i < e + 1; i++) {
            res.push(array[i]);
        }
        return res;
    }

}

//-----------------------------------------------------------------------------
/**
 * Esta classe serve para guardar funções úteis para números.
 * Esta classe não deve ser instanciada.
 *
 * @class Ion.Number
 */
Ion.Number = class {

	/**
	 * Checa se um número está dentro de um certo limite.
	 *
	 * @method Ion.Number.Between
	 * @param {Number} value O número que será checado.
	 * @param {Number} min O limite mínimo.
	 * @param {Number} max O limite máximo.
	 * @return {Bool} true ou false.
	 */
    static Between(value, min, max) {
        return value > min && value < max;
    }

}

//-----------------------------------------------------------------------------
/**
 * Este objeto serve para guardar propriedades internas do Ion.
 *
 * @object Ion.Main
 */
Ion.Main = {
    invokeList: [],
    invokeIdentifier: [],
    invokeRepeatingList: [],
    invokeRepeatingIdentifier: []
}

//-----------------------------------------------------------------------------
/**
 * Esta classe serve para guardar informações sobre os eventos do DOM.
 * Esta classe não deve ser instanciada.
 *
 * @class Ion.Events
 */
Ion.Events = class {
	
	// Mouse Events
	static OnMouseMove(event) {}
	static OnClick(event) {}
	static OnContextMenu(event) {}
	static OnDoubleClick(event) {}
	static OnMouseDown(event) {}
	static OnMouseEnter(event) {}
	static OnMouseLeave(event) {}
	static OnMouseOver(event) {}
	static OnMouseOut(event) {}
	static OnMouseUp(event) {}

	// Keyboard Events
	static OnKeyDown(event) {
		Ion.Input._state[event.keyCode] = true;
        Ion.Input._events[event.keyCode] = event;
        Ion.Input._any = true;
	}
	static OnKeyUp(event) {
		Ion.Input._state[event.keyCode] = false;
        Ion.Input._events[event.keyCode] = null;
        Ion.Input._any = false;
	}

	// Other Events
	static OnBlur() {
		Ion.Input.Reset();
	}
}

//-----------------------------------------------------------------------------
/**
 * Esta é a classe responsável pelo invocamento de uma função.
 * Ela foi feita para ser usada na função Ion.Invoke, e o instanciamento
 * externo não é recomendado.
 *
 * @class Ion.InvokeClass
 */
Ion.InvokeClass = class {

	/**
	 * Inicialização da classe.
	 *
	 * @constructor
	 * @param {Object} obj O objeto que possui a função a ser invocada.
	 * @param {Function} func A função a ser invocada.
	 * @param {Number} time O tempo em segundos para invocar a função.
	 * @param {Array} args Os argumentos da função.
	 */
    constructor(obj, func, time, args) {
        this._obj = obj;
        this._func = func;
        this._args = args;
        this._time = time;
        this._done = false;
    }

    /**
	 * Atualização principal.
	 *
	 * @method Update
	 */
    Update() {
        if (this.isDone()) return;
        let dif = Ion.TimeDifference.SecToMin;
        this._time = Ion.Mathf.MoveTowards(this._time, 0, dif);
        if (this.canInvoke()) {
            this._func.apply(this._obj, this._args);
            this._done = true;
        }
    }

    /**
	 * Indica se a função pode ser invocada.
	 *
	 * @method canInvoke
	 * @return {Bool} true ou false.
	 */
    canInvoke() {
        return this._time <= 0;
    }

    /**
	 * Indica se a função já foi invocada.
	 *
	 * @method isDone
	 * @return {Bool} true ou false.
	 */
    isDone() {
        return this._done == true;
    }
}

//-----------------------------------------------------------------------------
/**
 * Esta é a classe responsável pelo invocamento de uma função repetidamente.
 * Ela foi feita para ser usada na função Ion.InvokeRepeating, e o instanciamento
 * externo não é recomendado.
 *
 * @class Ion.InvokeRepeatingClass
 */
Ion.InvokeRepeatingClass = class {

	/**
	 * Inicialização da classe.
	 *
	 * @constructor
	 * @param {Object} obj O objeto que possui a função a ser invocada.
	 * @param {Function} func A função a ser invocada.
	 * @param {Number} time O tempo em segundos para invocar a função.
	 * @param {Number} repeat O tempo em segundos entre cada repetição.
	 * @param {Array} args Os argumentos da função.
	 */
    constructor(obj, func, time, repeat, args) {
        this._obj = obj;
        this._func = func;
        this._time = time;
        this._maxRepeat = repeat;
        this._repeatCount = 0;
        this._args = args;
    }

    /**
	 * Atualização principal.
	 *
	 * @method Update
	 */
    Update() {
        let dif = Ion.TimeDifference.SecToMin;
        this._time = Ion.Mathf.MoveTowards(this._time, 0, dif);
        if (this.canStartInvoking() && this._repeatCount <= 0) {
            this._func.apply(this._obj, this._args);
            this._repeatCount = this._maxRepeat;
        }
        if (this._repeatCount > 0) {
            this._repeatCount = Ion.Mathf.MoveTowards(this._repeatCount, 0, dif);
        }
    }

    /**
	 * Indica se é possível iniciar o invocamento da função.
	 *
	 * @method canStartInvoking
	 * @return {Bool} true ou false.
	 */
    canStartInvoking() {
        return this._time <= 0;
    }
}

//-----------------------------------------------------------------------------
/**
 * Um objeto que guarda informações sobre a diferença de tempo.
 *
 * @object Ion.TimeDifference
 */
Ion.TimeDifference = {
    McToMs: 0.001,
    MsToSec: 0.001,
    SecToMin: 0.016667,
    MinToHour: 0.016667,
    HourToDay: 0.041667,
    DayToWeek: 0.142857,
    WeekToYear: 0.019165
}

//-----------------------------------------------------------------------------
/**
 * Uma coleção de funções matemáticas comuns.
 * Esta classe não deve ser instanciada.
 *
 * @class Ion.InvokeRepeatingClass
 */
Ion.Mathf = class {

	/**
	 * Constante de conversão de degrees para radians.
	 *
	 * @get Ion.Mathf.Deg2Rad
	 * @return {Number} Número de conversão.
	 */
    static get Deg2Rad() {
        return 0.0174532924;
    }

    /**
	 * Constante de conversão de radians para degrees.
	 *
	 * @get Ion.Mathf.Rad2Deg
	 * @return {Number} Número de conversão.
	 */
    static get Rad2Deg() {
        return 57.29578;
    }

    /**
	 * Um float bem pequeno.
	 * O menor valor que um float pode ter diferente de zero.
	 * Com as seguintes regras:
	 * - anyValue + Epsilon = anyValue
	 * - anyValue - Epsilon = anyValue
	 * - 0 + Epsilon = Epsilon
	 * - 0 - Epsilon = -Epsilon
	 *
	 * @get Ion.Mathf.Epsilon
	 * @return {Number} Epsilon.
	 */
    static get Epsilon() {
        return Number.EPSILON;
    }

    /**
	 * Uma representação de infinito positivo.
	 * @get Ion.Mathf.Infinity
	 * @return {Number} Infinity.
	 */
    static get Infinity() {
        return Number.POSITIVE_INFINITY;
    }

    /**
	 * Uma representação de infinito negativo.
	 * @get Ion.Mathf.NegativeInfinity
	 * @return {Number} -Infinity.
	 */
    static get NegativeInfinity() {
        return Number.NEGATIVE_INFINITY;
    }

    /**
	 * Compara dois floats e retorna true se eles forem similares.
	 *
	 * A imprecisão de floats faz com que comparações de igualdade
	 * usando == seja imprecisa. Por exemplo, (1.0 == 10.0 / 10.0) pode
	 * não retornar true sempre. Approximately compara dois floats
	 * e retorna true se eles estiverem dentro de um pequeno valor
	 * (Epsilon) de cada um.
	 *
	 * @method Ion.Mathf.Approximately
	 * @param {Number} a O primeiro float.
	 * @param {Number} b O segundo float.
	 * @return {Bool} true ou false.
	 */
    static Approximately(a, b) {
        return Math.abs(b - a) < Math.max(10 - 6.0 * Math.max(Math.abs(a), Math.abs(b)), Number.EPSILON * 8.0);
    }

    /**
	 * Prende um valor entre um número mínimo e um número máximo.
	 *
	 * @method Ion.Mathf.Clamp
	 * @param {Number} value O valor a ser restringido.
	 * @param {Number} min O limite mínimo.
	 * @param {Number} max O limite máximo.
	 * @return {Number} O valor restringido.
	 */
    static Clamp(value, min, max) {
        return value < min ? min : value > max ? max : value;
    }

    /**
	 * Prende um valor entre 0 e 1.
	 *
	 * @method Ion.Mathf.Clamp01
	 * @param {Number} value O valor a ser restringido.
	 * @return {Number} O valor restringido.
	 */
    static Clamp01(value) {
        return value < 0 ? 0 : value > 1 ? 1 : value;
    }

    /**
	 * Obtém a potência mais próxima de dois.
	 *
	 * @method Ion.Mathf.ClosestPowerOfTwo
	 * @param {Number} value O valor necessário.
	 * @return {Number} A potência mais próxima de dois.
	 */
    static ClosestPowerOfTwo(int) {
        let i = Math.floor(int);
        if (i <= 0) return 0;
        let exp = Math.log2(i);
        let hp = Math.pow(2, Math.ceil(exp));
        let lp = Math.pow(2, Math.floor(exp));
        return (hp - i) <= (i - lp) ? hp : lp;
    }

    /**
	 * Converte uma temperatura de cor em Kelvin para uma cor RGB.
	 * Dada uma temperatura de cor (em Kelvin), estima-se o equivalente
	 * em RGB.
	 *
	 * A temperatura de cor expressa a aparência de cor da luz emitida
	 * pela fonte de luz.
	 * Essa definição está baseada na relação entre a temperatura de um
	 * material hipotético e padronizado, conhecido como "corpo negro radiador",
	 * e a distribuição de energia da luz emitida à medida que a temperatura
	 * do corpo negro é elevada a partir do zero absoluto. - Wikipedia
	 *
	 * A temperatura será limitada entre 1000 e 40000 graus.
	 *
	 * @method Ion.Mathf.CorrelatedColorTemperatureToRGB
	 * @param {Number} kelvin A temperatura em Kelvin.
	 * @return {Ion.Color} A temperatura de cor em rgb.
	 */
    static CorrelatedColorTemperatureToRGB(kelvin) {
        kelvin = this.Clamp(kelvin, 1000, 40000) / 100;
        let r = 0;
        let g = 0;
        let b = 0;
        let calc = 0;
        if (kelvin <= 66) {
            r = 255;
        } else {
            calc = kelvin - 60;
            calc = 329.698727446 * (Math.pow(calc, -0.1332047592));
            r = this.Clamp(Math.floor(calc), 0, 255);
        }
        if (kelvin <= 66) {
            calc = kelvin;
            calc = 99.4708025861 * Math.log(calc) - 161.1195681661;
            g = this.Clamp(Math.floor(calc), 0, 255);
        } else {
            calc = kelvin - 60;
            calc = 288.1221695283 * (Math.pow(calc), -0.0755148492);
            g = this.Clamp(Math.floor(calc), 0, 255);
        }
        if (kelvin >= 66) {
            b = 255;
        } else if (kelvin <= 19) {
            b = 0;
        } else {
            calc = kelvin - 10;
            calc = 138.5177312231 * Math.log(calc) - 305.0447927307;
            b = this.Clamp(Math.floor(calc), 0, 255);
        }
        return new Ion.Color(r, g, b);
    }

    /**
	 * Repete o valor t, para que ele nunca seja maior que length,
	 * e nunca seja menor que 0.
	 *
	 * Isso é similar ao módulo, mas ele funciona com floats.
	 * Por exemplo, usando 3.0 para t e 2.5 para length, o
	 * resultado seria 0. Note que o comportamento não é
	 * definido para números negativos como o módulo.
	 *
	 * @method Ion.Mathf.Repeat
	 * @param {Number} t O valor que será repetido.
	 * @param {Number} length O limite.
	 * @return {Number} O resultado.
	 */
    static Repeat(t, length) {
        return t - Math.floor(t / length) * length;
    }

    /**
	 * Repete o valor t, para que ele nunca seja maior que length,
	 * e nunca seja menor que 0.
	 *
	 * O resultado será movido para frente e para trás entre 0 e length;
	 *
	 * @method Ion.Mathf.PingPong
	 * @param {Number} t O valor que será repetido.
	 * @param {Number} length O limite.
	 * @return {Number} O resultado.
	 */
    static PingPong(t, length) {
    	t = Ion.Mathf.Repeat(t, length * 2.0);
    	return length - Math.abs(t - length);
    }

    /**
	 * Interpola linearmente entre a e b por t.
	 * O parâmetro t é limitado entre 0 e 1.
	 *
	 * Quando t = 0 retorna a.
	 * Quando t = 1 retorna b.
	 * Quando t = 0.5 retorna o meio entre a e b.
	 *
	 * @method Ion.Mathf.Lerp
	 * @param {Number} a O valor inicial.
	 * @param {Number} b O valor final.
	 * @param {Number} t A interpolação entre os dois números.
	 * @return {Number} O resultado.
	 */
    static Lerp(a, b, t) {
        return a + (b - a) * this.Clamp01(t);
    }

    /**
	 * Interpola linearmente entre a e b por t sem limite em t.
	 *
	 * O parâmetro t não é limitado, e um valor baseado em a e b
	 * é suportado. Se t for menor que zero, ou maior que um, então
	 * LerpUnclamped irá resultar em um valor fora do limite entre
	 * a e b.
	 * Suponhamos que â = 0.33, e b = 1.5. Se t = -0.25 então o
	 * valor retornado seria 0.0375.
	 *
	 * @method Ion.Mathf.LerpUnclamped
	 * @param {Number} a O valor inicial.
	 * @param {Number} b O valor final.
	 * @param {Number} t A interpolação entre os dois números.
	 * @return {Number} O resultado.
	 */
    static LerpUnclamped(a, b, t) {
        return a + (b - a) * t;
    }

    /**
	 * Calcula o parâmetro linear t que produz o valor interpolante
	 * dentro do limite entre a e b.
	 *
	 * @method Ion.Mathf.InverseLerp
	 * @param {Number} a O valor inicial.
	 * @param {Number} b O valor final.
	 * @param {Number} value O valor de interpolação.
	 * @return {Number} O resultado.
	 */
    static InverseLerp(a, b, value) {
    	let result = 0;
    	if (a != b) {
    		result = Mathf.Clamp01((value - a) / (b - a));
    	}
    	return result;
    }

    /**
	 * O mesmo que Lerp, mas garante que os valores interpolem
	 * corretamente quando eles se envolvem em torno de 360 graus.
	 * O parâmetro t é limitado entre 0 e 1.
	 *
	 * @method Ion.Mathf.LerpAngle
	 * @param {Number} a O valor inicial.
	 * @param {Number} b O valor final.
	 * @param {Number} t A interpolação entre os dois números.
	 * @return {Number} O resultado.
	 */
    static LerpAngle(a, b, t) {
        let num = this.Repeat(b - a, 360.0);
        if (num > 180.0) num -= 360.0;
        return a + num * this.Clamp01(t);
    }

    /**
	 * Obtém o sinal de f.
	 * Retorna 1 quando f for positivo ou zero, -1 quando f for negativo.
	 *
	 * @method Ion.Mathf.Sign
	 * @param {Number} f O número.
	 * @return {Number} O resultado.
	 */
    static Sign(f) {
        return f < 0 ? -1 : 1;
    }

    /**
	 * Calcula a menor diferença entre dois ângulos em degrees.
	 *
	 * @method Ion.Mathf.DeltaAngle
	 * @param {Number} current O ângulo atual.
	 * @param {Number} target O ângulo alvo.
	 * @return {Number} O resultado.
	 */
    static DeltaAngle(current, target) {
        let num = this.Repeat(target - current, 360.0);
        if (num > 180.0) num -= 360.0;
        return num;
    }

    /**
	 * Move o valor current até o valor target.
	 *
	 * Isso é essencialmente o mesmo que Lerp, mas ao invés ele garante
	 * que a velocidade nunca exceda maxDelta.
	 * Valores negativos afastam current de target.
	 *
	 * @method Ion.Mathf.MoveTowards
	 * @param {Number} current O valor atual.
	 * @param {Number} target O valor para qual ele deve se mover.
	 * @param {Number} maxDelta A maior mudança que pode ser aplicada no valor.
	 * @return {Number} O resultado.
	 */
    static MoveTowards(current, target, maxDelta) {
        let result = 0;
        if (Math.abs(target - current) <= maxDelta) {
            result = target;
        } else {
            result = current + this.Sign(target - current) * maxDelta;
        }
        return result;
    }

    /**
	 * O mesmo que MoveTowards, mas faz com que os valores interpolem
	 * corretamente quando eles se envolvem em torno de 360 graus.
	 *
	 * Assume-se que as variáveis current e target estejam em degrees.
	 * Por razões de otimização, valores negativos de maxDelta não são
	 * suportados e podem causar oscilações. Para mover current pra longe
	 * de target, adicione 180 para o ângulo.
	 *
	 * @method Ion.Mathf.MoveTowardsAngle
	 * @param {Number} current O ângulo atual.
	 * @param {Number} target O ângulo para qual ele deve se mover.
	 * @param {Number} maxDelta A maior mudança que pode ser aplicada no valor.
	 * @return {Number} O resultado.
	 */
    static MoveTowardsAngle(current, target, maxDelta) {
        let num = this.DeltaAngle(current, target);
        let result = 0;
        if (-maxDelta < num && num < maxDelta) {
            result = target;
        } else {
            target = current + num;
            result = this.MoveTowards(current, target, maxDelta);
        }
        return result;
    }

    /**
	 * Interpola entre min e max com suavidade nos limites.
	 *
	 * Esta função interpola entre min e max de maneira similar que Lerp.
	 * Porém, a interpolação irá aumentar sua velocidade gradualmente do
	 * início e irá diminuir no final. Isso é útil para criar animações
	 * naturais, fadings e outras transições.
	 *
	 * @method Ion.Mathf.SmoothStep
	 * @param {Number} from O valor que será movido.
	 * @param {Number} to Para onde o valor deve se mover.
	 * @param {Number} t A interpolação aplicada entre os valores.
	 * @return {Number} O resultado.
	 */
    static SmoothStep(from, to, t) {
        t = this.Clamp01(t);
        t = -2.0 * t * t * t + 3.0 * t * t;
        return to * t + from * (1.0 - t);
    }
    
	/**
	 * Move um valor em uma curva Bezier com um ponto de controle.
	 *
	 * @method Ion.Mathf.Bezier2
	 * @param {Number} start O ponto inicial da curva.
	 * @param {Number} control O ponto de controle da curva.
	 * @param {Number} end O ponto final da curva.
	 * @param {Number} t O ponto de interpolação na curva.
	 * @return {Number} O resultado.
	 */
    static Bezier2(start, control, end, t) {
    	return (((1-t)*(1-t))*start)+(2*t*(1-t)*control)+((t*t)*end);
    }
    
	/**
	 * Move um valor em uma curva Bezier com dois pontos de controle.
	 *
	 * @method Ion.Mathf.Bezier3
	 * @param {Number} start O ponto inicial da curva.
	 * @param {Number} control1 O primeiro ponto de controle da curva.
	 * @param {Number} control2 O segundo ponto de controle da curva.
	 * @param {Number} end O ponto final da curva.
	 * @param {Number} t O ponto de interpolação na curva.
	 * @return {Number} O resultado.
	 */
    static Bezier3(start, control1, control2, end, t) {
    	return (((-start+3*(start-control2)+end)*t+(3*(start+control2)-6*start))*t+3*(control1-start))*t+start;
    }

}

//-----------------------------------------------------------------------------
/**
 * Representação de vectors e pontos 2D.
 *
 * @class Ion.Vector
 */
Ion.Vector = class {

	/**
	 * Inicialização da classe.
	 *
	 * @constructor
	 * @param {Number} x O ponto horizontal do vector.
	 * @param {Number} y O ponto vertical do vector.
	 */
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    
	/**
	 * Indica se esse vector possui os mesmos pontos que outro vector.
	 *
	 * @method Equals
	 * @param {Ion.Vector} vector O outro vector a ser checado.
	 * @return {Bool} true ou false.
	 */
    Equals(vector) {
        return vector.x == this.x && vector.y == this.y;
    }
    
	/**
	 * Faz com que esse vector tenha uma magnitude de 1.
	 *
	 * @method Normalize
	 */
    Normalize() {
        if (this.x == 0 || this.y == 0) return this;
        let len = this.Magnitude();
        this.x /= len;
        this.y /= len;
        return this;
    }
    
	/**
	 * Obtém a magnitude deste vector.
	 *
	 * @method Magnitude
	 * @return {Number} a magnitude.
	 */
    Magnitude() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    
	/**
	 * Obtém a magnitude ao quadrado deste vector.
	 * Calcular a magnitude ao quadrado ao invés da magnitude
	 * é muito mais rápido.
	 * Às vezes quando se está comparando a magnitude de dois vectors,
	 * você pode simplesmente comparar suas magnitudes ao quadrado.
	 *
	 * @method SqrMagnitude
	 * @return {Number} A magnitude.
	 */
    SqrMagnitude() {
        return this.x * this.x + this.y * this.y;
    }
    
    /**
     * Obtém este vector com uma magnitude de 1.
     *
     * @method Normalized
     * @return {Number} A magnitude.
     */
    Normalized() {
        let result = new Ion.Vector(this.x, this.y);
        result.Normalize();
        return result;
    }
    
    /**
     * Multiplica os componentes deste vector pelos componentes de outro.
     *
     * @method Scale
     * @param {Ion.Vector} scale O outro vector.
     */
    Scale(scale) {
        this.x *= scale.x;
        this.y *= scale.y;
    }
    
    /**
     * Ajusta os componentes x e y deste vector de uma só vez.
     *
     * @method Set
     * @param {Number} x O novo x.
     * @param {Number} y O novo y.
     */
    Set(x, y) {
        this.x = x;
        this.y = y;
    }
    
	/**
     * Obtém uma boa formatação deste vector no formato string.
     *
     * @method ToString
     * @return {String} Este vector em string.
     */
    ToString() {
        return '(' + String(this.x) + ', ' + String(this.y) + ')';
    }
    
    /**
     * Retorna este vector com seus componentes subtraídos por
     * um número ou pelos componentes de outro vector.
     *
     * @method Minus
     * @param {Number || Ion.Vector} v O valor de subtração.
     * @return {Ion.Vector} Este vector.
     */
    Minus(v) {
        if (v instanceof Ion.Vector) {
            return new Ion.Vector(this.x - v.x, this.y - v.y);
        } else {
            return new Ion.Vector(this.x - v, this.y - v);
        }
    }
    
    /**
     * Retorna este vector com seus componentes somados por
     * um número ou pelos componentes de outro vector.
     *
     * @method Plus
     * @param {Number || Ion.Vector} v O valor da soma.
     * @return {Ion.Vector} Este vector.
     */
    Plus(v) {
        if (v instanceof Ion.Vector) {
            return new Ion.Vector(this.x + v.x, this.y + v.y);
        } else {
            return new Ion.Vector(this.x + v, this.y + v);
        }
    }
    
	/**
     * Retorna este vector com seus componentes multiplicados por
     * um número ou pelos componentes de outro vector.
     *
     * @method Multiplied
     * @param {Number || Ion.Vector} v O valor de multiplicação.
     * @return {Ion.Vector} Este vector.
     */
    Multiplied(v) {
        if (v instanceof Ion.Vector) {
            return new Ion.Vector(this.x * v.x, this.y * v.y);
        } else {
            return new Ion.Vector(this.x * v, this.y * v);
        }
    }
    
    /**
     * Retorna este vector com seus componentes divididos por
     * um número ou pelos componentes de outro vector.
     *
     * @method Divided
     * @param {Number || Ion.Vector} v O valor de divisão.
     * @return {Ion.Vector} Este vector.
     */
    Divided(v) {
        if (v instanceof Ion.Vector) {
            return new Ion.Vector(this.x / v.x, this.y / v.y);
        } else {
            return new Ion.Vector(this.x / v, this.y / v);
        }
    }
    
    /**
     * Obtém este vector com seus componentes negativos.
     *
     * @get Neg
     * @return {Ion.Vector} Este vector negativo.
     */
    get Neg() {
        return new Ion.Vector(-this.x, -this.y);
    }
    
	/**
     * Retorna uma cópia deste vector.
     *
     * @method Clone
     * @return {Ion.Vector} Uma cópia deste vector.
     */
    Clone() {
        return new Ion.Vector(this.x, this.y);
    }

    /**
     * Obtém um vector apontado para cima.
     *
     * @get Ion.Vector.Up
     * @return {Ion.Vector} O resultado.
     */
    static get Up() {
        return new Ion.Vector(0, -1);
    }

    /**
     * Obtém um vector apontado para a esquerda.
     *
     * @get Ion.Vector.Left
     * @return {Ion.Vector} O resultado.
     */
    static get Left() {
        return new Ion.Vector(-1, 0);
    }

    /**
     * Obtém um vector apontado para a direita.
     *
     * @get Ion.Vector.Right
     * @return {Ion.Vector} O resultado.
     */
    static get Right() {
        return new Ion.Vector(1, 0);
    }

    /**
     * Obtém um vector apontado para baixo.
     *
     * @get Ion.Vector.Down
     * @return {Ion.Vector} O resultado.
     */
    static get Down() {
        return new Ion.Vector(0, 1);
    }

    /**
     * Um jeito de se obter um Ion.Vector(1, 1).
     *
     * @get Ion.Vector.One
     * @return {Ion.Vector} O resultado.
     */
    static get One() {
        return new Ion.Vector(1, 1);
    }

    /**
     * Um jeito de se obter um Ion.Vector(0, 0).
     *
     * @get Ion.Vector.Zero
     * @return {Ion.Vector} O resultado.
     */
    static get Zero() {
        return new Ion.Vector(0, 0);
    }

    /**
     * Obtém o ângulo em degrees entre from e to.
     *
     * @method Ion.Vector.Angle
     * @param {Ion.Vector} from O vector inicial.
     * @param {Ion.Vector} to O vector de destino.
     * @return {Number} O resultado.
     */
    static Angle(from, to) {
        return Math.acos(Ion.Mathf.Clamp(Ion.Vector.Dot(from.Normalized(), to.Normalized()), -1, 1)) * 57.29578;
    }

    /**
     * Obtém uma cópia de um vector com sua magnitude limitada a maxLength.
     *
     * @method Ion.Vector.ClampMagnitude
     * @param {Ion.Vector} vector O vector requerido.
     * @param {Number} maxLength A magnitude máxima.
     * @return {Ion.Vector} O resultado.
     */
    static ClampMagnitude(vector, maxLength) {
        let result;
        if (vector.SqrMagnitude() > maxLength * maxLength) {
            result = vector.Normalized() * maxLength;
        } else {
            result = vector;
        }
        return result;
    }

    /**
     * Obtém a distância entre os vectors a e b.
     *
     * @method Ion.Vector.Distance
     * @param {Ion.Vector} a O primeiro vector.
     * @param {Ion.Vector} b O segundo vector.
     * @return {Number} O resultado.
     */
    static Distance(a, b) {
        return (a.Minus(b)).Magnitude();
    }

    /**
     * Move o vector current até o vector target.
     * É essencialmente o mesmo que Ion.Vector.Lerp, mas garante
     * que a velocidade nunca ultrapasse maxDistanceDelta.
     *
     * @method Ion.Vector.MoveTowards
     * @param {Ion.Vector} current O vector que será movido.
     * @param {Ion.Vector} target O vector de destino.
     * @param {Number} maxDistanceDelta A velocidade máxima.
     */
    static MoveTowards(current, target, maxDistanceDelta) {
        let a = target.Minus(current);
        let magnitude = a.Magnitude();
        let result;
        if (magnitude <= maxDistanceDelta || magnitude == 0) {
            result = target;
        } else {
            result = a.Divided(magnitude).Multiplied(maxDistanceDelta).Plus(current);
        }
        return result;
    }

    /**
     * Interpola linearmente entre os vectors a e b por t.
     * O parâmetro t é limitado entre 0 e 1.
     *
     * @method Ion.Vector.Lerp
     * @param {Ion.Vector} a O vector inicial.
     * @param {Ion.Vector} b O vector final.
     * @param {Number} t O valor da interpolação.
     * @return {Ion.Vector} O vector resultante.
     */
    static Lerp(a, b, t) {
        t = Ion.Mathf.Clamp01(t);
        return new Ion.Vector(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
    }

    /**
     * Interpola linearmente entre os vectors a e b por t sem limite em t.
     *
     * @method Ion.Vector.LerpUnclamped
     * @param {Ion.Vector} a O vector inicial.
     * @param {Ion.Vector} b O vector final.
     * @param {Number} t O valor da interpolação.
     * @return {Ion.Vector} O vector resultante.
     */
    static LerpUnclamped(a, b, t) {
        return new Ion.Vector(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
    }

    /**
     * Reflete um vector de um vector definido por um normal.
     *
     * @method Ion.Vector.Reflect
     * @param {Ion.Vector} inDirection A direção a ser refletida.
     * @param {Ion.Vector} inNormal A direção normal.
     * @return {Ion.Vector} O vector resultante.
     */
    static Reflect(inDirection, inNormal) {
        return inDirection.Plus(inNormal.Multiplied(-2.0 * Ion.Vector.Dot(inNormal, inDirection)));
    }

    /**
     * O ponto de dois vectors.
     *
     * @method Ion.Vector.Dot
     * @param {Ion.Vector} lhs O primeiro vector.
     * @param {Ion.Vector} rhs O segundo vector.
     * @return {Number} O resultado.
     */
    static Dot(lhs, rhs) {
        return lhs.x * rhs.x + lhs.y * rhs.y;
    }

    /**
     * Retorna um vector feito dos menores componentes de dois vectors.
     *
     * @method Ion.Vector.Min
     * @param {Ion.Vector} lhs O primeiro vector.
     * @param {Ion.Vector} rhs O segundo vector.
     * @return {Ion.Vector} O resultado.
     */
    static Min(lhs, rhs) {
        return new Ion.Vector(Math.min(lhs.x, rhs.x), Math.min(lhs.y, rhs.y));
    }

    /**
     * Retorna um vector feito dos maiores componentes de dois vectors.
     *
     * @method Ion.Vector.Max
     * @param {Ion.Vector} lhs O primeiro vector.
     * @param {Ion.Vector} rhs O segundo vector.
     * @return {Ion.Vector} O resultado.
     */
    static Max(lhs, rhs) {
        return new Ion.Vector(Math.max(lhs.x, rhs.x), Math.max(lhs.y, rhs.y));
    }
    
	/**
     * Move um vector em uma curva com um ponto de controle.
     *
     * @method Ion.Vector.Bezier2
     * @param {Ion.Vector || ex*} start O ponto inicial da curva.
     * @param {Ion.Vector || ex*} control O controle da curva.
     * @param {Ion.Vector || ex*} end O ponto final da curva.
     * @param {Number} t A interpolação.
     * @param {Ion.Vector} vector ex2*
     * @return {Ion.Vector} O Vector resultante.
     *
     * ex: Qualquer objeto com propriedades x, y ou uma array.
     * ex2: Para evitar a criação de vários vectors, pode-se definir um vector já criado.
     */
    static Bezier2(start, control, end, t, vector) {
    	vector = vector || Ion.Vector.Zero;
    	start = Ion.Rectangle.Convert(start);
    	control = Ion.Rectangle.Convert(control);
    	end = Ion.Rectangle.Convert(end);
    	vector.x = Ion.Mathf.Bezier2(start.x, control.x, end.x, t);
    	vector.y = Ion.Mathf.Bezier2(start.y, control.y, end.y, t);
    	return vector;
    }
    
	/**
     * Move um vector em uma curva com dois pontos de controle.
     *
     * @method Ion.Vector.Bezier3
     * @param {Ion.Vector || ex*} start O ponto inicial da curva.
     * @param {Ion.Vector || ex*} control1 O primeiro controle da curva.
     * @param {Ion.Vector || ex*} control2 O segundo controle da curva.
     * @param {Ion.Vector || ex*} end O ponto final da curva.
     * @param {Number} t A interpolação.
     * @param {Ion.Vector} vector ex2*
     * @return {Ion.Vector} O Vector resultante.
     *
     * ex: Qualquer objeto com propriedades x, y ou uma array.
     * ex2: Para evitar a criação de vários vectors, pode-se definir um vector já criado.
     */
    static Bezier3(start, control1, control2, end, t, vector) {
    	vector = vector || Ion.Vector.Zero;
    	start = Ion.Rectangle.Convert(start);
    	control1 = Ion.Rectangle.Convert(control1);
    	control2 = Ion.Rectangle.Convert(control2);
    	end = Ion.Rectangle.Convert(end);
    	vector.x = Ion.Mathf.Bezier3(start.x, control1.x, control2.x, end.x, t);
    	vector.y = Ion.Mathf.Bezier3(start.y, control1.y, control2.y, end.y, t);
    	return vector;
    }

}

//-----------------------------------------------------------------------------
/**
 * Um objeto com funções para obter o estado do teclado.
 *
 * @object Ion.Input
 */
Ion.Input = {

    LongPressWait: 120, // Tempo de espera para um Long Press
    RepeatWait: 30, // Tempo de espera para um Repeat
    RepeatTick: 4, // Tempo entre cada Repeat.

    _trigger: [],
    _repeat: [],
    _release: [],
    _longPress: [],
    _state: [],
    _events: [],

    /**
     * Atualização do Input.
     *
     * @method Ion.Input.Update
     */
    Update: function() {
        this.AnyPress = false;
        for (let i = 0; i < this._state.length; i++) {
            this._trigger[i] = this._trigger[i] || { count: 0, triggered: false };
            this._repeat[i] = this._repeat[i] || { count: 0, repeated: false };
            this._release[i] = this._release[i] || { count: 0, released: true };
            this._longPress[i] = this._longPress[i] || { count: 0, longPressed: false }
                // Check if a key is pressed down
            if (this._state[i] == true) {
                // Check if the key was long pressed
                if (this._longPress[i].count >= this.LongPressWait) {
                    this._longPress[i].longPressed = true;
                } else {
                    this._longPress[i].longPressed = false;
                    this._longPress[i].count++;
                }

                // Check if the key was triggered
                if (this._trigger[i].count <= 0) {
                    this._trigger[i].triggered = true;
                    this._trigger[i].count++;
                } else if (this._trigger[i].count > 0) {
                    this._trigger[i].triggered = false;
                }
                // Check if the key was repeated
                if (this._trigger[i].triggered && this._repeat[i].count <= 0) {
                    this._repeat[i].repeated = true;
                    this._repeat[i].count = this.RepeatWait;
                } else if (this._repeat[i].count > 0) {
                    this._repeat[i].count--;
                    this._repeat[i].repeated = false;
                } else if (this._repeat[i].count <= 0) {
                    this._repeat[i].count = this.RepeatTick;
                    this._repeat[i].repeated = true;
                }
                // Reset the released
                this._release[i].released = false;
                this._release[i].count = 0;
            } else { // Check if a key was released
                if (this._release[i].count <= 0) {
                    this._release[i].released = true;
                    this._release[i].count++;
                } else if (this._release[i].count > 0) {
                    this._release[i].released = false;
                }
                // Reset the trigger
                this._trigger[i].count = 0;
                this._trigger[i].triggered = false;

                // Reset the repeat
                this._repeat[i].count = 0;
                this._repeat[i].repeated = false;

                // Reset the long press
                this._longPress[i].count = 0;
                this._longPress[i].longPressed = false;
            }
        }
    },

    /**
     * Checa se uma tecla está sendo pressionada usando um código virtual.
     *
     * @method Ion.Input.GetKey
     * @param {Number || Ion.KeyCode} code O código virtual da tecla.
     * @return {Bool} true ou false;
     */
    GetKey: function(code) {
        return this._state[code] == true;
    },

    /**
     * Checa se uma tecla foi pressionada uma vez usando um código virtual.
     *
     * @method Ion.Input.GetKeyDown
     * @param {Number || Ion.KeyCode} code O código virtual da tecla.
     * @return {Bool} true ou false;
     */
    GetKeyDown: function(code) {
        return this._trigger[code] && this._trigger[code].triggered;
    },

    /**
     * Checa se uma tecla foi solta usando um código virtual.
     *
     * @method Ion.Input.GetKeyUp
     * @param {Number || Ion.KeyCode} code O código virtual da tecla.
     * @return {Bool} true ou false;
     */
    GetKeyUp: function(code) {
        return this._release[code] && this._release[code].released;
    },

    /**
     * Checa se uma tecla está sendo repetida usando um código virtual.
     *
     * @method Ion.Input.GetKeyRepeat
     * @param {Number || Ion.KeyCode} code O código virtual da tecla.
     * @return {Bool} true ou false;
     */
    GetKeyRepeat: function(code) {
        return this._repeat[code] && this._repeat[code].repeated;
    },

    /**
     * Checa se uma tecla está sendo pressionada por muito tempo usando um código virtual.
     *
     * @method Ion.Input.GetKeyLong
     * @param {Number || Ion.KeyCode} code O código virtual da tecla.
     * @return {Bool} true ou false;
     */
    GetKeyLong: function(code) {
        return this._longPress[code] && this._longPress[code].longPressed;
    },

    /**
     * Checa se uma tecla está sendo pressionada.
     * Usa-se o nome do botão definido em Ion.KeyCode.
     *
     * @method Ion.Input.GetButton
     * @param {String} code O nome do botão.
     * @return {Bool} true ou false;
     */
    GetButton: function(name) {
        return this._state[Ion.KeyCode[name]] == true;
    },

    /**
     * Checa se uma tecla está foi pressionada uma vez.
     * Usa-se o nome do botão definido em Ion.KeyCode.
     *
     * @method Ion.Input.GetButtonDown
     * @param {String} code O nome do botão.
     * @return {Bool} true ou false;
     */
    GetButtonDown: function(name) {
        return this._trigger[Ion.KeyCode[name]] && this._trigger[Ion.KeyCode[name]].triggered;
    },

    /**
     * Checa se uma tecla foi solta.
     * Usa-se o nome do botão definido em Ion.KeyCode.
     *
     * @method Ion.Input.GetButtonUp
     * @param {String} code O nome do botão.
     * @return {Bool} true ou false;
     */
    GetButtonUp: function(name) {
        return this._release[Ion.KeyCode[name]] && this._release[Ion.KeyCode[name]].released;
    },

    /**
     * Checa se uma tecla está sendo repetida.
     * Usa-se o nome do botão definido em Ion.KeyCode.
     *
     * @method Ion.Input.GetButtonRepeat
     * @param {String} code O nome do botão.
     * @return {Bool} true ou false;
     */
    GetButtonRepeat: function(name) {
        return this._repeat[Ion.KeyCode[name]] && this._repeat[Ion.KeyCode[name]].repeated;
    },

    /**
     * Checa se uma tecla está sendo pressionada por muito tempo.
     * Usa-se o nome do botão definido em Ion.KeyCode.
     *
     * @method Ion.Input.GetButtonLong
     * @param {String} code O nome do botão.
     * @return {Bool} true ou false;
     */
    GetButtonLong: function(name) {
        return this._longPress[Ion.KeyCode[name]] && this._longPress[Ion.KeyCode[name]].longPressed;
    },

    /**
     * Reseta todos os estados do teclado.
     *
     * @method Ion.Input.Reset
     */
    Reset: function() {
        this._trigger = [];
        this._repeat = [];
        this._release = [];
        this._longPress = [];
        this._state = [];
        this._events = [];
    }

}

//-----------------------------------------------------------------------------
/**
 * Uma lista de códigos virtuais do teclado.
 *
 * @object Ion.KeyCode
 */
Ion.KeyCode = {
    MouseL: 1,
    MouseR: 2,
    MouseM: 4,
    BackSpace: 8,
    Tab: 9,
    Return: 13,
    Shift: 16,
    Ctrl: 17,
    Alt: 18,
    Pause: 19,
    Caps: 20,
    Esc: 27,
    Space: 32,
    PgUp: 33,
    PgDn: 34,
    End: 35,
    LeftArrow: 37,
    UpArrow: 38,
    RightArrow: 39,
    DownArrow: 40,
    Insert: 45,
    Delete: 46,
    N0: 48,
    N1: 49,
    N2: 50,
    N3: 51,
    N4: 52,
    N5: 53,
    N6: 54,
    N7: 55,
    N8: 56,
    N9: 57,
    A: 65,
    B: 66,
    C: 67,
    D: 68,
    E: 69,
    F: 70,
    G: 71,
    H: 72,
    I: 73,
    J: 74,
    K: 75,
    L: 76,
    M: 77,
    N: 78,
    O: 79,
    P: 80,
    Q: 81,
    R: 82,
    S: 83,
    T: 84,
    U: 85,
    V: 86,
    W: 87,
    X: 88,
    Y: 89,
    Z: 90,
    Num0: 96,
    Num1: 97,
    Num2: 98,
    Num3: 99,
    Num4: 100,
    Num5: 101,
    Num6: 102,
    Num7: 103,
    Num8: 104,
    Num9: 105,
    Multiply: 106,
    Add: 107,
    Separator: 108,
    Subtract: 109,
    Decimal: 110,
    Divide: 111,
    F1: 112,
    F2: 113,
    F3: 114,
    F4: 115,
    F5: 116,
    F6: 117,
    F7: 118,
    F8: 119,
    F9: 120,
    F10: 121,
    F11: 122,
    F12: 123,
    F13: 124,
    F14: 125,
    F15: 126,
    F16: 127,
    F17: 128,
    F18: 129,
    F19: 130,
    F20: 131,
    F21: 132,
    F22: 133,
    F23: 134,
    F24: 135,
    Ced: 186,
    Plus: 187,
    Comma: 188,
    Minus: 189,
    Dot: 190,
    Colon: 191,
    Quote: 192,
    Lash: 193,
    Accute: 219,
    Bracket2: 220,
    Bracket: 221,
    Til: 222,
    Backlash: 226,
}

//-----------------------------------------------------------------------------
/**
 * Uma lista de códigos virtuais do teclado convertidas em caracteres.
 *
 * @object Ion.KeyChar
 */
Ion.KeyChar = {
    Caps: {
        Num0: '0',
        Num1: '1',
        Num2: '2',
        Num3: '3',
        Num4: '4',
        Num5: '5',
        Num6: '6',
        Num7: '7',
        Num8: '8',
        Num9: '9',
        N0: ')',
        N1: '!',
        N2: '@',
        N3: '#',
        N4: '$',
        N5: '%',
        N6: '¨',
        N7: '&',
        N8: '*',
        N9: '(',
        A: 'A',
        B: 'B',
        C: 'C',
        D: 'D',
        E: 'E',
        F: 'F',
        G: 'G',
        H: 'H',
        I: 'I',
        J: 'J',
        K: 'K',
        L: 'L',
        M: 'M',
        N: 'N',
        O: 'O',
        P: 'P',
        Q: 'Q',
        R: 'R',
        S: 'S',
        T: 'T',
        U: 'U',
        V: 'V',
        W: 'W',
        X: 'X',
        Y: 'Y',
        Z: 'Z',
        Ced: 'Ç',
        Multiply: '*',
        Subtract: '-',
        Plus: '+',
        Dot: '>',
        Accute: '`',
        Bracket2: '}',
        Add: '+',
        Decimal: ',',
        Comma: '<',
        Colon: ':',
        Quote: '"',
        Backlash: '|',
        Separator: '.',
        Divide: '/',
        Minus: '_',
        Lash: '?',
        Bracket: '{',
        Til: '^',
        Space: ' '
    },
    Lower: {
        Num0: '0',
        Num1: '1',
        Num2: '2',
        Num3: '3',
        Num4: '4',
        Num5: '5',
        Num6: '6',
        Num7: '7',
        Num8: '8',
        Num9: '9',
        N0: '0',
        N1: '1',
        N2: '2',
        N3: '3',
        N4: '4',
        N5: '5',
        N6: '6',
        N7: '7',
        N8: '8',
        N9: '9',
        A: 'a',
        B: 'b',
        C: 'c',
        D: 'd',
        E: 'e',
        F: 'f',
        G: 'g',
        H: 'h',
        I: 'i',
        J: 'j',
        K: 'k',
        L: 'l',
        M: 'm',
        N: 'n',
        O: 'o',
        P: 'p',
        Q: 'q',
        R: 'r',
        S: 's',
        T: 't',
        U: 'u',
        V: 'v',
        W: 'w',
        X: 'x',
        Y: 'y',
        Z: 'z',
        Ced: 'ç',
        Multiply: '*',
        Subtract: '-',
        Plus: '=',
        Dot: '.',
        Accute: '´',
        Bracket2: ']',
        Add: '+',
        Decimal: ',',
        Comma: ',',
        Colon: ';',
        Quote: "'",
        Backlash: '\\',
        Separator: '.',
        Divide: '/',
        Minus: '-',
        Lash: '/',
        Bracket: '[',
        Til: '~',
        Space: ' '
    },
    Accented: {
        'a': ['á', 'à', 'ã', 'â', 'ä'],
        'e': ['é', 'è', '~e', 'ê', 'ë'],
        'i': ['í', 'ì', '~i', 'î', 'ï'],
        'o': ['ó', 'ò', 'õ', 'ô', 'ö'],
        'u': ['ú', 'ù', '~u', 'û', 'ü'],
        'y': ['ý', '`y', '~y', '^y', 'ÿ'],
        'A': ['Á', 'À', 'Ã', 'Â', 'Ä'],
        'E': ['É', 'È', '~E', 'Ê', 'Ë'],
        'I': ['Í', 'Ì', '~I', 'Î', 'Ï'],
        'O': ['Ó', 'Ò', 'Õ', 'Ô', 'Ö'],
        'U': ['Ú', 'Ù', '~U', 'Û', 'Ü'],
        'Y': ['Ý', '`Y', '~Y', '^Y', '¨Y']
    },
    Accents: ['´', '~', '^', '`', '¨'],
    AccentList: ['A', 'a', 'E', 'e', 'I', 'i', 'O', 'o', 'U', 'u', 'Y', 'y']
}

//-----------------------------------------------------------------------------
/**
 * Uma classe básica que representa um retângulo.
 *
 * @class Ion.Rectangle
 */
Ion.Rectangle = class {
	
	/**
     * Obtém um retângulo vazio.
     *
     * @get Ion.Rectangle.Empty
     * @return {Ion.Rectangle} um retângulo vazio, com largura e altura de 1.
     */
    static get Empty() { return new Ion.Rectangle(0, 0, 1, 1) }
    
    /**
     * Obtém um retângulo com todos os seus elementos zerados.
     *
     * @get Ion.Rectangle.Zero
     * @return {Ion.Rectangle} um retângulo zerado;
     */
    static get Zero() { return new Ion.Rectangle(0, 0, 0, 0) }
    
    /**
     * Obtém um retângulo com dimensões iguais. Basicamente um quadrado.
     *
     * @method Ion.Rectangle.Box
     * @param {Number} size O tamanho.
     * @return {Ion.Rectangle} um retângulo.
     */
    static Box(size) { return new Ion.Rectangle(0, 0, size, size) }

    /**
     * Converte um objeto ou uma array em um retângulo.
     *
     * A array pode estar vazia, mas os elementos são na ordem x, y, width e height.
     * Caso o argumento não seja uma array, assume-se que ele seja um
     * objeto, e as propriedades x, y, width e height serão procuradas.
     * Ele irá converter apenas as propriedades e elementos que encontrar.
     *
     * @method Ion.Rectangle.Convert
     * @param {Any} O objeto que será convertido em um retângulo.
     * @return {Ion.Rectangle} o retângulo convertido.
     */
    static Convert(obj) {
        let x, y, w, h;
        if (obj instanceof Array) {
            x = obj[0] || 0;
            y = obj[1] || 0;
            w = obj[2] || 1;
            h = obj[3] || 1;
        } else {
            x = obj.x || 0;
            y = obj.y || 0;
            w = obj.width || 1;
            h = obj.height || 1;
        }
        return new Ion.Rectangle(x, y, w, h);
    }
    
	/**
     * Indica se um retângulo está dentro de outro.
     *
     * @method Ion.Rectangle.In
     * @param {Ion.Rectangle} rect1 O primeiro retângulo.
     * @param {Ion.Rectangle} rect2 O segundo retângulo.
     * @return {Bool} true ou false;
     */
    static In(rect1, rect2) {
        let c1 = rect1.x + rect1.width > rect2.x;
        let c2 = rect1.x < rect2.x + rect2.width;
        let c3 = rect1.y + rect1.height > rect2.y;
        let c4 = rect1.y < rect2.y + rect2.height;
        return (c1 && c2 && c3 && c4);
    }
    
	/**
     * Inicialização do retângulo.
     *
     * @constructor
     * @param {Number} x A coordenada x do retângulo.
     * @param {Number} y A coordenada y do retângulo.
     * @param {Number} width A largura do retângulo.
     * @param {Number} height A altura do retângulo.
     */
    constructor(x, y, width, height) {
        this.x = x || 0;
        this.y = y || 0;
        this.width = width || 1;
        this.height = height || 1;
        this.bitmap = null;
    }
    
    /**
     * Converte este retângulo em uma array.
     *
     * @method ToArray
     * @return {Array} Uma array com os elementos do retângulo.
     */
    ToArray() {
        return [this.x, this.y, this.width, this.height];
    }
    
    /**
     * Converte este retângulo em uma string formatada.
     *
     * @method ToString
     * @return {String} Uma string com os elementos do retângulo.
     */
    ToString() {
        return '(' + this.x + ', ' + this.y + ', ' + this.width + ', ' + this.height + ')';
    }
    
    /**
     * Caso este retângulo possua um bitmap, ele será completamente preenchido
     * dadas as dimensões do retângulo.
     *
     * @method Fill
     * @param {String} color Uma cor em css.
     */
    Fill(color) {
        if (!this.bitmap) return;
        this.bitmap.fillRect(this.x, this.y, this.width, this.height, color);
    }
    
    /**
     * Obtém uma cópia deste retângulo.
     *
     * @method Clone
     * @return {Ion.Rectangle} A cópia deste retângulo.
     */
    Clone() {
        return new Ion.Rectangle(this.x, this.y, this.width, this.height);
    }
    
    /**
     * Enche este retângulo, fazendo-o crescer em todas as direções.
     *
     * @method Pad
     * @param {Number} x O quanto ele irá crescer horizontalmente.
     * @param {Number} y O quanto ele irá crescer verticalmente.
     */
    Pad(x, y) {
        x = x || 0;
        y = y || (y !== 0 ? x : 0);
        this.x -= x;
        this.y -= y;
        this.width += x * 2;
        this.height += y * 2;
    }
    
    /**
     * Faz com que este retângulo se encaixe com outro retângulo.
     *
     * @method Fit
     * @param {Ion.Rectangle} rectangle O retângulo para encaixar.
     */
    Fit(rectangle) {
        if (this.x < rectangle.x) {
            this.width += this.x;
            if (this.width < 0) this.width = 0;
            this.x = rectangle.x;
        }
        if (this.y < rectangle.y) {
            this.height += this.y;
            if (this.height < 0) this.height = 0;
            this.y = rectangle.y;
        }
        if (this.x + this.width > rectangle.x + rectangle.width) {
            this.width = rectangle.width - this.x;
            if (this.width < 0) this.width = 0;
        }
        if (this.y + this.height > rectangle.y + rectangle.height) {
            this.height = rectangle.height - this.y;
            if (this.height < 0) this.height = 0;
        }
    }
    /**
     * Amplia este retângulo para que ele inclua outro retângulo.
     *
     * @method Enlarge
     * @param {Ion.Rectangle} rectangle O retângulo para incluir.
     */
    Enlarge(rectangle) {
        if (rectangle === Ion.Rectangle.Zero) return;
        let x1 = Math.min(this.x, rectangle.x);
        let x2 = Math.max(this.x + this.width, rectangle.x + rectangle.width);
        let y1 = Math.min(this.y, rectangle.y);
        let y2 = Math.max(this.y + this.height, rectangle.y + rectangle.height);
        this.x = x1;
        this.width = x2 - x1;
        this.y = y1;
        this.height = y2 - y1;
    }
}

//-----------------------------------------------------------------------------
/**
 * Representação de um círculo.
 *
 * @class Ion.Circle
 */
Ion.Circle = class {
	
	/**
     * Indica se um ponto está dentro de um círculo.
     *
     * @method Ion.Circle.In
     * @param {Ion.Circle} circle O círculo em si.
     * @param {Object} point O ponto que será checado.
     * @return {Bool} true ou false;
     */
    static In(circle, point) {
        if (point.radius <= 0) return false;
        let px = point.x - circle.x;
        let py = point.y - circle.y;
        return Math.pow(circle.x - px, 2) + Math.pow(circle.y - py, 2) <= Math.pow(circle.radius, 2);
    }
    
	/**
     * Inicialização do círculo.
     *
     * @constructor
     * @param {Number} x A posição horizontal do círculo.
     * @param {Number} y A posição vertical do círculo.
     * @param {Number} radius O tamanho do círculo.
     */
    constructor(x, y, radius) {
        this.x = x || 0;
        this.y = y || 0;
        this.radius = radius || 0;
        this.bitmap = null;
    }
    
	/**
     * Caso este círculo possua um bitmap, ele será preenchido
     * dadas as dimensões deste círculo.
     *
     * @method Fill
     * @param {String} color Uma cor em css.
     */
    Fill(color) {
        if (!this.bitmap) return;
        let rr = this.radius * this.radius;
        for (let i = 0; i < this.radius; i++) {
            let adj = Math.ceil(Math.sqrt(rr - (i * i)));
            let xd = this.x - adj;
            let wd = 2 * adj;
            this.bitmap.fillRect(xd, this.y - i, wd, 1, color);
            this.bitmap.fillRect(xd, this.y + i, wd, 1, color);
        }
    }
    
	/**
     * Obtém uma cópia deste círculo.
     *
     * @method Clone
     * @return {Ion.Circle} A cópia do círculo.
     */
    Clone() {
        return new Ion.Circle(this.x, this.y, this.radius);
    }
}

//-----------------------------------------------------------------------------
/**
 * Representação de um polígono.
 *
 * @class Ion.Polygon
 */
Ion.Polygon = class {
	
	/**
     * Inicialização do polígono.
     *
     * @constructor
     */
    constructor() {
        this.points = [];
        this.bitmap = null;
    }
    
	/**
     * Adiciona uma array de pontos ao polígono.
     * Os pontos serão convertidos usando Ion.Rectangle.Convert.
     *
     * @method AddPoints
     * @param {Array} points Uma array de pontos a serem adicionados.
     */
    AddPoints(points) {
        for (let i = 0; i < points.length; i++) {
            this.points.push(Ion.Rectangle.Convert(points[i]));
        }
    }
    
	/**
     * Adiciona um único ponto ao polígono.
     * O ponto será convertido usando Ion.Rectangle.Convert.
     *
     * @method AddPoint
     * @param {Number} x A coordenada x do ponto.
     * @param {Number} y A coordenada y do ponto.
     */
    AddPoint(x, y) {
        this.points.push(Ion.Rectangle.Convert([x, y]));
    }
    
	/**
     * Obtém uma string com o número de pontos deste polígono.
     *
     * @method ToString
     * @return {String} A string com o número de pontos.
     */
    ToString() {
        return '(Points: ' + this.points.length() + ')';
    }
    
	/**
     * Checa se um ponto está dentro deste polígono.
     *
     * @method In
     * @param {Object || Array} point O ponto.
     */
    In(point) {
        if (this.points.size < 3) return false;
        point = Ion.Rectangle.Convert(point);
        this._inCheck(this.points, point, [0, 0], true);
    }
    
	/**
     * Caso este polígono possua um bitmap, ele será preenchido
     * dadas as coordenadas de cada ponto.
     *
     * @method Fill
     * @param {String} color A cor em css.
     */
    Fill(color) {
        for (let i = 0; i < 5; i++) {
            this._inCheck(this.points, color);
        }
    }
    
    // Helper para o In
    _inCheck(pts, point, pos, checking) {
        pos = pos || [0, 0];
        checking = checking || false;
        let n = pts.length - 2;
        for (let i = 0; i < n; i++) {
            if (checking) {
                if (this._onPolygonProcess(Ion.Array.Range(pts, i, i + 2), point, pos, true)) {
                    return true;
                }
            } else {
                this._onPolygonProcess(Ion.Array.Range(pts, i, i + 2), point, pos, false);
            }
        }
        if (checking) return false;
    }
    
    // Processamento do polígono, para checar ou para preencher.
    _onPolygonProcess(pts, point, pos, checking) {
        pos = pos || [0, 0];
        checking = checking || false;
        if (pts.length != 3) return false;
        if (!checking && !this.bitmap) return false;
        let is = false;
        let c;
        if (!checking) c = point;
        pts = pts.sort(function(a, b) { return a.y - b.y });
        let pt1 = pts[0];
        let pt2 = pts[1];
        let pt3 = pts[2];
        let dxt = (pt3.x - pt1.x) / 2;
        let dyt = (pt3.y - pt1.y) / 2;
        let dxm = pt2.x - pt1.x;
        let dym = pt2.y - pt1.y;
        let dm = Math.abs(dym);
        if (dm > 0 && dyt != 0) {
            for (let i = 0; i < dm + 2; i++) {
                let ix = ((dxm * i) / (dm + 2));
                let iy = ((dym * i) / (dm + 1));
                let edx = ix - (dxt * iy / dyt);
                if (checking) {
                    if (point[0] > ix + pt1.x - Math.max(edx, 0) - 2 + pos[0] && point[0] < ix + pt1.x - Math.max(edx, 0) - 2 + pos[0] + Math.abs(edx) + 4) {
                        if (point[1] > iy + pt1.y + pos[1] && point[1] < iy + pt1.y + pos[1]) {
                            is = true;
                        }
                    }
                } else {
                    this.bitmap.fillRect(ix + pt1.x - Math.max(edx, 0) - 2 + pos[0], iy + pt1.y + pos[1], Math.abs(edx) + 4, 1, c);
                }
            }
        }
        dxm = pt2.x - pt3.x;
        dym = pt2.y - pt3.y;
        dm = Math.abs(dym);
        if (dm > 0 && dyt != 0) {
            for (let i = 0; i < dm + 2; i++) {
                let ix = ((dxm * i) / (dm + 1));
                let iy = ((dym * i) / (dm + 1));
                let edx = ix - (dxt * iy / dyt);
                if (checking) {
                    if (point[0] > ix + pt3.x - Math.max(edx, 0) - 2 + pos[0] && point[0] < ix + pt3.x - Math.max(edx, 0) - 2 + pos[0] + Math.abs(edx) + 4) {
                        if (point[1] > iy + pt3.y + pos[1] && point[1] < iy + pt3.y + pos[1]) {
                            is = true;
                        }
                    }
                } else {
                    this.bitmap.fillRect(ix + pt3.x - Math.max(edx, 0) - 2 + pos[0], iy + pt3.y + pos[1], Math.abs(edx) + 4, 1, c);
                }
            }
        }
        return checking ? is : null;
    }
}

//-----------------------------------------------------------------------------
/**
 * Representação de cores RGB.
 *
 * @class Ion.Color
 */
Ion.Color = class {
	
	// Cores estáticas pré-definidas
    static get Red() { return Ion.Color.Per(1, 0, 0) }
    static get Green() { return Ion.Color.Per(0, 1, 0) }
    static get Blue() { return Ion.Color.Per(0, 0, 1) }
    static get White() { return Ion.Color.Per(1, 1, 1) }
    static get Black() { return Ion.Color.Per(0, 0, 0) }
    static get Yellow() { return Ion.Color.Per(1, 0.921568632, 0.0156862754) }
    static get Cyan() { return Ion.Color.Per(0, 1, 1) }
    static get Magenta() { return Ion.Color.Per(1, 0, 1) }
    static get Gray() { return Ion.Color.Per(0.5, 0.5, 0.5) }
    static get Grey() { return Ion.Color.Per(0.5, 0.5, 0.5) }

    /**
     * Obtém uma cor RGB usando valores de 0 a 1.
     *
     * @method Ion.Color.Per
     * @param {Number} r Tonalidade vermelha, de 0 a 1.
     * @param {Number} g Tonalidade verde, de 0 a 1.
     * @param {Number} b Tonalidade azul, de 0 a 1.
     * @return {Ion.Color} Retorna um objeto Ion.Color.
     */
    static Per(r, g, b) {
        let m = 255.0;
        return new Ion.Color(r * m, g * m, b * m);
    }

	/**
     * Converte uma string de cor em hex (#00FF00) em uma cor RGB.
     *
     * @method Ion.Color.Hex
     * @param {String} hex A string em hex.
     * @param {Bool} asObj Se é para retornar um Ion.Color ou uma string em Css.
     * @return {Ion.Color || String} a cor convertida.
     */
    static Hex(hex, asObj) {
        asObj = asObj || false;
        let shr = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex = hex.replace(shr, function(m, r, g, b) {
            return r + r + g + g + b + b;
        });
        let res = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        let r = parseInt(res[1], 16);
        let g = parseInt(res[2], 16);
        let b = parseInt(res[3], 16);
        if (asObj) {
            return res ? new Ion.Color(r, g, b) : null;
        } else {
            return 'rgb(' + r + ', ' + g + ', ' + b + ')';
        }
    }

    /**
     * Interpola linearmente entre duas cores por t.
     * O valor t é limitado entre 0 e 1.
     *
     * @method Ion.Color.Lerp
     * @param {Ion.Color} a A primeira cor.
     * @param {Ion.Color} b A segunda cor.
     * @param {Number} t O valor de interpolação.
     * @return {Ion.Color} A cor retornada.
     */
    static Lerp(a, b, t) {
        t = Ion.Mathf.Clamp01(t);
        return new Ion.Color(a.Red + (b.Red - a.Red) * t, a.Green + (b.Green - a.Green) * t, a.Blue + (b.Blue - a.Blue) * t);
    }

    /**
     * Interpola linearmente entre duas cores por t sem limite em t.
     *
     * @method Ion.Color.LerpUnclamped
     * @param {Ion.Color} a A primeira cor.
     * @param {Ion.Color} b A segunda cor.
     * @param {Number} t O valor de interpolação.
     * @return {Ion.Color} A cor retornada.
     */
    static LerpUnclamped(a, b, t) {
        return new Ion.Color(a.Red + (b.Red - a.Red) * t, a.Green + (b.Green - a.Green) * t, a.Blue + (b.Blue - a.Blue) * t);
    }

    /**
     * Inicialização da classe.
     *
     * @constructor
     * @param {Number} r O valor vermelho da cor, de 0 a 255.
     * @param {Number} g O valor verde da cor, de 0 a 255.
     * @param {Number} b O valor azul da cor, de 0 a 255.
     */
    constructor(r, g, b) {
        this.Red = r;
        this.Green = g;
        this.Blue = b;
    }

    // Obtém as propriedades dessa cor convertidas em porcentagem.
    get R() { return this.Red / 255.0 }
    get G() { return this.Green / 255.0 }
    get B() { return this.Blue / 255.0 }

    /**
     * Obtém esta cor com seus elementos multiplicados por um valor.
     *
     * @method RGBMultiplied(multiplier)
     * @param {Number} multiplier O multiplicador.
     * @return {Ion.Color} A cor multiplicada.
     */
    RGBMultiplied(multiplier) {
        return new Ion.Color(this.Red * multiplier, this.Green * multiplier, this.Blue * multiplier);
    }

	/**
     * Obtém esta cor com seus elementos multiplicados pelos elementos de outra cor.
     *
     * @method ColorMultiplied
     * @param {Ion.Color} multiplier A cor multiplicadora.
     * @return {Ion.Color} A cor multiplicada.
     */
    ColorMultiplied(multiplier) {
        return new Ion.Color(this.Red * multiplier.Red, this.Green * multiplier.Green, this.Blue * multiplier.Blue);
    }

    /**
     * Converte uma cor RGB para HSV.
     * Note que os elementos da array hsv serão modificados com os resultados, e
     * nenhum valor será retornado.
     *
     * @method RGBToHSV
     * @param {Ion.Color} rgbColor A cor a ser convertida.
     * @outparam {Array} uma array cujos elementos serão modificados com os valores convertidos.
     */
    RGBToHSV(rgbColor, hsv) {
        if (rgbColor.B > rgbColor.G && rgbColor.B > rgbColor.R) {
            Ion.Color._rgbToHsvHelper(4.0, rgbColor.B, rgbColor.R, rgbColor.G, hsv);
        } else if (rgbColor.G > rgbColor.R) {
            Ion.Color._rgbToHsvHelper(2.0, rgbColor.G, rgbColor.B, rgbColor.R, hsv);
        } else {
            Ion.Color._rgbToHsvHelper(0.0, rgbColor.r, rgbColor.G, rgbColor.B, hsv);
        }
    }
    
    // Ajudante para a conversão de rgb para hsv.
    _rgbToHsvHelper(offset, dominantColor, colorone, colortwo, hsv) {
        let h = hsv[0];
        let s = hsv[1];
        let v = hsv[2];
        v = dominantColor;
        if (v != 0.0) {
            let num = 0.0;
            if (colorone > colortwo) {
                num = colortwo;
            } else {
                num = colorone;
            }
            let num2 = v - num;
            if (num2 != 0) {
                s = num2 / v;
                h = offset + (colorone - colortwo) / num2;
            } else {
                s = 0.0;
                h = offset + (colorone - colortwo);
            }
            if (h < 0.0) {
                h += 1.0;
            }
        } else {
            s = 0.0;
            h = 0.0;
        }
        hsv[0] = h;
        hsv[1] = s;
        hsv[2] = v;
    }

    /**
     * Converte uma array de valores HSV para uma cor RGB.
     *
     * @method HSVToRGB
     * @param {Array} hsv Uma array com valores hsv.
     * @param {Bool} hdr Se é necessário High Dynamic Range.
     * @return {Ion.Color} A cor convertida.
     */
    HSVToRGB(hsv, hdr) {
        hdr = hdr || false;
        let white = { red: 0.0, green: 0.0, blue: 0.0 }
        h = hsv[0];
        s = hsv[1];
        v = hsv[2];
        if (s == 0.0) {
            white.red = v;
            white.green = v;
            white.blue = v;
        } else if (v == 0.0) {
            white.red = 0.0;
            white.green = 0.0;
            white.blue = 0.0
        } else {
            white.red = 0.0;
            white.green = 0.0;
            white.blue = 0.0;
            let num = h * 6.0;
            let num2 = Math.floor(num);
            let num3 = num - num2;
            let num4 = v * (1.0 - s);
            let num5 = v * (1.0 - s * num3);
            let num6 = v * (1.0 - s * (1.0 - num3));
            switch (num2 + 1) {
                case 0:
                    white.red = v;
                    white.green = num4;
                    white.blue = num5;
                    break;
                case 1:
                    white.red = v;
                    white.green = num6;
                    white.blue = num4;
                    break;
                case 2:
                    white.red = num5;
                    white.green = v;
                    white.blue = num4;
                    break;
                case 3:
                    white.red = num4;
                    white.green = v;
                    white.blue = num6;
                    break;
                case 4:
                    white.red = num4;
                    white.green = num5;
                    white.blue = v;
                    break;
                case 5:
                    white.red = num6;
                    white.green = num4;
                    white.blue = v;
                    break;
                case 6:
                    white.red = v;
                    white.green = num4;
                    white.blue = num5;
                    break;
                case 7:
                    white.red = v;
                    white.green = num6;
                    white.blue = num4;
                    break;
            }
            if (!hdr) {
                white.red = Ion.Mathf.Clamp(white.red, 0.0, 1.0);
                white.green = Ion.Mathf.Clamp(white.green, 0.0, 1.0);
                white.blue = Ion.Mathf.Clamp(white.blue, 0.0, 1.0);
            }
        }
        return Ion.Color.Per(white.red, white.green, white.blue);
    }

    /**
     * Obtém esta cor convertida em uma string em css.
     *
     * @get Css
     * @return {String} A cor convertida em Css.
     */
    get Css() {
    	return 'rgb(' + Math.floor(this.Red) + ', ' + Math.floor(this.Green) + ', ' + Math.floor(this.Blue) + ')';
    }
}

//-----------------------------------------------------------------------------
/**
 * Simulação dos eixos de um joystick.
 *
 * @class Ion.Axis
 */
Ion.Axis = class {
	
    /**
     * Inicialização da classe.
     *
     * @constructor
     * @param {String} posInput O nome do botão positivo definido em Ion.KeyCode.
     * @param {String} negInput O nome do botão negativo definido em Ion.KeyCode.
     */
    constructor(posInput, negInput) {
        this.axis = 0.0; // O eixo principal.
        this.inc = 0.1; // O quanto o eixo irá aumentar.
        this.dec = 0.1; // O quanto o eixo irá diminuir.
        this.limit = 1.00; // O limite do eixo.
        this.positiveInput = posInput; // O botão positivo;
        this.negativeInput = negInput; // O botão negativo.
        this._tap = false;
        this.doubleTapWait = 0.3; // Tempo de espera para pressionar duas vezes.
        this._tapWaitCount = 0.0;
        this._currentDirection = 0.0;
        this._doubleTapped = false;
        this._caxis = 0.0;
    }
    
    /**
     * Altera ambos os botões positivos e negativos de uma vez.
     *
     * @method SetInput
     * @param {String} pos O nome do botão positivo definido em Ion.KeyCode.
     * @param {String} neg O nome do botão negativo definido em Ion.KeyCode.
     */
    SetInput(pos, neg) {
        this.positiveInput = pos;
        this.negativeInput = neg;
    }
    
    /**
     * Atualização da classe.
     *
     * @method Update
     */
    Update() {
        if (Ion.Input.GetButton(this.positiveInput)) {
            this._increase();
        }
        if (Ion.Input.GetButton(this.negativeInput)) {
            this._decrease();
        }
        if (this.axis > 0 && !Ion.Input.GetButton(this.positiveInput)) {
            this.axis - this.dec < 0 ? this.axis = 0.0 : this.axis -= this.dec;
        }
        if (this.axis < 0 && !Ion.Input.GetButton(this.negativeInput)) {
            this.axis + this.dec > 0 ? this.axis = 0.0 : this.axis += this.dec;
        }
        this._updateTap();
        this._updateDoubleTap();
    }
    
    // Atualiza o o toque.
    _updateTap() {
        this._doubleTapped = false;
        if (this.Raw() != 0 && this._caxis != this.Raw()) {
            this._tap = true;
            this._caxis = this.Raw();
        } else if (this.Raw() == 0.0 && this._tap == false) {
            this._caxis = 0.0;
        } else {
            this._tap = false;
        }
    }
    
    // Atualiza o toque duplo.
    _updateDoubleTap() {
        if (this._tapWaitCount > 0.0) {
            let time = Ion.TimeDifference.SecToMin;
            this._tapWaitCount = Ion.Mathf.MoveTowards(this._tapWaitCount, 0, time);
        }
        if (this.Raw() != 0 && this.isTapped()) {
            if (this._currentDirection !== this.Raw()) {
                this._currentDirection = this.Raw();
                this._tapWaitCount = this.doubleTapWait;
            } else if (this._currentDirection === this.Raw() && this._tapWaitCount <= 0) {
                this._tapWaitCount = this.doubleTapWait;
            } else if (this._currentDirection === this.Raw() && this._tapWaitCount > 0) {
                this._doubleTapped = true;
                this._tapWaitCount = 0;
            }
        }
    }
    
    // Aumenta o eixo.
    _increase() {
        this.axis += this.inc;
        this.axis = Ion.Mathf.Clamp(this.axis, -this.limit, this.limit);
    }
    
    // Diminui o eixo.
    _decrease() {
        this.axis -= this.inc;
        this.axis = Ion.Mathf.Clamp(this.axis, -this.limit, this.limit);
    }
    
    /**
     * Obtém o eixo puro, sem aumento.
     *
     * @method Raw
     * @return {Number} O eixo puro.
     */
    Raw() {
        return this.axis < 0 ? -1 : this.axis > 0 ? 1 : 0;
    }
    
    /**
     * Indica se o eixo foi pressionado uma vez para qualquer um dos lados.
     *
     * @method isTapped
     * @return {Bool} true ou false.
     */
    isTapped() {
        return this._tap;
    }
    
    /**
     * Indica se o eixo foi pressionado duas vezes para qualquer um dos lados.
     *
     * @method isDoubleTapped
     * @return {Bool} true ou false.
     */
    isDoubleTapped() {
        return this._doubleTapped;
    }

}

//-----------------------------------------------------------------------------
/**
 * Um sprite com movimento baseado em vectors.
 *
 * @class Ion.ISprite
 */
Ion.ISprite = class extends Sprite {
	
    /**
     * Inicialização da classe.
     *
     * @constructor
     * @param {Bitmap} bitmap O bitmap a ser usado para o sprite.
     */
    constructor(bitmap) {
        super.constructor(bitmap ? bitmap : new Bitmap(1, 1));
        
        // Caso este sprite tenha um parent, ele se moverá de acordo
        // com a posição do parent. A menos que isso seja true.
        // Você pode ativar e desativar isso sempre que quiser.
        this.IgnoreParentTransform = false;
        // Este método irá lidar com o movimento do sprite de acordo com a
        // variável acima.
        this.transform.updateTransform = this.customTransform;
        
        this.Position = Ion.Vector.Zero;
        this.Attractor = Ion.Vector.Zero;
        this.Velocity = Ion.Vector.Zero;
        this.MovePoints = [];
        this._backupPoints = [];
        this.__anchor = 'topLeft';
        this.SetAnchor();
    }
    
    /**
     * Obtém os limites deste sprite convertidos em um retângulo.
     *
     * @method Rect
     * @return {Ion.Rectangle} O retângulo com os limites do sprite.
     */
    Rect() {
        return Ion.Rectangle.Convert(this.getBounds());
    }
    
    /**
     * Ajusta o ponto de origem do sprite com alguns atalhos.
     *
     * @method SetAnchor
     * @param {String} anchor Ver logo abaixo, fazendo o favor :')
     */
    SetAnchor(anchor) {
        if (anchor) this.__anchor = anchor;
        switch (this.__anchor) {
            case 'topLeft':
                this.anchor.set(0, 0);
                break;
            case 'top':
                this.anchor.set(0.5, 0);
                break;
            case 'topRight':
                this.anchor.set(1, 0);
                break;
            case 'centerLeft':
                this.anchor.set(0, 0.5);
                break;
            case 'center':
                this.anchor.set(0.5, 0.5);
                break;
            case 'centerRight':
                this.anchor.set(1, 0.5);
                break;
            case 'bottomLeft':
                this.anchor.set(0, 1);
                break;
            case 'bottom':
                this.anchor.set(0.5, 1);
                break;
            case 'bottomRight':
                this.anchor.set(1, 1);
                break;
        }
    }
    // Troca do update original pelo Update, pois letras maiúsculas possuem mais presença u.u.
    get update() { return this.Update }
    
    /**
     * Atualização do sprite.
     *
     * @method Update
     */
    Update() {
    	// Este sprite usa suas coordenadas baseadas em um vector, para fácil
    	// uso dos métodos da classe Ion.Vector.
        this.x = this.Position.x;
        this.y = this.Position.y;
        this.Position = this.Position.Plus(this.Velocity); // A posição se move na direção da velocidade.
    }
    
    /**
     * Move este sprite na direção do seu atrator (que é um vector) usando Ion.Vector.Lerp.
     *
     * @method MoveToAttractor
     * @param {Number} speed A velocidade máxima de movimento.
     * @param {Bool} clamped Se a velocidade do Lerp deve ser limitada a 0 e 1.
     * @param {Ion.Vector} otherAttractor Um atrator alternativo.
     */
	MoveToAttractor(speed, clamped, otherAttractor) {
	    otherAttractor = otherAttractor || this.Attractor;
		if (!clamped) {
			this.Position = Ion.Vector.LerpUnclamped(this.Position, otherAttractor, speed);
		} else {
			this.Position = Ion.Vector.Lerp(this.Position, otherAttractor, speed);
		}
	}
	
    /**
     * Move este sprite usando sua lista de movimento.
     *
     * Note que os pontos de movimento serão deletados um por um assim que o
     * sprite chegar no destino, então é recomendado fazer um backup dos pontos
     * de movimento antes deste processo, caso queira.
     *
     * @method MoveByList
     * @param {Number} speed A velocidade máxima de movimento.
     * @param {Bool} smooth Se ele deve se mover usando SmoothStep ou MoveTowards.
     * @param {Number} smoothPrecision A precisão para checar se o movimento smooth chegou ao destino.
     * @param {Bool} reverse Se o sprite deve se mover com a lista reversa.
     */
	MoveByList(speed, smooth, smoothPrecision, reverse) {
	    if (this.MovePoints.length <= 0) return;
	    let i = reverse ? this.MovePoints.length - 1 : 0;
	    let pos = Ion.Vector.Zero;
	    if (smooth) {
	        let x = Ion.Mathf.SmoothStep(this.Position.x, this.MovePoints[i].x, speed);
	        let y = Ion.Mathf.SmoothStep(this.Position.y, this.MovePoints[i].y, speed);
	        pos = new Ion.Vector(x, y);
	    } else {
	        pos = Ion.Vector.MoveTowards(this.Position, this.MovePoints[i], speed);
	    }
	    this.Position = pos;
	    if (smooth) {
	        let sp = smoothPrecision;
	        let mx = this.MovePoints[i].x;
	        let my = this.MovePoints[i].y;
	        let c1 = Ion.Number.Between(this.Position.x, mx - sp, mx + sp);
	        let c2 = Ion.Number.Between(this.Position.y, my - sp, my + sp);
	        if (c1 && c2) {
	            this.MovePoints[i] = null;
	            this.MovePoints = Ion.Array.Compact(this.MovePoints);
	        }
	    } else {
	        let a1 = this.Position.x == this.MovePoints[i].x;
	        let a2 = this.Position.y == this.MovePoints[i].y;
	        if (a1 && a2) {
	            this.MovePoints[i] = null;
	            this.MovePoints = Ion.Array.Compact(this.MovePoints);
	        }
	    }
	}
	
    /**
     * Move este sprite até o ponto determinado.
     *
     * @method MoveTo
     * @param {Ion.Vector} vector O vector de destino.
     * @param {Bool} smooth Se ele deve se mover usando MoveToAttractor ou MoveTowards.
     * @param {Number} speed A velocidade máxima de movimento.
     * @param {Bool} clamped Se a velocidade deve ser limitada a 0 e 1.
     */
	MoveTo(vector, smooth, speed, clamped) {
	    speed = speed || 0.1;
	    if (smooth) this.MoveToAttractor(speed, clamped, vector);
	    if (!smooth) this.Position = Ion.Vector.MoveTowards(this.Position, vector, speed);
	}
	
    /**
     * Realiza um backup dos pontos de movimento.
     *
     * @method BackupMovePoints
     * @return {Array} Retorna o backup da lista de movimentos.
     */
	BackupMovePoints() {
	    this._backupPoints = [];
	    for (let i = 0; i < this.MovePoints.length; i++) {
	        this._backupPoints.push(this.MovePoints[i].Clone());
	    }
	    return this._backupPoints;
	}
	
    /**
     * Restaura a lista de movimentos usando os pontos de backup.
     *
     * @method BackupMovePoints
     * @return {Array} Retorna os pontos de movimento.
     */
	RestoreMovePoints() {
	    this.MovePoints = [];
	    for (let i = 0; i < this._backupPoints.length; i++) {
	        this.MovePoints.push(this._backupPoints[i].Clone());
	    }
	    return this.MovePoints;
	}

    /**
     * Indica se este sprite está saindo ou está fora da tela.
     *
     * @method isOutOfScreen
     * @return {Bool} true ou false.
     */
	isOutOfScreen() {
	    let res = false;
	    if (this.Rect().x < 0) { res = true }
	    if (this.Rect().x + this.Rect().width > Graphics.width) { res = true }
	    if (this.Rect().y < 0) { res = true }
	    if (this.Rect().y + this.Rect().height > Graphics.height) { res = true }
	    return res;
	}

    /**
     * Indica se um ponto está dentro das dimensões deste sprite.
     *
     * @method isHovered
     * @param {Ion.Vector} point O ponto a ser checado.
     * @return {Bool} true ou false.
     */
	isHovered(point) {
	    let res = false;
	    if (point) {
	        let c1 = (point.x >= this.Rect().x && point.x <= this.Rect().x + this.Rect().width);
	        let c2 = (point.y >= this.Rect().y && point.y <= this.Rect().y + this.Rect().height);
	        res = c1 && c2;
	    }
	    return res;
	}

	// Passa as informações do sprite principal para o transform.
	updateTransform() {
	    this.Update();
	    this._boundsID++;
	    this.transform.updateTransform(this.parent.transform, this.IgnoreParentTransform);

	    // TODO: check render flags, how to process stuff here
	    this.worldAlpha = this.alpha * this.parent.worldAlpha;

	    for (var i = 0, j = this.children.length; i < j; ++i) {
	        var child = this.children[i];

	        if (child.visible) {
	            child.updateTransform();
	        }
	    }
	}

	// Lida com o transform baseado na posição do parent.
	// Note que, ao desativar o IgnoreParentTransform, a posição não irá
	// mudar até que o sprite se mova e uma nova atualização no transform seja chamada.
	customTransform(parentTransform, ignoreParent, fromSprite) {
	    var pt = parentTransform.worldTransform;
	    var wt = this.worldTransform;
	    var lt = this.localTransform;
	    if (ignoreParent) {
	        if (!this.___ignoreParent___) {
	            this.___ignoreParent___ = true;
	        }
	    } else if (ignoreParent === false) {
	        this.___ignoreParent___ = false;
	    }
	    if (this._localID !== this._currentLocalID) {
	        // get the matrix values of the displayobject based on its transform properties..
	        var a = this._cr * this.scale._x;
	        var b = this._sr * this.scale._x;
	        var c = -this._sr * this.scale._y;
	        var d = this._cr * this.scale._y;

	        lt.a = this._cy * a + this._sy * c;
	        lt.b = this._cy * b + this._sy * d;
	        lt.c = this._nsx * a + this._cx * c;
	        lt.d = this._nsx * b + this._cx * d;

	        lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c);
	        lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d);
	        this._currentLocalID = this._localID;

	        // force an update..
	        this._parentID = -1;
	    }

	    if (this._parentID !== parentTransform._worldID) {
	        // concat the parent matrix with the objects transform.
	        wt.a = lt.a * pt.a + lt.b * pt.c;
	        wt.b = lt.a * pt.b + lt.b * pt.d;
	        wt.c = lt.c * pt.a + lt.d * pt.c;
	        wt.d = lt.c * pt.b + lt.d * pt.d;
	        wt.tx = lt.tx * pt.a + lt.ty * pt.c + (this.___ignoreParent___ ? 0 : pt.tx);
	        wt.ty = lt.tx * pt.b + lt.ty * pt.d + (this.___ignoreParent___ ? 0 : pt.ty);
	        

	        this._parentID = parentTransform._worldID;

	        // update the id of the transform..
	        this._worldID++;
	    }
	}

}

//-----------------------------------------------------------------------------
/**
 * Basicamente um bitmap que ia ter mais coisa mas eu fiquei cansado e não fiz.
 *
 * @class Ion.IBitmap
 */
Ion.IBitmap = class extends Bitmap {
    
	/**
     * Obtém um bitmap com o tamanho da tela.
     *
     * @get Ion.IBitmap.Screen
     * @return {Ion.IBitmap} Retorna um bitmap com o tamanho da tela.
     */
    static get Screen() { return new Ion.IBitmap(Graphics.width, Graphics.height) }
    
	/**
     * Obtém um bitmap com suas dimensões iguais. Basicamente um quadrado.
     *
     * @method Ion.IBitmap.Box
     * @param {Number} size O tamanho das dimensões.
     * @return {Ion.IBitmap} Um bitmap com suas dimensões iguais.
     */
    static Box(size) { return new Ion.IBitmap(size, size) }
    
	/**
     * Desenha uma linha reta.
     *
     * @method DrawLine
     * @param {c*} from O ponto inicial da linha.
     * @param {c*} to O ponto final da linha.
     * @param {String} color A cor da linha.
     * @param {Number} size O tamanho da linha.
     * @param {Number} offset O deslocamento da posição da linha.
     *
     * c* Este argumento será convertido usando Ion.Rectangle.Convert.
     */
    DrawLine(from, to, color, size, offset) {
        color = color || Ion.Color.Black.Css;
        size = size || 1;
        offset = offset || 0;
        let f = Ion.Rectangle.Convert(from);
        let t = Ion.Rectangle.Convert(to);
        let stPos = new Ion.Vector(f.x, f.y);
        let enPos = new Ion.Vector(t.x, t.y);
        let distance = Ion.Vector.Distance(stPos, enPos);
        for (let i = 0; i < distance; i++) {
            let x = stPos.x - (size * offset);
            let y = stPos.y - (size * offset);
            this.DrawPixel([x, y], color, size);
            stPos = Ion.Vector.MoveTowards(stPos, enPos, 1);
        }
    }

    /**
     * Desenha um círculo.
     *
     * @method DrawCircle
     * @param {c*} position O ponto inicial do círculo.
     * @param {String} color A cor do círculo.
     * @param {Number} radius O tamanho do círculo.
     *
     * c* Este argumento será convertido usando Ion.Rectangle.Convert.
     */
    DrawCircle(position, color, radius) {
        position = Ion.Rectangle.Convert(position);
        color = color || Ion.Color.Black.Css;
        radius = radius || 0;
        let rr = radius * radius;
        for (let i = 0; i < radius; i++) {
            let adj = Math.ceil(Math.sqrt(rr - (i * i)));
            let xd = position.x - adj;
            let wd = 2 * adj;
            this.fillRect(xd, position.y - i, wd, 1, color);
            this.fillRect(xd, position.y + i, wd, 1, color);
        }
    }

	/**
     * Desenha um retângulo.
     *
     * @method DrawRectangle
     * @param {c*} position A posição do retângulo.
     * @param {Number} width a largura do retângulo.
     * @param {Number} height a altura do retângulo.
     * @param {String} color A cor do círculo.
     *
     * c* Este argumento será convertido usando Ion.Rectangle.Convert.
     */
    DrawRectangle(position, width, height, color) {
        width = width || 1;
        height = height || 1;
        if (width <= 0 || height <= 0) { return }
        position = Ion.Rectangle.Convert(position);
        color = color || Ion.Color.Black.Css;
        this.fillRect(position.x, position.y, width, height, color);
    }

    /**
     * Preenche um simples pixel.
     *
     * @method DrawPixel
     * @param {c*} position A posição do pixel.
     * @param {String} color A cor do círculo.
     * @param {Number} size O tamanho do pixel.
     * @param {Number} offset O deslocamento do pixel.
     *
     * c* Este argumento será convertido usando Ion.Rectangle.Convert.
     */
    DrawPixel(position, color, size, offset) {
        color = color || Ion.Color.Black.Css;
        size = size || 1;
        offset = offset || 0;
        position = Ion.Rectangle.Convert(position);
        let x = position.x - (size * offset);
        let y = position.y - (size * offset);
        this.fillRect(x, y, size, size, color);
    }

    /**
     * Preenche todo o bitmap com uma cor.
     *
     * @method Fill
     * @param {String} color A cor de preenchimento.
     */
    Fill(color) {
        color = color || "rgb(255, 255, 255)";
        this.fillRect(0, 0, this.width, this.height, color);
    }
	
}

//-----------------------------------------------------------------------------
/**
 * O resto lida com os trampos.
 */
Ion.__inputKeyDown = Input._onKeyDown;
Ion.__inputKeyUp = Input._onKeyUp;
Ion.__inputOnLostFocus = Input._onLostFocus;
Input._onKeyDown = function() {
    Ion.Events.OnKeyDown.apply(Ion.Events.OnKeyDown, arguments);
    Ion.__inputKeyDown.apply(this, arguments);
}
Input._onKeyUp = function() {
    Ion.Events.OnKeyUp.apply(Ion.Events.OnKeyUp, arguments);
    Ion.__inputKeyUp.apply(this, arguments);
}
Input._onLostFocus = function() {
    Ion.Events.OnBlur.apply(Ion.Events.OnBlur, arguments);
    Ion.__inputOnLostFocus.apply(this, arguments);
}

document.addEventListener('mousemove', Ion.Events.OnMouseMove);
document.addEventListener('click', Ion.Events.OnClick);
document.addEventListener('contextmenu', Ion.Events.OnContextMenu);
document.addEventListener('dblclick', Ion.Events.OnDoubleClick);
document.addEventListener('mousedown', Ion.Events.OnMouseDown);
document.addEventListener('mouseenter', Ion.Events.OnMouseEnter);
document.addEventListener('mouseleave', Ion.Events.OnMouseLeave);
document.addEventListener('mouseover', Ion.Events.OnMouseOver);
document.addEventListener('mouseout', Ion.Events.OnMouseOut);
document.addEventListener('mouseup', Ion.Events.OnMouseUp);

Ion._sbUpdate = Scene_Title.prototype.update;
Scene_Title.prototype.update = function() {
    Ion.Update();
    Ion._sbUpdate.apply(this, arguments);
}
