JavaScript 一些实用辅助类库

"use strict";

var __emptyPoint = null,

__emptyContext = null;

const ColorRefTable = [
    ['aliceblue','#f0f8ff'], ['antiquewhite','#faebd7'], ['aqua','#00ffff'],
    ['aquamarine','#7fffd4'], ['azure','#f0ffff'], ['beige','#f5f5dc'],
    ['bisque','#ffe4c4'], ['black','#000000'], ['blanchedalmond','#ffebcd'],
    ['blue','#0000ff'], ['blueviolet','#8a2be2'], ['brown','#a52a2a'],
    ['burlywood','#deb887'], ['cadetblue','#5f9ea0'], ['chartreuse','#7fff00'],
    ['chocolate','#d2691e'], ['coral','#ff7f50'], ['cornflowerblue'],
    ['cornsilk','#fff8dc'], ['crimson','#dc143c'], ['cyan','#00ffff'],
    ['darkblue','#00008b'], ['darkcyan','#008b8b'], ['darkgoldenrod','#b8860b'],
    ['darkgray','#a9a9a9'], ['darkgreen','#006400'], ['darkgrey','#a9a9a9'],
    ['darkkhaki','#bdb76b'], ['darkmagenta','#8b008b'], ['firebrick','#b22222'],
    ['darkolivegreen','#556b2f'], ['darkorange','#ff8c00'], ['darkorchid','#9932cc'],
    ['darkred','#8b0000'], ['darksalmon','#e9967a'], ['darkseagreen','#8fbc8f'], 
    ['darkslateblue','#483d8b'], ['darkslategray','#2f4f4f'], ['darkslategrey','#2f4f4f'],
    ['darkturquoise','#00ced1'], ['darkviolet','#9400d3'], ['deeppink','#ff1493'],
    ['deepskyblue','#00bfff'], ['dimgray','#696969'], ['dimgrey','#696969'],
    ['dodgerblue','#1e90ff'], ['floralwhite','#fffaf0'], ['forestgreen','#228b22'],
    ['fuchsia','#ff00ff'], ['gainsboro','#dcdcdc'], ['ghostwhite','#f8f8ff'],
    ['gold','#ffd700'], ['goldenrod','#daa520'], ['gray','#808080'],
    ['green','#008000'], ['greenyellow','#adff2f'], ['grey','#808080'],
    ['honeydew','#f0fff0'], ['hotpink','#ff69b4'], ['indianred','#cd5c5c'],
    ['indigo','#4b0082'], ['ivory','#fffff0'], ['khaki','#f0e68c'],
    ['lavender','#e6e6fa'], ['lavenderblush','#fff0f5'], ['lawngreen','#7cfc00'],
    ['lemonchiffon','#fffacd'], ['lightblue','#add8e6'], ['lightcoral','#f08080'],
    ['lightcyan','#e0ffff'], ['lightgoldenrodyellow','#fafad2'], ['lightgray','#d3d3d3'],
    ['lightgreen','#90ee90'], ['lightgrey','#d3d3d3'], ['lightpink','#ffb6c1'],
    ['lightsalmon','#ffa07a'], ['lightseagreen','#20b2aa'], ['lightskyblue','#87cefa'],
    ['lightslategray','#778899'], ['lightslategrey','#778899'], ['lightsteelblue','#b0c4de'],
    ['lightyellow','#ffffe0'], ['lime','#00ff00'], ['limegreen','#32cd32'],
    ['linen','#faf0e6'], ['magenta','#ff00ff'], ['maroon','#800000'],
    ['mediumaquamarine','#66cdaa'], ['mediumblue','#0000cd'], ['mediumorchid','#ba55d3'],
    ['mediumpurple','#9370db'], ['mediumseagreen','#3cb371'], ['mediumslateblue','#7b68ee'],
    ['mediumspringgreen','#00fa9a'], ['mediumturquoise','#48d1cc'], ['mediumvioletred','#c71585'],
    ['midnightblue','#191970'], ['mintcream','#f5fffa'], ['mistyrose','#ffe4e1'],
    ['moccasin','#ffe4b5'], ['navajowhite','#ffdead'], ['navy','#000080'],
    ['oldlace','#fdf5e6'], ['olive','#808000'], ['olivedrab','#6b8e23'],
    ['orange','#ffa500'], ['orangered','#ff4500'], ['orchid','#da70d6'],
    ['palegoldenrod','#eee8aa'], ['palegreen','#98fb98'], ['paleturquoise','#afeeee'],
    ['palevioletred','#db7093'], ['papayawhip','#ffefd5'], ['peachpuff','#ffdab9'],
    ['peru','#cd853f'], ['pink','#ffc0cb'], ['plum','#dda0dd'],
    ['powderblue','#b0e0e6'], ['purple','#800080'], ['red','#ff0000'],
    ['rosybrown','#bc8f8f'], ['royalblue','#4169e1'], ['saddlebrown','#8b4513'],
    ['salmon','#fa8072'], ['sandybrown','#f4a460'], ['seagreen','#2e8b57'],
    ['seashell','#fff5ee'], ['sienna','#a0522d'], ['silver','#c0c0c0'],
    ['skyblue','#87ceeb'], ['slateblue','#6a5acd'], ['slategray','#708090'],
    ['slategrey','#708090'], ['snow','#fffafa'], ['springgreen','#00ff7f'],
    ['steelblue','#4682b4'], ['tan','#d2b48c'], ['teal','#008080'],
    ['thistle','#d8bfd8'], ['tomato','#ff6347'], ['turquoise','#40e0d0'],
    ['violet','#ee82ee'], ['wheat','#f5deb3'], ['white','#ffffff'],
    ['whitesmoke','#f5f5f5'], ['yellow','#ffff00'], ['yellowgreen','#9acd32']
],

UTILS = {

    toAngle(v){
        return v / 180 * Math.PI;
    },

    emptyArray(arr){
        return !Array.isArray(arr) || arr.length === 0;
    },

    isObject(obj){
        
        return obj !== null && typeof obj === "object" && Array.isArray(obj) === false;
        
    },
    
    isNumber(num){

        return typeof num === "number" && isNaN(num) === false;

    },

    //获取最后一个点后面的字符
    getFileType(string){
        let type = "", str = string.split('').reverse().join('');
        for(let k = 0, len = str.length; k < len; k++){
            if(str[k] === ".") break;
            type += str[k];
        }
        return type.split('').reverse().join('');
    },

    //删除 string 所有的空格
    deleteSpaceAll(str){
        const len = str.length;
        var result = '';
        for(let i = 0; i < len; i++){
            if(str[i] !== '') result += str[i]
        }

        return result
    },

    //删除 string 两边空格
    removeSpaceSides(string){

        return string.replace(/(^\s*)|(\s*$)/g, "");

    },

    //返回 num 与 num1 之间的随机数
    random(num, num1){
        
        if(num < num1) return Math.random() * (num1 - num) + num;

        else if(num > num1) return Math.random() * (num - num1) + num1;

        else return num;
        
    },

    //生成 UUID
    generateUUID: function (){
        const _lut = [];
    
        for ( let i = 0; i < 256; i ++ ) {
    
            _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
    
        }
    
        return function (){
            const d0 = Math.random() * 0xffffffff | 0;
            const d1 = Math.random() * 0xffffffff | 0;
            const d2 = Math.random() * 0xffffffff | 0;
            const d3 = Math.random() * 0xffffffff | 0;
            const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
            _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
            _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
            _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
    
            return uuid.toLowerCase(); //toLowerCase() 这里展平连接的字符串以节省堆内存空间
        }
    }(),

    //欧几里得距离(两点的直线距离)
    distance(x, y, x1, y1){
        
        return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));

    },

    downloadFile(blob, fileName){
        const link = document.createElement("a");
        link.href = URL.createObjectURL(blob);
        link.download = fileName;
        link.click();
    },

    loadFileJSON(callback){
        const input = document.createElement("input");
        input.type = "file";
        input.accept = ".json";
        
        input.onchange = a => {
            if(a.target.files.length === 0) return;
            const fr = new FileReader();
            fr.onloadend = b => callback(b.target.result);
            fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]);
        }
        
        input.click();
    },

    get emptyPoint(){
        if(__emptyPoint === null) __emptyPoint = new Point();
        return __emptyPoint;
    },

    get emptyContext(){
        if(__emptyContext === null) __emptyContext = document.createElement("canvas").getContext('2d')
        return __emptyContext;
    },

}




