ThreeJS(javascript 3D库) 一些常用的功能

   1 "use strict";
   2 
   3 var __emptyPoint = null, __emptyContext = null, __emptyPointA = null;
   4 
   5 const ColorRefTable = {
   6     "aliceblue": "#f0f8ff",
   7     "antiquewhite": "#faebd7",
   8     "aqua": "#00ffff",
   9     "aquamarine": "#7fffd4",
  10     "azure": "#f0ffff",
  11     "beige": "#f5f5dc",
  12     "bisque": "#ffe4c4",
  13     "black": "#000000",
  14     "blanchedalmond": "#ffebcd",
  15     "blue": "#0000ff",
  16     "blueviolet": "#8a2be2",
  17     "brown": "#a52a2a",
  18     "burlywood": "#deb887",
  19     "cadetblue": "#5f9ea0",
  20     "chartreuse": "#7fff00",
  21     "chocolate": "#d2691e",
  22     "coral": "#ff7f50",
  23     "cornsilk": "#fff8dc",
  24     "crimson": "#dc143c",
  25     "cyan": "#00ffff",
  26     "darkblue": "#00008b",
  27     "darkcyan": "#008b8b",
  28     "darkgoldenrod": "#b8860b",
  29     "darkgray": "#a9a9a9",
  30     "darkgreen": "#006400",
  31     "darkgrey": "#a9a9a9",
  32     "darkkhaki": "#bdb76b",
  33     "darkmagenta": "#8b008b",
  34     "firebrick": "#b22222",
  35     "darkolivegreen": "#556b2f",
  36     "darkorange": "#ff8c00",
  37     "darkorchid": "#9932cc",
  38     "darkred": "#8b0000",
  39     "darksalmon": "#e9967a",
  40     "darkseagreen": "#8fbc8f",
  41     "darkslateblue": "#483d8b",
  42     "darkslategray": "#2f4f4f",
  43     "darkslategrey": "#2f4f4f",
  44     "darkturquoise": "#00ced1",
  45     "darkviolet": "#9400d3",
  46     "deeppink": "#ff1493",
  47     "deepskyblue": "#00bfff",
  48     "dimgray": "#696969",
  49     "dimgrey": "#696969",
  50     "dodgerblue": "#1e90ff",
  51     "floralwhite": "#fffaf0",
  52     "forestgreen": "#228b22",
  53     "fuchsia": "#ff00ff",
  54     "gainsboro": "#dcdcdc",
  55     "ghostwhite": "#f8f8ff",
  56     "gold": "#ffd700",
  57     "goldenrod": "#daa520",
  58     "gray": "#808080",
  59     "green": "#008000",
  60     "greenyellow": "#adff2f",
  61     "grey": "#808080",
  62     "honeydew": "#f0fff0",
  63     "hotpink": "#ff69b4",
  64     "indianred": "#cd5c5c",
  65     "indigo": "#4b0082",
  66     "ivory": "#fffff0",
  67     "khaki": "#f0e68c",
  68     "lavender": "#e6e6fa",
  69     "lavenderblush": "#fff0f5",
  70     "lawngreen": "#7cfc00",
  71     "lemonchiffon": "#fffacd",
  72     "lightblue": "#add8e6",
  73     "lightcoral": "#f08080",
  74     "lightcyan": "#e0ffff",
  75     "lightgoldenrodyellow": "#fafad2",
  76     "lightgray": "#d3d3d3",
  77     "lightgreen": "#90ee90",
  78     "lightgrey": "#d3d3d3",
  79     "lightpink": "#ffb6c1",
  80     "lightsalmon": "#ffa07a",
  81     "lightseagreen": "#20b2aa",
  82     "lightskyblue": "#87cefa",
  83     "lightslategray": "#778899",
  84     "lightslategrey": "#778899",
  85     "lightsteelblue": "#b0c4de",
  86     "lightyellow": "#ffffe0",
  87     "lime": "#00ff00",
  88     "limegreen": "#32cd32",
  89     "linen": "#faf0e6",
  90     "magenta": "#ff00ff",
  91     "maroon": "#800000",
  92     "mediumaquamarine": "#66cdaa",
  93     "mediumblue": "#0000cd",
  94     "mediumorchid": "#ba55d3",
  95     "mediumpurple": "#9370db",
  96     "mediumseagreen": "#3cb371",
  97     "mediumslateblue": "#7b68ee",
  98     "mediumspringgreen": "#00fa9a",
  99     "mediumturquoise": "#48d1cc",
 100     "mediumvioletred": "#c71585",
 101     "midnightblue": "#191970",
 102     "mintcream": "#f5fffa",
 103     "mistyrose": "#ffe4e1",
 104     "moccasin": "#ffe4b5",
 105     "navajowhite": "#ffdead",
 106     "navy": "#000080",
 107     "oldlace": "#fdf5e6",
 108     "olive": "#808000",
 109     "olivedrab": "#6b8e23",
 110     "orange": "#ffa500",
 111     "orangered": "#ff4500",
 112     "orchid": "#da70d6",
 113     "palegoldenrod": "#eee8aa",
 114     "palegreen": "#98fb98",
 115     "paleturquoise": "#afeeee",
 116     "palevioletred": "#db7093",
 117     "papayawhip": "#ffefd5",
 118     "peachpuff": "#ffdab9",
 119     "peru": "#cd853f",
 120     "pink": "#ffc0cb",
 121     "plum": "#dda0dd",
 122     "powderblue": "#b0e0e6",
 123     "purple": "#800080",
 124     "red": "#ff0000",
 125     "rosybrown": "#bc8f8f",
 126     "royalblue": "#4169e1",
 127     "saddlebrown": "#8b4513",
 128     "salmon": "#fa8072",
 129     "sandybrown": "#f4a460",
 130     "seagreen": "#2e8b57",
 131     "seashell": "#fff5ee",
 132     "sienna": "#a0522d",
 133     "silver": "#c0c0c0",
 134     "skyblue": "#87ceeb",
 135     "slateblue": "#6a5acd",
 136     "slategray": "#708090",
 137     "slategrey": "#708090",
 138     "snow": "#fffafa",
 139     "springgreen": "#00ff7f",
 140     "steelblue": "#4682b4",
 141     "tan": "#d2b48c",
 142     "teal": "#008080",
 143     "thistle": "#d8bfd8",
 144     "tomato": "#ff6347",
 145     "turquoise": "#40e0d0",
 146     "violet": "#ee82ee",
 147     "wheat": "#f5deb3",
 148     "white": "#ffffff",
 149     "whitesmoke": "#f5f5f5",
 150     "yellow": "#ffff00",
 151     "yellowgreen": "#9acd32"
 152 },
 153 
 154 UTILS = {
 155 
 156     get isMobile(){
 157         return /Android|webOS|iPhone|iPod|BlackBerry|SymbianOS|Windows Phone|iPad/i.test(navigator.userAgent);
 158     },
 159     
 160     emptyArray(arr){
 161         return !Array.isArray(arr) || arr.length === 0;
 162     },
 163 
 164     createContext(w = 1, h = 1){
 165         const canvas = document.createElement("canvas"),
 166         context = canvas.getContext("2d");
 167         canvas.width = w;
 168         canvas.height = h;
 169         return context;
 170     },
 171 
 172     createElem(tagName, className = "", textContent = ""){
 173         const elem = document.createElement(tagName);
 174         if(className !== "") elem.className = className;
 175         if(textContent !== "") elem.textContent = textContent;
 176         return elem;
 177     },
 178 
 179     createInput(type, className = ""){
 180         const input = document.createElement("input");
 181         input.type = type;
 182         if(className !== "") input.className = className;
 183         return input;
 184     },
 185 
 186     appendChilds(parentElem, ...nodes){
 187         const msgContainer = document.createDocumentFragment();
 188         for(let k = 0, len = nodes.length; k < len; k++) msgContainer.appendChild(nodes[k]);
 189         parentElem.appendChild(msgContainer);
 190     },
 191 
 192     isObject(obj){
 193         
 194         return obj !== null && typeof obj === "object" && Array.isArray(obj) === false;
 195         
 196     },
 197     
 198     isNumber(num){
 199 
 200         return typeof num === "number" && isNaN(num) === false;
 201 
 202     },
 203 
 204     //获取最后一个点后面的字符
 205     getFileType(string){
 206         let type = "", str = string.split('').reverse().join('');
 207         for(let k = 0, len = str.length; k < len; k++){
 208             if(str[k] === ".") break;
 209             type += str[k];
 210         }
 211         return type.split('').reverse().join('');
 212     },
 213 
 214     //删除 string 所有的空格
 215     deleteSpaceAll(str){
 216         const len = str.length;
 217         var result = '';
 218         for(let i = 0; i < len; i++){
 219             if(str[i] !== '') result += str[i]
 220         }
 221 
 222         return result
 223     },
 224 
 225     //删除 string 两边空格
 226     removeSpaceSides(string){
 227 
 228         return string.replace(/(^\s*)|(\s*$)/g, "");
 229 
 230     },
 231 
 232     //返回 num 与 num1 之间的随机数
 233     random(num, num1){
 234         
 235         if(num < num1) return Math.random() * (num1 - num) + num;
 236 
 237         else if(num > num1) return Math.random() * (num - num1) + num1;
 238 
 239         else return num;
 240         
 241     },
 242 
 243     //生成 UUID
 244     generateUUID: function (){
 245         const _lut = [];
 246     
 247         for ( let i = 0; i < 256; i ++ ) {
 248     
 249             _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
 250     
 251         }
 252     
 253         return function (){
 254             const d0 = Math.random() * 0xffffffff | 0;
 255             const d1 = Math.random() * 0xffffffff | 0;
 256             const d2 = Math.random() * 0xffffffff | 0;
 257             const d3 = Math.random() * 0xffffffff | 0;
 258             const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
 259             _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
 260             _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
 261             _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
 262     
 263             return uuid.toLowerCase(); //toLowerCase() 这里展平连接的字符串以节省堆内存空间
 264         }
 265     }(),
 266 
 267     //欧几里得距离(两点的直线距离)
 268     distance(x, y, x1, y1){
 269         
 270         return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
 271 
 272     },
 273 
 274     downloadFile(blob, fileName){
 275         const link = document.createElement("a");
 276         link.href = URL.createObjectURL(blob);
 277         link.download = fileName;
 278         link.click();
 279     },
 280 
 281     loadFileJSON(callback){
 282         const input = document.createElement("input");
 283         input.type = "file";
 284         input.accept = ".json";
 285         
 286         input.onchange = a => {
 287             if(a.target.files.length === 0) return;
 288             const fr = new FileReader();
 289             fr.onloadend = b => callback(b.target.result);
 290             fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]);
 291         }
 292         
 293         input.click();
 294     },
 295 
 296     get emptyPoint(){
 297         if(__emptyPoint === null) __emptyPoint = new Point();
 298         return __emptyPoint;
 299     },
 300 
 301     get emptyPointA(){
 302         if(__emptyPointA === null) __emptyPointA = new Point();
 303         return __emptyPointA;
 304     },
 305 
 306     get emptyContext(){
 307         if(__emptyContext === null) __emptyContext = document.createElement("canvas").getContext('2d')
 308         return __emptyContext;
 309     },
 310 
 311     /* 把 target 以相等的比例缩放至 result 大小
 312         target, result: Object{width, height}; //也可以是img元素
 313     */
 314     setSizeToSameScale(target, result = {width: 100, height: 100}){
 315         if(!target) return result;
 316 
 317         const width = target.width,
 318         height = target.height,
 319         ratio = width / height,
 320         scale = ratio < 1 ? ratio * result.width / width : result.width / width;
 321 
 322         result.width = width * scale;
 323         result.height = height * scale;
 324 
 325         return result;
 326     },
 327 
 328     //小数保留 count 位
 329     floatKeep(float, count = 3){
 330         count = Math.pow(10, count);
 331         return Math.ceil(float * count) / count;
 332     },
 333 
 334 }
 335 
 336 
 337 
 338 
 339 /* SecurityDoor 安检门
 340 作用: 有时候我们要给一个功能加一个启禁用锁, 就比如用一个.enable属性表示此功能是否启用, 
 341 a, b是两个使用此功能的人; a 需要把此功能禁用(.enable = false), 
 342 过了一会b也要禁用此功能(.enable = false), 又过了一会a又要在次启用此功能,
 343 此时a让此功能启用了(.enable = true), 很显然这违背了b, 对于b来说此功能还在禁用状态,
 344 实际并非如b所想, 所以当多个人使用此功能时一个.enable满足不了它们;
 345 或许还有其它解决方法就比如让所有使用此功能的人(a,b)加一个属性表示自己是否可以使用此功能,
 346 但我更希望用一个专门的数组去装载它们, 而不是在某个使用者身上都添加一个属性;
 347 
 348 parameter:
 349     list: Array[];
 350 
 351 attribute:
 352     list: Array; //私有, 默认 null
 353     empty: Bool; //只读, 列表是否为空
 354 
 355 method:
 356     clear()
 357     equals(v)
 358     add(v)
 359     remove(v)
 360 */
 361 class SecurityDoor{
 362 
 363     #list = null;
 364     #onchange = null;
 365 
 366     get empty(){
 367         return this.#list === null;
 368     }
 369 
 370     constructor(list, onchange){
 371         if(UTILS.emptyArray(list) === false) this.#list = list;
 372         if(typeof onchange === "function") this.#onchange = onchange;
 373     }
 374 
 375     add(sign){
 376         if(this.#list !== null){
 377             if(this.#list.includes(sign) === false) this.#list.push(sign);
 378         }
 379         else{
 380             this.#list = [sign];
 381             if(this.#onchange !== null) this.#onchange(false);
 382         }
 383     }
 384     
 385     clear(){
 386         this.#list = null;
 387     }
 388 
 389     equals(sign){
 390         return this.#list !== null && this.#list.includes(sign);
 391     }
 392 
 393     remove(sign){
 394         if(this.#list !== null){
 395             sign = this.#list.indexOf(sign);
 396             if(sign !== -1){
 397                 if(this.#list.length > 1) this.#list.splice(sign, 1);
 398                 else{
 399                     this.#list = null;
 400                     if(this.#onchange !== null) this.#onchange(true);
 401                 }
 402             }
 403         }
 404     }
 405 
 406 }
 407 
 408 
 409 
 410 
 411 /* RunningList
 412 
 413 */
 414 class RunningList{
 415 
 416     #runName = "";
 417     #running = false;
 418     #list = [];
 419     #disabls = [];
 420 
 421     constructor(runName = 'update'){
 422         this.#runName = runName;
 423     }
 424 
 425     clear(){
 426         this.#list.length = 0;
 427         this.#disabls.length = 0;
 428     }
 429 
 430     add(v){
 431         if(this.#list.includes(v) === false) this.#list.push(v);
 432         else{
 433             const i = this.#disabls.indexOf(v);
 434             if(i !== -1) this.#disabls.splice(i, 1);
 435         }
 436     }
 437 
 438     remove(v){
 439         if(this.#running) this.#disabls.push(v);
 440         else{
 441             const i = this.#list.indexOf(v);
 442             if(i !== -1) this.#list.splice(i, 1);
 443         }
 444     }
 445 
 446     update(){
 447         var len = this.#list.length;
 448         if(len === 0) return;
 449 
 450         var k;
 451         this.#running = true;
 452         if(this.#runName !== ''){
 453             for(k = 0; k < len; k++) this.#list[k][this.#runName]();
 454         }else{
 455             for(k = 0; k < len; k++) this.#list[k]();
 456         }
 457         this.#running = false;
 458 
 459         var i;
 460         len = this.#disabls.length;
 461         for(k = 0; k < len; k++){
 462             i = this.#list.indexOf(this.#disabls[k]);
 463             if(i !== -1) this.#list.splice(i, 1);
 464         }
 465         this.#disabls.length = 0;
 466     }
 467 
 468 }
 469 
 470 
 471 
 472 
 473 /* AnimateLoop 动画循环
 474 attributes:
 475     onupdate: Func;
 476     //只读:
 477     running: Bool;
 478     
 479 method:
 480     play(onupdate: Func): this; //onupdate 可选
 481     stop(): this;
 482     update(): undefined; //使用前必须已定义.onupdate 属性
 483 */
 484 class AnimateLoop{
 485 
 486     #id = -1;
 487     #animate = null;
 488 
 489     get running(){
 490         return this.#id !== -1;
 491     }
 492 
 493     constructor(onupdate = null){
 494         const animate = () => {
 495             this.#id = requestAnimationFrame(animate);
 496             this.onupdate();
 497         }
 498 
 499         this.#animate = animate;
 500         this.onupdate = onupdate;
 501     }
 502 
 503     stop(){
 504         if(this.#id !== -1){
 505             cancelAnimationFrame(this.#id);
 506             this.#id = -1;
 507         }
 508         return this;
 509     }
 510 
 511     play(onupdate){
 512         if(typeof onupdate === "function") this.onupdate = onupdate;
 513         if(this.onupdate !== null){
 514             this.stop();
 515             this.#animate();
 516         }
 517         return this;
 518     }
 519 
 520     update(){
 521         if(this.#id === -1){
 522             this.onupdate();
 523         }
 524     }
 525 
 526 }
 527 
 528 
 529 
 530 
 531 /** Ajax
 532 parameter:
 533     option = {
 534         url:        可选, 默认 ''
 535         method:        可选, post 或 get请求, 默认 post
 536         asy:        可选, 是否异步执行, 默认 true
 537         success:    可选, 成功回调, 默认 null
 538         error:        可选, 超时或失败调用, 默认 null
 539         change:        可选, 请求状态改变时调用, 默认 null
 540         data:        可选, 如果定义则在初始化时自动执行.send(data)方法
 541     }
 542 
 543 demo:
 544     const data = `email=${email}&password=${password}`,
 545 
 546     //默认 post 请求:
 547     ajax = new Ajax({
 548         url: './login',
 549         data: data,
 550         success: mes => console.log(mes),
 551     });
 552     
 553     //get 请求:
 554     ajax.method = "get";
 555     ajax.send(data);
 556 */
 557 class Ajax{
 558     
 559     constructor(option = {}){
 560         this.url = option.url || "";
 561         this.method = option.method || "post";
 562         this.asy = typeof option.asy === "boolean" ? option.asy : true;
 563         this.success = option.success || null;
 564         this.error = option.error || null;
 565         this.change = option.change || null;
 566 
 567         //init XML
 568         this.xhr = new XMLHttpRequest();
 569 
 570         this.xhr.onerror = this.xhr.ontimeout = option.error || null;
 571 
 572         this.xhr.onreadystatechange = event => {
 573         
 574             if(event.target.readyState === 4 && event.target.status === 200){
 575 
 576                 if(this.success !== null) this.success(event.target.responseText, event);
 577                 
 578             }
 579 
 580             else if(this.change !== null) this.change(event);
 581 
 582         }
 583 
 584         if(option.data) this.send(option.data);
 585     }
 586 
 587     send(data = ""){
 588         if(this.method === "get"){
 589             this.xhr.open(this.method, this.url+"?"+data, this.asy);
 590             this.xhr.send();
 591         }
 592         
 593         else if(this.method === "post"){
 594             this.xhr.open(this.method, this.url, this.asy);
 595             this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
 596             this.xhr.send(data);
 597         }
 598     }
 599     
 600 }
 601 
 602 
 603 
 604 
 605 /* IndexedDB 本地数据库
 606 
 607 parameter:
 608     name: String;                //需要打开的数据库名称(如果不存在则会新建一个) 必须
 609     done: Function(IndexedDB);    //链接数据库成功时的回调 默认 null
 610     version: Number;             //数据库版本(高版本数据库将覆盖低版本的数据库) 默认 1 
 611 
 612 attribute:
 613     database: IndexedDB;            //链接完成的数据库对象
 614     transaction: IDBTransaction;    //事务管理(读和写)
 615     objectStore: IDBObjectStore;    //当前的事务
 616 
 617 method:
 618     set(data, key, callback)        //添加或更新
 619     get(key, callback)                //获取
 620     delete(key, callback)            //删除
 621 
 622     traverse(callback)                //遍历
 623     getAll(callback)                //获取全部
 624     clear(callback)                    //清理所以数据
 625     close()                         //关闭数据库链接
 626 
 627 readOnly:
 628 
 629 static:
 630     indexedDB: Object;
 631 
 632 demo:
 633     
 634     new IndexedDB('TEST', db => {
 635 
 636         conosle.log(db);
 637 
 638     });
 639 
 640 */
 641 class IndexedDB{
 642 
 643     static indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
 644 
 645     get objectStore(){ //每个事务只能使用一次, 所以每次都需要重新创建
 646         return this.database.transaction(this.name, 'readwrite').objectStore(this.name);
 647     }
 648 
 649     constructor(name, done = null, version = 1){
 650 
 651         if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB");
 652         
 653         if(typeof name !== 'string') return console.warn('IndexedDB: 参数错误');
 654 
 655         this.name = name;
 656         this.database = null;
 657 
 658         const request = IndexedDB.indexedDB.open(name, version);
 659         
 660         request.onupgradeneeded = (e)=>{ //数据库不存在 或 版本号不同时 触发
 661             if(!this.database) this.database = e.target.result;
 662             if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name);
 663         }
 664         
 665         request.onsuccess = (e)=>{
 666             this.database = e.target.result;
 667             if(typeof done === 'function') done(this);
 668         }
 669         
 670         request.onerror = (e)=>{
 671             console.error(e);
 672         }
 673         
 674     }
 675 
 676     close(){
 677 
 678         return this.database.close();
 679 
 680     }
 681 
 682     clear(callback){
 683         
 684         this.objectStore.clear().onsuccess = callback;
 685         
 686     }
 687 
 688     traverse(callback){
 689         
 690         this.objectStore.openCursor().onsuccess = callback;
 691 
 692     }
 693 
 694     set(data, key = 0, callback){
 695         
 696         this.objectStore.put(data, key).onsuccess = callback;
 697 
 698     }
 699     
 700     get(key = 0, callback){
 701 
 702         this.objectStore.get(key).onsuccess = callback;
 703         
 704     }
 705 
 706     del(key = 0, callback){
 707 
 708         this.objectStore.delete(key).onsuccess = callback;
 709 
 710     }
 711     
 712     getAll(callback){
 713 
 714         this.objectStore.getAll().onsuccess = callback;
 715 
 716     }
 717 
 718 }
 719 
 720 
 721 
 722 
 723 /* TreeStruct 树结构基类
 724 
 725 attribute:
 726     parent: TreeStruct;
 727     children: Array[TreeStruct];
 728 
 729 method:
 730     add(v: TreeStruct): v;         //v添加到自己的子集
 731     remove(v: TreeStruct): v;     //删除v, 前提v必须是自己的子集
 732     export(): Array[Object];    //TreeStruct 转为 可导出的结构, 包括其所有的后代
 733 
 734     getPath(v: TreeStruct): Array[TreeStruct];     //获取自己到v的路径
 735 
 736     traverse(callback: Function): undefined;  //迭代自己的每一个后代, 包括自己
 737         callback(value: TreeStruct); //如返回 "continue" 则不在迭代其后代(不是结束迭代, 而是只结束当前节点的后代);
 738 
 739     traverseUp(callback): undefined; //向上遍历每一个父, 包括自己
 740         callback(value: TreeStruct); //如返回 "break" 立即停止遍历;
 741 
 742 static:
 743     import(arr: Array[Object]): TreeStruct; //.export() 返回的 arr 转为 TreeStruct
 744 
 745 */
 746 class TreeStruct{
 747 
 748     static import(arr){
 749 
 750         //json = JSON.parse(json);
 751         const len = arr.length;
 752 
 753         for(let k = 0, v; k < len; k++){
 754             v = Object.assign(new TreeStruct(), arr[k]);
 755             v.parent = arr[arr[k].parent] || null;
 756             if(v.parent !== null) v.parent.add(v);
 757             arr[k] = v;
 758         }
 759 
 760         return arr[0];
 761 
 762     }
 763 
 764     constructor(){
 765         this.parent = null;
 766         this.children = [];
 767     }
 768 
 769     getPath(v){
 770 
 771         var path;
 772 
 773         const pathA = [];
 774         this.traverseUp(tar => {
 775             if(v === tar){
 776                 path = pathA;
 777                 return "break";
 778             }
 779             pathA.push(tar);
 780         });
 781 
 782         if(path) return path;
 783 
 784         const pathB = [];
 785         v.traverseUp(tar => {
 786             if(this === tar){
 787                 path = pathB.reverse();
 788                 return "break";
 789             }
 790             else{
 791                 let i = pathA.indexOf(tar);
 792                 if(i !== -1){
 793                     pathA.splice(i);
 794                     pathA.push(tar);
 795                     path = pathA.concat(pathB.reverse());
 796                     return "break";
 797                 }
 798             }
 799             pathB.push(tar);
 800         });
 801 
 802         return path;
 803         
 804     }
 805 
 806     add(v){
 807         v.parent = this;
 808         if(this.children.includes(v) === false) this.children.push(v);
 809         
 810         return v;
 811     }
 812 
 813     remove(v){
 814         const i = this.children.indexOf(v);
 815         if(i !== -1) this.children.splice(i, 1);
 816         v.parent = null;
 817 
 818         return v;
 819     }
 820 
 821     traverse(callback, key = 0){
 822 
 823         if(callback(this, key) !== "continue"){
 824 
 825             for(let k = 0, len = this.children.length; k < len; k++){
 826 
 827                 this.children[k].traverse(callback, k);
 828     
 829             }
 830 
 831         }
 832 
 833     }
 834 
 835     traverseUp(callback){
 836 
 837         var par = this.parent;
 838 
 839         while(par !== null){
 840             if(callback(par) === "break") return;
 841             par = par.parent;
 842         }
 843 
 844     }
 845 
 846     export(){
 847 
 848         const result = [], arr = [];
 849         var obj = null;
 850 
 851         this.traverse(v => {
 852             obj = Object.assign({}, v);
 853             obj.parent = arr.indexOf(v.parent);
 854             delete obj.children;
 855             result.push(obj);
 856             arr.push(v);
 857         });
 858         
 859         return result; //JSON.stringify(result);
 860 
 861     }
 862 
 863 }
 864 
 865 
 866 
 867 
 868 /* Point
 869 parameter: 
 870     x = 0, y = 0;
 871 
 872 attribute
 873     x, y: Number;
 874 
 875 method:
 876     set(x, y): this;
 877     angle(origin): Number;
 878     copy(point): this;
 879     clone(): Point;
 880     distance(point): Number;            //获取欧几里得距离
 881     distanceMHD(point): Number;            //获取曼哈顿距离
 882     distanceCompare(point): Number;        //获取用于比较的距离(相对于.distance() 效率更高)
 883     equals(point): Bool;                //是否恒等
 884     reverse(): this;                    //取反值
 885     rotate(origin: Object{x,y}, angle): this;    //旋转点
 886     normalize(): this;                    //归一
 887 */
 888 class Point{
 889 
 890     constructor(x = 0, y = 0){
 891         this.x = x;
 892         this.y = y;
 893     }
 894 
 895     set(x = 0, y = 0){
 896         this.x = x;
 897         this.y = y;
 898 
 899         return this;
 900     }
 901 
 902     angle(origin){
 903 
 904         return Math.atan2(this.y - origin.y, this.x - origin.x);
 905 
 906     }
 907 
 908     copy(point){
 909         
 910         this.x = point.x;
 911         this.y = point.y;
 912         return this;
 913         //return Object.assign(this, point);
 914 
 915     }
 916     
 917     clone(){
 918 
 919         return new this.constructor().copy(this);
 920         //return Object.assign(new this.constructor(), this);
 921         
 922     }
 923 
 924     distance(point){
 925         
 926         return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
 927 
 928     }
 929 
 930     distanceMHD(point){
 931 
 932         return Math.abs(this.x - point.x) + Math.abs(this.y - point.y);
 933 
 934     }
 935 
 936     distanceCompare(point){
 937     
 938         return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2);
 939 
 940     }
 941 
 942     equals(point){
 943 
 944         return point.x === this.x && point.y === this.y;
 945 
 946     }
 947 
 948     reverse(){
 949         this.x = -this.x;
 950         this.y = -this.y;
 951 
 952         return this;
 953     }
 954 
 955     rotate(origin, angle){
 956         const c = Math.cos(angle), s = Math.sin(angle), 
 957         x = this.x - origin.x, y = this.y - origin.y;
 958 
 959         this.x = x * c - y * s + origin.x;
 960         this.y = x * s + y * c + origin.y;
 961 
 962         return this;
 963     }
 964 
 965     normalize(){
 966         const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1);
 967         this.x *= len;
 968         this.y *= len;
 969 
 970         return this;
 971     }
 972 
 973 /*     add(point){
 974         this.x += point.x;
 975         this.y += point.y;
 976         return this;
 977     }
 978 
 979     sub(point){
 980         this.x -= point.x;
 981         this.y -= point.y;
 982         return this;
 983     }
 984 
 985     multiply(point){
 986         this.x *= point.x;
 987         this.y *= point.y;
 988         return this;
 989     }
 990 
 991     divide(point){
 992         this.x /= point.x;
 993         this.y /= point.y;
 994         return this;
 995     } */
 996 
 997 }
 998 
 999 
1000 
1001 
1002 /* Rotate
1003 parameter:
1004     x, y, a = 0
1005 
1006 attribute:
1007     angle: Number;
1008     origin: Point;
1009 
1010 method:
1011     set(x, y, a): this;
1012 */
1013 class Rotate{
1014 
1015     constructor(x, y, a = 0){
1016         this.angle = a;
1017         this.origin = new Point(x, y);
1018     }
1019 
1020     set(x, y, a){
1021         this.origin.x = x;
1022         this.origin.y = y;
1023         this.angle = a;
1024         return this;
1025     }
1026 
1027     pos(x, y){
1028         this.origin.x = x;
1029         this.origin.y = y;
1030         return this;
1031     }
1032 
1033     copy(rotate){
1034         this.angle = rotate.angle;
1035         this.origin.copy(rotate.origin);
1036         return this;
1037     }
1038     
1039     clone(){
1040         return new this.constructor().copy(this);
1041     }
1042 
1043     toAngle(v){
1044         this.angle = v / 180 * Math.PI;
1045         return this;
1046     }
1047 
1048 }
1049 
1050 
1051 
1052 
1053 /* Line
1054 parameter: x, y, x1, y1: Number;
1055 attribute: x, y, x1, y1: Number;
1056 method:
1057     set(x, y, x1, y1): this;                    
1058     copy(line): this;
1059     clone(): Line;
1060     containsPoint(x, y): Bool;                             //点是否在线上
1061     intersectPoint(line: Line, point: Point): Point;    //如果不相交则返回null, 否则返回交点Point
1062     isIntersect(line): Bool;                             //this与line是否相交
1063 */
1064 class Line{
1065 
1066     constructor(x = 0, y = 0, x1 = 0, y1 = 0){
1067         this.x = x;
1068         this.y = y;
1069         this.x1 = x1;
1070         this.y1 = y1;
1071     }
1072 
1073     set(x = 0, y = 0, x1 = 0, y1 = 0){
1074         this.x = x;
1075         this.y = y;
1076         this.x1 = x1;
1077         this.y1 = y1;
1078         return this;
1079     }
1080 
1081     copy(line){
1082         this.x = line.x;
1083         this.y = line.y;
1084         this.x1 = line.x1;
1085         this.y1 = line.y1;
1086         return this;
1087         //return Object.assign(this, line);
1088     }
1089     
1090     clone(){
1091         return new this.constructor().copy(this);
1092         //return Object.assign(new this.constructor(), this);
1093     }
1094 
1095     containsPoint(x, y){
1096         return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0;
1097     }
1098 
1099     intersectPoint(line, point){
1100         //解线性方程组, 求线段交点
1101         //如果分母为0则平行或共线, 不相交
1102         var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1);
1103         if(denominator === 0) return null;
1104 
1105         //线段所在直线的交点坐标 (x , y)
1106         const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 
1107         + (this.y1 - this.y) * (line.x1 - line.x) * this.x 
1108         - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator;
1109 
1110         const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 
1111         + (this.x1 - this.x) * (line.y1 - line.y) * this.y 
1112         - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator;
1113 
1114         //判断交点是否在两条线段上
1115         if(this.containsPoint(x, y) && line.containsPoint(x, y)){
1116             point.x = x;
1117             point.y = y;
1118             return point;
1119         }
1120 
1121         return null;
1122     }
1123 
1124     isIntersect(line){
1125         //快速排斥:
1126         //两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的
1127 
1128         //这里的确如此,这一步是判定两矩形是否相交
1129         //1.线段ab的低点低于cd的最高点(可能重合)
1130         //2.cd的最左端小于ab的最右端(可能重合)
1131         //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
1132         //4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
1133         //综上4个条件,两条线段组成的矩形是重合的
1134         //特别要注意一个矩形含于另一个矩形之内的情况
1135         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;
1136 
1137         //跨立实验:
1138         //如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
1139         //也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
1140         var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y),
1141         v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y),
1142         w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y),
1143         z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y);
1144         
1145         return u*v <= 0.00000001 && w*z <= 0.00000001;
1146     }
1147 
1148 }
1149 
1150 
1151 
1152 
1153 /* Box 矩形
1154 parameter: 
1155     x = 0, y = 0, w = 0, h = 0;
1156 
1157 attribute:
1158     x,y: Number; 位置
1159     w,h: Number; 大小
1160 
1161     只读
1162     mx, my: Number; //
1163 
1164 method:
1165     set(x, y, w, h): this;
1166     pos(x, y): this; //设置位置
1167     size(w, h): this; //设置大小
1168     setFromRotate(rotate: Rotate): this;        //根据 rotate 旋转自己
1169     setFromShapeRect(shapeRect): this;            //ShapeRect 转为 Box (忽略 ShapeRect 的旋转和缩放)
1170     setFromCircle(circle, inner: Bool): this;    //
1171     setFromPolygon(polygon, inner = true): this;//
1172     toArray(array: Array, index: Integer): this;
1173     copy(box): this;                             //复制
1174     clone(): Box;                                  //克隆
1175     center(box): this;                            //设置位置在box居中
1176     distance(x, y): Number;                     //左上角原点 与 x,y 的直线距离
1177     isEmpty(): Boolean;                         //.w.h是否小于等于零
1178     maxX(): Number;                             //返回 max x(this.x+this.w);
1179     maxY(): Number;                             //返回 max y(this.y+this.h);
1180     expand(box): undefined;                     //扩容; 把box合并到this
1181     equals(box): Boolean;                         //this与box是否恒等
1182     intersectsBox(box): Boolean;                 //box与this是否相交(box在this内部也会返回true)
1183     containsPoint(x, y): Boolean;                 //x,y点是否在this内
1184     containsBox(box): Boolean;                    //box是否在this内(只是相交返回fasle)
1185     computeOverflow(b: Box, r: Box): undefined;    //this相对b超出的部分赋值到r; r的size小于或等于0的话说明完全超出
1186 */
1187 class Box{
1188 
1189     get mx(){
1190         return this.x + this.w;
1191     }
1192 
1193     get my(){
1194         return this.y + this.h;
1195     }
1196 
1197     get cx(){
1198         return this.w / 2 + this.x;
1199     }
1200 
1201     get cy(){
1202         return this.h / 2 + this.y;
1203     }
1204 
1205     constructor(x = 0, y = 0, w = 0, h = 0){
1206         //this.set(x, y, w, h);
1207         this.x = x;
1208         this.y = y;
1209         this.w = w;
1210         this.h = h;
1211     }
1212     
1213     set(x, y, w, h){
1214         this.x = x;
1215         this.y = y;
1216         this.w = w;
1217         this.h = h;
1218         return this;
1219     }
1220 
1221     pos(x, y){
1222         this.x = x;
1223         this.y = y;
1224         return this;
1225     }
1226     
1227     size(w, h){
1228         this.w = w;
1229         this.h = h;
1230         return this;
1231     }
1232 
1233     setFromRotate(rotate){
1234         var minX = this.x, minY = this.y, maxX = 0, maxY = 0;
1235         const point = UTILS.emptyPoint,
1236         run = function (){
1237             if(point.x < minX) minX = point.x;
1238             else if(point.x > maxX) maxX = point.x;
1239             if(point.y < minY) minY = point.y;
1240             else if(point.y > maxY) maxY = point.y;
1241         }
1242 
1243         point.set(this.x, this.y).rotate(rotate.origin, rotate.angle); run();
1244         point.set(this.mx, this.y).rotate(rotate.origin, rotate.angle); run();
1245         point.set(this.mx, this.my).rotate(rotate.origin, rotate.angle); run();
1246         point.set(this.x, this.my).rotate(rotate.origin, rotate.angle); run();
1247 
1248         this.x = minX;
1249         this.y = minY;
1250         this.w = maxX - minX;
1251         this.h = maxY - minY;
1252 
1253         return this;
1254     }
1255 
1256     setFromShapeRect(shapeRect){
1257         this.width = shapeRect.width;
1258         this.height = shapeRect.height;
1259         this.x = shapeRect.position.x - this.width / 2;
1260         this.y = shapeRect.position.y - this.height / 2;
1261         return this;
1262     }
1263 
1264     setFromCircle(circle, inner = true){
1265         if(inner === true){
1266             this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x;
1267             this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y;
1268             this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x;
1269         }
1270 
1271         else{
1272             this.x = circle.x - circle.r;
1273             this.y = circle.y - circle.r;
1274             this.w = this.h = circle.r * 2;
1275         }
1276         return this;
1277     }
1278 
1279     setFromPolygon(polygon, inner = true){
1280         if(inner === true){
1281             console.warn('Box: 暂不支持第二个参数为true');
1282         }
1283 
1284         else{
1285             const len = polygon.path.length;
1286             let x = Infinity, y = Infinity, mx = 0, my = 0;
1287             for(let k = 0, v; k < len; k+=2){
1288                 v = polygon.path[k];
1289                 if(v < x) x = v;
1290                 else if(v > mx) mx = v;
1291 
1292                 v = polygon.path[k+1];
1293                 if(v < y) y = v;
1294                 else if(v > my) my = v;
1295 
1296             }
1297 
1298             this.set(x, y, mx - x, my - y);
1299 
1300         }
1301         return this;
1302     }
1303 
1304     toArray(array, index){
1305         array[index] = this.x;
1306         array[index+1] = this.y;
1307         array[index+2] = this.w;
1308         array[index+3] = this.h;
1309 
1310         return this;
1311     }
1312 
1313     copy(box){
1314         this.x = box.x;
1315         this.y = box.y;
1316         this.w = box.w;
1317         this.h = box.h;
1318         return this;
1319         //return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h);
1320     }
1321     
1322     clone(){
1323         return new this.constructor().copy(this);
1324         //return Object.assign(new this.constructor(), this);
1325     }
1326 
1327     center(box){
1328         this.x = (box.w - this.w) / 2 + box.x;
1329         this.y = (box.h - this.h) / 2 + box.y;
1330         return this;
1331     }
1332 
1333     distance(x, y){
1334         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
1335     }
1336 
1337     isEmpty(){
1338         return this.w <= 0 || this.h <= 0;
1339     }
1340 
1341     maxX(){
1342         return this.x + this.w;
1343     }
1344 
1345     maxY(){
1346         return this.y + this.h;
1347     }
1348 
1349     equals(box){
1350         return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h;
1351     }
1352 
1353     expand(box){
1354         var v = Math.min(this.x, box.x);
1355         this.w = Math.max(this.x + this.w - v, box.x + box.w - v);
1356         this.x = v;
1357 
1358         v = Math.min(this.y, box.y);
1359         this.h = Math.max(this.y + this.h - v, box.y + box.h - v);
1360         this.y = v;
1361     }
1362 
1363     intersectsBox(box){
1364         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;
1365     }
1366 
1367     containsPoint(x, y){
1368         return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true;
1369     }
1370 
1371     containsBox(box){
1372         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;
1373     }
1374 
1375     computeOverflow(p, r){
1376         r["copy"](this);
1377         
1378         if(this["x"] < p["x"]){
1379             r["x"] = p["x"];
1380             r["w"] -= p["x"] - this["x"];
1381         }
1382 
1383         if(this["y"] < p["y"]){
1384             r["y"] = p["y"];
1385             r["h"] -= p["y"] - this["y"];
1386         }
1387 
1388         var m = p["x"] + p["w"];
1389         if(r["x"] + r["w"] > m) r["w"] = m - r["x"];
1390 
1391         m = p["y"] + p["h"];
1392         if(r["y"] + r["h"] > m) r["h"] = m - r["y"];
1393     }
1394     
1395 }
1396 
1397 
1398 
1399 
1400 /* Circle 圆形
1401 parameter:
1402 attribute:
1403     x,y: Number; 中心点
1404     r: Number; 半径
1405 
1406     //只读
1407     r2: Number; //返回直径 r*2
1408 
1409 method:
1410     set(x, y, r): this;
1411     pos(x, y): this;
1412     copy(circle: Circle): this;
1413     clone(): Circle;
1414     distance(x, y): Number;
1415     equals(circle: Circle): Bool;
1416     expand(circle: Circle): undefined;                     //扩容; 把circle合并到this
1417     containsPoint(x, y): Bool; 
1418     intersectsCircle(circle: Circle): Bool;
1419     intersectsBox(box: Box): Bool;
1420     setFromBox(box, inner = true): this;
1421 
1422 */
1423 class Circle{
1424 
1425     get r2(){
1426         return this.r * 2;
1427     }
1428 
1429     constructor(x = 0, y = 0, r = -1){
1430         //this.set(0, 0, -1);
1431         this.x = x;
1432         this.y = y;
1433         this.r = r;
1434     }
1435 
1436     set(x, y, r){
1437         this.x = x;
1438         this.y = y;
1439         this.r = r;
1440 
1441         return this;
1442     }
1443 
1444     setFromBox(box, world = true, inner = true){
1445         this.x = box.w / 2 + (world === true ? box.x : 0);
1446         this.y = box.h / 2 + (world === true ? box.y : 0);
1447         this.r = inner === true ? Math.min(box.w, box.h) / 2 : UTILS.distance(0, 0, box.w, box.h) / 2;
1448     
1449         return this;
1450     }
1451 
1452     toArray(array, index){
1453         array[index] = this.x;
1454         array[index+1] = this.y;
1455         array[index+2] = this.r;
1456         
1457         return this;
1458     }
1459 
1460     pos(x, y){
1461         this.x = x;
1462         this.y = y;
1463 
1464         return this;
1465     }
1466 
1467     copy(circle){
1468         this.r = circle.r;
1469         this.x = circle.x;
1470         this.y = circle.y;
1471 
1472         return this;
1473     }
1474 
1475     clone(){
1476 
1477         return new this.constructor().copy(this);
1478 
1479     }
1480 
1481     distance(x, y){
1482         
1483         return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2));
1484 
1485     }
1486 
1487     expand(circle){
1488 
1489     }
1490 
1491     equals(circle){
1492 
1493         return circle.x === this.x && circle.y === this.y && circle.r === this.r;
1494 
1495     }
1496 
1497     containsPoint(x, y){
1498 
1499         return (Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2) <= Math.pow(this.r, 2));
1500 
1501     }
1502 
1503     intersectsCircle(circle){
1504 
1505         return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2));
1506 
1507     }
1508 
1509     intersectsBox(box){
1510         
1511         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));
1512     
1513     }
1514 
1515 }
1516 
1517 
1518 
1519 
1520 /* Polygon 多边形
1521 
1522 parameter: 
1523     path: Array[x, y];
1524 
1525 attribute:
1526 
1527     //只读属性
1528     path: Array[x, y]; 
1529 
1530 method:
1531     add(x, y): this;             //x,y添加至path;
1532     containsPoint(x, y): Bool;    //x,y是否在多边形的内部(注意: 在路径上也返回 true)
1533     
1534 */
1535 class Polygon{
1536 
1537     #position = null;
1538     #path2D = null;
1539 
1540     get path(){
1541         
1542         return this.#position;
1543 
1544     }
1545 
1546     constructor(path = []){
1547         this.#position = path;
1548 
1549         this.#path2D = new Path2D();
1550         
1551         var len = path.length;
1552         if(len >= 2){
1553             if(len % 2 !== 0){
1554                 len -= 1;
1555                 path.splice(len, 1);
1556             }
1557 
1558             const con = this.#path2D;
1559             con.moveTo(path[0], path[1]);
1560             for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]);
1561             
1562         }
1563 
1564     }
1565 
1566     add(x, y){
1567         this.#position.push(x, y);
1568         this.#path2D.lineTo(x, y);
1569         return this;
1570     }
1571 
1572     containsPoint(x, y){
1573         
1574         return UTILS.emptyContext.isPointInPath(this.#path2D, x, y);
1575 
1576     }
1577 
1578     isInPolygon(checkPoint, polygonPoints) {
1579         var counter = 0;
1580         var i;
1581         var xinters;
1582         var p1, p2;
1583         var pointCount = polygonPoints.length;
1584         p1 = polygonPoints[0];
1585         for (i = 1; i <= pointCount; i++) {
1586             p2 = polygonPoints[i % pointCount];
1587             if (
1588                 checkPoint[0] > Math.min(p1[0], p2[0]) &&
1589                 checkPoint[0] <= Math.max(p1[0], p2[0])
1590             ) {
1591                 if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
1592                     if (p1[0] != p2[0]) {
1593                         xinters =
1594                             (checkPoint[0] - p1[0]) *
1595                                 (p2[1] - p1[1]) /
1596                                 (p2[0] - p1[0]) +
1597                             p1[1];
1598                         if (p1[1] == p2[1] || checkPoint[1] <= xinters) {
1599                             counter++;
1600                         }
1601                     }
1602                 }
1603             }
1604             p1 = p2;
1605         }
1606         if (counter % 2 == 0) {
1607             return false;
1608         } else {
1609             return true;
1610         }
1611     }
1612 
1613     containsPolygon(polygon){
1614         const path = polygon.path, len = path.length;
1615         for(let k = 0; k < len; k += 2){
1616             if(this.containsPoint(path[k], path[k+1]) === false) return false;
1617         }
1618 
1619         return true;
1620     }
1621 
1622     toPoints(){
1623         const path = this.path, len = path.length, result = [];
1624         
1625         for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1]));
1626 
1627         return result;
1628     }
1629 
1630     toLines(){
1631         const path = this.path, len = path.length, result = [];
1632         
1633         for(let k = 0, x = NaN, y; k < len; k += 2){
1634 
1635             if(isNaN(x)){
1636                 x = path[k];
1637                 y = path[k+1];
1638                 continue;
1639             }
1640 
1641             const line = new Line(x, y, path[k], path[k+1]);
1642             
1643             x = line.x1;
1644             y = line.y1;
1645 
1646             result.push(line);
1647 
1648         }
1649 
1650         return result;
1651     }
1652 
1653     merge(polygon){
1654 
1655         const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [],
1656         
1657         pointA = new Point(), pointB = new Point(),
1658 
1659         forEachNodes = (pathA, pathB, funcA = null, funcB = null) => {
1660             for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){
1661                 if(funcA !== null) funcA(pathA[k]);
1662 
1663                 for(let i = 0; i < lenB; i++){
1664                     if(funcB !== null) funcB(pathB[i], pathA[k]);
1665                 }
1666     
1667             }
1668         }
1669 
1670         if(this.containsPolygon(polygon)){console.log('this -> polygon');
1671             forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => {
1672                 if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone());
1673             });
1674 
1675             return newLines;
1676         }
1677 
1678         //收集所有的交点 (保存至 line)
1679         forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => {
1680             if(lineB.nodes === undefined) lineB.nodes = [];
1681             if(lineA.intersectPoint(lineB, pointA) === pointA){
1682                 const node = {
1683                     lineA: lineA, 
1684                     lineB: lineB, 
1685                     point: pointA.clone(),
1686                     disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)),
1687                     disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)),
1688                 }
1689                 lineA.nodes.push(node);
1690                 lineB.nodes.push(node);
1691                 nodes.push(node);
1692             }
1693         });
1694 
1695         //交点以原点为目标排序
1696         for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr);
1697         for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr);
1698 
1699         var _loopTypeA, _loopTypeB;
1700         const result_loop = {
1701             lines: null,
1702             loopType: '',
1703             line: null,
1704             count: 0,
1705             indexed: 0,
1706         },
1707         
1708         //遍历某条线
1709         loop = (lines, index, loopType) => {
1710             const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this;
1711         
1712             var line, i = 1;
1713             while(true){
1714                 if(loopType === 'next') index = index === length - 1 ? 0 : index + 1;
1715                 else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1;
1716                 line = lines[index];
1717 
1718                 result_loop.count = line.nodes.length;
1719                 if(result_loop.count !== 0){
1720                     result_loop.lines = lines;
1721                     result_loop.loopType = loopType;
1722                     result_loop.line = line;
1723                     result_loop.indexed = index;
1724                     if(loopType === 'next') addLine(line, model);
1725 
1726                     return result_loop;
1727                 }
1728                 
1729                 addLine(line, model);
1730                 if(indexed === i++) break;
1731 
1732             }
1733             
1734         },
1735 
1736         //更新或创建交点的索引
1737         setNodeIndex = (lines, index, loopType) => {
1738             const line = lines[index], count = line.nodes.length;
1739             if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB;
1740 
1741             if(loopType === undefined) return;
1742             
1743             if(line.nodeIndex === undefined){
1744                 line.nodeIndex = loopType === 'next' ? 0 : count - 1;
1745                 line.nodeState = count === 1 ? 'end' : 'start';
1746             
1747             }
1748 
1749             else{
1750                 if(line.nodeState === 'end' || line.nodeState === ''){
1751                     line.nodeState = '';
1752                     return;
1753                 }
1754 
1755                 if(loopType === 'next'){
1756                     line.nodeIndex += 1;
1757 
1758                     if(line.nodeIndex === count - 1) line.nodeState = 'end';
1759                     else line.nodeState = 'run';
1760                 }
1761 
1762                 else if(loopType === 'back'){
1763                     line.nodeIndex -= 1;
1764 
1765                     if(line.nodeIndex === 0) line.nodeState = 'end';
1766                     else line.nodeState = 'run';
1767                 }
1768 
1769             }
1770 
1771         },
1772 
1773         //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端;
1774         getLoopType = (lines, index, nodePoint) => {
1775             const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1],
1776 
1777             model = lines === linesA ? polygon : this,
1778             isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false,
1779             isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false;
1780             
1781             if(isLineBack && isLineNext){
1782                 const len = line.nodes.length;
1783                 if(len >= 2){
1784                     if(line.nodes[len - 1].point.equals(nodePoint)) return 'next';
1785                     else if(line.nodes[0].point.equals(nodePoint)) return 'back';
1786                 }
1787                 
1788                 else console.warn('路径复杂', line);
1789                 
1790             }
1791 
1792             else if(isLineNext){
1793                 return 'next';
1794             }
1795 
1796             else if(isLineBack){
1797                 return 'back';
1798             }
1799 
1800             return '';
1801         },
1802 
1803         //添加线至新的形状数组
1804         addLine = (line, model) => {
1805             //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line);
1806             if(line.nodes.length === 0) newLines.push(line);
1807             else if(model.containsPoint(line.x, line.y) === false) newLines.push(line);
1808             
1809         },
1810 
1811         //处理拥有交点的线
1812         computeNodes = v => {
1813             if(v === undefined || v.count === 0) return;
1814             
1815             setNodeIndex(v.lines, v.indexed, v.loopType);
1816         
1817             //添加交点
1818             const node = v.line.nodes[v.line.nodeIndex];
1819             if(newLines.includes(node.point) === false) newLines.push(node.point);
1820             else return;
1821 
1822             var lines = v.lines === linesA ? linesB : linesA, 
1823             line = lines === linesA ? node.lineA : node.lineB, 
1824             index = lines.indexOf(line);
1825 
1826             setNodeIndex(lines, index);
1827         
1828             //选择交点状态
1829             var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState;
1830             if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){
1831                 if(line.nodeState === 'start'){
1832                     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];
1833                     
1834                     if(newLines.includes(backLine) && backLine.nodes.length === 0){
1835                         nodeState = 'run';
1836                     }
1837 
1838                 }
1839                 else if(line.nodeState === 'end'){
1840                     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];
1841                     const model = v.lines === linesA ? polygon : this;
1842                     if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){
1843                         nodeState = 'run';
1844                     }
1845                     
1846                 }
1847             }
1848 
1849             switch(nodeState){
1850 
1851                 //不跳线
1852                 case 'run': 
1853                     if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this);
1854                     return computeNodes(loop(v.lines, v.indexed, v.loopType));
1855 
1856                 //跳线
1857                 case 'start': 
1858                 case 'end': 
1859                     const loopType = getLoopType(lines, index, node.point);
1860                     if(loopType !== ''){
1861                         if(lines === linesA) _loopTypeA = loopType;
1862                         else _loopTypeB = loopType;
1863                         if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon);
1864                         return computeNodes(loop(lines, index, loopType));
1865                     }
1866                     break;
1867 
1868             }
1869 
1870         }
1871         
1872         //获取介入点
1873         var startLine = null;
1874         for(let k = 0, len = nodes.length, node; k < len; k++){
1875             node = nodes[k];
1876             if(node.lineA.nodes.length !== 0){
1877                 startLine = node.lineA;
1878                 if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){
1879                     startLine = node.lineB.nodes[0].lineB;
1880                     result_loop.lines = linesB;
1881                     result_loop.loopType = _loopTypeB = 'next';
1882                 }
1883                 else{
1884                     result_loop.lines = linesA;
1885                     result_loop.loopType = _loopTypeA = 'next';
1886                 }
1887                 result_loop.line = startLine;
1888                 result_loop.count = startLine.nodes.length;
1889                 result_loop.indexed = result_loop.lines.indexOf(startLine);
1890                 break;
1891             }
1892         }
1893 
1894         if(startLine === null){
1895             console.warn('Polygon: 找不到介入点, 终止了合并');
1896             return newLines;
1897         }
1898 
1899         computeNodes(result_loop);
1900     
1901         return newLines;
1902     }
1903 
1904 }
1905 
1906 
1907 
1908 
1909 /* ShapeRect (一般2d矩形的原点在左上, 此矩形类的原点在中间)
1910 parameter: 
1911     width = 0, height = 0
1912 
1913 attribute: 
1914     width, height: Number;     //矩形的宽高
1915     position: Point;        //位置(是旋转和缩放的中心点)
1916     rotation: Number;        //旋转(绕 Z 轴旋转的弧度)
1917     scale: Point;            //缩放
1918 
1919 method:
1920     setFromBox(box): this;                //Box 转为 ShapeRect
1921     compute(): undefined;                //计算出矩形
1922     applyCanvas(context): undefined;    //矩形应用到画布的上下文中
1923 
1924 demo:
1925     const canvasRect = new ShapeRect(100, 150); console.log(canvasRect);
1926 
1927     const canvas = document.createElement("canvas");
1928     canvas.width = WORLD.width;
1929     canvas.height = WORLD.height;
1930     canvas.style = `
1931         position: absolute;
1932         z-index: 9999;
1933         background: rgb(127,127,127);
1934     `;
1935     document.body.appendChild(canvas);
1936 
1937     canvasRect.position.set(300, 300);
1938     canvasRect.rotation = UTILS.toAngle(45);
1939     canvasRect.scale.set(1.5, 1.5);
1940     canvasRect.compute();
1941 
1942     const context = canvas.getContext("2d");
1943     canvasRect.applyCanvas(context);
1944 
1945     context.strokeStyle = "red";
1946     context.stroke();
1947 */
1948 class ShapeRect{
1949 
1950     #dots = [];
1951     #size = new Point();
1952     #min = new Point();
1953     #max = new Point();
1954 
1955     constructor(width = 0, height = 0){
1956         this.width = width;
1957         this.height = height;
1958         this.position = new Point();
1959         this.rotation = 0;
1960         this.scale = new Point(1, 1);
1961     }
1962 
1963     setFromBox(box){
1964         this.width = box.w;
1965         this.height = box.h;
1966         this.position.set(box.cx, box.cy);
1967         return this;
1968     }
1969 
1970     compute(){
1971         //scale
1972         this.#size.set(this.width * this.scale.x, this.height * this.scale.y);
1973         
1974         //position
1975         this.#min.set(this.position.x - this.#size.x / 2, this.position.y - this.#size.y / 2);
1976         this.#max.set(this.#min.x + this.#size.x, this.#min.y + this.#size.y);
1977         
1978         //rotation
1979         const point = UTILS.emptyPoint;
1980         this.#dots.length = 0;
1981         
1982         point.set(this.#min.x, this.#min.y).rotate(this.position, this.rotation);
1983         this.#dots.push(point.x, point.y);
1984 
1985         point.set(this.#max.x, this.#min.y).rotate(this.position, this.rotation);
1986         this.#dots.push(point.x, point.y);
1987 
1988         point.set(this.#max.x, this.#max.y).rotate(this.position, this.rotation);
1989         this.#dots.push(point.x, point.y);
1990 
1991         point.set(this.#min.x, this.#max.y).rotate(this.position, this.rotation);
1992         this.#dots.push(point.x, point.y);
1993 
1994         //缩放旋转后的 min 与 max
1995         //this.#min.set(Math.min(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.min(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7]));
1996         //this.#max.set(Math.max(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.max(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7]))
1997     }
1998 
1999     drawImage(img, context){
2000         UTILS.emptyContext.canvas.width = this.#size.x;
2001         UTILS.emptyContext.canvas.height = this.#size.y;
2002         UTILS.emptyContext.drawImage(img, 0, 0, this.#size.x, this.#size.y);
2003 
2004         const imageData = UTILS.emptyContext.getImageData(0, 0, this.#size.x, this.#size.y),
2005         width = imageData.width, height = imageData.height,
2006         len = width * height, point = UTILS.emptyPoint, center = UTILS.emptyPointA.set(width/2, height/2),
2007 
2008         newImageData = UTILS.emptyContext.createImageData(width, height);
2009         
2010         for(let k = 0; k < len; k++){
2011             point.set(k % width, Math.floor(k / width)).rotate(center, this.rotation);
2012             if(point.x < 0 || point.y < 0 || point.x > newImageData.width-1 || point.y > newImageData.height-1) continue;
2013 
2014             point.set((width * Math.round(point.y) + Math.round(point.x)) * 4, k * 4);
2015             newImageData.data[point.x] = imageData.data[point.y];
2016             newImageData.data[point.x + 1] = imageData.data[point.y + 1];
2017             newImageData.data[point.x + 2] = imageData.data[point.y + 2];
2018             newImageData.data[point.x + 3] = imageData.data[point.y + 3];
2019         }
2020 
2021         context.putImageData(newImageData, 20, 20);
2022     }
2023 
2024     applyCanvas(context){
2025         context.beginPath();
2026         context.moveTo(this.#dots[0], this.#dots[1]);
2027 
2028         for(let k = 2, len = this.#dots.length; k < len; k += 2){
2029             context.lineTo(this.#dots[k], this.#dots[k + 1]);
2030         }
2031 
2032         context.closePath();
2033     }
2034 
2035 }
2036 
2037 
2038 
2039 
2040 /* RGBColor
2041 parameter: 
2042     r, g, b
2043 
2044 method:
2045     set(r, g, b: Number): this;            //rgb: 0 - 255; 第一个参数可以为 css color
2046     setFormHex(hex: Number): this;         //
2047     setFormHSV(h, s, v: Number): this;    //h:0-360; s,v:0-100; 颜色, 明度, 暗度
2048     setFormString(str: String): Number;    //str: 英文|css color; 返回的是透明度 (如果为 rgba 则返回a; 否则总是返回1)
2049 
2050     copy(v: RGBColor): this;
2051     clone(): RGBColor;
2052 
2053     getHex(): Number;
2054     getHexString(): String;
2055     getHSV(result: Object{h, s, v}): result;    //result: 默认是一个新的Object
2056     getRGBA(alpha: Number): String;             //alpha: 0 - 1; 默认 1
2057     getStyle()                                     //.getRGBA()别名
2058 
2059     stringToColor(str: String): String; //str 转为 css color; 如果str不是color格式则返回 ""
2060 
2061 */
2062 class RGBColor{
2063 
2064     constructor(r = 255, g = 255, b = 255){
2065         this.r = r;
2066         this.g = g;
2067         this.b = b;
2068     }
2069 
2070     copy(v){
2071         this.r = v.r;
2072         this.g = v.g;
2073         this.b = v.b;
2074         return this;
2075     }
2076 
2077     clone(){
2078         return new this.constructor().copy(this);
2079     }
2080 
2081     set(r, g, b){
2082         if(typeof r !== "string"){
2083             this.r = r || 255;
2084             this.g = g || 255;
2085             this.b = b || 255;
2086         }
2087 
2088         else this.setFormString(r);
2089         
2090         return this;
2091     }
2092 
2093     setFormHex(hex){
2094         hex = Math.floor( hex );
2095 
2096         this.r = hex >> 16 & 255;
2097         this.g = hex >> 8 & 255;
2098         this.b = hex & 255;
2099         return this;
2100     }
2101 
2102     setFormHSV(h, s, v){
2103         h = h >= 360 ? 0 : h;
2104         var s=s/100;
2105         var v=v/100;
2106         var h1=Math.floor(h/60) % 6;
2107         var f=h/60-h1;
2108         var p=v*(1-s);
2109         var q=v*(1-f*s);
2110         var t=v*(1-(1-f)*s);
2111         var r,g,b;
2112         switch(h1){
2113             case 0:
2114                 r=v;
2115                 g=t;
2116                 b=p;
2117                 break;
2118             case 1:
2119                 r=q;
2120                 g=v;
2121                 b=p;
2122                 break;
2123             case 2:
2124                 r=p;
2125                 g=v;
2126                 b=t;
2127                 break;
2128             case 3:
2129                 r=p;
2130                 g=q;
2131                 b=v;
2132                 break;
2133             case 4:
2134                 r=t;
2135                 g=p;
2136                 b=v;
2137                 break;
2138             case 5:
2139                 r=v;
2140                 g=p;
2141                 b=q;
2142                 break;
2143         }
2144 
2145         this.r = Math.round(r*255);
2146         this.g = Math.round(g*255);
2147         this.b = Math.round(b*255);
2148         return this;
2149     }
2150 
2151     setFormString(color){
2152         if(typeof color !== "string") return 1;
2153         var _color = this.stringToColor(color);
2154         
2155         if(_color[0] === "#"){
2156             const len = _color.length;
2157             if(len === 4){
2158                 _color = _color.slice(1);
2159                 this.setFormHex(parseInt("0x"+_color + "" + _color));
2160             }
2161             else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1)));
2162         }
2163 
2164         else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){
2165             const arr = [];
2166             for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){
2167                 
2168                 if(is === true){
2169                     if(_color[k] === "," || _color[k] === ")"){
2170                         arr.push(parseFloat(v));
2171                         v = "";
2172                     }
2173                     else v += _color[k];
2174                     
2175                 }
2176 
2177                 else if(_color[k] === "(") is = true;
2178                 
2179             }
2180 
2181             this.set(arr[0], arr[1], arr[2]);
2182             return arr[3] === undefined ? 1 : arr[3];
2183         }
2184         
2185         return 1;
2186     }
2187 
2188     getHex(){
2189 
2190         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;
2191 
2192     }
2193 
2194     getHexString(){
2195 
2196         return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
2197 
2198     }
2199 
2200     getHSV(result){
2201         result = result || {}
2202         var r=this.r/255;
2203         var g=this.g/255;
2204         var b=this.b/255;
2205         var h,s,v;
2206         var min=Math.min(r,g,b);
2207         var max=v=Math.max(r,g,b);
2208         var l=(min+max)/2;
2209         var difference = max-min;
2210         
2211         if(max==min){
2212             h=0;
2213         }else{
2214             switch(max){
2215                 case r: h=(g-b)/difference+(g < b ? 6 : 0);break;
2216                 case g: h=2.0+(b-r)/difference;break;
2217                 case b: h=4.0+(r-g)/difference;break;
2218             }
2219             h=Math.round(h*60);
2220         }
2221         if(max==0){
2222             s=0;
2223         }else{
2224             s=1-min/max;
2225         }
2226         s=Math.round(s*100);
2227         v=Math.round(v*100);
2228         result.h = h;
2229         result.s = s;
2230         result.v = v;
2231         return result;
2232     }
2233 
2234     getStyle(){
2235         return this.getRGBA(1);
2236     }
2237 
2238     getRGBA(alpha = 1){
2239         return `rgba(${this.r},${this.g},${this.b},${alpha})`;
2240     }
2241 
2242     stringToColor(str){
2243         var _color = "";
2244         for(let k = 0, len = str.length; k < len; k++){
2245             if(str[k] === " ") continue;
2246             _color += str[k];
2247         }
2248         
2249         if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color;
2250 
2251         return ColorRefTable[_color] || "";
2252     }
2253 
2254 }
2255 
2256 
2257 
2258 
2259 /* Timer 定时器 
2260 
2261 parameter:
2262     func: Function; //定时器运行时的回调; 默认 null, 如果定义构造器将自动调用一次.restart()方法
2263     speed: Number; //延迟多少毫秒执行一次 func; 默认 3000;
2264     step: Integer; //执行多少次: 延迟speed毫秒执行一次 func; 默认 Infinity;
2265     
2266 attribute:
2267     func, speed, step;    //这些属性可以随时更改;
2268 
2269     //只读属性
2270     readyState: String;    //定时器状态; 可能值: '', 'start', 'running', 'done'; ''表示定时器从未启动
2271     number: Number;        //运行的次数
2272 
2273 method:
2274     start(func, speed): this;    //启动定时器 (如果定时器正在运行则什么都不会做)
2275     restart(): undefined;        //重启定时器
2276     stop(): undefined;            //停止定时器
2277 
2278 demo:
2279     //每 3000 毫秒 打印一次 timer.number
2280     new Timer(timer => {
2281         console.log(timer.number);
2282     }, 3000);
2283 
2284 */
2285 class Timer{
2286 
2287     #restart = -1;
2288     #speed = 0;
2289     #isRun = false;
2290     #i = 0;
2291     #readyState = ''; //start|running
2292 
2293     get number(){
2294         return this.#i;
2295     }
2296     
2297     get readyState(){
2298         return this.#i >= this.step ? 'done' : this.#readyState;
2299     }
2300 
2301     get running(){
2302         return this.#isRun;
2303     }
2304 
2305     constructor(func = null, speed = 3000, step = Infinity){
2306         this.func = func;
2307         this.speed = speed;
2308         this.step = step;
2309         //this.onDone = null;
2310     
2311         if(typeof this.func === "function") this.restart();
2312 
2313     }
2314 
2315     start(func, time){
2316         if(typeof func === 'function') this.func = func;
2317         if(UTILS.isNumber(time) === true) this.speed = time;
2318         this.restart();
2319 
2320         return this;
2321     }
2322 
2323     restart(){
2324         if(this.#isRun === false){
2325             setTimeout(this._loop, this.speed);
2326             this.#isRun = true;
2327             this.#restart = -1;
2328             this.#i = 0;
2329             this.#readyState = 'start';
2330             
2331         }
2332 
2333         else{
2334             this.#restart = Date.now();
2335             this.#speed = this.speed;
2336 
2337         }
2338 
2339     }
2340 
2341     stop(){
2342         if(this.#isRun === true){
2343             this.#restart = -1;
2344             this.#i = this.step;
2345         }
2346 
2347     }
2348 
2349     _loop = () => {
2350 
2351         //重启计时器
2352         if(this.#restart !== -1){
2353             
2354             let gone = Date.now() - this.#restart;
2355             this.#restart = -1;
2356             
2357             if(gone >= this.#speed) gone = this.speed;
2358             else{
2359                 if(this.#speed === this.speed) gone = this.#speed - gone;
2360                 else gone = (this.#speed - gone) / this.#speed * this.speed;
2361             }
2362             
2363             setTimeout(this._loop, gone);
2364             
2365             this.#i = 1;
2366             if(this.func !== null) this.func(this);
2367 
2368         }
2369 
2370         //正在运行
2371         else if(this.#i < this.step){
2372 
2373             setTimeout(this._loop, this.speed);
2374 
2375             this.#i++;
2376             if(this.#readyState !== 'running') this.#readyState = 'running';
2377             if(this.func !== null) this.func(this);
2378 
2379         }
2380 
2381         //完成
2382         else this.#isRun = false;
2383 
2384     }
2385 
2386 }
2387 
2388 
2389 
2390 
2391 /* SeekPath A*寻路
2392 
2393 parameter: 
2394     option: Object{
2395         angle: Number,         //8 || 16
2396         timeout: Number,     //单位为毫秒
2397         size: Number,         //每格的宽高
2398         lenX, lenY: Number,    //长度
2399         disables: Array[0||1],
2400         heights: Array[Number],
2401         path: Array[], //存放寻路结果 默认创建一个空数组
2402     }
2403 
2404 attribute:
2405     size: Number;     //每个索引的大小
2406     lenX: Number;     //最大长度x (设置此属性时, 你需要重新.initMap(heights);)
2407     lenY: Number;     //最大长度y (设置此属性时, 你需要重新.initMap(heights);)
2408 
2409     //此属性已废弃 range: Box;            //本次的搜索范围, 默认: 0,0,lenX,lenY
2410     angle: Number;         //8四方向 或 16八方向 默认 16
2411     timeout: Number;     //超时毫秒 默认 500
2412     mapRange: Box;        //地图box
2413     //此属性已废弃(run函数不在检测相邻的高) maxHeight: Number;     //相邻可走的最大高 默认 6
2414 
2415     //只读属性
2416     success: Bool;            //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false)
2417     path: Array[x, y, z];    //存放.run()返回的路径
2418     map: Map;                 //地图的缓存数据
2419 
2420 method:
2421     initMap(heights: Array[Number]): undefiend;     //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数
2422     run(x, y, x1, y1: Number): Array[x, y, z];         //参数索引坐标
2423     getDots(x, y, a, r): Array[ix, iy];             //获取周围的点 x,y, a:8|16, r:存放结果数组
2424     getLegalPoints(ix, iy, count): Array[x, y, z];    //获取 ix, iy 周围 合法的, 相邻的 count 个点
2425 
2426 demo:
2427     const sp = new SeekPath({
2428         angle: 16,
2429         timeout: 500,
2430         //maxHeight: 6,
2431         size: 10,
2432         lenX: 1000,
2433         lenY: 1000,
2434     }),
2435 
2436     path = sp.run(0, 0, 1000, 1000);
2437 
2438     console.log(sp);
2439 
2440 */
2441 class SeekPath{
2442 
2443     static _open = []
2444     static _dots = [] //.run() .getLegalPoints()
2445     static dots4 = []; //._check()
2446     static _sort = function (a, b){return a["f"] - b["f"];}
2447 
2448     #map = null;
2449     #path = null;
2450     #success = true;
2451     #halfX = 50;
2452     #halfY = 50;
2453 
2454     #size = 10;
2455     #lenX = 10;
2456     #lenY = 10;
2457 
2458     constructor(option = {}){
2459         this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向
2460         this.timeout = option.timeout || 500; //超时毫秒
2461         //this.maxHeight = option.maxHeight || 6;
2462         this.mapRange = new Box();
2463         this.size = option.size || 10;
2464         this.lenX = option.lenX || 10;
2465         this.lenY = option.lenY || 10;
2466         this.#path = Array.isArray(option.path) ? option.path : [];
2467         this.initMap(option.disable, option.height);
2468         option = undefined
2469     }
2470 
2471     //this.#map[x][y] = Object{height: 此点的高,默认0, is: 此点是否可走,默认1(disable)};
2472     get map(){
2473         return this.#map;
2474     }
2475 
2476     //this.#path = Array[x,y,z]
2477     get path(){
2478         return this.#path;
2479     }
2480 
2481     get success(){
2482         return this.#success;
2483     }
2484 
2485     get size(){
2486         return this.#size;
2487     }
2488 
2489     set size(v){
2490         this.#size = v;
2491         v = v / 2;
2492         this.#halfX = v * this.#lenX;
2493         this.#halfY = v * this.#lenY;
2494     }
2495 
2496     get lenX(){
2497         return this.#lenX;
2498     }
2499 
2500     set lenX(v){
2501         this.#lenX = v;
2502         v = this.#size / 2;
2503         this.#halfX = v * this.#lenX;
2504         this.#halfY = v * this.#lenY;
2505         
2506     }
2507 
2508     get lenY(){
2509         return this.#lenY;
2510     }
2511 
2512     set lenY(v){
2513         this.#lenY = v;
2514         v = this.#size / 2;
2515         this.#halfX = v * this.#lenX;
2516         this.#halfY = v * this.#lenY;
2517         
2518     }
2519 
2520     toScene(n, v){ //n = "x|y"
2521         //n = n === "y" ? "lenY" : "lenX";
2522         if(n === "y" || n === "z") return v * this.#size - this.#halfY;
2523         return v * this.#size - this.#halfX;
2524     }
2525     
2526     toIndex(n, v){
2527         //n = n === "y" ? "lenY" : "lenX";
2528         if(n === "y" || n === "z") return Math.round((this.#halfY + v) / this.#size);
2529         return Math.round((this.#halfX + v) / this.#size);
2530     }
2531 
2532     initMap(disable, height){
2533         
2534         disable = Array.isArray(disable) === true ? disable : null;
2535         height = Array.isArray(height) === true ? height : null;
2536         
2537         const lenX = this.lenX, lenY = this.lenY;
2538         var getHeight = (ix, iy) => {
2539             if(height === null) return 0;
2540             ix = height[ix * lenY + iy];
2541             if(ix === undefined) return 0;
2542             return ix;
2543         },
2544         getDisable = (ix, iy) => {
2545             if(disable === null) return 1;
2546             ix = disable[ix * lenY + iy];
2547             if(ix === undefined) return 0;
2548             return ix;
2549         },
2550 
2551         map = []//new Map();
2552 
2553         for(let x = 0, y, m; x < lenX; x++){
2554             m = []//new Map();
2555             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:""});
2556             map[x] = m;//map.set(x, m);
2557         }
2558         
2559         this.#map = map;
2560         this._id = -1;
2561         this._updateID();
2562         this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1);
2563 
2564         map = disable = height = getHeight = undefined;
2565 
2566     }
2567 
2568     getLegalPoints(ix, iy, count, result = []){  //不包括 ix, iy
2569         const _dots = SeekPath._dots;
2570         result.length = 0;
2571         result[0] = this.#map[ix][iy];
2572         count += 1;
2573         
2574         while(result.length < count){
2575             for(let k = 0, i, n, d, len = result.length; k < len; k++){
2576                 n = result[k];
2577                 this.getDots(n.x, n.y, this.angle, _dots);
2578                 for(i = 0; i < this.angle; i += 2){
2579                     d = this.#map[_dots[i]][_dots[i+1]];
2580                     if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){
2581                         if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){
2582                             result.push(d);
2583                         }
2584                     }
2585                 }
2586             }
2587         }
2588     
2589         result.splice(0, 1);
2590         return result;
2591     }
2592 
2593     getLinePoints(now, next, count, result = []){ //不包括 now 
2594         if(count % 2 !== 0) count += 1;
2595 
2596         const len = count / 2, angle90 = 90/180*Math.PI;
2597 
2598         var i, ix, iy, n, nn = next, is = false;
2599 
2600         UTILS.emptyPoint.set(now.x, now.y).rotate(next, angle90);
2601         var disX = UTILS.emptyPoint.x - next.x, 
2602         disY = UTILS.emptyPoint.y - next.y;
2603         
2604         for(i = 0; i < len; i++){
2605             if(is){
2606                 result[len-1-i] = nn;
2607                 continue;
2608             }
2609             ix = disX + disX * i + next.x;
2610             iy = disY + disY * i + next.y;
2611 
2612             n = this.#map[ix][iy];
2613             if(n.is === 1) nn = n;
2614             else is = true;
2615             result[len-1-i] = nn;
2616         }
2617 
2618         //result[len] = next;
2619         is = false;
2620         nn = next;
2621 
2622         UTILS.emptyPoint.set(now.x, now.y).rotate(next, -angle90);
2623         disX = UTILS.emptyPoint.x - next.x, 
2624         disY = UTILS.emptyPoint.y - next.y;
2625 
2626         for(i = 0; i < len; i++){
2627             if(is){
2628                 result[len+i] = nn;
2629                 continue;
2630             }
2631             ix = disX + disX * i + next.x;
2632             iy = disY + disY * i + next.y;
2633 
2634             n = this.#map[ix][iy];
2635             if(n.is === 1) nn = n;
2636             else is = true;
2637             result[len+i] = nn;
2638         }
2639 
2640         return result;
2641     }
2642 
2643     getDots(x, y, a, r = []){ //获取周围的点 x,y, a:8|16, r:存放结果数组
2644         r.length = 0;
2645         const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1;
2646         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);
2647         else r.push(x, y_1, x, y1, x_1, y, x1, y);
2648     }
2649 
2650     getDisMHD(nodeA, nodeB){
2651         return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
2652     }
2653 
2654     _updateID(){ //更新标记
2655         this._id++;
2656         this._openID = "o_"+this._id;
2657         this._closeID = "c_"+this._id;
2658     }
2659 
2660     _check(dotA, dotB){ //检测 a 是否能到 b
2661         //获取 dotB 周围的4个点 并 遍历这4个点
2662         this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4);
2663         for(let k = 0, x, y; k < 8; k += 2){
2664             x = SeekPath.dots4[k]; 
2665             y = SeekPath.dots4[k+1];
2666             if(this.mapRange.containsPoint(x, y) === false) continue;
2667 
2668             //找出 dotA 与 dotB 相交的两个点:
2669             if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){
2670                 //如果其中一个交点是不可走的则 dotA 到 dotB 不可走, 既返回 false
2671                 if(this.#map[x][y].is === 0) return false;
2672             }
2673 
2674         }
2675 
2676         return true;
2677     }
2678 
2679     run(x, y, x1, y1, path = this.#path){
2680         path.length = 0;
2681         if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path;
2682         
2683         var _n = this.#map[x][y];
2684         if(_n.is === 0) return path;
2685 
2686         const _sort = SeekPath._sort,
2687         _open = SeekPath._open,
2688         _dots = SeekPath._dots, 
2689         time = Date.now();
2690 
2691         //var isDot = true, 
2692         var suc = _n, k, mhd, g, h, f, _d;
2693 
2694         _n.g = 0;
2695         _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 
2696         _n.f = _n.h;
2697         _n.p = null;
2698         this._updateID();
2699         _n.id = this._openID;
2700         _open.push(_n);
2701         
2702         while(_open.length !== 0){
2703             if(Date.now() - time > this.timeout) break;
2704 
2705             _open.sort(_sort);
2706             _n = _open.shift();
2707             if(_n.x === x1 && _n.y === y1){
2708                 suc = _n;
2709                 break;
2710             }
2711             
2712             if(suc.h > _n.h) suc = _n;
2713             _n.id = this._closeID;
2714             this.getDots(_n.x, _n.y, this.angle, _dots);
2715             
2716             for(k = 0; k < this.angle; k += 2){
2717                 
2718                 _d = this.#map[_dots[k]][_dots[k+1]];
2719                 if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue;
2720 
2721                 mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y);
2722                 g = _n["g"] + (mhd === 1 ? 10 : 14);
2723                 h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10;
2724                 f = g + h;
2725             
2726                 if(_d.id !== this._openID){
2727                     //如果是斜角和8方向:
2728                     if(mhd !== 1 && this.angle === 16){
2729                         if(this._check(_n, _d)){
2730                             _d.g = g;
2731                             _d.h = h;
2732                             _d.f = f;
2733                             _d.p = _n;
2734                             _d.id = this._openID;
2735                             _open.push(_d);
2736                         }
2737                     }else{
2738                         _d.g = g;
2739                         _d.h = h;
2740                         _d.f = f;
2741                         _d.p = _n;
2742                         _d.id = this._openID;
2743                         _open.push(_d);
2744                     }
2745                 }
2746 
2747                 else if(g < _d.g){
2748                     _d.g = g;
2749                     _d.f = g + _d.h;
2750                     _d.p = _n;
2751                 }
2752     
2753             }
2754         }
2755 
2756         this.#success = suc === _n;
2757 
2758         while(suc !== null){
2759             path.unshift(suc);
2760             //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"]));
2761             suc = suc["p"];
2762         }
2763 
2764         _open.length = _dots.length = 0;
2765         
2766         return path;
2767     }
2768 
2769 }
2770 
2771 
2772 
2773 
2774 /* TweenValue (从 原点 以规定的时间到达  终点)
2775 
2776 parameter: origin, end, time, onUpdate, onEnd;
2777 
2778 attribute:
2779     origin: Object; //原点(起点)
2780     end: Object; //终点
2781     time: Number; //origin 到 end 花费的时间
2782     onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null;
2783     onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是"restart"将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间)
2784 
2785 method:
2786     reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性
2787     reverse(): undefined; //this.end 复制 this.origin 的原始值
2788     update(): undefined; //Tween 通过此方法统一更新 TweenValue
2789 
2790 demo: 
2791     //init Tween:
2792     const tween = new Tween(),
2793     animate = function (){
2794         requestAnimationFrame(animate);
2795         tween.update();
2796     }
2797 
2798     //init TweenValue:
2799     const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
2800     
2801     animate();
2802     tween.start(v1);
2803 
2804 */
2805 class TweenValue{
2806 
2807     constructor(origin = {}, end = {}, time = 500, onEnd = null, onUpdate = null, onStart = null){
2808         this.origin = origin;
2809         this.end = end;
2810         this.time = time;
2811 
2812         this.onUpdate = onUpdate;
2813         this.onEnd = onEnd;
2814         this.onStart = onStart;
2815         
2816         //以下属性不能直接设置
2817         this._r = null;
2818         this._t = 0;
2819         this._v = Object.create(null);
2820     }
2821 
2822     _start(){
2823         var v = "";
2824         for(v in this.origin) this._v[v] = this.origin[v];
2825         if(this.onStart !== null) this.onStart(this);
2826         this._t = Date.now();
2827         //this.update();
2828     }
2829 
2830     reset(origin, end){
2831         this.origin = origin;
2832         this.end = end;
2833         this._v = Object.create(null);
2834     }
2835 
2836     reverse(){
2837         var n = "";
2838         for(n in this.origin) this.end[n] = this._v[n];
2839     }
2840 
2841     update(){
2842 
2843         if(this["_r"] !== null){
2844 
2845             var ted = Date["now"]() - this["_t"];
2846 
2847             if(ted >= this["time"]){
2848 
2849                 for(ted in this["origin"]) this["origin"][ted] = this["end"][ted];
2850 
2851                 if(this["onEnd"] !== null){
2852 
2853                     if(this["onEnd"](this) === "restart"){
2854                         if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
2855                         this["_start"]();
2856                     }
2857 
2858                     else this["_r"]["stop"](this);
2859                     
2860                 }
2861 
2862                 else this["_r"]["stop"](this);
2863 
2864             }
2865 
2866             else{
2867                 ted = ted / this["time"];
2868                 let n = "";
2869                 for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n];
2870                 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]);
2871             }
2872 
2873         }
2874 
2875     }
2876 
2877 }
2878 
2879 
2880 
2881 
2882 /* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue)
2883 
2884 parameter:
2885 attribute:
2886 method:
2887     start(value: TweenValue): undefined;
2888     stop(value: TweenValue): undefined;
2889 
2890 static:
2891     Value: TweenValue;
2892 
2893 demo:
2894     //init Tween:
2895     const tween = new Tween(),
2896     animate = function (){
2897         requestAnimationFrame(animate);
2898         tween.update();
2899     }
2900 
2901     //init TweenValue:
2902     const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
2903         v2.reverse(); //v2.end 复制起始值
2904         return "restart"; //返回"restart"表示不删除队列, 需要继续补间
2905     });
2906     
2907     animate();
2908     tween.start(v2);
2909 
2910 */
2911 class Tween extends RunningList{
2912 
2913     static Value = TweenValue;
2914 
2915     constructor(){
2916         super();
2917     }
2918 
2919     start(value){
2920         this.add(value);
2921         value._r = this;
2922         value._start();
2923     }
2924 
2925     stop(value){
2926         this.remove(value);
2927         value._r = null;
2928     }
2929 
2930 }
2931 
2932 
2933 
2934 
2935 /* TweenTarget 朝着轴插值(有效的跟踪动态目标, 注意此类需要配合 RunningList 类使用, 因为此类在任何情况下都没有阻止你调用.update()方法)
2936 parameter:    
2937     v1 = {x: 0}, 
2938     v2 = {x: 100}, 
2939     distance = 1,        //每次移动的距离
2940     onUpdate = null,    //
2941     onEnd = null
2942 
2943 attribute:
2944     v1: Object;             //起点
2945     v2: Object;             //终点
2946     onUpdate: Function;        //
2947     onEnd: Function;         //
2948 
2949 method:
2950     update(): undefined;                        //一般在动画循环里执行此方法
2951     updateAxis(): undefined;                     //更新v1至v2的方向轴 (初始化时构造器自动调用一次)
2952     setDistance(distance: Number): undefined;     //设置每次移动的距离 (初始化时构造器自动调用一次)
2953 
2954 demo:
2955     const ttc = new TweenTarget({x:0, y:0}, {x:100, y:100}, 10),
2956 
2957     //计时器模拟动画循环函数, 每秒执行一次.update()
2958     timer = new Timer(() => {
2959         ttc.update();
2960         console.log('update: ', ttc.v1);
2961 
2962     }, 1000, Infinity);
2963 
2964     ttc.onEnd = v => {
2965         timer.stop();
2966         console.log('end: ', v);
2967     }
2968 
2969     timer.start();
2970     console.log(ttc);
2971 
2972 */
2973 class TweenTarget{
2974 
2975     #distance = 1;
2976     #distancePow2 = 1;
2977     #axis = {};
2978     get distance(){return this.#distance;}
2979 
2980     constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){
2981         this.v1 = v1;
2982         this.v2 = v2;
2983         this.onUpdate = onUpdate;
2984         this.onEnd = onEnd;
2985     
2986         this.setDistance(distance);
2987         this.updateAxis();
2988     }
2989 
2990     setDistance(v = 1){
2991         this.#distance = v;
2992         this.#distancePow2 = Math.pow(v, 2);
2993     }
2994 
2995     updateAxis(){
2996         var n, v, len = 0;
2997 
2998         for(n in this.v1){
2999             v = this.v2[n] - this.v1[n];
3000             len += v * v;
3001             this.#axis[n] = v;
3002 
3003         }
3004 
3005         len = Math.sqrt(len);
3006 
3007         if(len !== 0){
3008             
3009             for(n in this.v1) this.#axis[n] *= 1 / len;
3010 
3011         }
3012     }
3013 
3014     update(){
3015         var n, len = 0;
3016 
3017         for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2);
3018 
3019         if(len > this.#distancePow2){
3020 
3021             for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance;
3022             if(this.onUpdate !== null) this.onUpdate(this.v1);
3023 
3024         }
3025 
3026         else{
3027 
3028             for(n in this.v1) this.v1[n] = this.v2[n];
3029             if(this.onEnd !== null) this.onEnd(this.v1);
3030             
3031         }
3032     }
3033 
3034 }
3035 
3036 
3037 
3038 
3039 /* EventDispatcher 自定义事件管理器
3040 parameter: 
3041 attribute: 
3042 
3043 method:
3044     clearEvents(eventName): undefined;             //清除eventName列表, 如果 eventName 未定义清除所有事件
3045     customEvents(eventName, eventParam): this;    //创建自定义事件 eventParam 可选 默认{}
3046     getParam(eventName): eventParam;
3047     trigger(eventName, callback): undefined;    //触发 (callback: 可选)
3048     register(eventName, callback): undefined;    //
3049     deleteEvent(eventName, callback): undefined; //
3050 
3051 demo:
3052     const eventDispatcher = new EventDispatcher();
3053     eventDispatcher.customEvents("test", {name: "test"});
3054 
3055     eventDispatcher.register("test", eventParam => {
3056         console.log(eventParam) //Object{name: "test"}
3057     });
3058 
3059     eventDispatcher.trigger("test");
3060 
3061 */
3062 class EventDispatcher{
3063     
3064     constructor(){
3065         this._eventsList = {};
3066         this.__eventsList = [];
3067         this.__trigger = "";
3068     }
3069 
3070     clearEvents(eventName){ 
3071 
3072         //if(this.__trigger === eventName) return console.warn("EventDispatcher: 清除事件失败");
3073         if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = []
3074 
3075         else this._eventsList = {}
3076 
3077     }
3078     
3079     customEvents(eventName, eventParam = {}){ 
3080         //if(typeof eventName !== "string") return console.warn("EventDispatcher: 注册自定义事件失败");
3081         if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在");
3082         //eventParam = eventParam || {}
3083         //if(eventParam.type === undefined) eventParam.type = eventName;
3084         this._eventsList[eventName] = {func: [], param: eventParam}
3085         return this;
3086     }
3087 
3088     getParam(eventName){
3089         return this._eventsList[eventName]["param"];
3090     }
3091     
3092     trigger(eventName, callback){
3093         //if(this._eventsList[eventName] === undefined) return;
3094         
3095         const obj = this._eventsList[eventName];
3096         var k, len = obj.func.length;
3097 
3098         if(len !== 0){
3099             if(typeof callback === "function") callback(obj["param"]); //更新参数(eventParam)
3100 
3101             //触发过程(如果触发过程中删除事件, 不仅 len 没有变, 而且还会漏掉一个key, 所以在触发过程中删除的事件要特殊处理)
3102             this.__trigger = eventName;
3103             for(k = 0; k < len; k++) obj["func"][k](obj["param"]);
3104             this.__trigger = "";
3105             //触发过程结束
3106             
3107             //删除在触发过程中要删除的事件
3108             len = this.__eventsList.length;
3109             for(k = 0; k < len; k++) this.deleteEvent(eventName, this.__eventsList[k]);
3110             this.__eventsList.length = 0;
3111     
3112         }
3113 
3114     }
3115     
3116     register(eventName, callback){
3117         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
3118         const obj = this._eventsList[eventName];
3119         //if(obj.func.includes(callback) === false) obj.func.push(callback);
3120         //else console.warn("EventDispatcher: 回调函数重复");
3121         obj.func.push(callback);
3122     }
3123     
3124     deleteEvent(eventName, callback){
3125         if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在");
3126         
3127         if(this.__trigger === eventName) this.__eventsList.push(callback);
3128         else{
3129             const obj = this._eventsList[eventName], i = obj.func.indexOf(callback);
3130             if(i !== -1) obj.func.splice(i, 1);
3131         }
3132         
3133     }
3134 
3135 }
3136 
3137 
3138 
3139 
3140 export {
3141     UTILS, 
3142     ColorRefTable, 
3143     AnimateLoop,
3144     Ajax, 
3145     IndexedDB, 
3146     TreeStruct, 
3147     Point, 
3148     Line, 
3149     Box, 
3150     Circle, 
3151     Polygon, 
3152     RGBColor, 
3153     Timer, 
3154     SeekPath, 
3155     RunningList, 
3156     TweenValue, 
3157     Tween, 
3158     TweenTarget, 
3159     EventDispatcher, 
3160     Rotate,
3161     ShapeRect,
3162     SecurityDoor,
3163 }
Utils.js

 

  1 "use strict"
  2 
  3 import {
  4     Vector3, 
  5     Skeleton, 
  6     Frustum, 
  7     Matrix4, 
  8     Mesh, 
  9     sRGBEncoding, 
 10     RepeatWrapping, 
 11     TextureLoader, 
 12     ObjectLoader, 
 13     AnimationLoader, 
 14     BufferGeometryLoader,
 15 } from 'three'; //import * as THREE from '../../../build/three.module.js';
 16 
 17 /* import {Sky} from '../../jsm/objects/Sky.js';
 18 import {Water} from '../../jsm/objects/Water.js';
 19 import {CSS2DRenderer, CSS2DObject} from '../../jsm/renderers/CSS2DRenderer.js';
 20 import {mergeBufferGeometries} from '../../jsm/utils/BufferGeometryUtils.js';
 21 import {VertexNormalsHelper} from '../../jsm/helpers/VertexNormalsHelper.js';
 22 import { DecalGeometry } from '../../jsm/geometries/DecalGeometry.js';
 23 import { LightningStrike } from '../../jsm/geometries/LightningStrike.js'; */
 24 
 25 import {MTLLoader} from '../../jsm/loaders/MTLLoader.js';
 26 import {OBJLoader} from '../../jsm/loaders/OBJLoader.js';
 27 import {GLTFLoader} from '../../jsm/loaders/GLTFLoader.js';
 28 import {STLLoader} from '../../jsm/loaders/STLLoader.js';
 29 import {FBXLoader} from '../../jsm/loaders/FBXLoader.js';
 30 import {GLTFExporter} from '../../jsm/exporters/GLTFExporter.js';
 31 import {STLExporter} from '../../jsm/exporters/STLExporter.js';
 32 import {OBJExporter} from '../../jsm/exporters/OBJExporter.js';
 33 
 34 import {UTILS} from './Utils.js';
 35 
 36 /** ThreeUtils three.js实用类
 37 
 38 */
 39 const cameraFCVMatrix4 = new Matrix4(),
 40 
 41 textureNames = [
 42     "map", 
 43     "alphaMap", 
 44     "aoMap", 
 45     "envMap", 
 46     "lightMap", 
 47     "specularMap", 
 48     "emissiveMap", 
 49     "bumpMap", 
 50     "displacementMap", 
 51     "metalnessMap", 
 52     "roughnessMap", 
 53     "normalMap"
 54 ],
 55 
 56 _disposeMaterial = function (material){
 57     for(let k = 0, len = textureNames.length; k < len; k++){
 58         if(material[textureNames[k]]) material[textureNames[k]].dispose();
 59     }
 60     
 61     material.dispose();
 62 },
 63 
 64 ThreeUtils = {
 65 
 66     frustum: new Frustum(),
 67     
 68     textureLoader: new TextureLoader(),
 69     MTLLoader: MTLLoader,
 70     OBJLoader: OBJLoader,
 71     GLTFLoader: GLTFLoader,
 72     STLLoader: STLLoader,
 73     FBXLoader: FBXLoader,
 74     ObjectLoader: ObjectLoader,
 75     AnimationLoader: AnimationLoader,
 76     GeometryLoader: BufferGeometryLoader,
 77     
 78     GLTFExporter: GLTFExporter,
 79     STLExporter: STLExporter,
 80     OBJExporter: OBJExporter,
 81 
 82     traverse(object3D, callback){
 83 
 84         if(callback(object3D) !== "continue"){
 85 
 86             for(let k = 0, v, len = object3D.children.length; k < len; k++){
 87 
 88                 v = object3D.children[k];
 89 
 90                 if(v.children.length !== 0) this.traverse(v, callback);
 91                 else callback(v);
 92     
 93             }
 94 
 95         }
 96 
 97     },
 98     
 99     //清除 Object3D 缓存(不删除对象)
100     disposeObject3D(object3D){
101 
102         if(object3D.material !== undefined) this.disposeMaterial(object3D.material);
103         if(object3D.geometry !== undefined) object3D.geometry.dispose();
104         if(object3D.skeleton !== undefined) object3D.skeleton.dispose();
105 
106     },
107     
108     //支持数组材质
109     disposeMaterial(material){
110 
111         if(Array.isArray(material) === true){
112             for(let k = 0, c = material.length; k < c; k++) _disposeMaterial(material[k]);
113         }
114         else _disposeMaterial(material);
115 
116         return material;
117 
118     },
119     
120     //更换 geometry
121     updateGeometry(mesh, geometry){
122 
123         mesh.geometry.dispose();
124         mesh.geometry = geometry;
125 
126     },
127 
128     isObject3D(v){//v是否是object3D
129 
130         return UTILS.isObject(v) && v.isObject3D === true;
131         
132     },
133     
134     isBone(v){
135 
136         return (this.isObject3D(v) && v.isBone === true);
137 
138     },
139 
140     isSkinMesh(v){
141 
142         return (this.isObject3D(v) && v.isSkinnedMesh === true);
143 
144     },
145 
146     isCSS2D(v){
147 
148         return (this.isObject3D(v) && v.isCSS2DObject === true);
149 
150     },
151 
152     //克隆蒙皮网格(材质共享)
153     cloneSkinnedMesh(v){
154 
155         let bone = v.skeleton.bones[0].clone(true), bones = [];
156         bone.traverse((e)=>bones.push(e));
157 
158         const newMesh = v.clone();
159         newMesh.geometry = v.geometry.clone();
160         newMesh.children.length = 0;
161         newMesh.add(bone);
162         newMesh.bind(new Skeleton(bones));
163 
164         return newMesh;
165 
166     },
167 
168     //3维世界坐标 转为 屏幕坐标 必须: obj: Vector3 || Object3D; camera: 相机
169     //width: number屏幕宽; height: number屏幕高; 可选: vec3: Vector3;(结果复制到vec3中)
170     worldPosToScreen(obj, camera, width, height, vec3){
171 
172         vec3 = vec3 || new Vector3();
173         
174         if(this.isObject3D(obj) === true) obj.getWorldPosition(vec3);//如果是3D对象
175         else if(obj.isVector3 === true) vec3.copy(obj);//如果是Vector3对象
176         else return vec3;
177         
178         var _x = 0.5 * width, _y = 0.5 * height;//中心点坐标
179         vec3.project(camera);//转为标准设备坐标
180         vec3.set((vec3.x * _x) + _x, -(vec3.y * _y) + _y, 0);//转为屏幕坐标
181 
182         return vec3;
183 
184     },
185     
186     //检测一个点是否在相机视椎体内,返回boolean
187     cameraFrustumContainsVec3(camera, vec3){
188 
189         camera.updateMatrix();
190         camera.updateMatrixWorld();
191         this.frustum.setFromProjectionMatrix(cameraFCVMatrix4.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
192         return this.frustum.containsPoint(vec3);
193 
194     },
195 
196     loadModels(url, callback, type){
197 
198         if(!type) type = UTILS.getFileType(url);
199         var name = "", uuid;
200         
201         switch(type){
202             case "gltf": 
203             case "glb": 
204             name = "GLTFLoader";
205             break;
206             
207             case "fbx": 
208             name = "FBXLoader";
209             break;
210             
211             case "json": 
212             name = "ObjectLoader";
213             break;
214             
215             case "stl": //只由geometry, 文件体积最小
216             name = "STLLoader";
217             break;
218             
219             case "obj": //需要额外加载材质
220             name = "OBJLoader";
221             break;
222             
223             case "mtl":
224             name = "MTLLoader";
225             break;
226             
227             default: 
228             console.warn("ThreeUtils: 暂不支持: "+type);
229             return;
230         }
231         
232         const lode = new this[name]();
233         lode.load(url, (e)=>{
234             //ObjectLoader (bug: 有时候会执行两次回调)
235             if(this.isObject3D(e) === true){
236                 if(e.uuid !== uuid){
237                     callback(e);
238                     uuid = e.uuid;
239                 }
240             }
241 
242             //other
243             else callback(e.isBufferGeometry === true ? new Mesh(e) : e)
244             
245         });
246         
247         return lode;
248 
249     },
250 
251     loadGeometry(url, callback){
252 
253         //new this.GeometryLoader(url, callback);
254         
255     },
256 
257     loadTexture(url){
258 
259         const map = new TextureLoader().load(url);
260         map.encoding = sRGBEncoding;
261         map.wrapS = map.wrapT = RepeatWrapping;
262     
263         return map;
264 
265     },
266 
267 }
268 
269 
270 export {ThreeUtils}
ThreeUtils.js

 

  1 "use strict";
  2 
  3 /* O3D 此类用于测试 ThreeJS(javascript 3D库)
  4 attributes:
  5     orbitEnabled: bool;     //禁或启用轨道控制器
  6     transformEnabled: bool; //禁或启用变换控制器
  7     //只读属性:
  8     domElement: HTMLDivElement; //3D场景的父div
  9     animateLoop: AnimateLoop;   //这属性可以启或禁用动画循环 (可以的方法参见 Utils.js/AnimateLoop)
 10         手动更新3D场景: O3D.animateLoop.update()
 11 
 12 method:
 13     init(parentElement: HTMLElement): undefined;                //初始化O3D
 14     dispose(): undefined;                                       //销毁O3D (销毁后可重新init使用)
 15     resize(w, h: String): undefined;                            //设置或更新 domElement 的宽高 (渲染器和相机也会更新)
 16     intersect(offsetX, offsetY: Number, obj3ds: Array): Array;  //运行一次射线拾取
 17     clickEvent(callback: Func): undefined;                      //给 domElement 添加点击事件
 18     addEvent(eventName: String, callback: Func): undefined;     //
 19     removeEvent(eventName: String, callback: Func): undefined;  //
 20     appendObject3D(obj3d, parentObject3D: Object3D): undefined; //往场景添加3D对象
 21     removeObject3D(obj3d: Object3D): undefined;                 //从场景删除3D对象
 22 
 23     helloBox(w, h, d, ws, hs, ds: Number): undefined; //往3D场景添加一个box网格
 24     css2dObject(div: HTMLDivElement): CSS2DObject;    //创建一个 CSS2DObject 对象(用div生成一个3D对象)
 25 
 26     initBoxHelper(): undefined;                             //拾取辅助框
 27     initGridHelper(): undefined;                            //地面网格辅助
 28     initSelection(obj3ds: Object3D): undefined;             //点拾取3D对象
 29     initSelectionBox(startPoint, endPoint, deep): Object;   //矩形框拾取3D对象
 30 
 31 eventNames:
 32     "down", "up", "move"        //domElement 的鼠标事件
 33     "selectChange"              //拾取列表改变时触发
 34     "orbitControlsChange"       //OrbitControlsChange 轨道控制器的 change 事件
 35     "transformControlsChange"   //TransformControls 变换控制器的 change 事件
 36 
 37 demo:
 38     import { UTILS } from './lib/Utils.js';
 39     import { O3D } from './O3D.js';
 40 
 41     document.body.style.overflow = "hidden";
 42     O3D.init(); //初始化 O3D
 43 
 44     for(let i = 0, helloBox; i < 10; i++){
 45         helloBox = O3D.helloBox(); //添加一个box网格到场景
 46         helloBox.position.x = UTILS.random(-10, 10);
 47         helloBox.position.z = UTILS.random(-10, 10);
 48     }
 49 
 50     O3D.initGridHelper(); //地面辅助网格
 51     O3D.initBoxHelper(); //拾取辅助框
 52 
 53     //点拾取对象 (鼠标左键)
 54     O3D.initSelection();
 55 
 56     //矩形框拾取对象 (鼠标右键)
 57     const selectionBox = O3D.initSelectionBox().initHelper();
 58     selectionBox.helperEnabled = true; //启用矩形框拾取对象 (当它启用时, 轨道控制器的鼠标右键会失效)
 59 
 60     addEventListener("resize", () => O3D.resize(innerWidth+"px", innerHeight+"px"));
 61 
 62 */
 63 
 64 import {
 65     Vector2,
 66     Raycaster,
 67     sRGBEncoding,
 68     WebGLRenderer,
 69     PerspectiveCamera,
 70     Scene,
 71     Color,
 72     AmbientLight,
 73     Group,
 74     Mesh,
 75     BoxGeometry,
 76     BoxHelper,
 77     Box3,
 78     GridHelper,
 79     Frustum,
 80     Vector3,
 81     Sphere,
 82     Quaternion,
 83     Euler,
 84     //Clock, 
 85     //AnimationMixer,
 86 } from 'three';
 87 
 88 import { CSS2DRenderer, CSS2DObject } from '../jsm/renderers/CSS2DRenderer.js';
 89 import { OrbitControls } from '../jsm/controls/OrbitControls.js';
 90 import {TransformControls} from '../jsm/controls/TransformControls.js';
 91 
 92 import { ThreeUtils } from './lib/ThreeUtils.js';
 93 import { AnimateLoop, EventDispatcher, SecurityDoor } from './lib/Utils.js';
 94 
 95 var eventDispatcher, domElement, renderer, css2dRenderer, camera, scene, ambientLight, object3d, orbitControls, transformControls;
 96 
 97 //射线拾取
 98 const ray = new Raycaster(), ray_vec2 = new Vector2(), ray_result = [];
 99 
100 //拾取队列
101 const selectObject3ds = [], selectObject3ds_box3 = new Box3();
102 
103 //点击与抬起的延迟时间
104 const clickDelay = 300; 
105 
106 //动画循环
107 const animateLoop = new AnimateLoop(function (){
108     renderer["render"](scene, camera);
109     css2dRenderer["render"](scene, camera);
110 });
111 
112 //renderer
113 function exitRender(){
114     renderer.dispose();
115     //css2dRenderer.dispose();
116     if(domElement.parentElement) domElement.parentElement.removeChild(domElement);
117     eventDispatcher = domElement = css2dRenderer = renderer = undefined;
118 }
119 
120 function initRender(){
121     renderer = new WebGLRenderer({ //precision: "highp" "mediump" "lowp";
122         //antialias: true, 
123         //precision: "lowp",
124     });
125     renderer.debug.checkShaderErrors = false;
126     renderer.outputEncoding = sRGBEncoding;
127     renderer.setPixelRatio(window.devicePixelRatio);
128     renderer.domElement.style.zIndex = -1;
129     renderer.domElement.style.position = "absolute";
130     renderer.domElement.style.left = 0;
131     renderer.domElement.style.top = 0;
132 
133     //
134     css2dRenderer = new CSS2DRenderer();
135     css2dRenderer.domElement.style.zIndex = 0;
136     css2dRenderer.domElement.style.position = "absolute";
137     css2dRenderer.domElement.style.left = 0;
138     css2dRenderer.domElement.style.top = 0;
139     
140     //domElement
141     domElement = globalThis.document.createElement("div");
142     domElement.appendChild(renderer.domElement);
143     domElement.appendChild(css2dRenderer.domElement);
144     domElement.style = `
145         width: ${globalThis.innerWidth}px;
146         height: ${globalThis.innerHeight}px;
147     `;
148 
149     //event
150     const param_pointerdown = {pointerEvent: null, button:0, pointerId:0, pageX:0, pageY:0, offsetX:0, offsetY:0},
151     param_pointerup = {pointerEvent: null, button:0, pointerId:0, pageX:0, pageY:0, offsetX:0, offsetY:0},
152     param_pointermove = {pointerEvent: null, button:0, pointerId:0, pageX:0, pageY:0, offsetX:0, offsetY:0}
153     eventDispatcher = new EventDispatcher()
154     .customEvents('down', param_pointerdown)
155     .customEvents('up', param_pointerup)
156     .customEvents('move', param_pointermove)
157     .customEvents('selectChange', {targets: selectObject3ds});
158     domElement.onpointerdown = function (event) {
159         param_pointerdown.pointerEvent = event;
160         param_pointerdown.button = event.button;
161         param_pointerdown.pointerId = event.pointerId;
162         param_pointerdown.pageX = event.pageX;
163         param_pointerdown.pageY = event.pageY;
164         param_pointerdown.offsetX = event.offsetX;
165         param_pointerdown.offsetY = event.offsetY;
166         eventDispatcher.trigger("down");
167     }
168     domElement.onpointerup = function (event) {
169         param_pointerup.pointerEvent = event;
170         param_pointerup.button = event.button;
171         param_pointerup.pointerId = event.pointerId;
172         param_pointerup.pageX = event.pageX;
173         param_pointerup.pageY = event.pageY;
174         param_pointerup.offsetX = event.offsetX;
175         param_pointerup.offsetY = event.offsetY;
176         eventDispatcher.trigger("up");
177     }
178     domElement.onpointermove = function (event) {
179         param_pointermove.pointerEvent = event;
180         param_pointermove.button = event.button;
181         param_pointermove.pointerId = event.pointerId;
182         param_pointermove.pageX = event.pageX;
183         param_pointermove.pageY = event.pageY;
184         param_pointermove.offsetX = event.offsetX;
185         param_pointermove.offsetY = event.offsetY;
186         eventDispatcher.trigger("move");
187     }
188 }
189 
190 function initSelectsBox3(){
191     const _box3 = new Box3(), box3 = selectObject3ds_box3, vec3 = new Vector3(),
192 
193     updateBox3s = function (obj3d){
194         obj3d.getWorldPosition(vec3);
195         if(obj3d.geometry !== undefined){
196             if(obj3d.geometry.boundingBox === null) obj3d.geometry.computeBoundingBox();
197             _box3.copy(obj3d.geometry.boundingBox);
198             _box3.min.add(vec3);
199             _box3.max.add(vec3);
200             box3.union(_box3);
201         }else{
202             box3.expandByPoint(vec3);
203         }
204     },
205 
206     updateBox3 = function (){
207         if(selectObject3ds.length === 0) return;
208         const obj3d = selectObject3ds[0];
209         obj3d.getWorldPosition(vec3);
210         if(obj3d.geometry !== undefined){
211             if(obj3d.geometry.boundingBox === null) obj3d.geometry.computeBoundingBox();
212             box3.copy(obj3d.geometry.boundingBox);
213             box3.min.add(vec3);
214             box3.max.add(vec3);
215         }else{
216             box3.min.copy(vec3);
217             box3.max.copy(vec3);
218         }
219         for(let k = 1, len = selectObject3ds.length; k < len; k++) updateBox3s(selectObject3ds[k]);
220     }
221 
222     eventDispatcher.register("selectChange", updateBox3);
223 }
224 
225 
226 //PerspectiveCamera
227 function exitCamera(){
228     camera = undefined;
229 }
230 
231 function initCamera(){
232     camera = new PerspectiveCamera(45, globalThis.innerWidth / globalThis.innerHeight, 1, 1000);
233     camera.position.set(0, 5, 10);
234     camera.lookAt(0, 0, 0);
235     //camera.updateProjectionMatrix();
236 }
237 
238 
239 //Scene
240 function exitScene(){
241     scene.traverse(object3D => ThreeUtils.disposeObject3D(object3D));
242     object3d = scene = undefined
243 }
244 
245 function initScene(){
246     scene = new Scene();
247     //scene.autoUpdate = false; //默认值为true,若设置了这个值,则渲染器会检查每一帧是否需要更新场景及其中物体的矩阵。 当设为false时,你必须亲自手动维护场景中的矩阵
248     //scene.background = new Color("#b9c0bc");
249     //scene.fog = new this.THREE.Fog(new this.THREE.Color("#dce0de"), camera.far*0.8, camera.far);
250     //scene.fog = new this.THREE.FogExp2(scene.background, 0.0005);
251     //scene.overrideMaterial = new MeshBasicMaterial(); //如果不为空,它将强制场景中的每个物体使用这里的材质来渲染。默认值为null
252     object3d = new Group();
253     object3d.name = "O3D_root";
254     ambientLight = new AmbientLight();
255     scene.add(ambientLight, object3d);
256 }
257 
258 
259 //OrbitControls
260 function exitOrbitControls(){
261     orbitControls._securityDoor = undefined;
262     orbitControls.dispose();
263     orbitControls = undefined;
264 }
265 
266 function initOrbitControls(){
267     const onchange = function (){
268         eventDispatcher.trigger("orbitControlsChange");
269         animateLoop.update();
270     },
271     _onchange = function (enabled){
272         orbitControls.enabled = enabled;
273         if(enabled){
274             orbitControls.addEventListener("change", onchange);
275         }
276         else{
277             orbitControls.removeEventListener("change", onchange);
278         }
279     },
280 
281     _securityDoor = new SecurityDoor(null, _onchange);
282 
283     orbitControls = new OrbitControls(camera, domElement);
284     _onchange(_securityDoor.empty);
285     
286    /*  orbitControls.zoomSpeed = 5;
287     orbitControls.enablePan = true;
288     orbitControls.minDistance = 80;
289     orbitControls.maxDistance = 200;
290     orbitControls.minPolarAngle = Math.PI*0.2;
291     orbitControls.maxPolarAngle = Math.PI*0.54; */
292     //orbitControls.enabled
293     eventDispatcher.customEvents('orbitControlsChange');
294     orbitControls._securityDoor = _securityDoor;
295     orbitControls.update();
296 }
297 
298 //TransformControls
299 function exitTransformControls(){
300     transformControls.dispose();
301     transformControls = undefined;
302 }
303 
304 function initTransformControls(){
305     const ondragging = function (event){
306         if(event.value) orbitControls._securityDoor.add("O3D_transformControls");
307         else orbitControls._securityDoor.remove("O3D_transformControls");
308     },
309     _onchange = function (enabled){
310         transformControls.enabled = enabled;
311         if(enabled){
312             transformControls.addEventListener('dragging-changed', ondragging);
313             transformControls.addEventListener('change', onchange);
314             eventDispatcher.register("selectChange", onselectChange);
315         }
316         else{
317             transformControls.removeEventListener('dragging-changed', ondragging);
318             transformControls.removeEventListener('change', onchange);
319             eventDispatcher.deleteEvent("selectChange", onselectChange);
320         }
321     },
322     _securityDoor = new SecurityDoor(null, _onchange),
323     _object3d = new Group(),
324     _outPoint = new Vector3(),
325     _vec3 = new Vector3(),
326 
327     onselectChange = function (){
328         const len = selectObject3ds.length;
329         if(len === 0 || selectObject3ds_box3.isEmpty()) transformControls.detach(_object3d);
330         else{
331             selectObject3ds_box3.getCenter(_object3d.position);
332             _outPoint.copy(_object3d.position);
333             if(len === 1){
334                 _object3d.quaternion.copy(selectObject3ds[0].quaternion);
335                 _object3d.scale.copy(selectObject3ds[0].scale);
336             }else{
337                 _object3d.quaternion.set(0,0,0,1);
338                 _object3d.scale.set(1,1,1);
339             }
340             transformControls.attach(_object3d);
341         }
342     },
343 
344     updatePosition = function (obj3d){
345         switch(transformControls.mode){
346             case "translate": obj3d.position.add(_vec3); break;
347             case "rotate": obj3d.quaternion.copy(_object3d.quaternion); break;
348             case "scale": obj3d.scale.copy(_object3d.scale); break;
349         }
350     },
351 
352     onchange = function (){
353         _vec3.copy(_object3d.position).sub(_outPoint);
354         selectObject3ds.forEach(updatePosition);
355         _outPoint.copy(_object3d.position);
356         eventDispatcher.trigger("transformControlsChange");
357         animateLoop.update();
358     }
359 
360     transformControls = new TransformControls(camera, domElement);
361     //transformControls.mode = "translate"; //默认"translate"、"rotate" 和 "scale"
362     //transformControls.space = "world"; //默认"world" 和 "local";
363     transformControls.size = 0.5; //默认 1
364 
365     eventDispatcher.customEvents('transformControlsChange');
366     transformControls._securityDoor = _securityDoor;
367     transformControls._object = _object3d;
368     if(scene) scene.add(transformControls, _object3d);
369     _onchange(_securityDoor.empty);
370 }
371 
372 //
373 const O3D = {
374 
375     get orbitEnabled(){
376         return orbitControls._securityDoor.empty;
377     },
378 
379     set orbitEnabled(v){
380         if(v === true) orbitControls._securityDoor.remove("O3D_user");
381         else if(v === false) orbitControls._securityDoor.add("O3D_user");
382     },
383 
384     get transformEnabled(){
385         return transformControls._securityDoor.empty;
386     },
387 
388     set transformEnabled(v){
389         if(v === true) transformControls._securityDoor.remove("O3D_user");
390         else if(v === false) transformControls._securityDoor.add("O3D_user");
391     },
392     
393     get domElement(){
394         return domElement;
395     },
396 
397     get camera(){
398         return camera;
399     },
400 
401     get renderer(){
402         return renderer;
403     },
404 
405     get scene(){
406         return scene;
407     },
408 
409     get animateLoop(){
410         return animateLoop;
411     },
412 
413     init(parentElement = globalThis.document.body){
414         initRender();
415         initSelectsBox3();
416         initCamera();
417         initScene();
418         initOrbitControls();
419         initTransformControls();
420 
421         parentElement.appendChild(domElement);
422         this.resize();
423     },
424 
425     dispose(){
426         animateLoop.stop();
427         domElement.parentElement.removeChild(domElement);
428         exitRender();
429         exitCamera();
430         exitTransformControls();
431         exitOrbitControls();
432         exitScene();
433         selectObject3ds.length = ray_result.length = 0;
434     },
435 
436     resize(w = domElement.offsetWidth+"px", h = domElement.offsetHeight+"px"){
437         domElement.style.width = w;
438         domElement.style.height = h;
439 
440         renderer.setSize(domElement.offsetWidth, domElement.offsetHeight);
441         css2dRenderer.setSize(domElement.offsetWidth, domElement.offsetHeight);
442 
443         camera.aspect = domElement.offsetWidth / domElement.offsetHeight;
444         camera.updateProjectionMatrix();
445 
446         animateLoop.update();
447     },
448 
449     intersect(offsetX, offsetY, obj3ds = object3d.children){
450         ray_result.length = 0;
451         ray_vec2.x = offsetX / domElement.offsetWidth * 2 - 1;
452         ray_vec2.y = - offsetY / domElement.offsetHeight * 2 + 1;
453         ray.setFromCamera(ray_vec2, camera);
454         ray.intersectObjects(obj3ds, true, ray_result);
455         return ray_result;
456     },
457 
458     clickEvent(callback){
459         if(typeof callback !== "function") return console.warn("clickEvent: 参数必须是一个函数");
460         var pointerId = -1, isDown = false, sTime = 0;
461 
462         const _onup = () => {
463             eventDispatcher.deleteEvent("up", onup);
464             isDown = false;
465         },
466 
467         onup = event => {
468             if(isDown && event.pointerId === pointerId && Date.now() - sTime < clickDelay){
469                 _onup();
470                 callback(event);
471             }
472         },
473 
474         ondown = event => {
475             if(isDown) _onup();
476             if(event.button !== 0) return;
477 
478             sTime = Date.now();
479             pointerId = event.pointerId;
480             isDown = true;
481             
482             eventDispatcher.register("up", onup);
483         }
484 
485         eventDispatcher.register("down", ondown);
486     },
487 
488     addEvent(eventName, callback){
489         eventDispatcher.register(eventName, callback);
490     },
491 
492     removeEvent(eventName, callback){
493         eventDispatcher.deleteEvent(eventName, callback);
494     },
495 
496     helloBox(w, h, d, ws, hs, ds){
497         const mesh = new Mesh(new BoxGeometry(w, h, d, ws, hs, ds));
498         object3d.add(mesh);
499         return mesh;
500     },
501 
502     css2dObject(div = globalThis.document.createElement("div")){
503         return new CSS2DObject(div);
504     },
505 
506     appendObject3D(obj3d, parentObject3D = object3d){
507         if(ThreeUtils.isObject3D(obj3d)){
508             if(ThreeUtils.isObject3D(obj3d.parent)){
509                 ThreeUtils.disposeObject3D(obj3d);
510                 obj3d.parent.remove(obj3d);
511             }
512             parentObject3D.add(obj3d);
513         }
514     },
515 
516     removeObject3D(obj3d){
517         if(ThreeUtils.isObject3D(obj3d) && ThreeUtils.isObject3D(obj3d.parent)){
518             const i = selectObject3ds.indexOf(obj3d);
519             if(i !== -1){
520                 selectObject3ds.splice(i, 1);
521                 eventDispatcher.trigger("selectChange");
522             }
523             ThreeUtils.disposeObject3D(obj3d);
524             obj3d.parent.remove(obj3d);
525         }
526     },
527 
528     initBoxHelper(){
529         const boxHelper = new BoxHelper();
530         boxHelper.geometry.computeBoundingBox();
531         boxHelper.visible = false;
532         transformControls._object.add(boxHelper);
533 
534         const box3 = new Box3(), min = box3.min, max = box3.max,
535         array = boxHelper.geometry.attributes.position.array,
536         
537         updateBoxHelper = () => {
538             if(selectObject3ds.length === 0 || selectObject3ds_box3.isEmpty()){
539                 boxHelper.visible = false;
540                 
541             }else{
542                 box3.copy(selectObject3ds_box3);
543                 min.sub(transformControls._object.position);
544                 max.sub(transformControls._object.position);
545                 array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z;
546                 array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z;
547                 array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z;
548                 array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z;
549                 array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z;
550                 array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z;
551                 array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z;
552                 array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z;
553                 boxHelper.geometry.attributes.position.needsUpdate = true;
554                 boxHelper.geometry.computeBoundingSphere();
555                 boxHelper.visible = true;
556             }
557         }
558         
559         eventDispatcher.register("selectChange", updateBoxHelper);
560         
561     },
562 
563     initGridHelper(size = 20, divisions = 20, colorCenterLine, colorGrid){
564         scene.add(new GridHelper(size, divisions, colorCenterLine, colorGrid));
565     },
566 
567     initSelection(obj3ds = object3d.children){
568         var obj3d, i;
569         const func = event => {
570             obj3d = this.intersect(event.offsetX, event.offsetY, obj3ds)[0];
571             if(!obj3d){
572                 selectObject3ds.length = 0;
573                 eventDispatcher.trigger("selectChange");
574                 animateLoop.update();
575                 return;
576             }
577 
578             obj3d = obj3d.object;
579             i = selectObject3ds.indexOf(obj3d);
580             if(i === -1){
581                 //如果选中列表存在object3D的上级,就解除其上级的选中状态
582                 var par = obj3d.parent, i;
583                 while(par !== null){
584                     i = selectObject3ds.indexOf(par);
585                     if(i !== -1) selectObject3ds.splice(i, 1);
586                     par = par.parent;
587                 }
588 
589                 //如果选中列表存在object3D的下级,就解除其下级的选中状态
590                 obj3d.traverse(o => {
591                     if(obj3d !== o){
592                         i = selectObject3ds.indexOf(o);
593                         if(i !== -1) selectObject3ds.splice(i, 1);
594                     }
595                 });
596 
597                 selectObject3ds.push(obj3d);
598             }
599 
600             else selectObject3ds.splice(i, 1);
601 
602             eventDispatcher.trigger("selectChange");
603             animateLoop.update();
604         }
605         
606         this.clickEvent(func);
607     },
608 
609     initSelectionBox(startPoint = new Vector3(), endPoint = new Vector3(), deep = globalThis.Number.MAX_VALUE){
610         const _frustum = new Frustum();
611         const _tmpPoint = new Vector3();
612 
613         const _vecNear = new Vector3();
614         const _vecTopLeft = new Vector3();
615         const _vecTopRight = new Vector3();
616         const _vecDownRight = new Vector3();
617         const _vecDownLeft = new Vector3();
618 
619         const _vecFarTopLeft = new Vector3();
620         const _vecFarTopRight = new Vector3();
621         const _vecFarDownRight = new Vector3();
622         const _vecFarDownLeft = new Vector3();
623 
624         const _vectemp1 = new Vector3();
625         const _vectemp2 = new Vector3();
626         const _vectemp3 = new Vector3();
627 
628         const scope = this;
629         var _helperEnabled = null;
630 
631         return {
632 
633             get frustum(){
634                 return _frustum;
635             },
636 
637             get helperEnabled(){
638                 return _helperEnabled !== null && _helperEnabled.empty;
639             },
640 
641             set helperEnabled(v){
642                 if(_helperEnabled === null) return;
643                 if(v === true) _helperEnabled.remove("O3D_user");
644                 else if(v === false) _helperEnabled.add("O3D_user");
645             },
646             
647             updateFrustum(){
648                 // Avoid invalid frustum
649         
650                 if ( startPoint.x === endPoint.x ) {
651         
652                     endPoint.x += Number.EPSILON;
653         
654                 }
655         
656                 if ( startPoint.y === endPoint.y ) {
657         
658                     endPoint.y += Number.EPSILON;
659         
660                 }
661         
662                 camera.updateProjectionMatrix();
663                 camera.updateMatrixWorld();
664         
665                 if ( camera.isPerspectiveCamera ) {
666         
667                     _tmpPoint.copy( startPoint );
668                     _tmpPoint.x = Math.min( startPoint.x, endPoint.x );
669                     _tmpPoint.y = Math.max( startPoint.y, endPoint.y );
670                     endPoint.x = Math.max( startPoint.x, endPoint.x );
671                     endPoint.y = Math.min( startPoint.y, endPoint.y );
672         
673                     _vecNear.setFromMatrixPosition( camera.matrixWorld );
674                     _vecTopLeft.copy( _tmpPoint );
675                     _vecTopRight.set( endPoint.x, _tmpPoint.y, 0 );
676                     _vecDownRight.copy( endPoint );
677                     _vecDownLeft.set( _tmpPoint.x, endPoint.y, 0 );
678         
679                     _vecTopLeft.unproject( camera );
680                     _vecTopRight.unproject( camera );
681                     _vecDownRight.unproject( camera );
682                     _vecDownLeft.unproject( camera );
683         
684                     _vectemp1.copy( _vecTopLeft ).sub( _vecNear );
685                     _vectemp2.copy( _vecTopRight ).sub( _vecNear );
686                     _vectemp3.copy( _vecDownRight ).sub( _vecNear );
687                     _vectemp1.normalize();
688                     _vectemp2.normalize();
689                     _vectemp3.normalize();
690         
691                     _vectemp1.multiplyScalar( deep );
692                     _vectemp2.multiplyScalar( deep );
693                     _vectemp3.multiplyScalar( deep );
694                     _vectemp1.add( _vecNear );
695                     _vectemp2.add( _vecNear );
696                     _vectemp3.add( _vecNear );
697         
698                     const planes = _frustum.planes;
699         
700                     planes[ 0 ].setFromCoplanarPoints( _vecNear, _vecTopLeft, _vecTopRight );
701                     planes[ 1 ].setFromCoplanarPoints( _vecNear, _vecTopRight, _vecDownRight );
702                     planes[ 2 ].setFromCoplanarPoints( _vecDownRight, _vecDownLeft, _vecNear );
703                     planes[ 3 ].setFromCoplanarPoints( _vecDownLeft, _vecTopLeft, _vecNear );
704                     planes[ 4 ].setFromCoplanarPoints( _vecTopRight, _vecDownRight, _vecDownLeft );
705                     planes[ 5 ].setFromCoplanarPoints( _vectemp3, _vectemp2, _vectemp1 );
706                     planes[ 5 ].normal.multiplyScalar( - 1 );
707         
708                 } else if ( camera.isOrthographicCamera ) {
709         
710                     const left = Math.min( startPoint.x, endPoint.x );
711                     const top = Math.max( startPoint.y, endPoint.y );
712                     const right = Math.max( startPoint.x, endPoint.x );
713                     const down = Math.min( startPoint.y, endPoint.y );
714         
715                     _vecTopLeft.set( left, top, - 1 );
716                     _vecTopRight.set( right, top, - 1 );
717                     _vecDownRight.set( right, down, - 1 );
718                     _vecDownLeft.set( left, down, - 1 );
719         
720                     _vecFarTopLeft.set( left, top, 1 );
721                     _vecFarTopRight.set( right, top, 1 );
722                     _vecFarDownRight.set( right, down, 1 );
723                     _vecFarDownLeft.set( left, down, 1 );
724         
725                     _vecTopLeft.unproject( camera );
726                     _vecTopRight.unproject( camera );
727                     _vecDownRight.unproject( camera );
728                     _vecDownLeft.unproject( camera );
729         
730                     _vecFarTopLeft.unproject( camera );
731                     _vecFarTopRight.unproject( camera );
732                     _vecFarDownRight.unproject( camera );
733                     _vecFarDownLeft.unproject( camera );
734         
735                     const planes = _frustum.planes;
736         
737                     planes[ 0 ].setFromCoplanarPoints( _vecTopLeft, _vecFarTopLeft, _vecFarTopRight );
738                     planes[ 1 ].setFromCoplanarPoints( _vecTopRight, _vecFarTopRight, _vecFarDownRight );
739                     planes[ 2 ].setFromCoplanarPoints( _vecFarDownRight, _vecFarDownLeft, _vecDownLeft );
740                     planes[ 3 ].setFromCoplanarPoints( _vecFarDownLeft, _vecFarTopLeft, _vecTopLeft );
741                     planes[ 4 ].setFromCoplanarPoints( _vecTopRight, _vecDownRight, _vecDownLeft );
742                     planes[ 5 ].setFromCoplanarPoints( _vecFarDownRight, _vecFarTopRight, _vecFarTopLeft );
743                     planes[ 5 ].normal.multiplyScalar( - 1 );
744         
745                 } else {
746         
747                     console.error( 'Unsupported camera type' );
748         
749                 }
750             },
751 
752             initHelper(obj3ds, div){
753                 if(globalThis.Array.isArray(obj3ds) === false) obj3ds = object3d.children;
754                 if(globalThis.HTMLDivElement.prototype.isPrototypeOf(div) === false){
755                     div = globalThis.document.createElement("div");
756                     div.style.background = "rgba(0,173,255,0.2)";
757                     div.style.border = "1px solid rgb(0,173,255)";
758                 }
759 
760                 div.style.pointerEvents = 'none';
761                 div.style.position = "absolute";
762 
763                 var pointerId = -1, isDown = false, sTime = 0;
764                 
765                 const _onup = function (){
766                     isDown = false;
767                     setDIVBox(0, 0);
768                     orbitControls._securityDoor.remove("O3D_selectionBoxHelper");
769                     if(div.parentElement !== null) div.parentElement.removeChild(div);
770                     eventDispatcher.deleteEvent("move", onmove);
771                     eventDispatcher.deleteEvent("up", onup);
772                     //globalThis.document.body.removeEventListener("pointerup", onup);
773                 },
774                 
775                 _onchange = function (enabled){
776                     if(enabled){
777                         eventDispatcher.register("down", ondown);
778                     }else{
779                         eventDispatcher.deleteEvent("down", ondown);
780                         _onup();
781                     }
782                 },
783 
784                 _securityDoor = new SecurityDoor(null, _onchange),
785                 originPoint = new Vector2(),
786                 minPoint = new Vector2(),
787                 maxPoint = new Vector2(),
788                 sphere = new Sphere(),
789 
790                 setDIVBox = function (clientX, clientY, width = 0, height = 0) {
791                     div.style.left = clientX + 'px';
792                     div.style.top = clientY + 'px';
793                     div.style.width = width + 'px';
794                     div.style.height = height+ 'px';
795                 },
796 
797                 ondown = function (event){
798                     if(event.button === 0) return;
799                     if(isDown === true) _onup();
800                     sTime = Date.now();
801                     pointerId = event.pointerId;
802                     isDown = true;
803                     orbitControls._securityDoor.add("O3D_selectionBoxHelper");
804                     originPoint.set(event.pageX, event.pageY);
805                     domElement.parentElement.appendChild(div);
806                     eventDispatcher.register("move", onmove);
807                     eventDispatcher.register("up", onup);
808                     //globalThis.document.body.addEventListener("pointerup", onup);
809                 },
810 
811                 onmove = function (event){
812                     if(isDown === true && event.pointerId === pointerId){
813                         minPoint.set(Math.min(originPoint.x, event.pageX), Math.min(originPoint.y, event.pageY));
814                         maxPoint.set(Math.max(originPoint.x, event.pageX), Math.max(originPoint.y, event.pageY));
815                         setDIVBox(minPoint.x, minPoint.y, maxPoint.x - minPoint.x, maxPoint.y - minPoint.y);
816                     }
817                 },
818                 
819                 push = function (obj3d){
820                     //如果选中列表存在object3D的上级,就解除其上级的选中状态
821                     var par = obj3d.parent;
822                     while(par !== null){
823                         if(selectObject3ds.includes(par)) return;
824                         par = par.parent;
825                     }
826             
827                     selectObject3ds.push(obj3d);
828                 },
829 
830                 run = function (v){
831                     if(v.geometry !== undefined){
832                         if(v.geometry.boundingSphere === null) v.geometry.computeBoundingSphere();
833                         sphere.radius = v.geometry.boundingSphere.radius;
834                         v.getWorldPosition(sphere.center);
835                         if(_frustum.intersectsSphere(sphere)) push(v);
836                     }else{
837                         v.getWorldPosition(sphere.center);
838                         if(_frustum.containsPoint(sphere.center)) push(v);
839                     }
840                 },
841 
842                 onup = event => {
843                     if(isDown === false || event.pointerId !== pointerId) return;
844                     _onup();
845                     if(Date.now() - sTime < clickDelay) return;
846                     startPoint.x = minPoint.x / domElement.offsetWidth * 2 - 1;
847                     startPoint.y = -minPoint.y / domElement.offsetHeight * 2 + 1;
848                     endPoint.x = maxPoint.x / domElement.offsetWidth * 2 - 1;
849                     endPoint.y = -maxPoint.y / domElement.offsetHeight * 2 + 1;
850                     this.updateFrustum();
851                     selectObject3ds.length = 0;
852                     for(let k = 0, len = obj3ds.length; k < len; k++) obj3ds[k].traverse(run);
853                     eventDispatcher.trigger("selectChange");
854                     animateLoop.update();
855                 }
856                 
857                 _helperEnabled = _securityDoor;
858                 this.helperEnabled = false;
859                 return this;
860             },
861 
862         }
863     },
864 
865 }
866 
867 export { O3D }
O3D.js

 

 

index.html:

 1 <!DOCTYPE html>
 2 <html lang = "zh-cmn-Hans">
 3     
 4     <head>
 5         <title>treeStructScene</title>
 6         <meta charset = "utf-8" />
 7         <meta name="author" contect="admin, 3247940050@qq.com" /> <!-- 关于作者的联系方式 -->
 8         <meta http-equiv="Cache-Control" content="no-siteapp"/> <!-- 不允许百度贴广告 -->
 9         <meta name="format-detection" content="telphone=no, email=no"/> <!-- 不允许自动识别电话号码和邮箱号 -->
10         <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"> <!-- 不允许用户缩放 -->
11         <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <!-- 优先使用ie最新版本或谷歌内核 -->
12         <style>
13             *{padding:0; margin:0; color:#fff; font-size:12px;}
14             body{overflow: hidden; background: #000;}
15         </style>
16         <script type = "importmap">
17             {
18                 "imports": {
19                     "three": "./build/three.module.js"
20                 }
21             }
22         </script>
23         <script type = "text/javascript">
24             document.oncontextmenu = function (){return false;}//禁用默认的鼠标右键菜单
25         </script>
26     </head>
27 
28     <body>
29         
30         <script src = "./examples/js/main.js" type = "module"></script>
31 
32     </body>
33 
34 </html>

 

main.js:

 1 import { UTILS } from './lib/Utils.js';
 2 import { O3D } from './O3D.js';
 3 
 4 //初始化 O3D
 5 document.body.style.overflow = "hidden";
 6 O3D.init(document.body);
 7 O3D.resize(innerWidth+"px", innerHeight+"px"); //3D场景的宽高
 8 //O3D.domElement.style.position = "absolute";
 9 //O3D.domElement.style.left = "50px";
10 O3D.initGridHelper(); //地面辅助网格
11 O3D.initBoxHelper(); //拾取辅助框
12 
13 //随机位置添加box网格到场景
14 for(let i = 0, helloBox; i < 10; i++){
15     helloBox = O3D.helloBox();
16     helloBox.position.x = UTILS.random(-10, 10);
17     helloBox.position.z = UTILS.random(-10, 10);
18 }
19 O3D.animateLoop.update(); //更新一次场景
20 
21 //点拾取对象 (鼠标左键)
22 O3D.initSelection();
23 
24 //矩形框拾取对象 (鼠标右键)
25 const selectionBox = O3D.initSelectionBox().initHelper();
26 selectionBox.helperEnabled = true; //启用矩形框拾取对象 (当它启用时, 轨道控制器的鼠标右键会失效)
27 
28 addEventListener("resize", () => O3D.resize(innerWidth+"px", innerHeight+"px"));
29 
30 //测试 selectChange 事件 (拾取队列发送改变时触发)
31 O3D.addEvent("selectChange", event => console.log(event.targets));

 Three.js版本: 140dev

结果图:

 

 

innerWidth
posted @ 2022-09-29 10:54  鸡儿er  阅读(106)  评论(0编辑  收藏  举报