• 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,
    }
  • 相关阅读:
    k8s-[排查记录]解决节点无法查看pod日志
    k8s kube-proxy模式
    容器网络
    k8s-使用kubeadm安装集群
    k8s-Deployment重启方案
    k8s-NetworkPolicy-网络策略
    nodejs 解析终端特殊字符
    fluentd 日志自定义字段解析
    题目笔记 CF 1494b
    CF1225D Power Products(分解质因子 哈希)
  • 原文地址:https://www.cnblogs.com/weihexinCode/p/16619798.html
Copyright © 2020-2023  润新知