/** Ajax
parameter:
    option = {
        url:        可选, 默认 ''
        method:        可选, post 或 get请求, 默认 post
        asy:        可选, 是否异步执行, 默认 true
        success:    可选, 成功回调, 默认 null
        error:        可选, 超时或失败调用, 默认 null
        change:        可选, 请求状态改变时调用, 默认 null
        data:        可选, 如果定义则在初始化时自动执行.send(data)方法
    }

demo:
    const data = `email=${email}&password=${password}`,

    //默认 post 请求:
    ajax = new Ajax({
        url: './login',
        data: data,
        success: mes => console.log(mes),
    });
    
    //get 请求:
    ajax.method = "get";
    ajax.send(data);
*/
class Ajax{
    
    constructor(option = {}){
        this.url = option.url || "";
        this.method = option.method || "post";
        this.asy = typeof option.asy === "boolean" ? option.asy : true;
        this.success = option.success || null;
        this.error = option.error || null;
        this.change = option.change || null;

        //init XML
        this.xhr = new XMLHttpRequest();

        this.xhr.onerror = this.xhr.ontimeout = option.error || null;

        this.xhr.onreadystatechange = event => {
        
            if(event.target.readyState === 4 && event.target.status === 200){

                if(this.success !== null) this.success(event.target.responseText, event);
                
            }

            else if(this.change !== null) this.change(event);

        }

        if(option.data) this.send(option.data);
    }

    send(data = ""){
        if(this.method === "get"){
            this.xhr.open(this.method, this.url+"?"+data, this.asy);
            this.xhr.send();
        }
        
        else if(this.method === "post"){
            this.xhr.open(this.method, this.url, this.asy);
            this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            this.xhr.send(data);
        }
    }
    
}




/* IndexedDB 本地数据库

parameter:
    name: String;                //需要打开的数据库名称(如果不存在则会新建一个) 必须
    done: Function(IndexedDB);    //链接数据库成功时的回调 默认 null
    version: Number;             //数据库版本(高版本数据库将覆盖低版本的数据库) 默认 1 

attribute:
    database: IndexedDB;            //链接完成的数据库对象
    transaction: IDBTransaction;    //事务管理(读和写)
    objectStore: IDBObjectStore;    //当前的事务

method:
    set(data, key, callback)        //添加或更新
    get(key, callback)                //获取
    delete(key, callback)            //删除

    traverse(callback)                //遍历
    getAll(callback)                //获取全部
    clear(callback)                    //清理所以数据
    close()                         //关闭数据库链接

readOnly:

static:
    indexedDB: Object;

demo:
    
    new IndexedDB('TEST', db => {

        conosle.log(db);

    });

*/
class IndexedDB{

    static indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

    get objectStore(){ //每个事务只能使用一次, 所以每次都需要重新创建
        return this.database.transaction(this.name, 'readwrite').objectStore(this.name);
    }

    constructor(name, done = null, version = 1){

        if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB");
        
        if(typeof name !== 'string') return console.warn('IndexedDB: 参数错误');

        this.name = name;
        this.database = null;

        const request = IndexedDB.indexedDB.open(name, version);
        
        request.onupgradeneeded = (e)=>{ //数据库不存在 或 版本号不同时 触发
            if(!this.database) this.database = e.target.result;
            if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name);
        }
        
        request.onsuccess = (e)=>{
            this.database = e.target.result;
            if(typeof done === 'function') done(this);
        }
        
        request.onerror = (e)=>{
            console.error(e);
        }
        
    }

    close(){

        return this.database.close();

    }

    clear(callback){
        
        this.objectStore.clear().onsuccess = callback;
        
    }

    traverse(callback){
        
        this.objectStore.openCursor().onsuccess = callback;

    }

    set(data, key = 0, callback){
        
        this.objectStore.put(data, key).onsuccess = callback;

    }
    
    get(key = 0, callback){

        this.objectStore.get(key).onsuccess = callback;
        
    }

    del(key = 0, callback){

        this.objectStore.delete(key).onsuccess = callback;

    }
    
    getAll(callback){

        this.objectStore.getAll().onsuccess = callback;

    }

}




/* TreeStruct 树结构基类

attribute:
    parent: TreeStruct;
    children: Array[TreeStruct];

method:
    add(v: TreeStruct): v;         //v添加到自己的子集
    remove(v: TreeStruct): v;     //删除v, 前提v必须是自己的子集
    export(): Array[Object];    //TreeStruct 转为 可导出的结构, 包括其所有的后代

    getPath(v: TreeStruct): Array[TreeStruct];     //获取自己到v的路径

    traverse(callback: Function): undefined;  //迭代自己的每一个后代, 包括自己
        callback(value: TreeStruct); //如返回 "continue" 则不在迭代其后代(不是结束迭代, 而是只结束当前节点的后代);

    traverseUp(callback): undefined; //向上遍历每一个父, 包括自己
        callback(value: TreeStruct); //如返回 "break" 立即停止遍历;

static:
    import(arr: Array[Object]): TreeStruct; //.export() 返回的 arr 转为 TreeStruct

*/
class TreeStruct{

    static import(arr){

        //json = JSON.parse(json);
        const len = arr.length;

        for(let k = 0, v; k < len; k++){
            v = Object.assign(new TreeStruct(), arr[k]);
            v.parent = arr[arr[k].parent] || null;
            if(v.parent !== null) v.parent.add(v);
            arr[k] = v;
        }

        return arr[0];

    }

    constructor(){
        this.parent = null;
        this.children = [];
    }

    getPath(v){

        var path;

        const pathA = [];
        this.traverseUp(tar => {
            if(v === tar){
                path = pathA;
                return "break";
            }
            pathA.push(tar);
        });

        if(path) return path;

        const pathB = [];
        v.traverseUp(tar => {
            if(this === tar){
                path = pathB.reverse();
                return "break";
            }
            else{
                let i = pathA.indexOf(tar);
                if(i !== -1){
                    pathA.splice(i);
                    pathA.push(tar);
                    path = pathA.concat(pathB.reverse());
                    return "break";
                }
            }
            pathB.push(tar);
        });

        return path;
        
    }

    add(v){
        v.parent = this;
        if(this.children.includes(v) === false) this.children.push(v);
        
        return v;
    }

    remove(v){
        const i = this.children.indexOf(v);
        if(i !== -1) this.children.splice(i, 1);
        v.parent = null;

        return v;
    }

    traverse(callback, key = 0){

        if(callback(this, key) !== "continue"){

            for(let k = 0, len = this.children.length; k < len; k++){

                this.children[k].traverse(callback, k);
    
            }

        }

    }

    traverseUp(callback){

        var par = this.parent;

        while(par !== null){
            if(callback(par) === "break") return;
            par = par.parent;
        }

    }

    export(){

        const result = [], arr = [];
        var obj = null;

        this.traverse(v => {
            obj = Object.assign({}, v);
            obj.parent = arr.indexOf(v.parent);
            delete obj.children;
            result.push(obj);
            arr.push(v);
        });
        
        return result; //JSON.stringify(result);

    }

}

Object.defineProperties(TreeStruct.prototype, {
    
    isTreeStruct: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});



/* Point
parameter: 
    x = 0, y = 0;

attribute
    x, y: Number;

method:
    set(x, y): this;
    angle(): Number;
    copy(point): this;
    clone(): Point;
    distance(point): Number;            //获取欧几里得距离
    distanceMHD(point): Number;            //获取曼哈顿距离
    distanceCompare(point): Number;        //获取用于比较的距离(相对于.distance() 效率更高)
    equals(point): Bool;                //是否恒等
    reverse(): this;                    //取反值
    rotate(origin: Object{x,y}, angle): this;    //旋转点
    normalize(): this;                    //归一
*/
class Point{

    constructor(x = 0, y = 0){
        this.x = x;
        this.y = y;
    }

    set(x = 0, y = 0){
        this.x = x;
        this.y = y;

        return this;
    }

    angle(){

        return Math.atan2(this.y, this.x);

    }

    copy(point){
        
        return Object.assign(this, point);

    }
    
    clone(){

        return Object.assign(new this.constructor(), this);
        
    }

    distance(point){
        
        return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));

    }

    distanceMHD(point){

        return Math.abs(this.x - point.x) + Math.abs(this.y - point.y);

    }

    distanceCompare(point){
    
        return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2);

    }

    equals(point){

        return point.x === this.x && point.y === this.y;

    }

    reverse(){
        this.x = -this.x;
        this.y = -this.y;

        return this;
    }

    rotate(origin, angle){
        const c = Math.cos(angle), s = Math.sin(angle), 
        x = this.x - origin.x, y = this.y - origin.y;

        this.x = x * c - y * s + origin.x;
        this.y = x * s + y * c + origin.y;

        return this;
    }

    normalize(){
        const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1);
        this.x *= len;
        this.y *= len;

        return this;
    }

/*     add(point){
        this.x += point.x;
        this.y += point.y;
        return this;
    }

    sub(point){
        this.x -= point.x;
        this.y -= point.y;
        return this;
    }

    multiply(point){
        this.x *= point.x;
        this.y *= point.y;
        return this;
    }

    divide(point){
        this.x /= point.x;
        this.y /= point.y;
        return this;
    } */

}

Object.defineProperties(Point.prototype, {

    isPoint: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* Line
parameter: x, y, x1, y1: Number;
attribute: x, y, x1, y1: Number;
method:
    set(x, y, x1, y1): this;                    
    containsPoint(x, y): Bool;                             //点是否在线上
    intersectPoint(line: Line, point: Point): Point;    //如果不相交则返回null, 否则返回交点Point
    isIntersect(line): Bool;                             //this与line是否相交

*/
class Line{

    constructor(x = 0, y = 0, x1 = 0, y1 = 0){
        this.x = x;
        this.y = y;
        this.x1 = x1;
        this.y1 = y1;

    }

    set(x = 0, y = 0, x1 = 0, y1 = 0){
        this.x = x;
        this.y = y;
        this.x1 = x1;
        this.y1 = y1;
        return this;
    }

    containsPoint(x, y){

        return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0;

    }

    intersectPoint(line, point){
        //解线性方程组, 求线段交点
        //如果分母为0则平行或共线, 不相交
        var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1);
        if(denominator === 0) return null;

        //线段所在直线的交点坐标 (x , y)
        const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 
        + (this.y1 - this.y) * (line.x1 - line.x) * this.x 
        - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator;

        const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 
        + (this.x1 - this.x) * (line.y1 - line.y) * this.y 
        - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator;

        //判断交点是否在两条线段上
        if(this.containsPoint(x, y) && line.containsPoint(x, y)){
            point.x = x;
            point.y = y;
            return point;
        }

        return null;
    }

    isIntersect(line){
        //快速排斥:
        //两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的

        //这里的确如此,这一步是判定两矩形是否相交
        //1.线段ab的低点低于cd的最高点(可能重合)
        //2.cd的最左端小于ab的最右端(可能重合)
        //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
        //4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
        //综上4个条件,两条线段组成的矩形是重合的
        //特别要注意一个矩形含于另一个矩形之内的情况
        if(!(Math.min(this.x,this.x1)<=Math.max(line.x,line.x1) && Math.min(line.y,line.y1)<=Math.max(this.y,this.y1) && Math.min(line.x,line.x1)<=Math.max(this.x,this.x1) && Math.min(this.y,this.y1)<=Math.max(line.y,line.y1))) return false;

        //跨立实验:
        //如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
        //也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
        var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y),
        v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y),
        w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y),
        z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y);
        
        return u*v <= 0.00000001 && w*z <= 0.00000001;
    }

}




/* ShapeRect (一般2d矩形的原点在左上, 此矩形类的原点在中间)
parameter: 
    width = 0, height = 0

attribute: 
    width, height: Number;     //矩形的宽高
    position: Point;        //位置(是旋转和缩放的中心点)
    rotation: Number;        //旋转(绕 Z 轴旋转的弧度)
    scale: Point;            //缩放

method:
    setFromBox(box): this;                //Box 转为 ShapeRect
    compute(): undefined;                //计算出矩形
    applyCanvas(context): undefined;    //矩形应用到画布的上下文中

demo:
    const canvasRect = new ShapeRect(100, 150); console.log(canvasRect);

    const canvas = document.createElement("canvas");
    canvas.width = WORLD.width;
    canvas.height = WORLD.height;
    canvas.style = `
        position: absolute;
        z-index: 9999;
        background: rgb(127,127,127);
    `;
    document.body.appendChild(canvas);

    canvasRect.position.set(300, 300);
    canvasRect.rotation = UTILS.toAngle(45);
    canvasRect.scale.set(1.5, 1.5);
    canvasRect.compute();

    const context = canvas.getContext("2d");
    canvasRect.applyCanvas(context);

    context.strokeStyle = "red";
    context.stroke();
*/
class ShapeRect{

    #dots = [];

    constructor(width = 0, height = 0){
        this.width = width;
        this.height = height;
        this.position = new Point();
        this.rotation = 0;
        this.scale = new Point(1, 1);
    }

    setFromBox(box){
        this.width = box.w;
        this.height = box.h;
        this.position.set(box.cx, box.cy);
        return this;
    }

    compute(){
        //scale
        const width = this.width * this.scale.x, 
        height = this.height * this.scale.y,

        //position
        minX = this.position.x - width / 2, 
        minY = this.position.y - height / 2,
        maxX = minX + width, 
        maxY = minY + height,
        
        //rotation
        point = UTILS.emptyPoint;
        this.#dots.length = 0;

        point.set(minX, minY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);

        point.set(maxX, minY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);

        point.set(maxX, maxY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);

        point.set(minX, maxY).rotate(this.position, this.rotation);
        this.#dots.push(point.x, point.y);
    }

    applyCanvas(context){
        context.beginPath();
        context.moveTo(this.#dots[0], this.#dots[1]);

        for(let k = 2, len = this.#dots.length; k < len; k += 2){
            context.lineTo(this.#dots[k], this.#dots[k + 1]);
        }

        context.closePath();
    }

}




/* Box 矩形

parameter: 
    x = 0, y = 0, w = 0, h = 0;

attribute:
    x,y: Number; 位置
    w,h: Number; 大小

    只读
    mx, my: Number; //

method:
    set(x, y, w, h): this;
    pos(x, y): this; //设置位置
    size(w, h): this; //设置大小
    setFromShapeRect(shapeRect): this;            //ShapeRect 转为 Box (忽略 ShapeRect 的旋转和缩放)
    setFromCircle(circle, inner: Bool): this;    //
    toArray(array: Array, index: Integer): this;
    copy(box): this;                             //复制
    clone(): Box;                                  //克隆
    center(box): this;                            //设置位置在box居中
    distance(x, y): Number;                     //左上角原点 与 x,y 的直线距离
    isEmpty(): Boolean;                         //.w.h是否小于等于零
    maxX(): Number;                             //返回 max x(this.x+this.w);
    maxY(): Number;                             //返回 max y(this.y+this.h);
    expand(box): undefined;                     //扩容; 把box合并到this
    equals(box): Boolean;                         //this与box是否恒等
    intersectsBox(box): Boolean;                 //box与this是否相交(box在this内部也会返回true)
    containsPoint(x, y): Boolean;                 //x,y点是否在this内
    containsBox(box): Boolean;                    //box是否在this内(只是相交返回fasle)
    computeOverflow(b: Box, r: Box): undefined;    //this相对b超出的部分赋值到r; r的size小于或等于0的话说明完全超出
    
*/
class Box{

    get mx(){
        return this.x + this.w;
    }

    get my(){
        return this.y + this.h;
    }

    get cx(){
        return this.w / 2 + this.x;
    }

    get cy(){
        return this.h / 2 + this.y;
    }

    constructor(x = 0, y = 0, w = 0, h = 0){
        //this.set(x, y, w, h);
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }
    
    set(x, y, w, h){
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        return this;
    }

    pos(x, y){
        this.x = x;
        this.y = y;
        return this;
    }
    
    size(w, h){
        this.w = w;
        this.h = h;
        return this;
    }

    setFromShapeRect(shapeRect){
        this.width = shapeRect.width;
        this.height = shapeRect.height;
        this.x = shapeRect.position.x - this.width / 2;
        this.y = shapeRect.position.y - this.height / 2;
        return this;
    }

    setFromCircle(circle, inner = true){
        if(inner === true){
            this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x;
            this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y;
            this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x;
        }

        else{
            this.x = circle.x - circle.r;
            this.y = circle.y - circle.r;
            this.w = this.h = circle.r * 2;
        }
        return this;
    }

    setFromPolygon(polygon, inner = true){
        if(inner === true){
            console.warn('Box: 暂不支持第二个参数为true');
        }

        else{
            const len = polygon.path.length;
            let x = Infinity, y = Infinity, mx = 0, my = 0;
            for(let k = 0, v; k < len; k+=2){
                v = polygon.path[k];
                if(v < x) x = v;
                else if(v > mx) mx = v;

                v = polygon.path[k+1];
                if(v < y) y = v;
                else if(v > my) my = v;

            }

            this.set(x, y, mx - x, my - y);

        }
        return this;
    }

    toArray(array, index){
        array[index] = this.x;
        array[index+1] = this.y;
        array[index+2] = this.w;
        array[index+3] = this.h;

        return this;
    }

    copy(box){
        /* this.x = box.x;
        this.y = box.y;
        this.w = box.w;
        this.h = box.h; */
        return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h);
    }
    
    clone(){
        //return new this.constructor().copy(this);
        return Object.assign(new this.constructor(), this);
    }

    center(box){
        this.x = (box.w - this.w) / 2 + box.x;
        this.y = (box.h - this.h) / 2 + box.y;
        return this;
    }

    distance(x, y){
        return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
    }

    isEmpty(){
        return this.w <= 0 || this.h <= 0;
    }

    maxX(){
        return this.x + this.w;
    }

    maxY(){
        return this.y + this.h;
    }

    equals(box){
        return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h;
    }

    expand(box){
        var v = Math.min(this.x, box.x);
        this.w = Math.max(this.x + this.w - v, box.x + box.w - v);
        this.x = v;

        v = Math.min(this.y, box.y);
        this.h = Math.max(this.y + this.h - v, box.y + box.h - v);
        this.y = v;
    }

    intersectsBox(box){
        return box.x + box.w < this.x || box.x > this.x + this.w || box.y + box.h < this.y || box.y > this.y + this.h ? false : true;
    }

    containsPoint(x, y){
        return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true;
    }

    containsBox(box){
        return this.x <= box.x && box.x + box.w <= this.x + this.w && this.y <= box.y && box.y + box.h <= this.y + this.h;
    }

    computeOverflow(p, r){
        r["copy"](this);
        
        if(this["x"] < p["x"]){
            r["x"] = p["x"];
            r["w"] -= p["x"] - this["x"];
        }

        if(this["y"] < p["y"]){
            r["y"] = p["y"];
            r["h"] -= p["y"] - this["y"];
        }

        var m = p["x"] + p["w"];
        if(r["x"] + r["w"] > m) r["w"] = m - r["x"];

        m = p["y"] + p["h"];
        if(r["y"] + r["h"] > m) r["h"] = m - r["y"];
    }
    
}

Object.defineProperties(Box.prototype, {

    isBox: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    },

});




/* Circle 圆形
parameter:
attribute:
    x,y: Number; 中心点
    r: Number; 半径

    //只读
    r2: Number; //返回直径 r*2

method:
    set(x, y, r): this;
    pos(x, y): this;
    copy(circle: Circle): this;
    clone(): Circle;
    distance(x, y): Number;
    equals(circle: Circle): Bool;
    containsPoint(x, y): Bool; 
    intersectsCircle(circle: Circle): Bool;
    intersectsBox(box: Box): Bool;
    setFromBox(box, inner = true): this;

*/
class Circle{

    get r2(){
        return this.r * 2;
    }

    constructor(x = 0, y = 0, r = -1){
        //this.set(0, 0, -1);
        this.x = x;
        this.y = y;
        this.r = r;
    }

    set(x, y, r){
        this.x = x;
        this.y = y;
        this.r = r;

        return this;
    }

    setFromBox(box, world = true, inner = true){
        this.x = box.w / 2 + (world === true ? box.x : 0);
        this.y = box.h / 2 + (world === true ? box.y : 0);
        this.r = inner === true ? Math.min(box.w, box.h) / 2 : UTILS.distance(0, 0, box.w, box.h) / 2;
    
        return this;
    }

    toArray(array, index){
        array[index] = this.x;
        array[index+1] = this.y;
        array[index+2] = this.r;
        
        return this;
    }

    pos(x, y){
        this.x = x;
        this.y = y;

        return this;
    }

    copy(circle){
        this.r = circle.r;
        this.x = circle.x;
        this.y = circle.y;

        return this;
    }

    clone(){

        return new this.constructor().copy(this);

    }

    distance(x, y){
        
        return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));

    }

    equals(circle){

        return circle.x === this.x && circle.y === this.y && circle.r === this.r;

    }

    containsPoint(x, y){

        return (Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2) <= Math.pow(this.r, 2));

    }

    intersectsCircle(circle){

        return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2));

    }

    intersectsBox(box){
        
        return (Math.pow(Math.max(box.x, Math.min(box.x + box.w, this.x)) - this.x, 2) + Math.pow(Math.max(box.y, Math.min(box.y + box.h, this.y)) - this.y, 2) <= Math.pow(this.r, 2));
    
    }

}

Object.defineProperties(Circle.prototype, {

    isCircle: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* Polygon 多边形

parameter: 
    path: Array[x, y];

attribute:

    //只读属性
    path: Array[x, y]; 

method:
    add(x, y): this;             //x,y添加至path;
    containsPoint(x, y): Bool;    //x,y是否在多边形的内部(注意: 在路径上也返回 true)
    
*/
class Polygon{

    #position = null;
    #path2D = null;

    get path(){
        
        return this.#position;

    }

    constructor(path = []){
        this.#position = path;

        this.#path2D = new Path2D();
        
        var len = path.length;
        if(len >= 2){
            if(len % 2 !== 0){
                len -= 1;
                path.splice(len, 1);
            }

            const con = this.#path2D;
            con.moveTo(path[0], path[1]);
            for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]);
            
        }

    }

    add(x, y){
        this.#position.push(x, y);
        this.#path2D.lineTo(x, y);
        return this;
    }

    containsPoint(x, y){
        
        return UTILS.emptyContext.isPointInPath(this.#path2D, x, y);

    }

    isInPolygon(checkPoint, polygonPoints) {
        var counter = 0;
        var i;
        var xinters;
        var p1, p2;
        var pointCount = polygonPoints.length;
        p1 = polygonPoints[0];
        for (i = 1; i <= pointCount; i++) {
            p2 = polygonPoints[i % pointCount];
            if (
                checkPoint[0] > Math.min(p1[0], p2[0]) &&
                checkPoint[0] <= Math.max(p1[0], p2[0])
            ) {
                if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
                    if (p1[0] != p2[0]) {
                        xinters =
                            (checkPoint[0] - p1[0]) *
                                (p2[1] - p1[1]) /
                                (p2[0] - p1[0]) +
                            p1[1];
                        if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
                            counter++;
                        }
                    }
                }
            }
            p1 = p2;
        }
        if (counter % 2 == 0) {
            return false;
        } else {
            return true;
        }
    }

    containsPolygon(polygon){
        const path = polygon.path, len = path.length;
        for(let k = 0; k < len; k += 2){
            if(this.containsPoint(path[k], path[k+1]) === false) return false;
        }

        return true;
    }

    toPoints(){
        const path = this.path, len = path.length, result = [];
        
        for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));

        return result;
    }

    toLines(){
        const path = this.path, len = path.length, result = [];
        
        for(let k = 0, x = NaN, y; k < len; k += 2){

            if(isNaN(x)){
                x = path[k];
                y = path[k+1];
                continue;
            }

            const line = new Line(x, y, path[k], path[k+1]);
            
            x = line.x1;
            y = line.y1;

            result.push(line);

        }

        return result;
    }

    merge(polygon){

        const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [],
        
        pointA = new Point(), pointB = new Point(),

        forEachNodes = (pathA, pathB, funcA = null, funcB = null) => {
            for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){
                if(funcA !== null) funcA(pathA[k]);

                for(let i = 0; i < lenB; i++){
                    if(funcB !== null) funcB(pathB[i], pathA[k]);
                }
    
            }
        }

        if(this.containsPolygon(polygon)){console.log('this -> polygon');
            forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => {
                if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone());
            });

            return newLines;
        }

        //收集所有的交点 (保存至 line)
        forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => {
            if(lineB.nodes === undefined) lineB.nodes = [];
            if(lineA.intersectPoint(lineB, pointA) === pointA){
                const node = {
                    lineA: lineA, 
                    lineB: lineB, 
                    point: pointA.clone(),
                    disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)),
                    disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)),
                }
                lineA.nodes.push(node);
                lineB.nodes.push(node);
                nodes.push(node);
            }
        });

        //交点以原点为目标排序
        for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr);
        for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr);

        var _loopTypeA, _loopTypeB;
        const result_loop = {
            lines: null,
            loopType: '',
            line: null,
            count: 0,
            indexed: 0,
        },
        
        //遍历某条线
        loop = (lines, index, loopType) => {
            const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this;
        
            var line, i = 1;
            while(true){
                if(loopType === 'next') index = index === length - 1 ? 0 : index + 1;
                else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1;
                line = lines[index];

                result_loop.count = line.nodes.length;
                if(result_loop.count !== 0){
                    result_loop.lines = lines;
                    result_loop.loopType = loopType;
                    result_loop.line = line;
                    result_loop.indexed = index;
                    if(loopType === 'next') addLine(line, model);

                    return result_loop;
                }
                
                addLine(line, model);
                if(indexed === i++) break;

            }
            
        },

        //更新或创建交点的索引
        setNodeIndex = (lines, index, loopType) => {
            const line = lines[index], count = line.nodes.length;
            if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB;

            if(loopType === undefined) return;
            
            if(line.nodeIndex === undefined){
                line.nodeIndex = loopType === 'next' ? 0 : count - 1;
                line.nodeState = count === 1 ? 'end' : 'start';
            
            }

            else{
                if(line.nodeState === 'end' || line.nodeState === ''){
                    line.nodeState = '';
                    return;
                }

                if(loopType === 'next'){
                    line.nodeIndex += 1;

                    if(line.nodeIndex === count - 1) line.nodeState = 'end';
                    else line.nodeState = 'run';
                }

                else if(loopType === 'back'){
                    line.nodeIndex -= 1;

                    if(line.nodeIndex === 0) line.nodeState = 'end';
                    else line.nodeState = 'run';
                }

            }

        },

        //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端;
        getLoopType = (lines, index, nodePoint) => {
            const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1],

            model = lines === linesA ? polygon : this,
            isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false,
            isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false;
            
            if(isLineBack && isLineNext){
                const len = line.nodes.length;
                if(len >= 2){
                    if(line.nodes[len - 1].point.equals(nodePoint)) return 'next';
                    else if(line.nodes[0].point.equals(nodePoint)) return 'back';
                }
                
                else console.warn('路径复杂', line);
                
            }

            else if(isLineNext){
                return 'next';
            }

            else if(isLineBack){
                return 'back';
            }

            return '';
        },

        //添加线至新的形状数组
        addLine = (line, model) => {
            //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line);
            if(line.nodes.length === 0) newLines.push(line);
            else if(model.containsPoint(line.x, line.y) === false) newLines.push(line);
            
        },

        //处理拥有交点的线
        computeNodes = v => {
            if(v === undefined || v.count === 0) return;
            
            setNodeIndex(v.lines, v.indexed, v.loopType);
        
            //添加交点
            const node = v.line.nodes[v.line.nodeIndex];
            if(newLines.includes(node.point) === false) newLines.push(node.point);
            else return;

            var lines = v.lines === linesA ? linesB : linesA, 
            line = lines === linesA ? node.lineA : node.lineB, 
            index = lines.indexOf(line);

            setNodeIndex(lines, index);
        
            //选择交点状态
            var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState;
            if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){
                if(line.nodeState === 'start'){
                    const backLine = v.loopType === 'next' ? v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1] : v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1];
                    
                    if(newLines.includes(backLine) && backLine.nodes.length === 0){
                        nodeState = 'run';
                    }

                }
                else if(line.nodeState === 'end'){
                    const nextLine = v.loopType === 'next' ? v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1] : v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1];
                    const model = v.lines === linesA ? polygon : this;
                    if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){
                        nodeState = 'run';
                    }
                    
                }
            }

            switch(nodeState){

                //不跳线
                case 'run': 
                    if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this);
                    return computeNodes(loop(v.lines, v.indexed, v.loopType));

                //跳线
                case 'start': 
                case 'end': 
                    const loopType = getLoopType(lines, index, node.point);
                    if(loopType !== ''){
                        if(lines === linesA) _loopTypeA = loopType;
                        else _loopTypeB = loopType;
                        if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon);
                        return computeNodes(loop(lines, index, loopType));
                    }
                    break;

            }

        }
        
        //获取介入点
        var startLine = null;
        for(let k = 0, len = nodes.length, node; k < len; k++){
            node = nodes[k];
            if(node.lineA.nodes.length !== 0){
                startLine = node.lineA;
                if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){
                    startLine = node.lineB.nodes[0].lineB;
                    result_loop.lines = linesB;
                    result_loop.loopType = _loopTypeB = 'next';
                }
                else{
                    result_loop.lines = linesA;
                    result_loop.loopType = _loopTypeA = 'next';
                }
                result_loop.line = startLine;
                result_loop.count = startLine.nodes.length;
                result_loop.indexed = result_loop.lines.indexOf(startLine);
                break;
            }
        }

        if(startLine === null){
            console.warn('Polygon: 找不到介入点, 终止了合并');
            return newLines;
        }

        computeNodes(result_loop);
    
        return newLines;
    }

}




/* RGBColor
parameter: 
    r, g, b

method:
    set(r, g, b: Number): this;            //rgb: 0 - 255; 第一个参数可以为 css color
    setFormHex(hex: Number): this;         //
    setFormHSV(h, s, v: Number): this;    //h:0-360; s,v:0-100; 颜色, 明度, 暗度
    setFormString(str: String): Number;    //str: 英文|css color; 返回的是透明度 (如果为 rgba 则返回a; 否则总是返回1)

    copy(v: RGBColor): this;
    clone(): RGBColor;

    getHex(): Number;
    getHexString(): String;
    getHSV(result: Object{h, s, v}): result;    //result: 默认是一个新的Object
    getRGBA(alpha: Number): String;             //alpha: 0 - 1; 默认 1
    getStyle()                                     //.getRGBA()别名

    stringToColor(str: String): String; //str 转为 css color; 如果str不是color格式则返回 ""

*/
class RGBColor{

    constructor(r = 255, g = 255, b = 255){
        this.r = r;
        this.g = g;
        this.b = b;

    }

    copy(v){
        this.r = v.r;
        this.g = v.g;
        this.b = v.b;
        return this;
    }

    clone(){
        return new this.constructor().copy(this);
    }

    set(r, g, b){
        if(typeof r !== "string"){
            this.r = r || 255;
            this.g = g || 255;
            this.b = b || 255;
        }

        else this.setFormString(r);
        
        return this;
    }

    setFormHex(hex){
        hex = Math.floor( hex );

        this.r = hex >> 16 & 255;
        this.g = hex >> 8 & 255;
        this.b = hex & 255;
        return this;
    }

    setFormHSV(h, s, v){
        h = h >= 360 ? 0 : h;
        var s=s/100;
        var v=v/100;
        var h1=Math.floor(h/60) % 6;
        var f=h/60-h1;
        var p=v*(1-s);
        var q=v*(1-f*s);
        var t=v*(1-(1-f)*s);
        var r,g,b;
        switch(h1){
            case 0:
                r=v;
                g=t;
                b=p;
                break;
            case 1:
                r=q;
                g=v;
                b=p;
                break;
            case 2:
                r=p;
                g=v;
                b=t;
                break;
            case 3:
                r=p;
                g=q;
                b=v;
                break;
            case 4:
                r=t;
                g=p;
                b=v;
                break;
            case 5:
                r=v;
                g=p;
                b=q;
                break;
        }

        this.r = Math.round(r*255);
        this.g = Math.round(g*255);
        this.b = Math.round(b*255);
        return this;
    }

    setFormString(color){
        if(typeof color !== "string") return 1;
        var _color = this.stringToColor(color);
        
        if(_color[0] === "#"){
            const len = _color.length;
            if(len === 4){
                _color = _color.slice(1);
                this.setFormHex(parseInt("0x"+_color + "" + _color));
            }
            else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1)));
            
        }

        else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
            const arr = [];
            for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
                
                if(is === true){
                    if(_color[k] === "," || _color[k] === ")"){
                        arr.push(parseFloat(v));
                        v = "";
                    }
                    else v += _color[k];
                    
                }

                else if(_color[k] === "(") is = true;
                
            }

            this.set(arr[0], arr[1], arr[2]);
            return arr[3] === undefined ? 1 : arr[3];
        }
        
        return 1;
    }

    getHex(){

        return Math.max( 0, Math.min( 255, this.r ) ) << 16 ^ Math.max( 0, Math.min( 255, this.g ) ) << 8 ^ Math.max( 0, Math.min( 255, this.b ) ) << 0;

    }

    getHexString(){

        return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );

    }

    getHSV(result){
        result = result || {}
        var r=this.r/255;
        var g=this.g/255;
        var b=this.b/255;
        var h,s,v;
        var min=Math.min(r,g,b);
        var max=v=Math.max(r,g,b);
        var l=(min+max)/2;
        var difference = max-min;
        
        if(max==min){
            h=0;
        }else{
            switch(max){
                case r: h=(g-b)/difference+(g < b ? 6 : 0);break;
                case g: h=2.0+(b-r)/difference;break;
                case b: h=4.0+(r-g)/difference;break;
            }
            h=Math.round(h*60);
        }
        if(max==0){
            s=0;
        }else{
            s=1-min/max;
        }
        s=Math.round(s*100);
        v=Math.round(v*100);
        result.h = h;
        result.s = s;
        result.v = v;
        return result;
    }

    getStyle(){
        return this.getRGBA(1);
    }

    getRGBA(alpha){
        alpha = typeof alpha === 'number' ? alpha : 1;
        return 'rgba('+this.r+','+this.g+','+this.b+','+alpha+')';
    }

    stringToColor(str){
        var _color = "";
        for(let k = 0, len = str.length; k < len; k++){
            if(str[k] === " ") continue;
            _color += str[k];
        }
        
        if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
        else{
            for(let k = 0, len = ColorRefTable.length; k < len; k++){
                str = ColorRefTable[k];
                if(str[0] === _color) return str[1];
            }
        }

        return "";
    }

}

Object.defineProperties(RGBColor.prototype, {

    isRGBColor: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* Timer 定时器 

parameter:
    func: Function; //定时器运行时的回调; 默认 null, 如果定义构造器将自动调用一次.restart()方法
    speed: Number; //延迟多少毫秒执行一次 func; 默认 3000;
    step: Integer; //执行多少次: 延迟speed毫秒执行一次 func; 默认 Infinity;
    
attribute:
    func, speed, step;    //这些属性可以随时更改;

    //只读属性
    readyState: String;    //定时器状态; 可能值: '', 'start', 'running', 'done'; ''表示定时器从未启动
    number: Number;        //运行的次数

method:
    start(func, speed): this;    //启动定时器 (如果定时器正在运行则什么都不会做)
    restart(): undefined;        //重启定时器
    stop(): undefined;            //停止定时器

demo:
    //每 3000 毫秒 打印一次 timer.number, 10次后停止
    new Timer(timer => {
        console.log(timer.number);
        if(timer.number === 10) timer.stop();
    }, 3000);

*/
class Timer{

    #restart = -1;
    #speed = 0;
    #isRun = false;
    #i = 0;
    #readyState = ''; //start|running

    get number(){
        return this.#i;
    }
    
    get readyState(){
        return this.#i >= this.step ? 'done' : this.#readyState;
    }

    get running(){
        return this.#isRun;
    }

    constructor(func = null, speed = 3000, step = Infinity){
        this.func = func;
        this.speed = speed;
        this.step = step;
        //this.onDone = null;
    
        if(typeof this.func === "function") this.restart();

    }

    start(func, time){
        if(typeof func === 'function') this.func = func;
        if(UTILS.isNumber(time) === true) this.speed = time;
        this.restart();

        return this;
    }

    restart(){
        if(this.#isRun === false){
            setTimeout(this._loop, this.speed);
            this.#isRun = true;
            this.#restart = -1;
            this.#i = 0;
            this.#readyState = 'start';
            
        }

        else{
            this.#restart = Date.now();
            this.#speed = this.speed;

        }

    }

    stop(){
        if(this.#isRun === true){
            this.#restart = -1;
            this.#i = this.step;
        }

    }

    _loop = () => {

        //重启计时器
        if(this.#restart !== -1){
            
            let gone = Date.now() - this.#restart;
            this.#restart = -1;
            
            if(gone >= this.#speed) gone = this.speed;
            else{
                if(this.#speed === this.speed) gone = this.#speed - gone;
                else gone = (this.#speed - gone) / this.#speed * this.speed;
            }
            
            setTimeout(this._loop, gone);
            
            this.#i = 1;
            if(this.func !== null) this.func(this);

        }

        //正在运行
        else if(this.#i < this.step){

            setTimeout(this._loop, this.speed);

            this.#i++;
            if(this.#readyState !== 'running') this.#readyState = 'running';
            if(this.func !== null) this.func(this);

        }

        //完成
        else this.#isRun = false;

    }

}




/* SeekPath A*寻路

parameter: 
    option: Object{
        angle: Number,         //8 || 16
        timeout: Number,     //单位为毫秒
        size: Number,         //每格的宽高
        lenX, lenY: Number,    //长度
        disables: Array[0||1],
        heights: Array[Number],
        path: Array[], //存放寻路结果 默认创建一个空数组
    }

attribute:
    size: Number;     //每个索引的大小
    lenX: Number;     //最大长度x (设置此属性时, 你需要重新.initMap(heights);)
    lenY: Number;     //最大长度y (设置此属性时, 你需要重新.initMap(heights);)

    //此属性已废弃 range: Box;            //本次的搜索范围, 默认: 0,0,lenX,lenY
    angle: Number;         //8四方向 或 16八方向 默认 16
    timeout: Number;     //超时毫秒 默认 500
    mapRange: Box;        //地图box
    //此属性已废弃(run函数不在检测相邻的高) maxHeight: Number;     //相邻可走的最大高 默认 6

    //只读属性
    success: Bool;            //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false)
    path: Array[x, y, z];    //存放.run()返回的路径
    map: Map;                 //地图的缓存数据

method:
    initMap(heights: Array[Number]): undefiend;     //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数
    run(x, y, x1, y1: Number): Array[x, y, z];         //参数索引坐标
    getDots(x, y, a, r): Array[ix, iy];             //获取周围的点 x,y, a:8|16, r:存放结果数组
    getLegalPoints(ix, iy, count): Array[x, y, z];    //获取 ix, iy 周围 合法的, 相邻的 count 个点

demo:
    const sp = new SeekPath({
        angle: 16,
        timeout: 500,
        //maxHeight: 6,
        size: 10,
        lenX: 1000,
        lenY: 1000,
    }),

    path = sp.run(0, 0, 1000, 1000);

    console.log(sp);

*/
class SeekPath{

    static _open = []
    static _dots = [] //.run() .getLegalPoints()
    static dots4 = []; //._check()
    static _sort = function (a, b){return a["f"] - b["f"];}

    #map = null;
    #path = null;
    #success = true;
    #halfX = 50;
    #halfY = 50;

    #size = 10;
    #lenX = 10;
    #lenY = 10;

    constructor(option = {}){
        this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向
        this.timeout = option.timeout || 500; //超时毫秒
        //this.maxHeight = option.maxHeight || 6;
        this.mapRange = new Box();
        this.size = option.size || 10;
        this.lenX = option.lenX || 10;
        this.lenY = option.lenY || 10;
        this.#path = Array.isArray(option.path) ? option.path : [];
        this.initMap(option.disable, option.height);
        option = undefined
    }

    //this.#map[x][y] = Object{height: 此点的高,默认0, is: 此点是否可走,默认1(disable)};
    get map(){
        return this.#map;
    }

    //this.#path = Array[x,y,z]
    get path(){
        return this.#path;
    }

    get success(){
        return this.#success;
    }

    get size(){
        return this.#size;
    }

    set size(v){
        this.#size = v;
        v = v / 2;
        this.#halfX = v * this.#lenX;
        this.#halfY = v * this.#lenY;
    }

    get lenX(){
        return this.#lenX;
    }

    set lenX(v){
        this.#lenX = v;
        v = this.#size / 2;
        this.#halfX = v * this.#lenX;
        this.#halfY = v * this.#lenY;
        
    }

    get lenY(){
        return this.#lenY;
    }

    set lenY(v){
        this.#lenY = v;
        v = this.#size / 2;
        this.#halfX = v * this.#lenX;
        this.#halfY = v * this.#lenY;
        
    }

    toScene(n, v){ //n = "x|y"
        //n = n === "y" ? "lenY" : "lenX";
        if(n === "y" || n === "z") return v * this.#size - this.#halfY;
        return v * this.#size - this.#halfX;
    
    }
    
    toIndex(n, v){
        //n = n === "y" ? "lenY" : "lenX";
        if(n === "y" || n === "z") return Math.round((this.#halfY + v) / this.#size);
        return Math.round((this.#halfX + v) / this.#size);

    }

    initMap(disable, height){
        
        disable = Array.isArray(disable) === true ? disable : null;
        height = Array.isArray(height) === true ? height : null;
        
        const lenX = this.lenX, lenY = this.lenY;
        var getHeight = (ix, iy) => {
            if(height === null) return 0;
            ix = height[ix * lenY + iy];
            if(ix === undefined) return 0;
            return ix;
        },
        getDisable = (ix, iy) => {
            if(disable === null) return 1;
            ix = disable[ix * lenY + iy];
            if(ix === undefined) return 0;
            return ix;
        },

        map = []//new Map();

        for(let x = 0, y, m; x < lenX; x++){
            m = []//new Map();
            for(y = 0; y < lenY; y++) m[y] = {x:x, y:y, height:getHeight(x, y), is:getDisable(x, y),  g:0, h:0, f:0, p:null, id:""}//m.set(y, {x:x, y:y, height:getHeight(x, y),   g:0, h:0, f:0, p:null, id:""});
            map[x] = m;//map.set(x, m);
        }
        
        this.#map = map;
        this._id = -1;
        this._updateID();
        this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1);

        map = disable = height = getHeight = undefined;

    }

    getLegalPoints(ix, iy, count, result = []){
        const _dots = SeekPath._dots;
        result.length = 0;
        result[0] = this.#map[ix][iy];
        count += 1;
        
        while(result.length < count){
            for(let k = 0, i, n, d, len = result.length; k < len; k++){
                n = result[k];
                this.getDots(n.x, n.y, this.angle, _dots);
                for(i = 0; i < this.angle; i += 2){
                    d = this.#map[_dots[i]][_dots[i+1]];
                    if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){
                        if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){
                            result.push(d);
                        }
                    }
                }
            }
        }
    
        result.splice(0, 1);
        return result;
    }

    getLinePoints(now, next, count, result = []){
        if(count === 1) return result[0] = next;
        if(count % 2 === 0) count += 1;

        const len = Math.floor(count / 2), 
        nowPoint = UTILS.emptyPoint, 
        angle90 = UTILS.toAngle(90);

        var i, ix, iy, n, nn = next;

        nowPoint.set(now.x, now.y).rotate(next, angle90); //now 以 next 为原点顺时针旋转 90 度
        var disX = nowPoint.x - next.x, 
        disY = nowPoint.y - next.y;
        
        for(i = 0; i < len; i++){
            ix = disX + disX * i + next.x;
            iy = disY + disY * i + next.y;

            n = this.#map[ix][iy];
            if(n.is === 1) nn = n;
            result[len-1-i] = nn;
        }

        result[len] = next;
        nn = next

        nowPoint.set(now.x, now.y).rotate(next, -angle90);
        disX = nowPoint.x - next.x; 
        disY = nowPoint.y - next.y;

        for(i = 0; i < len; i++){
            ix = disX + disX * i + next.x;
            iy = disY + disY * i + next.y;

            n = this.#map[ix][iy];
            if(n.is === 1) nn = n;
            result[len+1+i] = nn;
        }

        return result;
    }

    getDots(x, y, a, r = []){ //获取周围的点 x,y, a:8|16, r:存放结果数组
        r.length = 0;
        const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1;
        if(a === 16) r.push(x_1, y_1, x, y_1, x1, y_1, x_1, y, x1, y, x_1, y1, x, y1, x1, y1);
        else r.push(x, y_1, x, y1, x_1, y, x1, y);
    }

    _updateID(){ //更新标记
        this._id++;
        this._openID = "o_"+this._id;
        this._closeID = "c_"+this._id;
    }

    _check(dotA, dotB){ //检测 a 是否能到 b
        //获取 dotB 周围的4个点 并 遍历这4个点
        this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4);
        for(let k = 0, x, y; k < 8; k += 2){
            x = SeekPath.dots4[k]; 
            y = SeekPath.dots4[k+1];
            if(this.mapRange.containsPoint(x, y) === false) continue;

            //找出 dotA 与 dotB 相交的两个点:
            if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){
                //如果其中一个交点是不可走的则 dotA 到 dotB 不可走, 既返回 false
                if(this.#map[x][y].is === 0) return false;
            }

        }

        return true;
    }

    run(x, y, x1, y1, path = this.#path){
        path.length = 0;
        if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path;
        
        var _n = this.#map[x][y];
        if(_n.is === 0) return path;

        const _sort = SeekPath._sort,
        _open = SeekPath._open,
        _dots = SeekPath._dots, 
        time = Date.now();

        //var isDot = true, 
        var suc = _n, k, mhd, g, h, f, _d;

        _n.g = 0;
        _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 
        _n.f = _n.h;
        _n.p = null;
        this._updateID();
        _n.id = this._openID;
        _open.push(_n);
        
        while(_open.length !== 0){
            if(Date.now() - time > this.timeout) break;

            _open.sort(_sort);
            _n = _open.shift();
            if(_n.x === x1 && _n.y === y1){
                suc = _n;
                break;
            }
            
            if(suc.h > _n.h) suc = _n;
            _n.id = this._closeID;
            this.getDots(_n.x, _n.y, this.angle, _dots);
            
            for(k = 0; k < this.angle; k += 2){
                
                _d = this.#map[_dots[k]][_dots[k+1]];
                if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue;

                mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y);
                g = _n["g"] + (mhd === 1 ? 10 : 14);
                h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10;
                f = g + h;
            
                if(_d.id !== this._openID){
                    //如果是斜角和8方向:
                    if(mhd !== 1 && this.angle === 16){
                        if(this._check(_n, _d)){
                            _d.g = g;
                            _d.h = h;
                            _d.f = f;
                            _d.p = _n;
                            _d.id = this._openID;
                            _open.push(_d);
                        }
                    }else{
                        _d.g = g;
                        _d.h = h;
                        _d.f = f;
                        _d.p = _n;
                        _d.id = this._openID;
                        _open.push(_d);
                    }
                }

                else if(g < _d.g){
                    _d.g = g;
                    _d.f = g + _d.h;
                    _d.p = _n;
                }
    
            }
        }

        this.#success = suc === _n;

        while(suc !== null){
            path.unshift(suc);
            //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"]));
            suc = suc["p"];
        }

        _open.length = _dots.length = 0;
        
        return path;
    }

}




/* RunningList
    如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), 而且还会漏掉一个key;

*/
class RunningList{

    /* static getProxy(runName){

        return new Proxy(new RunningList(runName), {

            get(tar, key){
                
            },

            set(tar, key, val){
                
            }
            
        });

    } */

    constructor(runName = 'update'){
        this._running = false;
        this._list = [];
        this._delList = [];
        this._runName = runName;
    }

    get length(){

        return this._list.length;

    }

    add(v){

        if(!this._list.includes(v)) this._list.push(v);

    }

    clear(){
        this._list.length = 0;
        this._delList.length = 0;
    }

    push(...v){

        v.forEach(_v => this._list.push(_v));

    }

    splice(v){
        if(this._running === true){
            if(!this._delList.includes(v)) this._delList.push(v);
        }

        else{
            const i = this._list.indexOf(v);
            if(i !== -1) this._list.splice(i, 1);
        }

    }

    update(){

        var k, len = this._list.length;

        this._running = true;
        if(this._runName !== ''){
            for(k = 0; k < len; k++) this._list[k][this._runName]();
        }else{
            for(k = 0; k < len; k++) this._list[k]();
        }
        this._running = false;

        var i;
        len = this._delList.length;
        for(k = 0; k < len; k++){
            //this.splice(this._delList[k]);
            i = this._list.indexOf(this._delList[k]);
            if(i !== -1) this._list.splice(i, 1);
        }
        this._delList.length = 0;
        
    }

}




/* TweenValue (从 原点 以规定的时间到达  终点)

parameter: origin, end, time, onUpdate, onEnd;

attribute:
    origin: Object; //原点(起点)
    end: Object; //终点
    time: Number; //origin 到 end 花费的时间
    onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null;
    onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是"restart"将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间)

method:
    reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性
    reverse(): undefined; //this.end 复制 this.origin 的原始值
    update(): undefined; //Tween 通过此方法统一更新 TweenValue

demo: 
    //init Tween:
    const tween = new Tween(),
    animate = function (){
        requestAnimationFrame(animate);
        tween.update();
    }

    //init TweenValue:
    const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
    
    animate();
    tween.start(v1);

*/
class TweenValue{

    constructor(origin = {}, end = {}, time = 500, onUpdate = null, onEnd = null, onStart = null){
        this.origin = origin;
        this.end = end;
        this.time = time;

        this.onUpdate = onUpdate;
        this.onEnd = onEnd;
        this.onStart = onStart;
        
        //以下属性不能直接设置
        this._r = null;
        this._t = 0;
        this._v = Object.create(null);

    }

    _start(){
        var v = "";
        for(v in this.origin) this._v[v] = this.origin[v];

        this._t = Date.now();
        //this.update();

    }

    reset(origin, end){
        this.origin = origin;
        this.end = end;
        this._v = Object.create(null);

    }

    reverse(){
        var n = "";
        for(n in this.origin) this.end[n] = this._v[n];

    }

    update(){

        if(this["_r"] !== null){

            var ted = Date["now"]() - this["_t"];

            if(ted >= this["time"]){

                for(ted in this["origin"]) this["origin"][ted] = this["end"][ted];

                if(this["onEnd"] !== null){

                    if(this["onEnd"](this) === "restart"){
                        if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
                        this["_start"]();
                    }

                    else this["_r"]["stop"](this);
                    
                }

                else this["_r"]["stop"](this);

            }

            else{
                ted = ted / this["time"];
                let n = "";
                for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n];
                if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
            }

        }

    }

}

Object.defineProperties(TweenValue.prototype, {

    isTweenValue: {
        configurable: false,
        enumerable: false,
        writable: false,
        value: true,
    }

});




/* TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween)

demo:
    const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)),
    animate = function (){
        requestAnimationFrame(animate);
        v1.update();
    }

    animate();
    v1.start();

*/
class TweenAlone extends TweenValue{

    constructor(origin, end, time, onUpdate, onEnd, onStart){
        super(origin, end, time, onUpdate, onEnd, onStart);
        
    }

    start(){
        if(this.onStart !== null) this.onStart();
        this._r = this;
        this._start();

    }

    stop(){
        this._r = null;
        
    }

}





/* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue)

parameter:
attribute:
method:
    start(value: TweenValue): undefined;
    stop(value: TweenValue): undefined;

static:
    Value: TweenValue;

demo:
    //init Tween:
    const tween = new Tween(),
    animate = function (){
        requestAnimationFrame(animate);
        tween.update();
    }

    //init TweenValue:
    const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
        v2.reverse(); //v2.end 复制起始值
        return "restart"; //返回"restart"表示不删除队列, 需要继续补间
    });
    
    animate();
    tween.start(v2);

*/
class Tween extends RunningList{

    static Value = TweenValue;

    constructor(){
        super();

    }

    start(value){
        if(value.onStart !== null) value.onStart();
        if(value._r === null) this.push(value);
        value._r = this;
        value._start(this);

    }

    stop(value){
        if(value._r !== null) this.splice(value);
        value._r = null;
        
    }

}




/* TweenTarget 朝着轴插值(有效的跟踪动态目标, 注意此类需要配合 RunningList 类使用, 因为此类在任何情况下都没有阻止你调用.update()方法)
parameter:    
    v1 = {x: 0}, 
    v2 = {x: 100}, 
    distance = 1,        //每次移动的距离
    onUpdate = null,    //
    onEnd = null

attribute:
    v1: Object;             //起点
    v2: Object;             //终点
    onUpdate: Function;        //
    onEnd: Function;         //

method:
    update(): undefined;                        //一般在动画循环里执行此方法
    updateAxis(): undefined;                     //更新v1至v2的方向轴 (初始化时构造器自动调用一次)
    setDistance(distance: Number): undefined;     //设置每次移动的距离 (初始化时构造器自动调用一次)

demo:
    const ttc = new TweenTarget({x:0, y:0}, {x:100, y:100}, 10),

    //计时器模拟动画循环函数, 每秒执行一次.update()
    timer = new Timer(() => {
        ttc.update();
        console.log('update: ', ttc.v1);

    }, 1000, Infinity);

    ttc.onEnd = v => {
        timer.stop();
        console.log('end: ', v);
    }

    timer.start();
    console.log(ttc);

*/
class TweenTarget{

    #distance = 1;
    #distancePow2 = 1;
    #axis = {};

    constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){
        this.v1 = v1;
        this.v2 = v2;
        this.onUpdate = onUpdate;
        this.onEnd = onEnd;
    
        this.setDistance(distance);
        this.updateAxis();
    }

    setDistance(v = 1){
        this.#distance = v;
        this.#distancePow2 = Math.pow(v, 2);
    }

    updateAxis(){
        var n, v, len = 0;

        for(n in this.v1){
            v = this.v2[n] - this.v1[n];
            len += v * v;
            this.#axis[n] = v;

        }

        len = Math.sqrt(len);

        if(len !== 0){
            
            for(n in this.v1) this.#axis[n] *= 1 / len;

        }
    }

    update(){
        var n, len = 0;

        for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2);

        if(len > this.#distancePow2){

            for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance;
            if(this.onUpdate !== null) this.onUpdate(this.v1);

        }

        else{

            for(n in this.v1) this.v1[n] = this.v2[n];
            if(this.onEnd !== null) this.onEnd(this.v1);
            
        }
    }

}




/* EventDispatcher 自定义事件管理器
parameter: 
attribute: 

method:
    clearEvents(eventName): undefined;             //清除eventName列表, 如果 eventName 未定义清除所有事件
    customEvents(eventName, eventParam): this;    //创建自定义事件 eventParam 可选 默认{}
    getParam(eventName): eventParam;
    trigger(eventName, callback): undefined;    //触发 (callback: 可选)
    register(eventName, callback): undefined;    //
    deleteEvent(eventName, callback): undefined; //

demo:
    const eventDispatcher = new EventDispatcher();
    eventDispatcher.customEvents("test", {name: "test"});

    eventDispatcher.register("test", eventParam => {
        console.log(eventParam) //Object{name: "test"}
    });

    eventDispatcher.trigger("test");

*/
class EventDispatcher{
    
    constructor(){
        this._eventsList = {};
        this.__eventsList = [];
        this.__trigger = "";

    }

    clearEvents(eventName){ 

        //if(this.__trigger === eventName) return console.warn("EventDispatcher: 清除事件失败");
        if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = []

        else this._eventsList = {}

    }
    
    customEvents(eventName, eventParam = {}){ 
        //if(typeof eventName !== "string") return console.warn("EventDispatcher: 注册自定义事件失败");
        if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在");
        //eventParam = eventParam || {}
        //if(eventParam.type === undefined) eventParam.type = eventName;
        this._eventsList[eventName] = {func: [], param: eventParam}
        return this;
    }

    getParam(eventName){
        return this._eventsList[eventName]["param"];
    }
    
    trigger(eventName, callback){
        //if(this._eventsList[eventName] === undefined) return;
        
        const obj = this._eventsList[eventName];
        var k, len = obj.func.length;

        if(len !== 0){
            if(typeof callback === "function") callback(obj["param"]); //更新参数(eventParam)

            //触发过程(如果触发过程中删除事件, 不仅 len 没有变, 而且还会漏掉一个key, 所以在触发过程中删除的事件要特殊处理)
            this.__trigger = eventName;
            for(k = 0; k < len; k++) obj["func"][k](obj["param"]);
            this.__trigger = "";
            //触发过程结束
            
            //删除在触发过程中要删除的事件
            len = this.__eventsList.length;
            for(k = 0; k < len; k++) this.deleteEvent(eventName, this.__eventsList[k]);
            this.__eventsList.length = 0;
    
        }

    }
    
    register(eventName, callback){
        if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
        const obj = this._eventsList[eventName];
        //if(obj.func.includes(callback) === false) obj.func.push(callback);
        //else console.warn("EventDispatcher: 回调函数重复");
        obj.func.push(callback);
    }
    
    deleteEvent(eventName, callback){
        if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
        
        if(this.__trigger === eventName) this.__eventsList.push(callback);
        else{
            const obj = this._eventsList[eventName], i = obj.func.indexOf(callback);
            if(i !== -1) obj.func.splice(i, 1);
        }
        
    }

}




export {
    UTILS, 
    ColorRefTable, 
    Ajax, 
    IndexedDB, 
    TreeStruct, 
    Point, 
    Line, 
    Box, 
    Circle, 
    Polygon, 
    RGBColor, 
    Timer, 
    SeekPath, 
    RunningList, 
    TweenValue, 
    TweenAlone, 
    Tween, 
    TweenTarget, 
    EventDispatcher, 
    ShapeRect,
}

 

posted @ 2022-09-02 23:00  鸡儿er  阅读(40)  评论(0编辑  收藏  举报