import { u as use, i as install$g, a as install$h, b as installLabelLayout, m as map$1, c as isArray, p as parse$1, R as Rect, g as getECData, e as each, d as indexOf, r as registerPostInit, f as registerPostUpdate, h as registerPreprocessor, Z as ZRImage, C as ComponentModel, j as merge, k as defaults, l as init, n as brushSingle, M as Model, o as ZRText, q as createTextStyle, s as ComponentView, t as getPrecisionSafe, v as Cartesian, w as inherits, A as Axis, x as getLayoutRect, y as createScale, z as mixinAxisModelCommonMethods, B as clone, O as OrdinalMeta, D as curry, E as getMap, F as createDimensions, S as SeriesData, G as lift, H as BoundingRect, I as parseGeoJSON, J as fixTextCoords, K as reduce$1, L as parseDate, N as dataStack, P as formatTpl, Q as encodeHTML, T as addCommas, U as isObject, V as getTooltipMarker, W as formatTime, X as getCoordinateSystemDimensions, Y as SeriesModel, _ as ChartView, $ as createSymbol, a0 as round, a1 as createList$1, a2 as Graph$1, a3 as linkSeriesData, a4 as linearMap, a5 as extend$2 } from "./index-CIyxDY2d.js"; use([install$g, install$h]); use(installLabelLayout); function derive(makeDefaultOpt, initialize, proto) { if (typeof initialize == "object") { proto = initialize; initialize = null; } var _super = this; var propList; if (!(makeDefaultOpt instanceof Function)) { propList = []; for (var propName in makeDefaultOpt) { if (makeDefaultOpt.hasOwnProperty(propName)) { propList.push(propName); } } } var sub2 = function(options) { _super.apply(this, arguments); if (makeDefaultOpt instanceof Function) { extend$1(this, makeDefaultOpt.call(this, options)); } else { extendWithPropList(this, makeDefaultOpt, propList); } if (this.constructor === sub2) { var initializers = sub2.__initializers__; for (var i = 0; i < initializers.length; i++) { initializers[i].apply(this, arguments); } } }; sub2.__super__ = _super; if (!_super.__initializers__) { sub2.__initializers__ = []; } else { sub2.__initializers__ = _super.__initializers__.slice(); } if (initialize) { sub2.__initializers__.push(initialize); } var Ctor = function() { }; Ctor.prototype = _super.prototype; sub2.prototype = new Ctor(); sub2.prototype.constructor = sub2; extend$1(sub2.prototype, proto); sub2.extend = _super.extend; sub2.derive = _super.extend; return sub2; } function extend$1(target, source) { if (!source) { return; } for (var name in source) { if (source.hasOwnProperty(name)) { target[name] = source[name]; } } } function extendWithPropList(target, source, propList) { for (var i = 0; i < propList.length; i++) { var propName = propList[i]; target[propName] = source[propName]; } } const extendMixin = { extend: derive, // DEPCRATED derive }; function Handler(action, context) { this.action = action; this.context = context; } var notifier = { /** * Trigger event * @param {string} name */ trigger: function(name) { if (!this.hasOwnProperty("__handlers__")) { return; } if (!this.__handlers__.hasOwnProperty(name)) { return; } var hdls = this.__handlers__[name]; var l = hdls.length, i = -1, args = arguments; switch (args.length) { case 1: while (++i < l) { hdls[i].action.call(hdls[i].context); } return; case 2: while (++i < l) { hdls[i].action.call(hdls[i].context, args[1]); } return; case 3: while (++i < l) { hdls[i].action.call(hdls[i].context, args[1], args[2]); } return; case 4: while (++i < l) { hdls[i].action.call(hdls[i].context, args[1], args[2], args[3]); } return; case 5: while (++i < l) { hdls[i].action.call(hdls[i].context, args[1], args[2], args[3], args[4]); } return; default: while (++i < l) { hdls[i].action.apply(hdls[i].context, Array.prototype.slice.call(args, 1)); } return; } }, /** * Register event handler * @param {string} name * @param {Function} action * @param {Object} [context] * @chainable */ on: function(name, action, context) { if (!name || !action) { return; } var handlers = this.__handlers__ || (this.__handlers__ = {}); if (!handlers[name]) { handlers[name] = []; } else { if (this.has(name, action)) { return; } } var handler = new Handler(action, context || this); handlers[name].push(handler); return this; }, /** * Register event, event will only be triggered once and then removed * @param {string} name * @param {Function} action * @param {Object} [context] * @chainable */ once: function(name, action, context) { if (!name || !action) { return; } var self2 = this; function wrapper() { self2.off(name, wrapper); action.apply(this, arguments); } return this.on(name, wrapper, context); }, /** * Alias of once('before' + name) * @param {string} name * @param {Function} action * @param {Object} [context] * @chainable */ before: function(name, action, context) { if (!name || !action) { return; } name = "before" + name; return this.on(name, action, context); }, /** * Alias of once('after' + name) * @param {string} name * @param {Function} action * @param {Object} [context] * @chainable */ after: function(name, action, context) { if (!name || !action) { return; } name = "after" + name; return this.on(name, action, context); }, /** * Alias of on('success') * @param {Function} action * @param {Object} [context] * @chainable */ success: function(action, context) { return this.once("success", action, context); }, /** * Alias of on('error') * @param {Function} action * @param {Object} [context] * @chainable */ error: function(action, context) { return this.once("error", action, context); }, /** * Remove event listener * @param {Function} action * @param {Object} [context] * @chainable */ off: function(name, action) { var handlers = this.__handlers__ || (this.__handlers__ = {}); if (!action) { handlers[name] = []; return; } if (handlers[name]) { var hdls = handlers[name]; var retains = []; for (var i = 0; i < hdls.length; i++) { if (action && hdls[i].action !== action) { retains.push(hdls[i]); } } handlers[name] = retains; } return this; }, /** * If registered the event handler * @param {string} name * @param {Function} action * @return {boolean} */ has: function(name, action) { var handlers = this.__handlers__; if (!handlers || !handlers[name]) { return false; } var hdls = handlers[name]; for (var i = 0; i < hdls.length; i++) { if (hdls[i].action === action) { return true; } } } }; var guid = 0; var ArrayProto = Array.prototype; var nativeForEach = ArrayProto.forEach; var util = { /** * Generate GUID * @return {number} * @memberOf clay.core.util */ genGUID: function() { return ++guid; }, /** * Relative path to absolute path * @param {string} path * @param {string} basePath * @return {string} * @memberOf clay.core.util */ relative2absolute: function(path, basePath) { if (!basePath || path.match(/^\//)) { return path; } var pathParts = path.split("/"); var basePathParts = basePath.split("/"); var item = pathParts[0]; while (item === "." || item === "..") { if (item === "..") { basePathParts.pop(); } pathParts.shift(); item = pathParts[0]; } return basePathParts.join("/") + "/" + pathParts.join("/"); }, /** * Extend target with source * @param {Object} target * @param {Object} source * @return {Object} * @memberOf clay.core.util */ extend: function(target, source) { if (source) { for (var name in source) { if (source.hasOwnProperty(name)) { target[name] = source[name]; } } } return target; }, /** * Extend properties to target if not exist. * @param {Object} target * @param {Object} source * @return {Object} * @memberOf clay.core.util */ defaults: function(target, source) { if (source) { for (var propName in source) { if (target[propName] === void 0) { target[propName] = source[propName]; } } } return target; }, /** * Extend properties with a given property list to avoid for..in.. iteration. * @param {Object} target * @param {Object} source * @param {Array.} propList * @return {Object} * @memberOf clay.core.util */ extendWithPropList: function(target, source, propList) { if (source) { for (var i = 0; i < propList.length; i++) { var propName = propList[i]; target[propName] = source[propName]; } } return target; }, /** * Extend properties to target if not exist. With a given property list avoid for..in.. iteration. * @param {Object} target * @param {Object} source * @param {Array.} propList * @return {Object} * @memberOf clay.core.util */ defaultsWithPropList: function(target, source, propList) { if (source) { for (var i = 0; i < propList.length; i++) { var propName = propList[i]; if (target[propName] == null) { target[propName] = source[propName]; } } } return target; }, /** * @param {Object|Array} obj * @param {Function} iterator * @param {Object} [context] * @memberOf clay.core.util */ each: function(obj, iterator, context) { if (!(obj && iterator)) { return; } if (obj.forEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, len = obj.length; i < len; i++) { iterator.call(context, obj[i], i, obj); } } else { for (var key in obj) { if (obj.hasOwnProperty(key)) { iterator.call(context, obj[key], key, obj); } } } }, /** * Is object * @param {} obj * @return {boolean} * @memberOf clay.core.util */ isObject: function(obj) { return obj === Object(obj); }, /** * Is array ? * @param {} obj * @return {boolean} * @memberOf clay.core.util */ isArray: function(obj) { return Array.isArray(obj); }, /** * Is array like, which have a length property * @param {} obj * @return {boolean} * @memberOf clay.core.util */ isArrayLike: function(obj) { if (!obj) { return false; } else { return obj.length === +obj.length; } }, /** * @param {} obj * @return {} * @memberOf clay.core.util */ clone: function(obj) { if (!util.isObject(obj)) { return obj; } else if (util.isArray(obj)) { return obj.slice(); } else if (util.isArrayLike(obj)) { var ret2 = new obj.constructor(obj.length); for (var i = 0; i < obj.length; i++) { ret2[i] = obj[i]; } return ret2; } else { return util.extend({}, obj); } } }; var Base = function() { this.__uid__ = util.genGUID(); }; Base.__initializers__ = [ function(opts) { util.extend(this, opts); } ]; util.extend(Base, extendMixin); util.extend(Base.prototype, notifier); var EXTENSION_LIST = [ "OES_texture_float", "OES_texture_half_float", "OES_texture_float_linear", "OES_texture_half_float_linear", "OES_standard_derivatives", "OES_vertex_array_object", "OES_element_index_uint", "WEBGL_compressed_texture_s3tc", "WEBGL_depth_texture", "EXT_texture_filter_anisotropic", "EXT_shader_texture_lod", "WEBGL_draw_buffers", "EXT_frag_depth", "EXT_sRGB", "ANGLE_instanced_arrays" ]; var PARAMETER_NAMES = [ "MAX_TEXTURE_SIZE", "MAX_CUBE_MAP_TEXTURE_SIZE" ]; function GLInfo(_gl) { var extensions = {}; var parameters = {}; for (var i = 0; i < EXTENSION_LIST.length; i++) { var extName = EXTENSION_LIST[i]; createExtension(extName); } for (var i = 0; i < PARAMETER_NAMES.length; i++) { var name = PARAMETER_NAMES[i]; parameters[name] = _gl.getParameter(_gl[name]); } this.getExtension = function(name2) { if (!(name2 in extensions)) { createExtension(name2); } return extensions[name2]; }; this.getParameter = function(name2) { return parameters[name2]; }; function createExtension(name2) { if (_gl.getExtension) { var ext = _gl.getExtension(name2); if (!ext) { ext = _gl.getExtension("MOZ_" + name2); } if (!ext) { ext = _gl.getExtension("WEBKIT_" + name2); } extensions[name2] = ext; } } } const glenum = { /* ClearBufferMask */ DEPTH_BUFFER_BIT: 256, STENCIL_BUFFER_BIT: 1024, COLOR_BUFFER_BIT: 16384, /* BeginMode */ POINTS: 0, LINES: 1, LINE_LOOP: 2, LINE_STRIP: 3, TRIANGLES: 4, TRIANGLE_STRIP: 5, TRIANGLE_FAN: 6, STREAM_DRAW: 35040, STATIC_DRAW: 35044, DYNAMIC_DRAW: 35048, /* CullFaceMode */ FRONT: 1028, BACK: 1029, FRONT_AND_BACK: 1032, /* FrontFaceDirection */ CW: 2304, CCW: 2305, /* DataType */ BYTE: 5120, UNSIGNED_BYTE: 5121, SHORT: 5122, UNSIGNED_SHORT: 5123, INT: 5124, UNSIGNED_INT: 5125, FLOAT: 5126, /* PixelFormat */ DEPTH_COMPONENT: 6402, ALPHA: 6406, RGB: 6407, RGBA: 6408, LUMINANCE: 6409, LUMINANCE_ALPHA: 6410, /* TextureMagFilter */ NEAREST: 9728, LINEAR: 9729, /* TextureMinFilter */ /* NEAREST */ /* LINEAR */ NEAREST_MIPMAP_NEAREST: 9984, LINEAR_MIPMAP_NEAREST: 9985, NEAREST_MIPMAP_LINEAR: 9986, LINEAR_MIPMAP_LINEAR: 9987, /* TextureTarget */ TEXTURE_2D: 3553, TEXTURE_CUBE_MAP: 34067, /* TextureWrapMode */ REPEAT: 10497, CLAMP_TO_EDGE: 33071, MIRRORED_REPEAT: 33648, /* Framebuffer Object. */ FRAMEBUFFER: 36160, RENDERBUFFER: 36161, DEPTH_STENCIL: 34041, COLOR_ATTACHMENT0: 36064, DEPTH_ATTACHMENT: 36096, STENCIL_ATTACHMENT: 36128, DEPTH_STENCIL_ATTACHMENT: 33306 }; function get(options) { var xhr = new XMLHttpRequest(); xhr.open("get", options.url); xhr.responseType = options.responseType || "text"; if (options.onprogress) { xhr.onprogress = function(e2) { if (e2.lengthComputable) { var percent = e2.loaded / e2.total; options.onprogress(percent, e2.loaded, e2.total); } else { options.onprogress(null); } }; } xhr.onload = function(e2) { if (xhr.status >= 400) { options.onerror && options.onerror(); } else { options.onload && options.onload(xhr.response); } }; if (options.onerror) { xhr.onerror = options.onerror; } xhr.send(null); } const request = { get }; var supportWebGL; var vendor = {}; vendor.supportWebGL = function() { if (supportWebGL == null) { try { var canvas = document.createElement("canvas"); var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); if (!gl) { throw new Error(); } } catch (e2) { supportWebGL = false; } } return supportWebGL; }; vendor.Int8Array = typeof Int8Array === "undefined" ? Array : Int8Array; vendor.Uint8Array = typeof Uint8Array === "undefined" ? Array : Uint8Array; vendor.Uint16Array = typeof Uint16Array === "undefined" ? Array : Uint16Array; vendor.Uint32Array = typeof Uint32Array === "undefined" ? Array : Uint32Array; vendor.Int16Array = typeof Int16Array === "undefined" ? Array : Int16Array; vendor.Float32Array = typeof Float32Array === "undefined" ? Array : Float32Array; vendor.Float64Array = typeof Float64Array === "undefined" ? Array : Float64Array; var g = {}; if (typeof window !== "undefined") { g = window; } else if (typeof global !== "undefined") { g = global; } vendor.requestAnimationFrame = g.requestAnimationFrame || g.msRequestAnimationFrame || g.mozRequestAnimationFrame || g.webkitRequestAnimationFrame || function(func) { setTimeout(func, 16); }; vendor.createCanvas = function() { return document.createElement("canvas"); }; vendor.createImage = function() { return new g.Image(); }; vendor.request = { get: request.get }; vendor.addEventListener = function(dom, type, func, useCapture) { dom.addEventListener(type, func, useCapture); }; vendor.removeEventListener = function(dom, type, func) { dom.removeEventListener(type, func); }; var LinkedList$1 = function() { this.head = null; this.tail = null; this._length = 0; }; LinkedList$1.prototype.insert = function(val) { var entry = new LinkedList$1.Entry(val); this.insertEntry(entry); return entry; }; LinkedList$1.prototype.insertAt = function(idx, val) { if (idx < 0) { return; } var next = this.head; var cursor = 0; while (next && cursor != idx) { next = next.next; cursor++; } if (next) { var entry = new LinkedList$1.Entry(val); var prev = next.prev; if (!prev) { this.head = entry; } else { prev.next = entry; entry.prev = prev; } entry.next = next; next.prev = entry; } else { this.insert(val); } }; LinkedList$1.prototype.insertBeforeEntry = function(val, next) { var entry = new LinkedList$1.Entry(val); var prev = next.prev; if (!prev) { this.head = entry; } else { prev.next = entry; entry.prev = prev; } entry.next = next; next.prev = entry; this._length++; }; LinkedList$1.prototype.insertEntry = function(entry) { if (!this.head) { this.head = this.tail = entry; } else { this.tail.next = entry; entry.prev = this.tail; this.tail = entry; } this._length++; }; LinkedList$1.prototype.remove = function(entry) { var prev = entry.prev; var next = entry.next; if (prev) { prev.next = next; } else { this.head = next; } if (next) { next.prev = prev; } else { this.tail = prev; } entry.next = entry.prev = null; this._length--; }; LinkedList$1.prototype.removeAt = function(idx) { if (idx < 0) { return; } var curr = this.head; var cursor = 0; while (curr && cursor != idx) { curr = curr.next; cursor++; } if (curr) { this.remove(curr); return curr.value; } }; LinkedList$1.prototype.getHead = function() { if (this.head) { return this.head.value; } }; LinkedList$1.prototype.getTail = function() { if (this.tail) { return this.tail.value; } }; LinkedList$1.prototype.getAt = function(idx) { if (idx < 0) { return; } var curr = this.head; var cursor = 0; while (curr && cursor != idx) { curr = curr.next; cursor++; } return curr.value; }; LinkedList$1.prototype.indexOf = function(value) { var curr = this.head; var cursor = 0; while (curr) { if (curr.value === value) { return cursor; } curr = curr.next; cursor++; } }; LinkedList$1.prototype.length = function() { return this._length; }; LinkedList$1.prototype.isEmpty = function() { return this._length === 0; }; LinkedList$1.prototype.forEach = function(cb, context) { var curr = this.head; var idx = 0; var haveContext = typeof context != "undefined"; while (curr) { if (haveContext) { cb.call(context, curr.value, idx); } else { cb(curr.value, idx); } curr = curr.next; idx++; } }; LinkedList$1.prototype.clear = function() { this.tail = this.head = null; this._length = 0; }; LinkedList$1.Entry = function(val) { this.value = val; this.next = null; this.prev = null; }; var LRU$1 = function(maxSize) { this._list = new LinkedList$1(); this._map = {}; this._maxSize = maxSize || 10; }; LRU$1.prototype.setMaxSize = function(size) { this._maxSize = size; }; LRU$1.prototype.put = function(key, value) { if (!this._map.hasOwnProperty(key)) { var len = this._list.length(); if (len >= this._maxSize && len > 0) { var leastUsedEntry = this._list.head; this._list.remove(leastUsedEntry); delete this._map[leastUsedEntry.key]; } var entry = this._list.insert(value); entry.key = key; this._map[key] = entry; } }; LRU$1.prototype.get = function(key) { var entry = this._map[key]; if (this._map.hasOwnProperty(key)) { if (entry !== this._list.tail) { this._list.remove(entry); this._list.insertEntry(entry); } return entry.value; } }; LRU$1.prototype.remove = function(key) { var entry = this._map[key]; if (typeof entry !== "undefined") { delete this._map[key]; this._list.remove(entry); } }; LRU$1.prototype.clear = function() { this._list.clear(); this._map = {}; }; var colorUtil = {}; var kCSSColorTable$1 = { "transparent": [0, 0, 0, 0], "aliceblue": [240, 248, 255, 1], "antiquewhite": [250, 235, 215, 1], "aqua": [0, 255, 255, 1], "aquamarine": [127, 255, 212, 1], "azure": [240, 255, 255, 1], "beige": [245, 245, 220, 1], "bisque": [255, 228, 196, 1], "black": [0, 0, 0, 1], "blanchedalmond": [255, 235, 205, 1], "blue": [0, 0, 255, 1], "blueviolet": [138, 43, 226, 1], "brown": [165, 42, 42, 1], "burlywood": [222, 184, 135, 1], "cadetblue": [95, 158, 160, 1], "chartreuse": [127, 255, 0, 1], "chocolate": [210, 105, 30, 1], "coral": [255, 127, 80, 1], "cornflowerblue": [100, 149, 237, 1], "cornsilk": [255, 248, 220, 1], "crimson": [220, 20, 60, 1], "cyan": [0, 255, 255, 1], "darkblue": [0, 0, 139, 1], "darkcyan": [0, 139, 139, 1], "darkgoldenrod": [184, 134, 11, 1], "darkgray": [169, 169, 169, 1], "darkgreen": [0, 100, 0, 1], "darkgrey": [169, 169, 169, 1], "darkkhaki": [189, 183, 107, 1], "darkmagenta": [139, 0, 139, 1], "darkolivegreen": [85, 107, 47, 1], "darkorange": [255, 140, 0, 1], "darkorchid": [153, 50, 204, 1], "darkred": [139, 0, 0, 1], "darksalmon": [233, 150, 122, 1], "darkseagreen": [143, 188, 143, 1], "darkslateblue": [72, 61, 139, 1], "darkslategray": [47, 79, 79, 1], "darkslategrey": [47, 79, 79, 1], "darkturquoise": [0, 206, 209, 1], "darkviolet": [148, 0, 211, 1], "deeppink": [255, 20, 147, 1], "deepskyblue": [0, 191, 255, 1], "dimgray": [105, 105, 105, 1], "dimgrey": [105, 105, 105, 1], "dodgerblue": [30, 144, 255, 1], "firebrick": [178, 34, 34, 1], "floralwhite": [255, 250, 240, 1], "forestgreen": [34, 139, 34, 1], "fuchsia": [255, 0, 255, 1], "gainsboro": [220, 220, 220, 1], "ghostwhite": [248, 248, 255, 1], "gold": [255, 215, 0, 1], "goldenrod": [218, 165, 32, 1], "gray": [128, 128, 128, 1], "green": [0, 128, 0, 1], "greenyellow": [173, 255, 47, 1], "grey": [128, 128, 128, 1], "honeydew": [240, 255, 240, 1], "hotpink": [255, 105, 180, 1], "indianred": [205, 92, 92, 1], "indigo": [75, 0, 130, 1], "ivory": [255, 255, 240, 1], "khaki": [240, 230, 140, 1], "lavender": [230, 230, 250, 1], "lavenderblush": [255, 240, 245, 1], "lawngreen": [124, 252, 0, 1], "lemonchiffon": [255, 250, 205, 1], "lightblue": [173, 216, 230, 1], "lightcoral": [240, 128, 128, 1], "lightcyan": [224, 255, 255, 1], "lightgoldenrodyellow": [250, 250, 210, 1], "lightgray": [211, 211, 211, 1], "lightgreen": [144, 238, 144, 1], "lightgrey": [211, 211, 211, 1], "lightpink": [255, 182, 193, 1], "lightsalmon": [255, 160, 122, 1], "lightseagreen": [32, 178, 170, 1], "lightskyblue": [135, 206, 250, 1], "lightslategray": [119, 136, 153, 1], "lightslategrey": [119, 136, 153, 1], "lightsteelblue": [176, 196, 222, 1], "lightyellow": [255, 255, 224, 1], "lime": [0, 255, 0, 1], "limegreen": [50, 205, 50, 1], "linen": [250, 240, 230, 1], "magenta": [255, 0, 255, 1], "maroon": [128, 0, 0, 1], "mediumaquamarine": [102, 205, 170, 1], "mediumblue": [0, 0, 205, 1], "mediumorchid": [186, 85, 211, 1], "mediumpurple": [147, 112, 219, 1], "mediumseagreen": [60, 179, 113, 1], "mediumslateblue": [123, 104, 238, 1], "mediumspringgreen": [0, 250, 154, 1], "mediumturquoise": [72, 209, 204, 1], "mediumvioletred": [199, 21, 133, 1], "midnightblue": [25, 25, 112, 1], "mintcream": [245, 255, 250, 1], "mistyrose": [255, 228, 225, 1], "moccasin": [255, 228, 181, 1], "navajowhite": [255, 222, 173, 1], "navy": [0, 0, 128, 1], "oldlace": [253, 245, 230, 1], "olive": [128, 128, 0, 1], "olivedrab": [107, 142, 35, 1], "orange": [255, 165, 0, 1], "orangered": [255, 69, 0, 1], "orchid": [218, 112, 214, 1], "palegoldenrod": [238, 232, 170, 1], "palegreen": [152, 251, 152, 1], "paleturquoise": [175, 238, 238, 1], "palevioletred": [219, 112, 147, 1], "papayawhip": [255, 239, 213, 1], "peachpuff": [255, 218, 185, 1], "peru": [205, 133, 63, 1], "pink": [255, 192, 203, 1], "plum": [221, 160, 221, 1], "powderblue": [176, 224, 230, 1], "purple": [128, 0, 128, 1], "red": [255, 0, 0, 1], "rosybrown": [188, 143, 143, 1], "royalblue": [65, 105, 225, 1], "saddlebrown": [139, 69, 19, 1], "salmon": [250, 128, 114, 1], "sandybrown": [244, 164, 96, 1], "seagreen": [46, 139, 87, 1], "seashell": [255, 245, 238, 1], "sienna": [160, 82, 45, 1], "silver": [192, 192, 192, 1], "skyblue": [135, 206, 235, 1], "slateblue": [106, 90, 205, 1], "slategray": [112, 128, 144, 1], "slategrey": [112, 128, 144, 1], "snow": [255, 250, 250, 1], "springgreen": [0, 255, 127, 1], "steelblue": [70, 130, 180, 1], "tan": [210, 180, 140, 1], "teal": [0, 128, 128, 1], "thistle": [216, 191, 216, 1], "tomato": [255, 99, 71, 1], "turquoise": [64, 224, 208, 1], "violet": [238, 130, 238, 1], "wheat": [245, 222, 179, 1], "white": [255, 255, 255, 1], "whitesmoke": [245, 245, 245, 1], "yellow": [255, 255, 0, 1], "yellowgreen": [154, 205, 50, 1] }; function clampCssByte$1(i) { i = Math.round(i); return i < 0 ? 0 : i > 255 ? 255 : i; } function clampCssAngle(i) { i = Math.round(i); return i < 0 ? 0 : i > 360 ? 360 : i; } function clampCssFloat$1(f) { return f < 0 ? 0 : f > 1 ? 1 : f; } function parseCssInt$1(str) { if (str.length && str.charAt(str.length - 1) === "%") { return clampCssByte$1(parseFloat(str) / 100 * 255); } return clampCssByte$1(parseInt(str, 10)); } function parseCssFloat$1(str) { if (str.length && str.charAt(str.length - 1) === "%") { return clampCssFloat$1(parseFloat(str) / 100); } return clampCssFloat$1(parseFloat(str)); } function cssHueToRgb$1(m1, m2, h) { if (h < 0) { h += 1; } else if (h > 1) { h -= 1; } if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; } if (h * 2 < 1) { return m2; } if (h * 3 < 2) { return m1 + (m2 - m1) * (2 / 3 - h) * 6; } return m1; } function lerpNumber(a, b, p) { return a + (b - a) * p; } function setRgba$1(out, r, g2, b, a) { out[0] = r; out[1] = g2; out[2] = b; out[3] = a; return out; } function copyRgba$1(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; } var colorCache$1 = new LRU$1(20); var lastRemovedArr$1 = null; function putToCache$1(colorStr, rgbaArr) { if (lastRemovedArr$1) { copyRgba$1(lastRemovedArr$1, rgbaArr); } lastRemovedArr$1 = colorCache$1.put(colorStr, lastRemovedArr$1 || rgbaArr.slice()); } colorUtil.parse = function(colorStr, rgbaArr) { if (!colorStr) { return; } rgbaArr = rgbaArr || []; var cached = colorCache$1.get(colorStr); if (cached) { return copyRgba$1(rgbaArr, cached); } colorStr = colorStr + ""; var str = colorStr.replace(/ /g, "").toLowerCase(); if (str in kCSSColorTable$1) { copyRgba$1(rgbaArr, kCSSColorTable$1[str]); putToCache$1(colorStr, rgbaArr); return rgbaArr; } if (str.charAt(0) === "#") { if (str.length === 4) { var iv = parseInt(str.substr(1), 16); if (!(iv >= 0 && iv <= 4095)) { setRgba$1(rgbaArr, 0, 0, 0, 1); return; } setRgba$1( rgbaArr, (iv & 3840) >> 4 | (iv & 3840) >> 8, iv & 240 | (iv & 240) >> 4, iv & 15 | (iv & 15) << 4, 1 ); putToCache$1(colorStr, rgbaArr); return rgbaArr; } else if (str.length === 7) { var iv = parseInt(str.substr(1), 16); if (!(iv >= 0 && iv <= 16777215)) { setRgba$1(rgbaArr, 0, 0, 0, 1); return; } setRgba$1( rgbaArr, (iv & 16711680) >> 16, (iv & 65280) >> 8, iv & 255, 1 ); putToCache$1(colorStr, rgbaArr); return rgbaArr; } return; } var op = str.indexOf("("), ep = str.indexOf(")"); if (op !== -1 && ep + 1 === str.length) { var fname = str.substr(0, op); var params = str.substr(op + 1, ep - (op + 1)).split(","); var alpha = 1; switch (fname) { case "rgba": if (params.length !== 4) { setRgba$1(rgbaArr, 0, 0, 0, 1); return; } alpha = parseCssFloat$1(params.pop()); // jshint ignore:line // Fall through. case "rgb": if (params.length !== 3) { setRgba$1(rgbaArr, 0, 0, 0, 1); return; } setRgba$1( rgbaArr, parseCssInt$1(params[0]), parseCssInt$1(params[1]), parseCssInt$1(params[2]), alpha ); putToCache$1(colorStr, rgbaArr); return rgbaArr; case "hsla": if (params.length !== 4) { setRgba$1(rgbaArr, 0, 0, 0, 1); return; } params[3] = parseCssFloat$1(params[3]); hsla2rgba$1(params, rgbaArr); putToCache$1(colorStr, rgbaArr); return rgbaArr; case "hsl": if (params.length !== 3) { setRgba$1(rgbaArr, 0, 0, 0, 1); return; } hsla2rgba$1(params, rgbaArr); putToCache$1(colorStr, rgbaArr); return rgbaArr; default: return; } } setRgba$1(rgbaArr, 0, 0, 0, 1); return; }; colorUtil.parseToFloat = function(colorStr, rgbaArr) { rgbaArr = colorUtil.parse(colorStr, rgbaArr); if (!rgbaArr) { return; } rgbaArr[0] /= 255; rgbaArr[1] /= 255; rgbaArr[2] /= 255; return rgbaArr; }; function hsla2rgba$1(hsla, rgba) { var h = (parseFloat(hsla[0]) % 360 + 360) % 360 / 360; var s = parseCssFloat$1(hsla[1]); var l = parseCssFloat$1(hsla[2]); var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; var m1 = l * 2 - m2; rgba = rgba || []; setRgba$1( rgba, clampCssByte$1(cssHueToRgb$1(m1, m2, h + 1 / 3) * 255), clampCssByte$1(cssHueToRgb$1(m1, m2, h) * 255), clampCssByte$1(cssHueToRgb$1(m1, m2, h - 1 / 3) * 255), 1 ); if (hsla.length === 4) { rgba[3] = hsla[3]; } return rgba; } function rgba2hsla(rgba) { if (!rgba) { return; } var R = rgba[0] / 255; var G = rgba[1] / 255; var B = rgba[2] / 255; var vMin = Math.min(R, G, B); var vMax = Math.max(R, G, B); var delta = vMax - vMin; var L = (vMax + vMin) / 2; var H; var S; if (delta === 0) { H = 0; S = 0; } else { if (L < 0.5) { S = delta / (vMax + vMin); } else { S = delta / (2 - vMax - vMin); } var deltaR = ((vMax - R) / 6 + delta / 2) / delta; var deltaG = ((vMax - G) / 6 + delta / 2) / delta; var deltaB = ((vMax - B) / 6 + delta / 2) / delta; if (R === vMax) { H = deltaB - deltaG; } else if (G === vMax) { H = 1 / 3 + deltaR - deltaB; } else if (B === vMax) { H = 2 / 3 + deltaG - deltaR; } if (H < 0) { H += 1; } if (H > 1) { H -= 1; } } var hsla = [H * 360, S, L]; if (rgba[3] != null) { hsla.push(rgba[3]); } return hsla; } colorUtil.lift = function(color, level) { var colorArr = colorUtil.parse(color); if (colorArr) { for (var i = 0; i < 3; i++) { if (level < 0) { colorArr[i] = colorArr[i] * (1 - level) | 0; } else { colorArr[i] = (255 - colorArr[i]) * level + colorArr[i] | 0; } } return colorUtil.stringify(colorArr, colorArr.length === 4 ? "rgba" : "rgb"); } }; colorUtil.toHex = function(color) { var colorArr = colorUtil.parse(color); if (colorArr) { return ((1 << 24) + (colorArr[0] << 16) + (colorArr[1] << 8) + +colorArr[2]).toString(16).slice(1); } }; colorUtil.fastLerp = function(normalizedValue, colors, out) { if (!(colors && colors.length) || !(normalizedValue >= 0 && normalizedValue <= 1)) { return; } out = out || []; var value = normalizedValue * (colors.length - 1); var leftIndex = Math.floor(value); var rightIndex = Math.ceil(value); var leftColor = colors[leftIndex]; var rightColor = colors[rightIndex]; var dv = value - leftIndex; out[0] = clampCssByte$1(lerpNumber(leftColor[0], rightColor[0], dv)); out[1] = clampCssByte$1(lerpNumber(leftColor[1], rightColor[1], dv)); out[2] = clampCssByte$1(lerpNumber(leftColor[2], rightColor[2], dv)); out[3] = clampCssFloat$1(lerpNumber(leftColor[3], rightColor[3], dv)); return out; }; colorUtil.fastMapToColor = colorUtil.fastLerp; colorUtil.lerp = function(normalizedValue, colors, fullOutput) { if (!(colors && colors.length) || !(normalizedValue >= 0 && normalizedValue <= 1)) { return; } var value = normalizedValue * (colors.length - 1); var leftIndex = Math.floor(value); var rightIndex = Math.ceil(value); var leftColor = colorUtil.parse(colors[leftIndex]); var rightColor = colorUtil.parse(colors[rightIndex]); var dv = value - leftIndex; var color = colorUtil.stringify( [ clampCssByte$1(lerpNumber(leftColor[0], rightColor[0], dv)), clampCssByte$1(lerpNumber(leftColor[1], rightColor[1], dv)), clampCssByte$1(lerpNumber(leftColor[2], rightColor[2], dv)), clampCssFloat$1(lerpNumber(leftColor[3], rightColor[3], dv)) ], "rgba" ); return fullOutput ? { color, leftIndex, rightIndex, value } : color; }; colorUtil.mapToColor = colorUtil.lerp; colorUtil.modifyHSL = function(color, h, s, l) { color = colorUtil.parse(color); if (color) { color = rgba2hsla(color); h != null && (color[0] = clampCssAngle(h)); s != null && (color[1] = parseCssFloat$1(s)); l != null && (color[2] = parseCssFloat$1(l)); return colorUtil.stringify(hsla2rgba$1(color), "rgba"); } }; colorUtil.modifyAlpha = function(color, alpha) { color = colorUtil.parse(color); if (color && alpha != null) { color[3] = clampCssFloat$1(alpha); return colorUtil.stringify(color, "rgba"); } }; colorUtil.stringify = function(arrColor, type) { if (!arrColor || !arrColor.length) { return; } var colorStr = arrColor[0] + "," + arrColor[1] + "," + arrColor[2]; if (type === "rgba" || type === "hsva" || type === "hsla") { colorStr += "," + arrColor[3]; } return type + "(" + colorStr + ")"; }; var parseColor = colorUtil.parseToFloat; var programKeyCache$1 = {}; function getDefineCode$1(defines) { var defineKeys = Object.keys(defines); defineKeys.sort(); var defineStr = []; for (var i = 0; i < defineKeys.length; i++) { var key = defineKeys[i]; var value = defines[key]; if (value === null) { defineStr.push(key); } else { defineStr.push(key + " " + value.toString()); } } return defineStr.join("\n"); } function getProgramKey$1(vertexDefines, fragmentDefines, enabledTextures) { enabledTextures.sort(); var defineStr = []; for (var i = 0; i < enabledTextures.length; i++) { var symbol = enabledTextures[i]; defineStr.push(symbol); } var key = getDefineCode$1(vertexDefines) + "\n" + getDefineCode$1(fragmentDefines) + "\n" + defineStr.join("\n"); if (programKeyCache$1[key]) { return programKeyCache$1[key]; } var id = util.genGUID(); programKeyCache$1[key] = id; return id; } var Material = Base.extend( function() { return ( /** @lends clay.Material# */ { /** * @type {string} */ name: "", /** * @type {Object} */ // uniforms: null, /** * @type {clay.Shader} */ // shader: null, /** * @type {boolean} */ depthTest: true, /** * @type {boolean} */ depthMask: true, /** * @type {boolean} */ transparent: false, /** * Blend func is a callback function when the material * have custom blending * The gl context will be the only argument passed in tho the * blend function * Detail of blend function in WebGL: * http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf * * Example : * function(_gl) { * _gl.blendEquation(_gl.FUNC_ADD); * _gl.blendFunc(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA); * } */ blend: null, /** * If update texture status automatically. */ autoUpdateTextureStatus: true, uniforms: {}, vertexDefines: {}, fragmentDefines: {}, _textureStatus: {}, // shadowTransparentMap : null // PENDING enable the uniform that only used in shader. _enabledUniforms: null } ); }, function() { if (!this.name) { this.name = "MATERIAL_" + this.__uid__; } if (this.shader) { this.attachShader(this.shader, true); } }, /** @lends clay.Material.prototype */ { precision: "highp", /** * Set material uniform * @example * mat.setUniform('color', [1, 1, 1, 1]); * @param {string} symbol * @param {number|array|clay.Texture|ArrayBufferView} value */ setUniform: function(symbol, value) { if (value === void 0) { console.warn('Uniform value "' + symbol + '" is undefined'); } var uniform = this.uniforms[symbol]; if (uniform) { if (typeof value === "string") { value = parseColor(value) || value; } uniform.value = value; if (this.autoUpdateTextureStatus && uniform.type === "t") { if (value) { this.enableTexture(symbol); } else { this.disableTexture(symbol); } } } }, /** * @param {Object} obj */ setUniforms: function(obj) { for (var key in obj) { var val = obj[key]; this.setUniform(key, val); } }, /** * @param {string} symbol * @return {boolean} */ isUniformEnabled: function(symbol) { return this._enabledUniforms.indexOf(symbol) >= 0; }, getEnabledUniforms: function() { return this._enabledUniforms; }, getTextureUniforms: function() { return this._textureUniforms; }, /** * Alias of setUniform and setUniforms * @param {object|string} symbol * @param {number|array|clay.Texture|ArrayBufferView} [value] */ set: function(symbol, value) { if (typeof symbol === "object") { for (var key in symbol) { var val = symbol[key]; this.setUniform(key, val); } } else { this.setUniform(symbol, value); } }, /** * Get uniform value * @param {string} symbol * @return {number|array|clay.Texture|ArrayBufferView} */ get: function(symbol) { var uniform = this.uniforms[symbol]; if (uniform) { return uniform.value; } }, /** * Attach a shader instance * @param {clay.Shader} shader * @param {boolean} keepStatus If try to keep uniform and texture */ attachShader: function(shader, keepStatus) { var originalUniforms = this.uniforms; this.uniforms = shader.createUniforms(); this.shader = shader; var uniforms = this.uniforms; this._enabledUniforms = Object.keys(uniforms); this._enabledUniforms.sort(); this._textureUniforms = this._enabledUniforms.filter(function(uniformName) { var type = this.uniforms[uniformName].type; return type === "t" || type === "tv"; }, this); var originalVertexDefines = this.vertexDefines; var originalFragmentDefines = this.fragmentDefines; this.vertexDefines = util.clone(shader.vertexDefines); this.fragmentDefines = util.clone(shader.fragmentDefines); if (keepStatus) { for (var symbol in originalUniforms) { if (uniforms[symbol]) { uniforms[symbol].value = originalUniforms[symbol].value; } } util.defaults(this.vertexDefines, originalVertexDefines); util.defaults(this.fragmentDefines, originalFragmentDefines); } var textureStatus = {}; for (var key in shader.textures) { textureStatus[key] = { shaderType: shader.textures[key].shaderType, type: shader.textures[key].type, enabled: keepStatus && this._textureStatus[key] ? this._textureStatus[key].enabled : false }; } this._textureStatus = textureStatus; this._programKey = ""; }, /** * Clone a new material and keep uniforms, shader will not be cloned * @return {clay.Material} */ clone: function() { var material = new this.constructor({ name: this.name, shader: this.shader }); for (var symbol in this.uniforms) { material.uniforms[symbol].value = this.uniforms[symbol].value; } material.depthTest = this.depthTest; material.depthMask = this.depthMask; material.transparent = this.transparent; material.blend = this.blend; material.vertexDefines = util.clone(this.vertexDefines); material.fragmentDefines = util.clone(this.fragmentDefines); material.enableTexture(this.getEnabledTextures()); material.precision = this.precision; return material; }, /** * Add a #define macro in shader code * @param {string} shaderType Can be vertex, fragment or both * @param {string} symbol * @param {number} [val] */ define: function(shaderType, symbol, val) { var vertexDefines = this.vertexDefines; var fragmentDefines = this.fragmentDefines; if (shaderType !== "vertex" && shaderType !== "fragment" && shaderType !== "both" && arguments.length < 3) { val = symbol; symbol = shaderType; shaderType = "both"; } val = val != null ? val : null; if (shaderType === "vertex" || shaderType === "both") { if (vertexDefines[symbol] !== val) { vertexDefines[symbol] = val; this._programKey = ""; } } if (shaderType === "fragment" || shaderType === "both") { if (fragmentDefines[symbol] !== val) { fragmentDefines[symbol] = val; if (shaderType !== "both") { this._programKey = ""; } } } }, /** * Remove a #define macro in shader code * @param {string} shaderType Can be vertex, fragment or both * @param {string} symbol */ undefine: function(shaderType, symbol) { if (shaderType !== "vertex" && shaderType !== "fragment" && shaderType !== "both" && arguments.length < 2) { symbol = shaderType; shaderType = "both"; } if (shaderType === "vertex" || shaderType === "both") { if (this.isDefined("vertex", symbol)) { delete this.vertexDefines[symbol]; this._programKey = ""; } } if (shaderType === "fragment" || shaderType === "both") { if (this.isDefined("fragment", symbol)) { delete this.fragmentDefines[symbol]; if (shaderType !== "both") { this._programKey = ""; } } } }, /** * If macro is defined in shader. * @param {string} shaderType Can be vertex, fragment or both * @param {string} symbol */ isDefined: function(shaderType, symbol) { switch (shaderType) { case "vertex": return this.vertexDefines[symbol] !== void 0; case "fragment": return this.fragmentDefines[symbol] !== void 0; } }, /** * Get macro value defined in shader. * @param {string} shaderType Can be vertex, fragment or both * @param {string} symbol */ getDefine: function(shaderType, symbol) { switch (shaderType) { case "vertex": return this.vertexDefines[symbol]; case "fragment": return this.fragmentDefines[symbol]; } }, /** * Enable a texture, actually it will add a #define macro in the shader code * For example, if texture symbol is diffuseMap, it will add a line `#define DIFFUSEMAP_ENABLED` in the shader code * @param {string} symbol */ enableTexture: function(symbol) { if (Array.isArray(symbol)) { for (var i = 0; i < symbol.length; i++) { this.enableTexture(symbol[i]); } return; } var status = this._textureStatus[symbol]; if (status) { var isEnabled = status.enabled; if (!isEnabled) { status.enabled = true; this._programKey = ""; } } }, /** * Enable all textures used in the shader */ enableTexturesAll: function() { var textureStatus = this._textureStatus; for (var symbol in textureStatus) { textureStatus[symbol].enabled = true; } this._programKey = ""; }, /** * Disable a texture, it remove a #define macro in the shader * @param {string} symbol */ disableTexture: function(symbol) { if (Array.isArray(symbol)) { for (var i = 0; i < symbol.length; i++) { this.disableTexture(symbol[i]); } return; } var status = this._textureStatus[symbol]; if (status) { var isDisabled = !status.enabled; if (!isDisabled) { status.enabled = false; this._programKey = ""; } } }, /** * Disable all textures used in the shader */ disableTexturesAll: function() { var textureStatus = this._textureStatus; for (var symbol in textureStatus) { textureStatus[symbol].enabled = false; } this._programKey = ""; }, /** * If texture of given type is enabled. * @param {string} symbol * @return {boolean} */ isTextureEnabled: function(symbol) { var textureStatus = this._textureStatus; return !!textureStatus[symbol] && textureStatus[symbol].enabled; }, /** * Get all enabled textures * @return {string[]} */ getEnabledTextures: function() { var enabledTextures = []; var textureStatus = this._textureStatus; for (var symbol in textureStatus) { if (textureStatus[symbol].enabled) { enabledTextures.push(symbol); } } return enabledTextures; }, /** * Mark defines are updated. */ dirtyDefines: function() { this._programKey = ""; }, getProgramKey: function() { if (!this._programKey) { this._programKey = getProgramKey$1( this.vertexDefines, this.fragmentDefines, this.getEnabledTextures() ); } return this._programKey; } } ); var GLMAT_EPSILON = 1e-6; var GLMAT_ARRAY_TYPE = Array; var GLMAT_RANDOM$1 = Math.random; var vec2$3 = {}; vec2$3.create = function() { var out = new GLMAT_ARRAY_TYPE(2); out[0] = 0; out[1] = 0; return out; }; vec2$3.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(2); out[0] = a[0]; out[1] = a[1]; return out; }; vec2$3.fromValues = function(x, y) { var out = new GLMAT_ARRAY_TYPE(2); out[0] = x; out[1] = y; return out; }; vec2$3.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; return out; }; vec2$3.set = function(out, x, y) { out[0] = x; out[1] = y; return out; }; vec2$3.add = function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; }; vec2$3.subtract = function(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; return out; }; vec2$3.sub = vec2$3.subtract; vec2$3.multiply = function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }; vec2$3.mul = vec2$3.multiply; vec2$3.divide = function(out, a, b) { out[0] = a[0] / b[0]; out[1] = a[1] / b[1]; return out; }; vec2$3.div = vec2$3.divide; vec2$3.min = function(out, a, b) { out[0] = Math.min(a[0], b[0]); out[1] = Math.min(a[1], b[1]); return out; }; vec2$3.max = function(out, a, b) { out[0] = Math.max(a[0], b[0]); out[1] = Math.max(a[1], b[1]); return out; }; vec2$3.scale = function(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; return out; }; vec2$3.scaleAndAdd = function(out, a, b, scale) { out[0] = a[0] + b[0] * scale; out[1] = a[1] + b[1] * scale; return out; }; vec2$3.distance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1]; return Math.sqrt(x * x + y * y); }; vec2$3.dist = vec2$3.distance; vec2$3.squaredDistance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1]; return x * x + y * y; }; vec2$3.sqrDist = vec2$3.squaredDistance; vec2$3.length = function(a) { var x = a[0], y = a[1]; return Math.sqrt(x * x + y * y); }; vec2$3.len = vec2$3.length; vec2$3.squaredLength = function(a) { var x = a[0], y = a[1]; return x * x + y * y; }; vec2$3.sqrLen = vec2$3.squaredLength; vec2$3.negate = function(out, a) { out[0] = -a[0]; out[1] = -a[1]; return out; }; vec2$3.inverse = function(out, a) { out[0] = 1 / a[0]; out[1] = 1 / a[1]; return out; }; vec2$3.normalize = function(out, a) { var x = a[0], y = a[1]; var len = x * x + y * y; if (len > 0) { len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; } return out; }; vec2$3.dot = function(a, b) { return a[0] * b[0] + a[1] * b[1]; }; vec2$3.cross = function(out, a, b) { var z = a[0] * b[1] - a[1] * b[0]; out[0] = out[1] = 0; out[2] = z; return out; }; vec2$3.lerp = function(out, a, b, t) { var ax = a[0], ay = a[1]; out[0] = ax + t * (b[0] - ax); out[1] = ay + t * (b[1] - ay); return out; }; vec2$3.random = function(out, scale) { scale = scale || 1; var r = GLMAT_RANDOM() * 2 * Math.PI; out[0] = Math.cos(r) * scale; out[1] = Math.sin(r) * scale; return out; }; vec2$3.transformMat2 = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[2] * y; out[1] = m[1] * x + m[3] * y; return out; }; vec2$3.transformMat2d = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[2] * y + m[4]; out[1] = m[1] * x + m[3] * y + m[5]; return out; }; vec2$3.transformMat3 = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[3] * y + m[6]; out[1] = m[1] * x + m[4] * y + m[7]; return out; }; vec2$3.transformMat4 = function(out, a, m) { var x = a[0], y = a[1]; out[0] = m[0] * x + m[4] * y + m[12]; out[1] = m[1] * x + m[5] * y + m[13]; return out; }; vec2$3.forEach = function() { var vec = vec2$3.create(); return function(a, stride, offset, count, fn, arg) { var i, l; if (!stride) { stride = 2; } if (!offset) { offset = 0; } if (count) { l = Math.min(count * stride + offset, a.length); } else { l = a.length; } for (i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; } return a; }; }(); var Vector2 = function(x, y) { x = x || 0; y = y || 0; this.array = vec2$3.fromValues(x, y); this._dirty = true; }; Vector2.prototype = { constructor: Vector2, /** * Add b to self * @param {clay.Vector2} b * @return {clay.Vector2} */ add: function(b) { vec2$3.add(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Set x and y components * @param {number} x * @param {number} y * @return {clay.Vector2} */ set: function(x, y) { this.array[0] = x; this.array[1] = y; this._dirty = true; return this; }, /** * Set x and y components from array * @param {Float32Array|number[]} arr * @return {clay.Vector2} */ setArray: function(arr) { this.array[0] = arr[0]; this.array[1] = arr[1]; this._dirty = true; return this; }, /** * Clone a new Vector2 * @return {clay.Vector2} */ clone: function() { return new Vector2(this.x, this.y); }, /** * Copy x, y from b * @param {clay.Vector2} b * @return {clay.Vector2} */ copy: function(b) { vec2$3.copy(this.array, b.array); this._dirty = true; return this; }, /** * Cross product of self and b, written to a Vector3 out * @param {clay.Vector3} out * @param {clay.Vector2} b * @return {clay.Vector2} */ cross: function(out, b) { vec2$3.cross(out.array, this.array, b.array); out._dirty = true; return this; }, /** * Alias for distance * @param {clay.Vector2} b * @return {number} */ dist: function(b) { return vec2$3.dist(this.array, b.array); }, /** * Distance between self and b * @param {clay.Vector2} b * @return {number} */ distance: function(b) { return vec2$3.distance(this.array, b.array); }, /** * Alias for divide * @param {clay.Vector2} b * @return {clay.Vector2} */ div: function(b) { vec2$3.div(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Divide self by b * @param {clay.Vector2} b * @return {clay.Vector2} */ divide: function(b) { vec2$3.divide(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Dot product of self and b * @param {clay.Vector2} b * @return {number} */ dot: function(b) { return vec2$3.dot(this.array, b.array); }, /** * Alias of length * @return {number} */ len: function() { return vec2$3.len(this.array); }, /** * Calculate the length * @return {number} */ length: function() { return vec2$3.length(this.array); }, /** * Linear interpolation between a and b * @param {clay.Vector2} a * @param {clay.Vector2} b * @param {number} t * @return {clay.Vector2} */ lerp: function(a, b, t) { vec2$3.lerp(this.array, a.array, b.array, t); this._dirty = true; return this; }, /** * Minimum of self and b * @param {clay.Vector2} b * @return {clay.Vector2} */ min: function(b) { vec2$3.min(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Maximum of self and b * @param {clay.Vector2} b * @return {clay.Vector2} */ max: function(b) { vec2$3.max(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiply * @param {clay.Vector2} b * @return {clay.Vector2} */ mul: function(b) { vec2$3.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Mutiply self and b * @param {clay.Vector2} b * @return {clay.Vector2} */ multiply: function(b) { vec2$3.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Negate self * @return {clay.Vector2} */ negate: function() { vec2$3.negate(this.array, this.array); this._dirty = true; return this; }, /** * Normalize self * @return {clay.Vector2} */ normalize: function() { vec2$3.normalize(this.array, this.array); this._dirty = true; return this; }, /** * Generate random x, y components with a given scale * @param {number} scale * @return {clay.Vector2} */ random: function(scale) { vec2$3.random(this.array, scale); this._dirty = true; return this; }, /** * Scale self * @param {number} scale * @return {clay.Vector2} */ scale: function(s) { vec2$3.scale(this.array, this.array, s); this._dirty = true; return this; }, /** * Scale b and add to self * @param {clay.Vector2} b * @param {number} scale * @return {clay.Vector2} */ scaleAndAdd: function(b, s) { vec2$3.scaleAndAdd(this.array, this.array, b.array, s); this._dirty = true; return this; }, /** * Alias for squaredDistance * @param {clay.Vector2} b * @return {number} */ sqrDist: function(b) { return vec2$3.sqrDist(this.array, b.array); }, /** * Squared distance between self and b * @param {clay.Vector2} b * @return {number} */ squaredDistance: function(b) { return vec2$3.squaredDistance(this.array, b.array); }, /** * Alias for squaredLength * @return {number} */ sqrLen: function() { return vec2$3.sqrLen(this.array); }, /** * Squared length of self * @return {number} */ squaredLength: function() { return vec2$3.squaredLength(this.array); }, /** * Alias for subtract * @param {clay.Vector2} b * @return {clay.Vector2} */ sub: function(b) { vec2$3.sub(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Subtract b from self * @param {clay.Vector2} b * @return {clay.Vector2} */ subtract: function(b) { vec2$3.subtract(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Transform self with a Matrix2 m * @param {clay.Matrix2} m * @return {clay.Vector2} */ transformMat2: function(m) { vec2$3.transformMat2(this.array, this.array, m.array); this._dirty = true; return this; }, /** * Transform self with a Matrix2d m * @param {clay.Matrix2d} m * @return {clay.Vector2} */ transformMat2d: function(m) { vec2$3.transformMat2d(this.array, this.array, m.array); this._dirty = true; return this; }, /** * Transform self with a Matrix3 m * @param {clay.Matrix3} m * @return {clay.Vector2} */ transformMat3: function(m) { vec2$3.transformMat3(this.array, this.array, m.array); this._dirty = true; return this; }, /** * Transform self with a Matrix4 m * @param {clay.Matrix4} m * @return {clay.Vector2} */ transformMat4: function(m) { vec2$3.transformMat4(this.array, this.array, m.array); this._dirty = true; return this; }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; if (Object.defineProperty) { var proto$4 = Vector2.prototype; Object.defineProperty(proto$4, "x", { get: function() { return this.array[0]; }, set: function(value) { this.array[0] = value; this._dirty = true; } }); Object.defineProperty(proto$4, "y", { get: function() { return this.array[1]; }, set: function(value) { this.array[1] = value; this._dirty = true; } }); } Vector2.add = function(out, a, b) { vec2$3.add(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.set = function(out, x, y) { vec2$3.set(out.array, x, y); out._dirty = true; return out; }; Vector2.copy = function(out, b) { vec2$3.copy(out.array, b.array); out._dirty = true; return out; }; Vector2.cross = function(out, a, b) { vec2$3.cross(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.dist = function(a, b) { return vec2$3.distance(a.array, b.array); }; Vector2.distance = Vector2.dist; Vector2.div = function(out, a, b) { vec2$3.divide(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.divide = Vector2.div; Vector2.dot = function(a, b) { return vec2$3.dot(a.array, b.array); }; Vector2.len = function(b) { return vec2$3.length(b.array); }; Vector2.lerp = function(out, a, b, t) { vec2$3.lerp(out.array, a.array, b.array, t); out._dirty = true; return out; }; Vector2.min = function(out, a, b) { vec2$3.min(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.max = function(out, a, b) { vec2$3.max(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.mul = function(out, a, b) { vec2$3.multiply(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.multiply = Vector2.mul; Vector2.negate = function(out, a) { vec2$3.negate(out.array, a.array); out._dirty = true; return out; }; Vector2.normalize = function(out, a) { vec2$3.normalize(out.array, a.array); out._dirty = true; return out; }; Vector2.random = function(out, scale) { vec2$3.random(out.array, scale); out._dirty = true; return out; }; Vector2.scale = function(out, a, scale) { vec2$3.scale(out.array, a.array, scale); out._dirty = true; return out; }; Vector2.scaleAndAdd = function(out, a, b, scale) { vec2$3.scaleAndAdd(out.array, a.array, b.array, scale); out._dirty = true; return out; }; Vector2.sqrDist = function(a, b) { return vec2$3.sqrDist(a.array, b.array); }; Vector2.squaredDistance = Vector2.sqrDist; Vector2.sqrLen = function(a) { return vec2$3.sqrLen(a.array); }; Vector2.squaredLength = Vector2.sqrLen; Vector2.sub = function(out, a, b) { vec2$3.subtract(out.array, a.array, b.array); out._dirty = true; return out; }; Vector2.subtract = Vector2.sub; Vector2.transformMat2 = function(out, a, m) { vec2$3.transformMat2(out.array, a.array, m.array); out._dirty = true; return out; }; Vector2.transformMat2d = function(out, a, m) { vec2$3.transformMat2d(out.array, a.array, m.array); out._dirty = true; return out; }; Vector2.transformMat3 = function(out, a, m) { vec2$3.transformMat3(out.array, a.array, m.array); out._dirty = true; return out; }; Vector2.transformMat4 = function(out, a, m) { vec2$3.transformMat4(out.array, a.array, m.array); out._dirty = true; return out; }; var SHADER_STATE_TO_ENABLE = 1; var SHADER_STATE_KEEP_ENABLE = 2; var SHADER_STATE_PENDING = 3; var enabledAttributeList = {}; function addLineNumbers(string) { var chunks = string.split("\n"); for (var i = 0, il = chunks.length; i < il; i++) { chunks[i] = i + 1 + ": " + chunks[i]; } return chunks.join("\n"); } function checkShaderErrorMsg(_gl, shader, shaderString) { if (!_gl.getShaderParameter(shader, _gl.COMPILE_STATUS)) { return [_gl.getShaderInfoLog(shader), addLineNumbers(shaderString)].join("\n"); } } var tmpFloat32Array16 = new vendor.Float32Array(16); var GLProgram = Base.extend({ uniformSemantics: {}, attributes: {} }, function() { this._locations = {}; this._textureSlot = 0; this._program = null; }, { bind: function(renderer) { this._textureSlot = 0; renderer.gl.useProgram(this._program); }, hasUniform: function(symbol) { var location = this._locations[symbol]; return location !== null && location !== void 0; }, useTextureSlot: function(renderer, texture, slot) { if (texture) { renderer.gl.activeTexture(renderer.gl.TEXTURE0 + slot); if (texture.isRenderable()) { texture.bind(renderer); } else { texture.unbind(renderer); } } }, currentTextureSlot: function() { return this._textureSlot; }, resetTextureSlot: function(slot) { this._textureSlot = slot || 0; }, takeCurrentTextureSlot: function(renderer, texture) { var textureSlot = this._textureSlot; this.useTextureSlot(renderer, texture, textureSlot); this._textureSlot++; return textureSlot; }, setUniform: function(_gl, type, symbol, value) { var locationMap = this._locations; var location = locationMap[symbol]; if (location === null || location === void 0) { return false; } switch (type) { case "m4": if (!(value instanceof Float32Array)) { for (var i = 0; i < value.length; i++) { tmpFloat32Array16[i] = value[i]; } value = tmpFloat32Array16; } _gl.uniformMatrix4fv(location, false, value); break; case "2i": _gl.uniform2i(location, value[0], value[1]); break; case "2f": _gl.uniform2f(location, value[0], value[1]); break; case "3i": _gl.uniform3i(location, value[0], value[1], value[2]); break; case "3f": _gl.uniform3f(location, value[0], value[1], value[2]); break; case "4i": _gl.uniform4i(location, value[0], value[1], value[2], value[3]); break; case "4f": _gl.uniform4f(location, value[0], value[1], value[2], value[3]); break; case "1i": _gl.uniform1i(location, value); break; case "1f": _gl.uniform1f(location, value); break; case "1fv": _gl.uniform1fv(location, value); break; case "1iv": _gl.uniform1iv(location, value); break; case "2iv": _gl.uniform2iv(location, value); break; case "2fv": _gl.uniform2fv(location, value); break; case "3iv": _gl.uniform3iv(location, value); break; case "3fv": _gl.uniform3fv(location, value); break; case "4iv": _gl.uniform4iv(location, value); break; case "4fv": _gl.uniform4fv(location, value); break; case "m2": case "m2v": _gl.uniformMatrix2fv(location, false, value); break; case "m3": case "m3v": _gl.uniformMatrix3fv(location, false, value); break; case "m4v": if (Array.isArray(value) && Array.isArray(value[0])) { var array = new vendor.Float32Array(value.length * 16); var cursor = 0; for (var i = 0; i < value.length; i++) { var item = value[i]; for (var j = 0; j < 16; j++) { array[cursor++] = item[j]; } } _gl.uniformMatrix4fv(location, false, array); } else { _gl.uniformMatrix4fv(location, false, value); } break; } return true; }, setUniformOfSemantic: function(_gl, semantic, val) { var semanticInfo = this.uniformSemantics[semantic]; if (semanticInfo) { return this.setUniform(_gl, semanticInfo.type, semanticInfo.symbol, val); } return false; }, // Used for creating VAO // Enable the attributes passed in and disable the rest // Example Usage: // enableAttributes(renderer, ["position", "texcoords"]) enableAttributes: function(renderer, attribList, vao) { var _gl = renderer.gl; var program = this._program; var locationMap = this._locations; var enabledAttributeListInContext; if (vao) { enabledAttributeListInContext = vao.__enabledAttributeList; } else { enabledAttributeListInContext = enabledAttributeList[renderer.__uid__]; } if (!enabledAttributeListInContext) { if (vao) { enabledAttributeListInContext = vao.__enabledAttributeList = []; } else { enabledAttributeListInContext = enabledAttributeList[renderer.__uid__] = []; } } var locationList = []; for (var i = 0; i < attribList.length; i++) { var symbol = attribList[i]; if (!this.attributes[symbol]) { locationList[i] = -1; continue; } var location = locationMap[symbol]; if (location == null) { location = _gl.getAttribLocation(program, symbol); if (location === -1) { locationList[i] = -1; continue; } locationMap[symbol] = location; } locationList[i] = location; if (!enabledAttributeListInContext[location]) { enabledAttributeListInContext[location] = SHADER_STATE_TO_ENABLE; } else { enabledAttributeListInContext[location] = SHADER_STATE_KEEP_ENABLE; } } for (var i = 0; i < enabledAttributeListInContext.length; i++) { switch (enabledAttributeListInContext[i]) { case SHADER_STATE_TO_ENABLE: _gl.enableVertexAttribArray(i); enabledAttributeListInContext[i] = SHADER_STATE_PENDING; break; case SHADER_STATE_KEEP_ENABLE: enabledAttributeListInContext[i] = SHADER_STATE_PENDING; break; // Expired case SHADER_STATE_PENDING: _gl.disableVertexAttribArray(i); enabledAttributeListInContext[i] = 0; break; } } return locationList; }, getAttribLocation: function(_gl, symbol) { var locationMap = this._locations; var location = locationMap[symbol]; if (location == null) { location = _gl.getAttribLocation(this._program, symbol); locationMap[symbol] = location; } return location; }, buildProgram: function(_gl, shader, vertexShaderCode, fragmentShaderCode) { var vertexShader = _gl.createShader(_gl.VERTEX_SHADER); var program = _gl.createProgram(); _gl.shaderSource(vertexShader, vertexShaderCode); _gl.compileShader(vertexShader); var fragmentShader = _gl.createShader(_gl.FRAGMENT_SHADER); _gl.shaderSource(fragmentShader, fragmentShaderCode); _gl.compileShader(fragmentShader); var msg = checkShaderErrorMsg(_gl, vertexShader, vertexShaderCode); if (msg) { return msg; } msg = checkShaderErrorMsg(_gl, fragmentShader, fragmentShaderCode); if (msg) { return msg; } _gl.attachShader(program, vertexShader); _gl.attachShader(program, fragmentShader); if (shader.attributeSemantics["POSITION"]) { _gl.bindAttribLocation(program, 0, shader.attributeSemantics["POSITION"].symbol); } else { var keys2 = Object.keys(this.attributes); _gl.bindAttribLocation(program, 0, keys2[0]); } _gl.linkProgram(program); _gl.deleteShader(vertexShader); _gl.deleteShader(fragmentShader); this._program = program; this.vertexCode = vertexShaderCode; this.fragmentCode = fragmentShaderCode; if (!_gl.getProgramParameter(program, _gl.LINK_STATUS)) { return "Could not link program\n" + _gl.getProgramInfoLog(program); } for (var i = 0; i < shader.uniforms.length; i++) { var uniformSymbol = shader.uniforms[i]; this._locations[uniformSymbol] = _gl.getUniformLocation(program, uniformSymbol); } } }); var loopRegex = /for\s*?\(int\s*?_idx_\s*\=\s*([\w-]+)\;\s*_idx_\s*<\s*([\w-]+);\s*_idx_\s*\+\+\s*\)\s*\{\{([\s\S]+?)(?=\}\})\}\}/g; function unrollLoop(shaderStr, defines, lightsNumbers) { function replace(match, start, end, snippet) { var unroll = ""; if (isNaN(start)) { if (start in defines) { start = defines[start]; } else { start = lightNumberDefines[start]; } } if (isNaN(end)) { if (end in defines) { end = defines[end]; } else { end = lightNumberDefines[end]; } } for (var idx = parseInt(start); idx < parseInt(end); idx++) { unroll += "{" + snippet.replace(/float\s*\(\s*_idx_\s*\)/g, idx.toFixed(1)).replace(/_idx_/g, idx) + "}"; } return unroll; } var lightNumberDefines = {}; for (var lightType in lightsNumbers) { lightNumberDefines[lightType + "_COUNT"] = lightsNumbers[lightType]; } return shaderStr.replace(loopRegex, replace); } function getDefineCode(defines, lightsNumbers, enabledTextures) { var defineStr = []; if (lightsNumbers) { for (var lightType in lightsNumbers) { var count = lightsNumbers[lightType]; if (count > 0) { defineStr.push("#define " + lightType.toUpperCase() + "_COUNT " + count); } } } if (enabledTextures) { for (var i = 0; i < enabledTextures.length; i++) { var symbol = enabledTextures[i]; defineStr.push("#define " + symbol.toUpperCase() + "_ENABLED"); } } for (var symbol in defines) { var value = defines[symbol]; if (value === null) { defineStr.push("#define " + symbol); } else { defineStr.push("#define " + symbol + " " + value.toString()); } } return defineStr.join("\n"); } function getExtensionCode(exts) { var extensionStr = []; for (var i = 0; i < exts.length; i++) { extensionStr.push("#extension GL_" + exts[i] + " : enable"); } return extensionStr.join("\n"); } function getPrecisionCode(precision) { return ["precision", precision, "float"].join(" ") + ";\n" + ["precision", precision, "int"].join(" ") + ";\n" + ["precision", precision, "sampler2D"].join(" ") + ";\n"; } function ProgramManager(renderer) { this._renderer = renderer; this._cache = {}; } ProgramManager.prototype.getProgram = function(renderable, material, scene) { var cache = this._cache; var isSkinnedMesh = renderable.isSkinnedMesh && renderable.isSkinnedMesh(); var isInstancedMesh = renderable.isInstancedMesh && renderable.isInstancedMesh(); var key = "s" + material.shader.shaderID + "m" + material.getProgramKey(); if (scene) { key += "se" + scene.getProgramKey(renderable.lightGroup); } if (isSkinnedMesh) { key += ",sk" + renderable.joints.length; } if (isInstancedMesh) { key += ",is"; } var program = cache[key]; if (program) { return program; } var lightsNumbers = scene ? scene.getLightsNumbers(renderable.lightGroup) : {}; var renderer = this._renderer; var _gl = renderer.gl; var enabledTextures = material.getEnabledTextures(); var extraDefineCode = ""; if (isSkinnedMesh) { var skinDefines = { SKINNING: null, JOINT_COUNT: renderable.joints.length }; if (renderable.joints.length > renderer.getMaxJointNumber()) { skinDefines.USE_SKIN_MATRICES_TEXTURE = null; } extraDefineCode += "\n" + getDefineCode(skinDefines) + "\n"; } if (isInstancedMesh) { extraDefineCode += "\n#define INSTANCING\n"; } var vertexDefineStr = extraDefineCode + getDefineCode(material.vertexDefines, lightsNumbers, enabledTextures); var fragmentDefineStr = extraDefineCode + getDefineCode(material.fragmentDefines, lightsNumbers, enabledTextures); var vertexCode = vertexDefineStr + "\n" + material.shader.vertex; var extensions = [ "OES_standard_derivatives", "EXT_shader_texture_lod" ].filter(function(ext) { return renderer.getGLExtension(ext) != null; }); if (extensions.indexOf("EXT_shader_texture_lod") >= 0) { fragmentDefineStr += "\n#define SUPPORT_TEXTURE_LOD"; } if (extensions.indexOf("OES_standard_derivatives") >= 0) { fragmentDefineStr += "\n#define SUPPORT_STANDARD_DERIVATIVES"; } var fragmentCode = getExtensionCode(extensions) + "\n" + getPrecisionCode(material.precision) + "\n" + fragmentDefineStr + "\n" + material.shader.fragment; var finalVertexCode = unrollLoop(vertexCode, material.vertexDefines, lightsNumbers); var finalFragmentCode = unrollLoop(fragmentCode, material.fragmentDefines, lightsNumbers); var program = new GLProgram(); program.uniformSemantics = material.shader.uniformSemantics; program.attributes = material.shader.attributes; var errorMsg = program.buildProgram(_gl, material.shader, finalVertexCode, finalFragmentCode); program.__error = errorMsg; cache[key] = program; return program; }; var uniformRegex = /uniform\s+(bool|float|int|vec2|vec3|vec4|ivec2|ivec3|ivec4|mat2|mat3|mat4|sampler2D|samplerCube)\s+([\s\S]*?);/g; var attributeRegex = /attribute\s+(float|int|vec2|vec3|vec4)\s+([\s\S]*?);/g; var defineRegex = /#define\s+(\w+)?(\s+[\d-.]+)?\s*;?\s*\n/g; var uniformTypeMap = { "bool": "1i", "int": "1i", "sampler2D": "t", "samplerCube": "t", "float": "1f", "vec2": "2f", "vec3": "3f", "vec4": "4f", "ivec2": "2i", "ivec3": "3i", "ivec4": "4i", "mat2": "m2", "mat3": "m3", "mat4": "m4" }; function createZeroArray(len) { var arr = []; for (var i = 0; i < len; i++) { arr[i] = 0; } return arr; } var uniformValueConstructor = { "bool": function() { return true; }, "int": function() { return 0; }, "float": function() { return 0; }, "sampler2D": function() { return null; }, "samplerCube": function() { return null; }, "vec2": function() { return createZeroArray(2); }, "vec3": function() { return createZeroArray(3); }, "vec4": function() { return createZeroArray(4); }, "ivec2": function() { return createZeroArray(2); }, "ivec3": function() { return createZeroArray(3); }, "ivec4": function() { return createZeroArray(4); }, "mat2": function() { return createZeroArray(4); }, "mat3": function() { return createZeroArray(9); }, "mat4": function() { return createZeroArray(16); }, "array": function() { return []; } }; var attributeSemantics = [ "POSITION", "NORMAL", "BINORMAL", "TANGENT", "TEXCOORD", "TEXCOORD_0", "TEXCOORD_1", "COLOR", // Skinning // https://github.com/KhronosGroup/glTF/blob/master/specification/README.md#semantics "JOINT", "WEIGHT" ]; var uniformSemantics = [ "SKIN_MATRIX", // Information about viewport "VIEWPORT_SIZE", "VIEWPORT", "DEVICEPIXELRATIO", // Window size for window relative coordinate // https://www.opengl.org/sdk/docs/man/html/gl_FragCoord.xhtml "WINDOW_SIZE", // Infomation about camera "NEAR", "FAR", // Time "TIME" ]; var matrixSemantics = [ "WORLD", "VIEW", "PROJECTION", "WORLDVIEW", "VIEWPROJECTION", "WORLDVIEWPROJECTION", "WORLDINVERSE", "VIEWINVERSE", "PROJECTIONINVERSE", "WORLDVIEWINVERSE", "VIEWPROJECTIONINVERSE", "WORLDVIEWPROJECTIONINVERSE", "WORLDTRANSPOSE", "VIEWTRANSPOSE", "PROJECTIONTRANSPOSE", "WORLDVIEWTRANSPOSE", "VIEWPROJECTIONTRANSPOSE", "WORLDVIEWPROJECTIONTRANSPOSE", "WORLDINVERSETRANSPOSE", "VIEWINVERSETRANSPOSE", "PROJECTIONINVERSETRANSPOSE", "WORLDVIEWINVERSETRANSPOSE", "VIEWPROJECTIONINVERSETRANSPOSE", "WORLDVIEWPROJECTIONINVERSETRANSPOSE" ]; var attributeSizeMap = { // WebGL does not support integer attributes "vec4": 4, "vec3": 3, "vec2": 2, "float": 1 }; var shaderIDCache = {}; var shaderCodeCache = {}; function getShaderID(vertex, fragment) { var key = "vertex:" + vertex + "fragment:" + fragment; if (shaderIDCache[key]) { return shaderIDCache[key]; } var id = util.genGUID(); shaderIDCache[key] = id; shaderCodeCache[id] = { vertex, fragment }; return id; } function removeComment(code) { return code.replace(/[ \t]*\/\/.*\n/g, "").replace(/[ \t]*\/\*[\s\S]*?\*\//g, ""); } function logSyntaxError() { console.error("Wrong uniform/attributes syntax"); } function parseDeclarations(type, line) { var speratorsRegexp = /[,=\(\):]/; var tokens = line.replace(/:\s*\[\s*(.*)\s*\]/g, "=" + type + "($1)").replace(/\s+/g, "").split(/(?=[,=\(\):])/g); var newTokens = []; for (var i = 0; i < tokens.length; i++) { if (tokens[i].match(speratorsRegexp)) { newTokens.push( tokens[i].charAt(0), tokens[i].slice(1) ); } else { newTokens.push(tokens[i]); } } tokens = newTokens; var TYPE_SYMBOL = 0; var TYPE_ASSIGN = 1; var TYPE_VEC = 2; var TYPE_ARR = 3; var TYPE_SEMANTIC = 4; var TYPE_NORMAL = 5; var opType = TYPE_SYMBOL; var declarations = {}; var declarationValue = null; var currentDeclaration; addSymbol(tokens[0]); function addSymbol(symbol) { if (!symbol) { logSyntaxError(); } var arrResult = symbol.match(/\[(.*?)\]/); currentDeclaration = symbol.replace(/\[(.*?)\]/, ""); declarations[currentDeclaration] = {}; if (arrResult) { declarations[currentDeclaration].isArray = true; declarations[currentDeclaration].arraySize = arrResult[1]; } } for (var i = 1; i < tokens.length; i++) { var token = tokens[i]; if (!token) { continue; } if (token === "=") { if (opType !== TYPE_SYMBOL && opType !== TYPE_ARR) { logSyntaxError(); break; } opType = TYPE_ASSIGN; continue; } else if (token === ":") { opType = TYPE_SEMANTIC; continue; } else if (token === ",") { if (opType === TYPE_VEC) { if (!(declarationValue instanceof Array)) { logSyntaxError(); break; } declarationValue.push(+tokens[++i]); } else { opType = TYPE_NORMAL; } continue; } else if (token === ")") { declarations[currentDeclaration].value = new vendor.Float32Array(declarationValue); declarationValue = null; opType = TYPE_NORMAL; continue; } else if (token === "(") { if (opType !== TYPE_VEC) { logSyntaxError(); break; } if (!(declarationValue instanceof Array)) { logSyntaxError(); break; } declarationValue.push(+tokens[++i]); continue; } else if (token.indexOf("vec") >= 0) { if (opType !== TYPE_ASSIGN && opType !== TYPE_SEMANTIC) { logSyntaxError(); break; } opType = TYPE_VEC; declarationValue = []; continue; } else if (opType === TYPE_ASSIGN) { if (type === "bool") { declarations[currentDeclaration].value = token === "true"; } else { declarations[currentDeclaration].value = parseFloat(token); } declarationValue = null; continue; } else if (opType === TYPE_SEMANTIC) { var semantic = token; if (attributeSemantics.indexOf(semantic) >= 0 || uniformSemantics.indexOf(semantic) >= 0 || matrixSemantics.indexOf(semantic) >= 0) { declarations[currentDeclaration].semantic = semantic; } else if (semantic === "ignore" || semantic === "unconfigurable") { declarations[currentDeclaration].ignore = true; } else { if (type === "bool") { declarations[currentDeclaration].value = semantic === "true"; } else { declarations[currentDeclaration].value = parseFloat(semantic); } } continue; } addSymbol(token); opType = TYPE_SYMBOL; } return declarations; } function Shader(vertex, fragment) { if (typeof vertex === "object") { fragment = vertex.fragment; vertex = vertex.vertex; } vertex = removeComment(vertex); fragment = removeComment(fragment); this._shaderID = getShaderID(vertex, fragment); this._vertexCode = Shader.parseImport(vertex); this._fragmentCode = Shader.parseImport(fragment); this.attributeSemantics = {}; this.matrixSemantics = {}; this.uniformSemantics = {}; this.matrixSemanticKeys = []; this.uniformTemplates = {}; this.attributes = {}; this.textures = {}; this.vertexDefines = {}; this.fragmentDefines = {}; this._parseAttributes(); this._parseUniforms(); this._parseDefines(); } Shader.prototype = { constructor: Shader, // Create a new uniform instance for material createUniforms: function() { var uniforms = {}; for (var symbol in this.uniformTemplates) { var uniformTpl = this.uniformTemplates[symbol]; uniforms[symbol] = { type: uniformTpl.type, value: uniformTpl.value() }; } return uniforms; }, _parseImport: function() { this._vertexCode = Shader.parseImport(this.vertex); this._fragmentCode = Shader.parseImport(this.fragment); }, _addSemanticUniform: function(symbol, uniformType, semantic) { if (attributeSemantics.indexOf(semantic) >= 0) { this.attributeSemantics[semantic] = { symbol, type: uniformType }; } else if (matrixSemantics.indexOf(semantic) >= 0) { var isTranspose = false; var semanticNoTranspose = semantic; if (semantic.match(/TRANSPOSE$/)) { isTranspose = true; semanticNoTranspose = semantic.slice(0, -9); } this.matrixSemantics[semantic] = { symbol, type: uniformType, isTranspose, semanticNoTranspose }; } else if (uniformSemantics.indexOf(semantic) >= 0) { this.uniformSemantics[semantic] = { symbol, type: uniformType }; } }, _addMaterialUniform: function(symbol, type, uniformType, defaultValueFunc, isArray2, materialUniforms) { materialUniforms[symbol] = { type: uniformType, value: isArray2 ? uniformValueConstructor["array"] : defaultValueFunc || uniformValueConstructor[type], semantic: null }; }, _parseUniforms: function() { var uniforms = {}; var self2 = this; var shaderType = "vertex"; this._uniformList = []; this._vertexCode = this._vertexCode.replace(uniformRegex, _uniformParser); shaderType = "fragment"; this._fragmentCode = this._fragmentCode.replace(uniformRegex, _uniformParser); self2.matrixSemanticKeys = Object.keys(this.matrixSemantics); function makeDefaultValueFunc(value) { return value != null ? function() { return value; } : null; } function _uniformParser(str, type, content) { var declaredUniforms = parseDeclarations(type, content); var uniformMainStr = []; for (var symbol in declaredUniforms) { var uniformInfo = declaredUniforms[symbol]; var semantic = uniformInfo.semantic; var tmpStr = symbol; var uniformType = uniformTypeMap[type]; var defaultValueFunc = makeDefaultValueFunc(declaredUniforms[symbol].value); if (declaredUniforms[symbol].isArray) { tmpStr += "[" + declaredUniforms[symbol].arraySize + "]"; uniformType += "v"; } uniformMainStr.push(tmpStr); self2._uniformList.push(symbol); if (!uniformInfo.ignore) { if (type === "sampler2D" || type === "samplerCube") { self2.textures[symbol] = { shaderType, type }; } if (semantic) { self2._addSemanticUniform(symbol, uniformType, semantic); } else { self2._addMaterialUniform( symbol, type, uniformType, defaultValueFunc, declaredUniforms[symbol].isArray, uniforms ); } } } return uniformMainStr.length > 0 ? "uniform " + type + " " + uniformMainStr.join(",") + ";\n" : ""; } this.uniformTemplates = uniforms; }, _parseAttributes: function() { var attributes = {}; var self2 = this; this._vertexCode = this._vertexCode.replace(attributeRegex, _attributeParser); function _attributeParser(str, type, content) { var declaredAttributes = parseDeclarations(type, content); var size = attributeSizeMap[type] || 1; var attributeMainStr = []; for (var symbol in declaredAttributes) { var semantic = declaredAttributes[symbol].semantic; attributes[symbol] = { // TODO Can only be float type: "float", size, semantic: semantic || null }; if (semantic) { if (attributeSemantics.indexOf(semantic) < 0) { throw new Error('Unkown semantic "' + semantic + '"'); } else { self2.attributeSemantics[semantic] = { symbol, type }; } } attributeMainStr.push(symbol); } return "attribute " + type + " " + attributeMainStr.join(",") + ";\n"; } this.attributes = attributes; }, _parseDefines: function() { var self2 = this; var shaderType = "vertex"; this._vertexCode = this._vertexCode.replace(defineRegex, _defineParser); shaderType = "fragment"; this._fragmentCode = this._fragmentCode.replace(defineRegex, _defineParser); function _defineParser(str, symbol, value) { var defines = shaderType === "vertex" ? self2.vertexDefines : self2.fragmentDefines; if (!defines[symbol]) { if (value === "false") { defines[symbol] = false; } else if (value === "true") { defines[symbol] = true; } else { defines[symbol] = value ? isNaN(parseFloat(value)) ? value.trim() : parseFloat(value) : null; } } return ""; } }, /** * Clone a new shader * @return {clay.Shader} */ clone: function() { var code = shaderCodeCache[this._shaderID]; var shader = new Shader(code.vertex, code.fragment); return shader; } }; if (Object.defineProperty) { Object.defineProperty(Shader.prototype, "shaderID", { get: function() { return this._shaderID; } }); Object.defineProperty(Shader.prototype, "vertex", { get: function() { return this._vertexCode; } }); Object.defineProperty(Shader.prototype, "fragment", { get: function() { return this._fragmentCode; } }); Object.defineProperty(Shader.prototype, "uniforms", { get: function() { return this._uniformList; } }); } var importRegex = /(@import)\s*([0-9a-zA-Z_\-\.]*)/g; Shader.parseImport = function(shaderStr) { shaderStr = shaderStr.replace(importRegex, function(str, importSymbol, importName) { var str = Shader.source(importName); if (str) { return Shader.parseImport(str); } else { console.error('Shader chunk "' + importName + '" not existed in library'); return ""; } }); return shaderStr; }; var exportRegex = /(@export)\s*([0-9a-zA-Z_\-\.]*)\s*\n([\s\S]*?)@end/g; Shader["import"] = function(shaderStr) { shaderStr.replace(exportRegex, function(str, exportSymbol, exportName, code) { var code = code.replace(/(^[\s\t\xa0\u3000]+)|([\u3000\xa0\s\t]+\x24)/g, ""); if (code) { var parts = exportName.split("."); var obj = Shader.codes; var i = 0; var key; while (i < parts.length - 1) { key = parts[i++]; if (!obj[key]) { obj[key] = {}; } obj = obj[key]; } key = parts[i]; obj[key] = code; } return code; }); }; Shader.codes = {}; Shader.source = function(name) { var parts = name.split("."); var obj = Shader.codes; var i = 0; while (obj && i < parts.length) { var key = parts[i++]; obj = obj[key]; } if (typeof obj !== "string") { console.error('Shader "' + name + '" not existed in library'); return ""; } return obj; }; const prezGLSL = "@export clay.prez.vertex\nuniform mat4 WVP : WORLDVIEWPROJECTION;\nattribute vec3 pos : POSITION;\nattribute vec2 uv : TEXCOORD_0;\nuniform vec2 uvRepeat : [1.0, 1.0];\nuniform vec2 uvOffset : [0.0, 0.0];\n@import clay.chunk.skinning_header\n@import clay.chunk.instancing_header\nvarying vec2 v_Texcoord;\nvoid main()\n{\n vec4 P = vec4(pos, 1.0);\n#ifdef SKINNING\n @import clay.chunk.skin_matrix\n P = skinMatrixWS * P;\n#endif\n#ifdef INSTANCING\n @import clay.chunk.instancing_matrix\n P = instanceMat * P;\n#endif\n gl_Position = WVP * P;\n v_Texcoord = uv * uvRepeat + uvOffset;\n}\n@end\n@export clay.prez.fragment\nuniform sampler2D alphaMap;\nuniform float alphaCutoff: 0.0;\nvarying vec2 v_Texcoord;\nvoid main()\n{\n if (alphaCutoff > 0.0) {\n if (texture2D(alphaMap, v_Texcoord).a <= alphaCutoff) {\n discard;\n }\n }\n gl_FragColor = vec4(0.0,0.0,0.0,1.0);\n}\n@end"; var mat4$2 = {}; mat4$2.create = function() { var out = new GLMAT_ARRAY_TYPE(16); out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; }; mat4$2.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(16); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; }; mat4$2.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; }; mat4$2.identity = function(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; }; mat4$2.transpose = function(out, a) { if (out === a) { var a01 = a[1], a02 = a[2], a03 = a[3], a12 = a[6], a13 = a[7], a23 = a[11]; out[1] = a[4]; out[2] = a[8]; out[3] = a[12]; out[4] = a01; out[6] = a[9]; out[7] = a[13]; out[8] = a02; out[9] = a12; out[11] = a[14]; out[12] = a03; out[13] = a13; out[14] = a23; } else { out[0] = a[0]; out[1] = a[4]; out[2] = a[8]; out[3] = a[12]; out[4] = a[1]; out[5] = a[5]; out[6] = a[9]; out[7] = a[13]; out[8] = a[2]; out[9] = a[6]; out[10] = a[10]; out[11] = a[14]; out[12] = a[3]; out[13] = a[7]; out[14] = a[11]; out[15] = a[15]; } return out; }; mat4$2.invert = function(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], b00 = a00 * a11 - a01 * a10, b01 = a00 * a12 - a02 * a10, b02 = a00 * a13 - a03 * a10, b03 = a01 * a12 - a02 * a11, b04 = a01 * a13 - a03 * a11, b05 = a02 * a13 - a03 * a12, b06 = a20 * a31 - a21 * a30, b07 = a20 * a32 - a22 * a30, b08 = a20 * a33 - a23 * a30, b09 = a21 * a32 - a22 * a31, b10 = a21 * a33 - a23 * a31, b11 = a22 * a33 - a23 * a32, det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1 / det; out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; return out; }; mat4$2.adjoint = function(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; out[0] = a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22); out[1] = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)); out[2] = a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12); out[3] = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)); out[4] = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)); out[5] = a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22); out[6] = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)); out[7] = a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12); out[8] = a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21); out[9] = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)); out[10] = a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11); out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)); out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)); out[13] = a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21); out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)); out[15] = a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11); return out; }; mat4$2.determinant = function(a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], b00 = a00 * a11 - a01 * a10, b01 = a00 * a12 - a02 * a10, b02 = a00 * a13 - a03 * a10, b03 = a01 * a12 - a02 * a11, b04 = a01 * a13 - a03 * a11, b05 = a02 * a13 - a03 * a12, b06 = a20 * a31 - a21 * a30, b07 = a20 * a32 - a22 * a30, b08 = a20 * a33 - a23 * a30, b09 = a21 * a32 - a22 * a31, b10 = a21 * a33 - a23 * a31, b11 = a22 * a33 - a23 * a32; return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; }; mat4$2.multiply = function(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; return out; }; mat4$2.multiplyAffine = function(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[4], a11 = a[5], a12 = a[6], a20 = a[8], a21 = a[9], a22 = a[10], a30 = a[12], a31 = a[13], a32 = a[14]; var b0 = b[0], b1 = b[1], b2 = b[2]; out[0] = b0 * a00 + b1 * a10 + b2 * a20; out[1] = b0 * a01 + b1 * a11 + b2 * a21; out[2] = b0 * a02 + b1 * a12 + b2 * a22; b0 = b[4]; b1 = b[5]; b2 = b[6]; out[4] = b0 * a00 + b1 * a10 + b2 * a20; out[5] = b0 * a01 + b1 * a11 + b2 * a21; out[6] = b0 * a02 + b1 * a12 + b2 * a22; b0 = b[8]; b1 = b[9]; b2 = b[10]; out[8] = b0 * a00 + b1 * a10 + b2 * a20; out[9] = b0 * a01 + b1 * a11 + b2 * a21; out[10] = b0 * a02 + b1 * a12 + b2 * a22; b0 = b[12]; b1 = b[13]; b2 = b[14]; out[12] = b0 * a00 + b1 * a10 + b2 * a20 + a30; out[13] = b0 * a01 + b1 * a11 + b2 * a21 + a31; out[14] = b0 * a02 + b1 * a12 + b2 * a22 + a32; return out; }; mat4$2.mul = mat4$2.multiply; mat4$2.mulAffine = mat4$2.multiplyAffine; mat4$2.translate = function(out, a, v) { var x = v[0], y = v[1], z = v[2], a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23; if (a === out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; } else { a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; out[12] = a00 * x + a10 * y + a20 * z + a[12]; out[13] = a01 * x + a11 * y + a21 * z + a[13]; out[14] = a02 * x + a12 * y + a22 * z + a[14]; out[15] = a03 * x + a13 * y + a23 * z + a[15]; } return out; }; mat4$2.scale = function(out, a, v) { var x = v[0], y = v[1], z = v[2]; out[0] = a[0] * x; out[1] = a[1] * x; out[2] = a[2] * x; out[3] = a[3] * x; out[4] = a[4] * y; out[5] = a[5] * y; out[6] = a[6] * y; out[7] = a[7] * y; out[8] = a[8] * z; out[9] = a[9] * z; out[10] = a[10] * z; out[11] = a[11] * z; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return out; }; mat4$2.rotate = function(out, a, rad2, axis) { var x = axis[0], y = axis[1], z = axis[2], len = Math.sqrt(x * x + y * y + z * z), s, c, t, a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, b00, b01, b02, b10, b11, b12, b20, b21, b22; if (Math.abs(len) < GLMAT_EPSILON) { return null; } len = 1 / len; x *= len; y *= len; z *= len; s = Math.sin(rad2); c = Math.cos(rad2); t = 1 - c; a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s; b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s; b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c; out[0] = a00 * b00 + a10 * b01 + a20 * b02; out[1] = a01 * b00 + a11 * b01 + a21 * b02; out[2] = a02 * b00 + a12 * b01 + a22 * b02; out[3] = a03 * b00 + a13 * b01 + a23 * b02; out[4] = a00 * b10 + a10 * b11 + a20 * b12; out[5] = a01 * b10 + a11 * b11 + a21 * b12; out[6] = a02 * b10 + a12 * b11 + a22 * b12; out[7] = a03 * b10 + a13 * b11 + a23 * b12; out[8] = a00 * b20 + a10 * b21 + a20 * b22; out[9] = a01 * b20 + a11 * b21 + a21 * b22; out[10] = a02 * b20 + a12 * b21 + a22 * b22; out[11] = a03 * b20 + a13 * b21 + a23 * b22; if (a !== out) { out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } return out; }; mat4$2.rotateX = function(out, a, rad2) { var s = Math.sin(rad2), c = Math.cos(rad2), a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; if (a !== out) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[4] = a10 * c + a20 * s; out[5] = a11 * c + a21 * s; out[6] = a12 * c + a22 * s; out[7] = a13 * c + a23 * s; out[8] = a20 * c - a10 * s; out[9] = a21 * c - a11 * s; out[10] = a22 * c - a12 * s; out[11] = a23 * c - a13 * s; return out; }; mat4$2.rotateY = function(out, a, rad2) { var s = Math.sin(rad2), c = Math.cos(rad2), a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; if (a !== out) { out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c - a20 * s; out[1] = a01 * c - a21 * s; out[2] = a02 * c - a22 * s; out[3] = a03 * c - a23 * s; out[8] = a00 * s + a20 * c; out[9] = a01 * s + a21 * c; out[10] = a02 * s + a22 * c; out[11] = a03 * s + a23 * c; return out; }; mat4$2.rotateZ = function(out, a, rad2) { var s = Math.sin(rad2), c = Math.cos(rad2), a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; if (a !== out) { out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; } out[0] = a00 * c + a10 * s; out[1] = a01 * c + a11 * s; out[2] = a02 * c + a12 * s; out[3] = a03 * c + a13 * s; out[4] = a10 * c - a00 * s; out[5] = a11 * c - a01 * s; out[6] = a12 * c - a02 * s; out[7] = a13 * c - a03 * s; return out; }; mat4$2.fromRotationTranslation = function(out, q, v) { var x = q[0], y = q[1], z = q[2], w = q[3], x2 = x + x, y2 = y + y, z2 = z + z, xx = x * x2, xy = x * y2, xz = x * z2, yy = y * y2, yz = y * z2, zz = z * z2, wx2 = w * x2, wy = w * y2, wz = w * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx2; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx2; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; }; mat4$2.fromQuat = function(out, q) { var x = q[0], y = q[1], z = q[2], w = q[3], x2 = x + x, y2 = y + y, z2 = z + z, xx = x * x2, yx = y * x2, yy = y * y2, zx = z * x2, zy = z * y2, zz = z * z2, wx2 = w * x2, wy = w * y2, wz = w * z2; out[0] = 1 - yy - zz; out[1] = yx + wz; out[2] = zx - wy; out[3] = 0; out[4] = yx - wz; out[5] = 1 - xx - zz; out[6] = zy + wx2; out[7] = 0; out[8] = zx + wy; out[9] = zy - wx2; out[10] = 1 - xx - yy; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return out; }; mat4$2.frustum = function(out, left, right, bottom, top, near, far) { var rl = 1 / (right - left), tb = 1 / (top - bottom), nf = 1 / (near - far); out[0] = near * 2 * rl; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = near * 2 * tb; out[6] = 0; out[7] = 0; out[8] = (right + left) * rl; out[9] = (top + bottom) * tb; out[10] = (far + near) * nf; out[11] = -1; out[12] = 0; out[13] = 0; out[14] = far * near * 2 * nf; out[15] = 0; return out; }; mat4$2.perspective = function(out, fovy, aspect, near, far) { var f = 1 / Math.tan(fovy / 2), nf = 1 / (near - far); out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = (far + near) * nf; out[11] = -1; out[12] = 0; out[13] = 0; out[14] = 2 * far * near * nf; out[15] = 0; return out; }; mat4$2.ortho = function(out, left, right, bottom, top, near, far) { var lr = 1 / (left - right), bt = 1 / (bottom - top), nf = 1 / (near - far); out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 2 * nf; out[11] = 0; out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = (far + near) * nf; out[15] = 1; return out; }; mat4$2.lookAt = function(out, eye, center, up) { var x0, x1, x2, y0, y1, y2, z0, z1, z2, len, eyex = eye[0], eyey = eye[1], eyez = eye[2], upx = up[0], upy = up[1], upz = up[2], centerx = center[0], centery = center[1], centerz = center[2]; if (Math.abs(eyex - centerx) < GLMAT_EPSILON && Math.abs(eyey - centery) < GLMAT_EPSILON && Math.abs(eyez - centerz) < GLMAT_EPSILON) { return mat4$2.identity(out); } z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz; len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); z0 *= len; z1 *= len; z2 *= len; x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0; len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); out[15] = 1; return out; }; mat4$2.frob = function(a) { return Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + Math.pow(a[6], 2) + Math.pow(a[7], 2) + Math.pow(a[8], 2) + Math.pow(a[9], 2) + Math.pow(a[10], 2) + Math.pow(a[11], 2) + Math.pow(a[12], 2) + Math.pow(a[13], 2) + Math.pow(a[14], 2) + Math.pow(a[15], 2)); }; var vec3$f = {}; vec3$f.create = function() { var out = new GLMAT_ARRAY_TYPE(3); out[0] = 0; out[1] = 0; out[2] = 0; return out; }; vec3$f.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(3); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; }; vec3$f.fromValues = function(x, y, z) { var out = new GLMAT_ARRAY_TYPE(3); out[0] = x; out[1] = y; out[2] = z; return out; }; vec3$f.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; return out; }; vec3$f.set = function(out, x, y, z) { out[0] = x; out[1] = y; out[2] = z; return out; }; vec3$f.add = function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; return out; }; vec3$f.subtract = function(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; out[2] = a[2] - b[2]; return out; }; vec3$f.sub = vec3$f.subtract; vec3$f.multiply = function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; out[2] = a[2] * b[2]; return out; }; vec3$f.mul = vec3$f.multiply; vec3$f.divide = function(out, a, b) { out[0] = a[0] / b[0]; out[1] = a[1] / b[1]; out[2] = a[2] / b[2]; return out; }; vec3$f.div = vec3$f.divide; vec3$f.min = function(out, a, b) { out[0] = Math.min(a[0], b[0]); out[1] = Math.min(a[1], b[1]); out[2] = Math.min(a[2], b[2]); return out; }; vec3$f.max = function(out, a, b) { out[0] = Math.max(a[0], b[0]); out[1] = Math.max(a[1], b[1]); out[2] = Math.max(a[2], b[2]); return out; }; vec3$f.scale = function(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; return out; }; vec3$f.scaleAndAdd = function(out, a, b, scale) { out[0] = a[0] + b[0] * scale; out[1] = a[1] + b[1] * scale; out[2] = a[2] + b[2] * scale; return out; }; vec3$f.distance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1], z = b[2] - a[2]; return Math.sqrt(x * x + y * y + z * z); }; vec3$f.dist = vec3$f.distance; vec3$f.squaredDistance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1], z = b[2] - a[2]; return x * x + y * y + z * z; }; vec3$f.sqrDist = vec3$f.squaredDistance; vec3$f.length = function(a) { var x = a[0], y = a[1], z = a[2]; return Math.sqrt(x * x + y * y + z * z); }; vec3$f.len = vec3$f.length; vec3$f.squaredLength = function(a) { var x = a[0], y = a[1], z = a[2]; return x * x + y * y + z * z; }; vec3$f.sqrLen = vec3$f.squaredLength; vec3$f.negate = function(out, a) { out[0] = -a[0]; out[1] = -a[1]; out[2] = -a[2]; return out; }; vec3$f.inverse = function(out, a) { out[0] = 1 / a[0]; out[1] = 1 / a[1]; out[2] = 1 / a[2]; return out; }; vec3$f.normalize = function(out, a) { var x = a[0], y = a[1], z = a[2]; var len = x * x + y * y + z * z; if (len > 0) { len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; out[2] = a[2] * len; } return out; }; vec3$f.dot = function(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }; vec3$f.cross = function(out, a, b) { var ax = a[0], ay = a[1], az = a[2], bx = b[0], by = b[1], bz = b[2]; out[0] = ay * bz - az * by; out[1] = az * bx - ax * bz; out[2] = ax * by - ay * bx; return out; }; vec3$f.lerp = function(out, a, b, t) { var ax = a[0], ay = a[1], az = a[2]; out[0] = ax + t * (b[0] - ax); out[1] = ay + t * (b[1] - ay); out[2] = az + t * (b[2] - az); return out; }; vec3$f.random = function(out, scale) { scale = scale || 1; var r = GLMAT_RANDOM$1() * 2 * Math.PI; var z = GLMAT_RANDOM$1() * 2 - 1; var zScale = Math.sqrt(1 - z * z) * scale; out[0] = Math.cos(r) * zScale; out[1] = Math.sin(r) * zScale; out[2] = z * scale; return out; }; vec3$f.transformMat4 = function(out, a, m) { var x = a[0], y = a[1], z = a[2], w = m[3] * x + m[7] * y + m[11] * z + m[15]; w = w || 1; out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; return out; }; vec3$f.transformMat3 = function(out, a, m) { var x = a[0], y = a[1], z = a[2]; out[0] = x * m[0] + y * m[3] + z * m[6]; out[1] = x * m[1] + y * m[4] + z * m[7]; out[2] = x * m[2] + y * m[5] + z * m[8]; return out; }; vec3$f.transformQuat = function(out, a, q) { var x = a[0], y = a[1], z = a[2], qx = q[0], qy = q[1], qz = q[2], qw = q[3], ix = qw * x + qy * z - qz * y, iy = qw * y + qz * x - qx * z, iz = qw * z + qx * y - qy * x, iw = -qx * x - qy * y - qz * z; out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy; out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz; out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx; return out; }; vec3$f.rotateX = function(out, a, b, c) { var p = [], r = []; p[0] = a[0] - b[0]; p[1] = a[1] - b[1]; p[2] = a[2] - b[2]; r[0] = p[0]; r[1] = p[1] * Math.cos(c) - p[2] * Math.sin(c); r[2] = p[1] * Math.sin(c) + p[2] * Math.cos(c); out[0] = r[0] + b[0]; out[1] = r[1] + b[1]; out[2] = r[2] + b[2]; return out; }; vec3$f.rotateY = function(out, a, b, c) { var p = [], r = []; p[0] = a[0] - b[0]; p[1] = a[1] - b[1]; p[2] = a[2] - b[2]; r[0] = p[2] * Math.sin(c) + p[0] * Math.cos(c); r[1] = p[1]; r[2] = p[2] * Math.cos(c) - p[0] * Math.sin(c); out[0] = r[0] + b[0]; out[1] = r[1] + b[1]; out[2] = r[2] + b[2]; return out; }; vec3$f.rotateZ = function(out, a, b, c) { var p = [], r = []; p[0] = a[0] - b[0]; p[1] = a[1] - b[1]; p[2] = a[2] - b[2]; r[0] = p[0] * Math.cos(c) - p[1] * Math.sin(c); r[1] = p[0] * Math.sin(c) + p[1] * Math.cos(c); r[2] = p[2]; out[0] = r[0] + b[0]; out[1] = r[1] + b[1]; out[2] = r[2] + b[2]; return out; }; vec3$f.forEach = function() { var vec = vec3$f.create(); return function(a, stride, offset, count, fn, arg) { var i, l; if (!stride) { stride = 3; } if (!offset) { offset = 0; } if (count) { l = Math.min(count * stride + offset, a.length); } else { l = a.length; } for (i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; } return a; }; }(); vec3$f.angle = function(a, b) { var tempA = vec3$f.fromValues(a[0], a[1], a[2]); var tempB = vec3$f.fromValues(b[0], b[1], b[2]); vec3$f.normalize(tempA, tempA); vec3$f.normalize(tempB, tempB); var cosine = vec3$f.dot(tempA, tempB); if (cosine > 1) { return 0; } else { return Math.acos(cosine); } }; Shader["import"](prezGLSL); var mat4Create = mat4$2.create; var errorShader = {}; function defaultGetMaterial(renderable) { return renderable.material; } function defaultGetUniform(renderable, material, symbol) { return material.uniforms[symbol].value; } function defaultIsMaterialChanged(renderabled, prevRenderable, material, prevMaterial) { return material !== prevMaterial; } function defaultIfRender(renderable) { return true; } function noop$1() { } var attributeBufferTypeMap = { float: glenum.FLOAT, byte: glenum.BYTE, ubyte: glenum.UNSIGNED_BYTE, short: glenum.SHORT, ushort: glenum.UNSIGNED_SHORT }; function VertexArrayObject(availableAttributes, availableAttributeSymbols, indicesBuffer) { this.availableAttributes = availableAttributes; this.availableAttributeSymbols = availableAttributeSymbols; this.indicesBuffer = indicesBuffer; this.vao = null; } function PlaceHolderTexture(renderer) { var blankCanvas; var webglTexture; this.bind = function(renderer2) { if (!blankCanvas) { blankCanvas = vendor.createCanvas(); blankCanvas.width = blankCanvas.height = 1; blankCanvas.getContext("2d"); } var gl = renderer2.gl; var firstBind = !webglTexture; if (firstBind) { webglTexture = gl.createTexture(); } gl.bindTexture(gl.TEXTURE_2D, webglTexture); if (firstBind) { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, blankCanvas); } }; this.unbind = function(renderer2) { renderer2.gl.bindTexture(renderer2.gl.TEXTURE_2D, null); }; this.isRenderable = function() { return true; }; } var Renderer = Base.extend( function() { return ( /** @lends clay.Renderer# */ { /** * @type {HTMLCanvasElement} * @readonly */ canvas: null, /** * Canvas width, set by resize method * @type {number} * @private */ _width: 100, /** * Canvas width, set by resize method * @type {number} * @private */ _height: 100, /** * Device pixel ratio, set by setDevicePixelRatio method * Specially for high defination display * @see http://www.khronos.org/webgl/wiki/HandlingHighDPI * @type {number} * @private */ devicePixelRatio: typeof window !== "undefined" && window.devicePixelRatio || 1, /** * Clear color * @type {number[]} */ clearColor: [0, 0, 0, 0], /** * Default: * _gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT | _gl.STENCIL_BUFFER_BIT * @type {number} */ clearBit: 17664, // Settings when getting context // http://www.khronos.org/registry/webgl/specs/latest/#2.4 /** * If enable alpha, default true * @type {boolean} */ alpha: true, /** * If enable depth buffer, default true * @type {boolean} */ depth: true, /** * If enable stencil buffer, default false * @type {boolean} */ stencil: false, /** * If enable antialias, default true * @type {boolean} */ antialias: true, /** * If enable premultiplied alpha, default true * @type {boolean} */ premultipliedAlpha: true, /** * If preserve drawing buffer, default false * @type {boolean} */ preserveDrawingBuffer: false, /** * If throw context error, usually turned on in debug mode * @type {boolean} */ throwError: true, /** * WebGL Context created from given canvas * @type {WebGLRenderingContext} */ gl: null, /** * Renderer viewport, read-only, can be set by setViewport method * @type {Object} */ viewport: {}, /** * Max joint number * @type {number} */ maxJointNumber: 20, // Set by FrameBuffer#bind __currentFrameBuffer: null, _viewportStack: [], _clearStack: [], _sceneRendering: null } ); }, function() { if (!this.canvas) { this.canvas = vendor.createCanvas(); } var canvas = this.canvas; try { var opts = { alpha: this.alpha, depth: this.depth, stencil: this.stencil, antialias: this.antialias, premultipliedAlpha: this.premultipliedAlpha, preserveDrawingBuffer: this.preserveDrawingBuffer }; this.gl = canvas.getContext("webgl", opts) || canvas.getContext("experimental-webgl", opts); if (!this.gl) { throw new Error(); } this._glinfo = new GLInfo(this.gl); if (this.gl.targetRenderer) { console.error("Already created a renderer"); } this.gl.targetRenderer = this; this.resize(); } catch (e2) { throw "Error creating WebGL Context " + e2; } this._programMgr = new ProgramManager(this); this._placeholderTexture = new PlaceHolderTexture(); }, /** @lends clay.Renderer.prototype. **/ { /** * Resize the canvas * @param {number} width * @param {number} height */ resize: function(width, height) { var canvas = this.canvas; var dpr = this.devicePixelRatio; if (width != null) { if (canvas.style) { canvas.style.width = width + "px"; canvas.style.height = height + "px"; } canvas.width = width * dpr; canvas.height = height * dpr; this._width = width; this._height = height; } else { this._width = canvas.width / dpr; this._height = canvas.height / dpr; } this.setViewport(0, 0, this._width, this._height); }, /** * Get renderer width * @return {number} */ getWidth: function() { return this._width; }, /** * Get renderer height * @return {number} */ getHeight: function() { return this._height; }, /** * Get viewport aspect, * @return {number} */ getViewportAspect: function() { var viewport = this.viewport; return viewport.width / viewport.height; }, /** * Set devicePixelRatio * @param {number} devicePixelRatio */ setDevicePixelRatio: function(devicePixelRatio) { this.devicePixelRatio = devicePixelRatio; this.resize(this._width, this._height); }, /** * Get devicePixelRatio * @param {number} devicePixelRatio */ getDevicePixelRatio: function() { return this.devicePixelRatio; }, /** * Get WebGL extension * @param {string} name * @return {object} */ getGLExtension: function(name) { return this._glinfo.getExtension(name); }, /** * Get WebGL parameter * @param {string} name * @return {*} */ getGLParameter: function(name) { return this._glinfo.getParameter(name); }, /** * Set rendering viewport * @param {number|Object} x * @param {number} [y] * @param {number} [width] * @param {number} [height] * @param {number} [devicePixelRatio] * Defaultly use the renderere devicePixelRatio * It needs to be 1 when setViewport is called by frameBuffer * * @example * setViewport(0,0,width,height,1) * setViewport({ * x: 0, * y: 0, * width: width, * height: height, * devicePixelRatio: 1 * }) */ setViewport: function(x, y, width, height, dpr) { if (typeof x === "object") { var obj = x; x = obj.x; y = obj.y; width = obj.width; height = obj.height; dpr = obj.devicePixelRatio; } dpr = dpr || this.devicePixelRatio; this.gl.viewport( x * dpr, y * dpr, width * dpr, height * dpr ); this.viewport = { x, y, width, height, devicePixelRatio: dpr }; }, /** * Push current viewport into a stack */ saveViewport: function() { this._viewportStack.push(this.viewport); }, /** * Pop viewport from stack, restore in the renderer */ restoreViewport: function() { if (this._viewportStack.length > 0) { this.setViewport(this._viewportStack.pop()); } }, /** * Push current clear into a stack */ saveClear: function() { this._clearStack.push({ clearBit: this.clearBit, clearColor: this.clearColor }); }, /** * Pop clear from stack, restore in the renderer */ restoreClear: function() { if (this._clearStack.length > 0) { var opt = this._clearStack.pop(); this.clearColor = opt.clearColor; this.clearBit = opt.clearBit; } }, bindSceneRendering: function(scene) { this._sceneRendering = scene; }, /** * Render the scene in camera to the screen or binded offline framebuffer * @param {clay.Scene} scene * @param {clay.Camera} camera * @param {boolean} [notUpdateScene] If not call the scene.update methods in the rendering, default true * @param {boolean} [preZ] If use preZ optimization, default false * @return {IRenderInfo} */ render: function(scene, camera2, notUpdateScene, preZ) { var _gl = this.gl; var clearColor = this.clearColor; if (this.clearBit) { _gl.colorMask(true, true, true, true); _gl.depthMask(true); var viewport = this.viewport; var needsScissor = false; var viewportDpr = viewport.devicePixelRatio; if (viewport.width !== this._width || viewport.height !== this._height || viewportDpr && viewportDpr !== this.devicePixelRatio || viewport.x || viewport.y) { needsScissor = true; _gl.enable(_gl.SCISSOR_TEST); _gl.scissor(viewport.x * viewportDpr, viewport.y * viewportDpr, viewport.width * viewportDpr, viewport.height * viewportDpr); } _gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); _gl.clear(this.clearBit); if (needsScissor) { _gl.disable(_gl.SCISSOR_TEST); } } if (!notUpdateScene) { scene.update(false); } scene.updateLights(); camera2 = camera2 || scene.getMainCamera(); if (!camera2) { console.error("Can't find camera in the scene."); return; } camera2.update(); var renderList = scene.updateRenderList(camera2, true); this._sceneRendering = scene; var opaqueList = renderList.opaque; var transparentList = renderList.transparent; var sceneMaterial = scene.material; scene.trigger("beforerender", this, scene, camera2, renderList); if (preZ) { this.renderPreZ(opaqueList, scene, camera2); _gl.depthFunc(_gl.LEQUAL); } else { _gl.depthFunc(_gl.LESS); } var worldViewMat = mat4Create(); var posViewSpace = vec3$f.create(); for (var i = 0; i < transparentList.length; i++) { var renderable = transparentList[i]; mat4$2.multiplyAffine(worldViewMat, camera2.viewMatrix.array, renderable.worldTransform.array); vec3$f.transformMat4(posViewSpace, renderable.position.array, worldViewMat); renderable.__depth = posViewSpace[2]; } this.renderPass(opaqueList, camera2, { getMaterial: function(renderable2) { return sceneMaterial || renderable2.material; }, sortCompare: this.opaqueSortCompare }); this.renderPass(transparentList, camera2, { getMaterial: function(renderable2) { return sceneMaterial || renderable2.material; }, sortCompare: this.transparentSortCompare }); scene.trigger("afterrender", this, scene, camera2, renderList); this._sceneRendering = null; }, getProgram: function(renderable, renderMaterial, scene) { renderMaterial = renderMaterial || renderable.material; return this._programMgr.getProgram(renderable, renderMaterial, scene); }, validateProgram: function(program) { if (program.__error) { var errorMsg = program.__error; if (errorShader[program.__uid__]) { return; } errorShader[program.__uid__] = true; if (this.throwError) { throw new Error(errorMsg); } else { this.trigger("error", errorMsg); } } }, updatePrograms: function(list, scene, passConfig) { var getMaterial = passConfig && passConfig.getMaterial || defaultGetMaterial; scene = scene || null; for (var i = 0; i < list.length; i++) { var renderable = list[i]; var renderMaterial = getMaterial.call(this, renderable); if (i > 0) { var prevRenderable = list[i - 1]; var prevJointsLen = prevRenderable.joints ? prevRenderable.joints.length : 0; var jointsLen = renderable.joints ? renderable.joints.length : 0; if (jointsLen === prevJointsLen && renderable.material === prevRenderable.material && renderable.lightGroup === prevRenderable.lightGroup) { renderable.__program = prevRenderable.__program; continue; } } var program = this._programMgr.getProgram(renderable, renderMaterial, scene); this.validateProgram(program); renderable.__program = program; } }, /** * Render a single renderable list in camera in sequence * @param {clay.Renderable[]} list List of all renderables. * @param {clay.Camera} [camera] Camera provide view matrix and porjection matrix. It can be null. * @param {Object} [passConfig] * @param {Function} [passConfig.getMaterial] Get renderable material. * @param {Function} [passConfig.getUniform] Get material uniform value. * @param {Function} [passConfig.isMaterialChanged] If material changed. * @param {Function} [passConfig.beforeRender] Before render each renderable. * @param {Function} [passConfig.afterRender] After render each renderable * @param {Function} [passConfig.ifRender] If render the renderable. * @param {Function} [passConfig.sortCompare] Sort compare function. * @return {IRenderInfo} */ renderPass: function(list, camera2, passConfig) { this.trigger("beforerenderpass", this, list, camera2, passConfig); passConfig = passConfig || {}; passConfig.getMaterial = passConfig.getMaterial || defaultGetMaterial; passConfig.getUniform = passConfig.getUniform || defaultGetUniform; passConfig.isMaterialChanged = passConfig.isMaterialChanged || defaultIsMaterialChanged; passConfig.beforeRender = passConfig.beforeRender || noop$1; passConfig.afterRender = passConfig.afterRender || noop$1; var ifRenderObject = passConfig.ifRender || defaultIfRender; this.updatePrograms(list, this._sceneRendering, passConfig); if (passConfig.sortCompare) { list.sort(passConfig.sortCompare); } var viewport = this.viewport; var vDpr = viewport.devicePixelRatio; var viewportUniform = [ viewport.x * vDpr, viewport.y * vDpr, viewport.width * vDpr, viewport.height * vDpr ]; var windowDpr = this.devicePixelRatio; var windowSizeUniform = this.__currentFrameBuffer ? [this.__currentFrameBuffer.getTextureWidth(), this.__currentFrameBuffer.getTextureHeight()] : [this._width * windowDpr, this._height * windowDpr]; var viewportSizeUniform = [ viewportUniform[2], viewportUniform[3] ]; var time = Date.now(); if (camera2) { mat4$2.copy(matrices.VIEW, camera2.viewMatrix.array); mat4$2.copy(matrices.PROJECTION, camera2.projectionMatrix.array); mat4$2.copy(matrices.VIEWINVERSE, camera2.worldTransform.array); } else { mat4$2.identity(matrices.VIEW); mat4$2.identity(matrices.PROJECTION); mat4$2.identity(matrices.VIEWINVERSE); } mat4$2.multiply(matrices.VIEWPROJECTION, matrices.PROJECTION, matrices.VIEW); mat4$2.invert(matrices.PROJECTIONINVERSE, matrices.PROJECTION); mat4$2.invert(matrices.VIEWPROJECTIONINVERSE, matrices.VIEWPROJECTION); var _gl = this.gl; var scene = this._sceneRendering; var prevMaterial; var prevProgram; var prevRenderable; var depthTest, depthMask; var culling, cullFace, frontFace; var transparent; var drawID; var currentVAO; var materialTakesTextureSlot; var vaoExt = null; for (var i = 0; i < list.length; i++) { var renderable = list[i]; var isSceneNode = renderable.worldTransform != null; var worldM; if (!ifRenderObject(renderable)) { continue; } if (isSceneNode) { worldM = renderable.isSkinnedMesh && renderable.isSkinnedMesh() ? renderable.offsetMatrix ? renderable.offsetMatrix.array : matrices.IDENTITY : renderable.worldTransform.array; } var geometry = renderable.geometry; var material = passConfig.getMaterial.call(this, renderable); var program = renderable.__program; var shader = material.shader; var currentDrawID = geometry.__uid__ + "-" + program.__uid__; var drawIDChanged = currentDrawID !== drawID; drawID = currentDrawID; if (isSceneNode) { mat4$2.copy(matrices.WORLD, worldM); mat4$2.multiply(matrices.WORLDVIEWPROJECTION, matrices.VIEWPROJECTION, worldM); mat4$2.multiplyAffine(matrices.WORLDVIEW, matrices.VIEW, worldM); if (shader.matrixSemantics.WORLDINVERSE || shader.matrixSemantics.WORLDINVERSETRANSPOSE) { mat4$2.invert(matrices.WORLDINVERSE, worldM); } if (shader.matrixSemantics.WORLDVIEWINVERSE || shader.matrixSemantics.WORLDVIEWINVERSETRANSPOSE) { mat4$2.invert(matrices.WORLDVIEWINVERSE, matrices.WORLDVIEW); } if (shader.matrixSemantics.WORLDVIEWPROJECTIONINVERSE || shader.matrixSemantics.WORLDVIEWPROJECTIONINVERSETRANSPOSE) { mat4$2.invert(matrices.WORLDVIEWPROJECTIONINVERSE, matrices.WORLDVIEWPROJECTION); } } renderable.beforeRender && renderable.beforeRender(this); passConfig.beforeRender.call(this, renderable, material, prevMaterial); var programChanged = program !== prevProgram; if (programChanged) { program.bind(this); program.setUniformOfSemantic(_gl, "VIEWPORT", viewportUniform); program.setUniformOfSemantic(_gl, "WINDOW_SIZE", windowSizeUniform); if (camera2) { program.setUniformOfSemantic(_gl, "NEAR", camera2.near); program.setUniformOfSemantic(_gl, "FAR", camera2.far); } program.setUniformOfSemantic(_gl, "DEVICEPIXELRATIO", vDpr); program.setUniformOfSemantic(_gl, "TIME", time); program.setUniformOfSemantic(_gl, "VIEWPORT_SIZE", viewportSizeUniform); if (scene) { scene.setLightUniforms(program, renderable.lightGroup, this); } } else { program = prevProgram; } if (programChanged || passConfig.isMaterialChanged( renderable, prevRenderable, material, prevMaterial )) { if (material.depthTest !== depthTest) { material.depthTest ? _gl.enable(_gl.DEPTH_TEST) : _gl.disable(_gl.DEPTH_TEST); depthTest = material.depthTest; } if (material.depthMask !== depthMask) { _gl.depthMask(material.depthMask); depthMask = material.depthMask; } if (material.transparent !== transparent) { material.transparent ? _gl.enable(_gl.BLEND) : _gl.disable(_gl.BLEND); transparent = material.transparent; } if (material.transparent) { if (material.blend) { material.blend(_gl); } else { _gl.blendEquationSeparate(_gl.FUNC_ADD, _gl.FUNC_ADD); _gl.blendFuncSeparate(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA); } } materialTakesTextureSlot = this._bindMaterial( renderable, material, program, prevRenderable || null, prevMaterial || null, prevProgram || null, passConfig.getUniform ); prevMaterial = material; } var matrixSemanticKeys = shader.matrixSemanticKeys; if (isSceneNode) { for (var k = 0; k < matrixSemanticKeys.length; k++) { var semantic = matrixSemanticKeys[k]; var semanticInfo = shader.matrixSemantics[semantic]; var matrix = matrices[semantic]; if (semanticInfo.isTranspose) { var matrixNoTranspose = matrices[semanticInfo.semanticNoTranspose]; mat4$2.transpose(matrix, matrixNoTranspose); } program.setUniform(_gl, semanticInfo.type, semanticInfo.symbol, matrix); } } if (renderable.cullFace !== cullFace) { cullFace = renderable.cullFace; _gl.cullFace(cullFace); } if (renderable.frontFace !== frontFace) { frontFace = renderable.frontFace; _gl.frontFace(frontFace); } if (renderable.culling !== culling) { culling = renderable.culling; culling ? _gl.enable(_gl.CULL_FACE) : _gl.disable(_gl.CULL_FACE); } this._updateSkeleton(renderable, program, materialTakesTextureSlot); if (drawIDChanged) { currentVAO = this._bindVAO(vaoExt, shader, geometry, program); } this._renderObject(renderable, currentVAO, program); passConfig.afterRender(this, renderable); renderable.afterRender && renderable.afterRender(this); prevProgram = program; prevRenderable = renderable; } this.trigger("afterrenderpass", this, list, camera2, passConfig); }, getMaxJointNumber: function() { return this.maxJointNumber; }, _updateSkeleton: function(object, program, slot) { var _gl = this.gl; var skeleton = object.skeleton; if (skeleton) { skeleton.update(); if (object.joints.length > this.getMaxJointNumber()) { var skinMatricesTexture = skeleton.getSubSkinMatricesTexture(object.__uid__, object.joints); program.useTextureSlot(this, skinMatricesTexture, slot); program.setUniform(_gl, "1i", "skinMatricesTexture", slot); program.setUniform(_gl, "1f", "skinMatricesTextureSize", skinMatricesTexture.width); } else { var skinMatricesArray = skeleton.getSubSkinMatrices(object.__uid__, object.joints); program.setUniformOfSemantic(_gl, "SKIN_MATRIX", skinMatricesArray); } } }, _renderObject: function(renderable, vao, program) { var _gl = this.gl; var geometry = renderable.geometry; var glDrawMode = renderable.mode; if (glDrawMode == null) { glDrawMode = 4; } var ext = null; var isInstanced = renderable.isInstancedMesh && renderable.isInstancedMesh(); if (isInstanced) { ext = this.getGLExtension("ANGLE_instanced_arrays"); if (!ext) { console.warn("Device not support ANGLE_instanced_arrays extension"); return; } } var instancedAttrLocations; if (isInstanced) { instancedAttrLocations = this._bindInstancedAttributes(renderable, program, ext); } if (vao.indicesBuffer) { var uintExt = this.getGLExtension("OES_element_index_uint"); var useUintExt = uintExt && geometry.indices instanceof Uint32Array; var indicesType = useUintExt ? _gl.UNSIGNED_INT : _gl.UNSIGNED_SHORT; if (isInstanced) { ext.drawElementsInstancedANGLE( glDrawMode, vao.indicesBuffer.count, indicesType, 0, renderable.getInstanceCount() ); } else { _gl.drawElements(glDrawMode, vao.indicesBuffer.count, indicesType, 0); } } else { if (isInstanced) { ext.drawArraysInstancedANGLE(glDrawMode, 0, geometry.vertexCount, renderable.getInstanceCount()); } else { _gl.drawArrays(glDrawMode, 0, geometry.vertexCount); } } if (isInstanced) { for (var i = 0; i < instancedAttrLocations.length; i++) { _gl.disableVertexAttribArray(instancedAttrLocations[i]); } } }, _bindInstancedAttributes: function(renderable, program, ext) { var _gl = this.gl; var instancedBuffers = renderable.getInstancedAttributesBuffers(this); var locations = []; for (var i = 0; i < instancedBuffers.length; i++) { var bufferObj = instancedBuffers[i]; var location = program.getAttribLocation(_gl, bufferObj.symbol); if (location < 0) { continue; } var glType = attributeBufferTypeMap[bufferObj.type] || _gl.FLOAT; _gl.enableVertexAttribArray(location); _gl.bindBuffer(_gl.ARRAY_BUFFER, bufferObj.buffer); _gl.vertexAttribPointer(location, bufferObj.size, glType, false, 0, 0); ext.vertexAttribDivisorANGLE(location, bufferObj.divisor); locations.push(location); } return locations; }, _bindMaterial: function(renderable, material, program, prevRenderable, prevMaterial, prevProgram, getUniformValue) { var _gl = this.gl; var sameProgram = prevProgram === program; var currentTextureSlot = program.currentTextureSlot(); var enabledUniforms = material.getEnabledUniforms(); var textureUniforms = material.getTextureUniforms(); var placeholderTexture = this._placeholderTexture; for (var u = 0; u < textureUniforms.length; u++) { var symbol = textureUniforms[u]; var uniformValue = getUniformValue(renderable, material, symbol); var uniformType = material.uniforms[symbol].type; if (uniformType === "t" && uniformValue) { uniformValue.__slot = -1; } else if (uniformType === "tv") { for (var i = 0; i < uniformValue.length; i++) { if (uniformValue[i]) { uniformValue[i].__slot = -1; } } } } placeholderTexture.__slot = -1; for (var u = 0; u < enabledUniforms.length; u++) { var symbol = enabledUniforms[u]; var uniform = material.uniforms[symbol]; var uniformValue = getUniformValue(renderable, material, symbol); var uniformType = uniform.type; var isTexture = uniformType === "t"; if (isTexture) { if (!uniformValue || !uniformValue.isRenderable()) { uniformValue = placeholderTexture; } } if (prevMaterial && sameProgram) { var prevUniformValue = getUniformValue(prevRenderable, prevMaterial, symbol); if (isTexture) { if (!prevUniformValue || !prevUniformValue.isRenderable()) { prevUniformValue = placeholderTexture; } } if (prevUniformValue === uniformValue) { if (isTexture) { program.takeCurrentTextureSlot(this, null); } else if (uniformType === "tv" && uniformValue) { for (var i = 0; i < uniformValue.length; i++) { program.takeCurrentTextureSlot(this, null); } } continue; } } if (uniformValue == null) { continue; } else if (isTexture) { if (uniformValue.__slot < 0) { var slot = program.currentTextureSlot(); var res = program.setUniform(_gl, "1i", symbol, slot); if (res) { program.takeCurrentTextureSlot(this, uniformValue); uniformValue.__slot = slot; } } else { program.setUniform(_gl, "1i", symbol, uniformValue.__slot); } } else if (Array.isArray(uniformValue)) { if (uniformValue.length === 0) { continue; } if (uniformType === "tv") { if (!program.hasUniform(symbol)) { continue; } var arr = []; for (var i = 0; i < uniformValue.length; i++) { var texture = uniformValue[i]; if (texture.__slot < 0) { var slot = program.currentTextureSlot(); arr.push(slot); program.takeCurrentTextureSlot(this, texture); texture.__slot = slot; } else { arr.push(texture.__slot); } } program.setUniform(_gl, "1iv", symbol, arr); } else { program.setUniform(_gl, uniform.type, symbol, uniformValue); } } else { program.setUniform(_gl, uniform.type, symbol, uniformValue); } } var newSlot = program.currentTextureSlot(); program.resetTextureSlot(currentTextureSlot); return newSlot; }, _bindVAO: function(vaoExt, shader, geometry, program) { var isStatic = !geometry.dynamic; var _gl = this.gl; var vaoId = this.__uid__ + "-" + program.__uid__; var vao = geometry.__vaoCache[vaoId]; if (!vao) { var chunks = geometry.getBufferChunks(this); if (!chunks || !chunks.length) { return; } var chunk = chunks[0]; var attributeBuffers = chunk.attributeBuffers; var indicesBuffer = chunk.indicesBuffer; var availableAttributes = []; var availableAttributeSymbols = []; for (var a = 0; a < attributeBuffers.length; a++) { var attributeBufferInfo = attributeBuffers[a]; var name = attributeBufferInfo.name; var semantic = attributeBufferInfo.semantic; var symbol; if (semantic) { var semanticInfo = shader.attributeSemantics[semantic]; symbol = semanticInfo && semanticInfo.symbol; } else { symbol = name; } if (symbol && program.attributes[symbol]) { availableAttributes.push(attributeBufferInfo); availableAttributeSymbols.push(symbol); } } vao = new VertexArrayObject( availableAttributes, availableAttributeSymbols, indicesBuffer ); if (isStatic) { geometry.__vaoCache[vaoId] = vao; } } var needsBindAttributes = true; if (vaoExt && isStatic) { if (vao.vao == null) { vao.vao = vaoExt.createVertexArrayOES(); } else { needsBindAttributes = false; } vaoExt.bindVertexArrayOES(vao.vao); } var availableAttributes = vao.availableAttributes; var indicesBuffer = vao.indicesBuffer; if (needsBindAttributes) { var locationList = program.enableAttributes(this, vao.availableAttributeSymbols, vaoExt && isStatic && vao); for (var a = 0; a < availableAttributes.length; a++) { var location = locationList[a]; if (location === -1) { continue; } var attributeBufferInfo = availableAttributes[a]; var buffer = attributeBufferInfo.buffer; var size = attributeBufferInfo.size; var glType = attributeBufferTypeMap[attributeBufferInfo.type] || _gl.FLOAT; _gl.bindBuffer(_gl.ARRAY_BUFFER, buffer); _gl.vertexAttribPointer(location, size, glType, false, 0, 0); } if (geometry.isUseIndices()) { _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, indicesBuffer.buffer); } } return vao; }, renderPreZ: function(list, scene, camera2) { var _gl = this.gl; var preZPassMaterial = this._prezMaterial || new Material({ shader: new Shader(Shader.source("clay.prez.vertex"), Shader.source("clay.prez.fragment")) }); this._prezMaterial = preZPassMaterial; _gl.colorMask(false, false, false, false); _gl.depthMask(true); this.renderPass(list, camera2, { ifRender: function(renderable) { return !renderable.ignorePreZ; }, isMaterialChanged: function(renderable, prevRenderable) { var matA = renderable.material; var matB = prevRenderable.material; return matA.get("diffuseMap") !== matB.get("diffuseMap") || (matA.get("alphaCutoff") || 0) !== (matB.get("alphaCutoff") || 0); }, getUniform: function(renderable, depthMaterial, symbol) { if (symbol === "alphaMap") { return renderable.material.get("diffuseMap"); } else if (symbol === "alphaCutoff") { if (renderable.material.isDefined("fragment", "ALPHA_TEST") && renderable.material.get("diffuseMap")) { var alphaCutoff = renderable.material.get("alphaCutoff"); return alphaCutoff || 0; } return 0; } else if (symbol === "uvRepeat") { return renderable.material.get("uvRepeat"); } else if (symbol === "uvOffset") { return renderable.material.get("uvOffset"); } else { return depthMaterial.get(symbol); } }, getMaterial: function() { return preZPassMaterial; }, sort: this.opaqueSortCompare }); _gl.colorMask(true, true, true, true); _gl.depthMask(true); }, /** * Dispose given scene, including all geometris, textures and shaders in the scene * @param {clay.Scene} scene */ disposeScene: function(scene) { this.disposeNode(scene, true, true); scene.dispose(); }, /** * Dispose given node, including all geometries, textures and shaders attached on it or its descendant * @param {clay.Node} node * @param {boolean} [disposeGeometry=false] If dispose the geometries used in the descendant mesh * @param {boolean} [disposeTexture=false] If dispose the textures used in the descendant mesh */ disposeNode: function(root, disposeGeometry, disposeTexture) { if (root.getParent()) { root.getParent().remove(root); } var disposedMap = {}; root.traverse(function(node) { var material = node.material; if (node.geometry && disposeGeometry) { node.geometry.dispose(this); } if (disposeTexture && material && !disposedMap[material.__uid__]) { var textureUniforms = material.getTextureUniforms(); for (var u = 0; u < textureUniforms.length; u++) { var uniformName = textureUniforms[u]; var val = material.uniforms[uniformName].value; var uniformType = material.uniforms[uniformName].type; if (!val) { continue; } if (uniformType === "t") { val.dispose && val.dispose(this); } else if (uniformType === "tv") { for (var k = 0; k < val.length; k++) { if (val[k]) { val[k].dispose && val[k].dispose(this); } } } } disposedMap[material.__uid__] = true; } if (node.dispose) { node.dispose(this); } }, this); }, /** * Dispose given geometry * @param {clay.Geometry} geometry */ disposeGeometry: function(geometry) { geometry.dispose(this); }, /** * Dispose given texture * @param {clay.Texture} texture */ disposeTexture: function(texture) { texture.dispose(this); }, /** * Dispose given frame buffer * @param {clay.FrameBuffer} frameBuffer */ disposeFrameBuffer: function(frameBuffer) { frameBuffer.dispose(this); }, /** * Dispose renderer */ dispose: function() { }, /** * Convert screen coords to normalized device coordinates(NDC) * Screen coords can get from mouse event, it is positioned relative to canvas element * NDC can be used in ray casting with Camera.prototype.castRay methods * * @param {number} x * @param {number} y * @param {clay.Vector2} [out] * @return {clay.Vector2} */ screenToNDC: function(x, y, out) { if (!out) { out = new Vector2(); } y = this._height - y; var viewport = this.viewport; var arr = out.array; arr[0] = (x - viewport.x) / viewport.width; arr[0] = arr[0] * 2 - 1; arr[1] = (y - viewport.y) / viewport.height; arr[1] = arr[1] * 2 - 1; return out; } } ); Renderer.opaqueSortCompare = Renderer.prototype.opaqueSortCompare = function(x, y) { if (x.renderOrder === y.renderOrder) { if (x.__program === y.__program) { if (x.material === y.material) { return x.geometry.__uid__ - y.geometry.__uid__; } return x.material.__uid__ - y.material.__uid__; } if (x.__program && y.__program) { return x.__program.__uid__ - y.__program.__uid__; } return 0; } return x.renderOrder - y.renderOrder; }; Renderer.transparentSortCompare = Renderer.prototype.transparentSortCompare = function(x, y) { if (x.renderOrder === y.renderOrder) { if (x.__depth === y.__depth) { if (x.__program === y.__program) { if (x.material === y.material) { return x.geometry.__uid__ - y.geometry.__uid__; } return x.material.__uid__ - y.material.__uid__; } if (x.__program && y.__program) { return x.__program.__uid__ - y.__program.__uid__; } return 0; } return x.__depth - y.__depth; } return x.renderOrder - y.renderOrder; }; var matrices = { IDENTITY: mat4Create(), WORLD: mat4Create(), VIEW: mat4Create(), PROJECTION: mat4Create(), WORLDVIEW: mat4Create(), VIEWPROJECTION: mat4Create(), WORLDVIEWPROJECTION: mat4Create(), WORLDINVERSE: mat4Create(), VIEWINVERSE: mat4Create(), PROJECTIONINVERSE: mat4Create(), WORLDVIEWINVERSE: mat4Create(), VIEWPROJECTIONINVERSE: mat4Create(), WORLDVIEWPROJECTIONINVERSE: mat4Create(), WORLDTRANSPOSE: mat4Create(), VIEWTRANSPOSE: mat4Create(), PROJECTIONTRANSPOSE: mat4Create(), WORLDVIEWTRANSPOSE: mat4Create(), VIEWPROJECTIONTRANSPOSE: mat4Create(), WORLDVIEWPROJECTIONTRANSPOSE: mat4Create(), WORLDINVERSETRANSPOSE: mat4Create(), VIEWINVERSETRANSPOSE: mat4Create(), PROJECTIONINVERSETRANSPOSE: mat4Create(), WORLDVIEWINVERSETRANSPOSE: mat4Create(), VIEWPROJECTIONINVERSETRANSPOSE: mat4Create(), WORLDVIEWPROJECTIONINVERSETRANSPOSE: mat4Create() }; Renderer.COLOR_BUFFER_BIT = glenum.COLOR_BUFFER_BIT; Renderer.DEPTH_BUFFER_BIT = glenum.DEPTH_BUFFER_BIT; Renderer.STENCIL_BUFFER_BIT = glenum.STENCIL_BUFFER_BIT; var Vector3 = function(x, y, z) { x = x || 0; y = y || 0; z = z || 0; this.array = vec3$f.fromValues(x, y, z); this._dirty = true; }; Vector3.prototype = { constructor: Vector3, /** * Add b to self * @param {clay.Vector3} b * @return {clay.Vector3} */ add: function(b) { vec3$f.add(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Set x, y and z components * @param {number} x * @param {number} y * @param {number} z * @return {clay.Vector3} */ set: function(x, y, z) { this.array[0] = x; this.array[1] = y; this.array[2] = z; this._dirty = true; return this; }, /** * Set x, y and z components from array * @param {Float32Array|number[]} arr * @return {clay.Vector3} */ setArray: function(arr) { this.array[0] = arr[0]; this.array[1] = arr[1]; this.array[2] = arr[2]; this._dirty = true; return this; }, /** * Clone a new Vector3 * @return {clay.Vector3} */ clone: function() { return new Vector3(this.x, this.y, this.z); }, /** * Copy from b * @param {clay.Vector3} b * @return {clay.Vector3} */ copy: function(b) { vec3$f.copy(this.array, b.array); this._dirty = true; return this; }, /** * Cross product of self and b, written to a Vector3 out * @param {clay.Vector3} a * @param {clay.Vector3} b * @return {clay.Vector3} */ cross: function(a, b) { vec3$f.cross(this.array, a.array, b.array); this._dirty = true; return this; }, /** * Alias for distance * @param {clay.Vector3} b * @return {number} */ dist: function(b) { return vec3$f.dist(this.array, b.array); }, /** * Distance between self and b * @param {clay.Vector3} b * @return {number} */ distance: function(b) { return vec3$f.distance(this.array, b.array); }, /** * Alias for divide * @param {clay.Vector3} b * @return {clay.Vector3} */ div: function(b) { vec3$f.div(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Divide self by b * @param {clay.Vector3} b * @return {clay.Vector3} */ divide: function(b) { vec3$f.divide(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Dot product of self and b * @param {clay.Vector3} b * @return {number} */ dot: function(b) { return vec3$f.dot(this.array, b.array); }, /** * Alias of length * @return {number} */ len: function() { return vec3$f.len(this.array); }, /** * Calculate the length * @return {number} */ length: function() { return vec3$f.length(this.array); }, /** * Linear interpolation between a and b * @param {clay.Vector3} a * @param {clay.Vector3} b * @param {number} t * @return {clay.Vector3} */ lerp: function(a, b, t) { vec3$f.lerp(this.array, a.array, b.array, t); this._dirty = true; return this; }, /** * Minimum of self and b * @param {clay.Vector3} b * @return {clay.Vector3} */ min: function(b) { vec3$f.min(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Maximum of self and b * @param {clay.Vector3} b * @return {clay.Vector3} */ max: function(b) { vec3$f.max(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiply * @param {clay.Vector3} b * @return {clay.Vector3} */ mul: function(b) { vec3$f.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Mutiply self and b * @param {clay.Vector3} b * @return {clay.Vector3} */ multiply: function(b) { vec3$f.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Negate self * @return {clay.Vector3} */ negate: function() { vec3$f.negate(this.array, this.array); this._dirty = true; return this; }, /** * Normalize self * @return {clay.Vector3} */ normalize: function() { vec3$f.normalize(this.array, this.array); this._dirty = true; return this; }, /** * Generate random x, y, z components with a given scale * @param {number} scale * @return {clay.Vector3} */ random: function(scale) { vec3$f.random(this.array, scale); this._dirty = true; return this; }, /** * Scale self * @param {number} scale * @return {clay.Vector3} */ scale: function(s) { vec3$f.scale(this.array, this.array, s); this._dirty = true; return this; }, /** * Scale b and add to self * @param {clay.Vector3} b * @param {number} scale * @return {clay.Vector3} */ scaleAndAdd: function(b, s) { vec3$f.scaleAndAdd(this.array, this.array, b.array, s); this._dirty = true; return this; }, /** * Alias for squaredDistance * @param {clay.Vector3} b * @return {number} */ sqrDist: function(b) { return vec3$f.sqrDist(this.array, b.array); }, /** * Squared distance between self and b * @param {clay.Vector3} b * @return {number} */ squaredDistance: function(b) { return vec3$f.squaredDistance(this.array, b.array); }, /** * Alias for squaredLength * @return {number} */ sqrLen: function() { return vec3$f.sqrLen(this.array); }, /** * Squared length of self * @return {number} */ squaredLength: function() { return vec3$f.squaredLength(this.array); }, /** * Alias for subtract * @param {clay.Vector3} b * @return {clay.Vector3} */ sub: function(b) { vec3$f.sub(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Subtract b from self * @param {clay.Vector3} b * @return {clay.Vector3} */ subtract: function(b) { vec3$f.subtract(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Transform self with a Matrix3 m * @param {clay.Matrix3} m * @return {clay.Vector3} */ transformMat3: function(m) { vec3$f.transformMat3(this.array, this.array, m.array); this._dirty = true; return this; }, /** * Transform self with a Matrix4 m * @param {clay.Matrix4} m * @return {clay.Vector3} */ transformMat4: function(m) { vec3$f.transformMat4(this.array, this.array, m.array); this._dirty = true; return this; }, /** * Transform self with a Quaternion q * @param {clay.Quaternion} q * @return {clay.Vector3} */ transformQuat: function(q) { vec3$f.transformQuat(this.array, this.array, q.array); this._dirty = true; return this; }, /** * Trasnform self into projection space with m * @param {clay.Matrix4} m * @return {clay.Vector3} */ applyProjection: function(m) { var v = this.array; m = m.array; if (m[15] === 0) { var w = -1 / v[2]; v[0] = m[0] * v[0] * w; v[1] = m[5] * v[1] * w; v[2] = (m[10] * v[2] + m[14]) * w; } else { v[0] = m[0] * v[0] + m[12]; v[1] = m[5] * v[1] + m[13]; v[2] = m[10] * v[2] + m[14]; } this._dirty = true; return this; }, eulerFromQuat: function(q, order) { Vector3.eulerFromQuat(this, q, order); }, eulerFromMat3: function(m, order) { Vector3.eulerFromMat3(this, m, order); }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; var defineProperty$3 = Object.defineProperty; if (defineProperty$3) { var proto$3 = Vector3.prototype; defineProperty$3(proto$3, "x", { get: function() { return this.array[0]; }, set: function(value) { this.array[0] = value; this._dirty = true; } }); defineProperty$3(proto$3, "y", { get: function() { return this.array[1]; }, set: function(value) { this.array[1] = value; this._dirty = true; } }); defineProperty$3(proto$3, "z", { get: function() { return this.array[2]; }, set: function(value) { this.array[2] = value; this._dirty = true; } }); } Vector3.add = function(out, a, b) { vec3$f.add(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.set = function(out, x, y, z) { vec3$f.set(out.array, x, y, z); out._dirty = true; }; Vector3.copy = function(out, b) { vec3$f.copy(out.array, b.array); out._dirty = true; return out; }; Vector3.cross = function(out, a, b) { vec3$f.cross(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.dist = function(a, b) { return vec3$f.distance(a.array, b.array); }; Vector3.distance = Vector3.dist; Vector3.div = function(out, a, b) { vec3$f.divide(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.divide = Vector3.div; Vector3.dot = function(a, b) { return vec3$f.dot(a.array, b.array); }; Vector3.len = function(b) { return vec3$f.length(b.array); }; Vector3.lerp = function(out, a, b, t) { vec3$f.lerp(out.array, a.array, b.array, t); out._dirty = true; return out; }; Vector3.min = function(out, a, b) { vec3$f.min(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.max = function(out, a, b) { vec3$f.max(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.mul = function(out, a, b) { vec3$f.multiply(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.multiply = Vector3.mul; Vector3.negate = function(out, a) { vec3$f.negate(out.array, a.array); out._dirty = true; return out; }; Vector3.normalize = function(out, a) { vec3$f.normalize(out.array, a.array); out._dirty = true; return out; }; Vector3.random = function(out, scale) { vec3$f.random(out.array, scale); out._dirty = true; return out; }; Vector3.scale = function(out, a, scale) { vec3$f.scale(out.array, a.array, scale); out._dirty = true; return out; }; Vector3.scaleAndAdd = function(out, a, b, scale) { vec3$f.scaleAndAdd(out.array, a.array, b.array, scale); out._dirty = true; return out; }; Vector3.sqrDist = function(a, b) { return vec3$f.sqrDist(a.array, b.array); }; Vector3.squaredDistance = Vector3.sqrDist; Vector3.sqrLen = function(a) { return vec3$f.sqrLen(a.array); }; Vector3.squaredLength = Vector3.sqrLen; Vector3.sub = function(out, a, b) { vec3$f.subtract(out.array, a.array, b.array); out._dirty = true; return out; }; Vector3.subtract = Vector3.sub; Vector3.transformMat3 = function(out, a, m) { vec3$f.transformMat3(out.array, a.array, m.array); out._dirty = true; return out; }; Vector3.transformMat4 = function(out, a, m) { vec3$f.transformMat4(out.array, a.array, m.array); out._dirty = true; return out; }; Vector3.transformQuat = function(out, a, q) { vec3$f.transformQuat(out.array, a.array, q.array); out._dirty = true; return out; }; function clamp(val, min, max) { return val < min ? min : val > max ? max : val; } var atan2 = Math.atan2; var asin$1 = Math.asin; var abs = Math.abs; Vector3.eulerFromQuat = function(out, q, order) { out._dirty = true; q = q.array; var target = out.array; var x = q[0], y = q[1], z = q[2], w = q[3]; var x2 = x * x; var y2 = y * y; var z2 = z * z; var w2 = w * w; var order = (order || "XYZ").toUpperCase(); switch (order) { case "XYZ": target[0] = atan2(2 * (x * w - y * z), w2 - x2 - y2 + z2); target[1] = asin$1(clamp(2 * (x * z + y * w), -1, 1)); target[2] = atan2(2 * (z * w - x * y), w2 + x2 - y2 - z2); break; case "YXZ": target[0] = asin$1(clamp(2 * (x * w - y * z), -1, 1)); target[1] = atan2(2 * (x * z + y * w), w2 - x2 - y2 + z2); target[2] = atan2(2 * (x * y + z * w), w2 - x2 + y2 - z2); break; case "ZXY": target[0] = asin$1(clamp(2 * (x * w + y * z), -1, 1)); target[1] = atan2(2 * (y * w - z * x), w2 - x2 - y2 + z2); target[2] = atan2(2 * (z * w - x * y), w2 - x2 + y2 - z2); break; case "ZYX": target[0] = atan2(2 * (x * w + z * y), w2 - x2 - y2 + z2); target[1] = asin$1(clamp(2 * (y * w - x * z), -1, 1)); target[2] = atan2(2 * (x * y + z * w), w2 + x2 - y2 - z2); break; case "YZX": target[0] = atan2(2 * (x * w - z * y), w2 - x2 + y2 - z2); target[1] = atan2(2 * (y * w - x * z), w2 + x2 - y2 - z2); target[2] = asin$1(clamp(2 * (x * y + z * w), -1, 1)); break; case "XZY": target[0] = atan2(2 * (x * w + y * z), w2 - x2 + y2 - z2); target[1] = atan2(2 * (x * z + y * w), w2 + x2 - y2 - z2); target[2] = asin$1(clamp(2 * (z * w - x * y), -1, 1)); break; default: console.warn("Unkown order: " + order); } return out; }; Vector3.eulerFromMat3 = function(out, m, order) { var te = m.array; var m11 = te[0], m12 = te[3], m13 = te[6]; var m21 = te[1], m22 = te[4], m23 = te[7]; var m31 = te[2], m32 = te[5], m33 = te[8]; var target = out.array; var order = (order || "XYZ").toUpperCase(); switch (order) { case "XYZ": target[1] = asin$1(clamp(m13, -1, 1)); if (abs(m13) < 0.99999) { target[0] = atan2(-m23, m33); target[2] = atan2(-m12, m11); } else { target[0] = atan2(m32, m22); target[2] = 0; } break; case "YXZ": target[0] = asin$1(-clamp(m23, -1, 1)); if (abs(m23) < 0.99999) { target[1] = atan2(m13, m33); target[2] = atan2(m21, m22); } else { target[1] = atan2(-m31, m11); target[2] = 0; } break; case "ZXY": target[0] = asin$1(clamp(m32, -1, 1)); if (abs(m32) < 0.99999) { target[1] = atan2(-m31, m33); target[2] = atan2(-m12, m22); } else { target[1] = 0; target[2] = atan2(m21, m11); } break; case "ZYX": target[1] = asin$1(-clamp(m31, -1, 1)); if (abs(m31) < 0.99999) { target[0] = atan2(m32, m33); target[2] = atan2(m21, m11); } else { target[0] = 0; target[2] = atan2(-m12, m22); } break; case "YZX": target[2] = asin$1(clamp(m21, -1, 1)); if (abs(m21) < 0.99999) { target[0] = atan2(-m23, m22); target[1] = atan2(-m31, m11); } else { target[0] = 0; target[1] = atan2(m13, m33); } break; case "XZY": target[2] = asin$1(-clamp(m12, -1, 1)); if (abs(m12) < 0.99999) { target[0] = atan2(m32, m22); target[1] = atan2(m13, m11); } else { target[0] = atan2(-m23, m33); target[1] = 0; } break; default: console.warn("Unkown order: " + order); } out._dirty = true; return out; }; Object.defineProperties(Vector3, { /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ POSITIVE_X: { get: function() { return new Vector3(1, 0, 0); } }, /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ NEGATIVE_X: { get: function() { return new Vector3(-1, 0, 0); } }, /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ POSITIVE_Y: { get: function() { return new Vector3(0, 1, 0); } }, /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ NEGATIVE_Y: { get: function() { return new Vector3(0, -1, 0); } }, /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ POSITIVE_Z: { get: function() { return new Vector3(0, 0, 1); } }, /** * @type {clay.Vector3} * @readOnly */ NEGATIVE_Z: { get: function() { return new Vector3(0, 0, -1); } }, /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ UP: { get: function() { return new Vector3(0, 1, 0); } }, /** * @type {clay.Vector3} * @readOnly * @memberOf clay.Vector3 */ ZERO: { get: function() { return new Vector3(); } } }); var EPSILON$1 = 1e-5; var Ray = function(origin, direction) { this.origin = origin || new Vector3(); this.direction = direction || new Vector3(); }; Ray.prototype = { constructor: Ray, // http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm /** * Calculate intersection point between ray and a give plane * @param {clay.Plane} plane * @param {clay.Vector3} [out] * @return {clay.Vector3} */ intersectPlane: function(plane, out) { var pn = plane.normal.array; var d = plane.distance; var ro = this.origin.array; var rd = this.direction.array; var divider = vec3$f.dot(pn, rd); if (divider === 0) { return null; } if (!out) { out = new Vector3(); } var t = (vec3$f.dot(pn, ro) - d) / divider; vec3$f.scaleAndAdd(out.array, ro, rd, -t); out._dirty = true; return out; }, /** * Mirror the ray against plane * @param {clay.Plane} plane */ mirrorAgainstPlane: function(plane) { var d = vec3$f.dot(plane.normal.array, this.direction.array); vec3$f.scaleAndAdd(this.direction.array, this.direction.array, plane.normal.array, -d * 2); this.direction._dirty = true; }, distanceToPoint: function() { var v = vec3$f.create(); return function(point) { vec3$f.sub(v, point, this.origin.array); var b = vec3$f.dot(v, this.direction.array); if (b < 0) { return vec3$f.distance(this.origin.array, point); } var c2 = vec3$f.lenSquared(v); return Math.sqrt(c2 - b * b); }; }(), /** * Calculate intersection point between ray and sphere * @param {clay.Vector3} center * @param {number} radius * @param {clay.Vector3} out * @return {clay.Vector3} */ intersectSphere: function() { var v = vec3$f.create(); return function(center, radius, out) { var origin = this.origin.array; var direction = this.direction.array; center = center.array; vec3$f.sub(v, center, origin); var b = vec3$f.dot(v, direction); var c2 = vec3$f.squaredLength(v); var d2 = c2 - b * b; var r2 = radius * radius; if (d2 > r2) { return; } var a = Math.sqrt(r2 - d2); var t0 = b - a; var t1 = b + a; if (!out) { out = new Vector3(); } if (t0 < 0) { if (t1 < 0) { return null; } else { vec3$f.scaleAndAdd(out.array, origin, direction, t1); return out; } } else { vec3$f.scaleAndAdd(out.array, origin, direction, t0); return out; } }; }(), // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/ /** * Calculate intersection point between ray and bounding box * @param {clay.BoundingBox} bbox * @param {clay.Vector3} * @return {clay.Vector3} */ intersectBoundingBox: function(bbox, out) { var dir = this.direction.array; var origin = this.origin.array; var min = bbox.min.array; var max = bbox.max.array; var invdirx = 1 / dir[0]; var invdiry = 1 / dir[1]; var invdirz = 1 / dir[2]; var tmin, tmax, tymin, tymax, tzmin, tzmax; if (invdirx >= 0) { tmin = (min[0] - origin[0]) * invdirx; tmax = (max[0] - origin[0]) * invdirx; } else { tmax = (min[0] - origin[0]) * invdirx; tmin = (max[0] - origin[0]) * invdirx; } if (invdiry >= 0) { tymin = (min[1] - origin[1]) * invdiry; tymax = (max[1] - origin[1]) * invdiry; } else { tymax = (min[1] - origin[1]) * invdiry; tymin = (max[1] - origin[1]) * invdiry; } if (tmin > tymax || tymin > tmax) { return null; } if (tymin > tmin || tmin !== tmin) { tmin = tymin; } if (tymax < tmax || tmax !== tmax) { tmax = tymax; } if (invdirz >= 0) { tzmin = (min[2] - origin[2]) * invdirz; tzmax = (max[2] - origin[2]) * invdirz; } else { tzmax = (min[2] - origin[2]) * invdirz; tzmin = (max[2] - origin[2]) * invdirz; } if (tmin > tzmax || tzmin > tmax) { return null; } if (tzmin > tmin || tmin !== tmin) { tmin = tzmin; } if (tzmax < tmax || tmax !== tmax) { tmax = tzmax; } if (tmax < 0) { return null; } var t = tmin >= 0 ? tmin : tmax; if (!out) { out = new Vector3(); } vec3$f.scaleAndAdd(out.array, origin, dir, t); return out; }, // http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm /** * Calculate intersection point between ray and three triangle vertices * @param {clay.Vector3} a * @param {clay.Vector3} b * @param {clay.Vector3} c * @param {boolean} singleSided, CW triangle will be ignored * @param {clay.Vector3} [out] * @param {clay.Vector3} [barycenteric] barycentric coords * @return {clay.Vector3} */ intersectTriangle: function() { var eBA = vec3$f.create(); var eCA = vec3$f.create(); var AO = vec3$f.create(); var vCross = vec3$f.create(); return function(a, b, c, singleSided, out, barycenteric) { var dir = this.direction.array; var origin = this.origin.array; a = a.array; b = b.array; c = c.array; vec3$f.sub(eBA, b, a); vec3$f.sub(eCA, c, a); vec3$f.cross(vCross, eCA, dir); var det = vec3$f.dot(eBA, vCross); if (singleSided) { if (det > -EPSILON$1) { return null; } } else { if (det > -EPSILON$1 && det < EPSILON$1) { return null; } } vec3$f.sub(AO, origin, a); var u = vec3$f.dot(vCross, AO) / det; if (u < 0 || u > 1) { return null; } vec3$f.cross(vCross, eBA, AO); var v = vec3$f.dot(dir, vCross) / det; if (v < 0 || v > 1 || u + v > 1) { return null; } vec3$f.cross(vCross, eBA, eCA); var t = -vec3$f.dot(AO, vCross) / det; if (t < 0) { return null; } if (!out) { out = new Vector3(); } if (barycenteric) { Vector3.set(barycenteric, 1 - u - v, u, v); } vec3$f.scaleAndAdd(out.array, origin, dir, t); return out; }; }(), /** * Apply an affine transform matrix to the ray * @return {clay.Matrix4} matrix */ applyTransform: function(matrix) { Vector3.add(this.direction, this.direction, this.origin); Vector3.transformMat4(this.origin, this.origin, matrix); Vector3.transformMat4(this.direction, this.direction, matrix); Vector3.sub(this.direction, this.direction, this.origin); Vector3.normalize(this.direction, this.direction); }, /** * Copy values from another ray * @param {clay.Ray} ray */ copy: function(ray) { Vector3.copy(this.origin, ray.origin); Vector3.copy(this.direction, ray.direction); }, /** * Clone a new ray * @return {clay.Ray} */ clone: function() { var ray = new Ray(); ray.copy(this); return ray; } }; var vec4$1 = {}; vec4$1.create = function() { var out = new GLMAT_ARRAY_TYPE(4); out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; return out; }; vec4$1.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(4); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; }; vec4$1.fromValues = function(x, y, z, w) { var out = new GLMAT_ARRAY_TYPE(4); out[0] = x; out[1] = y; out[2] = z; out[3] = w; return out; }; vec4$1.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; }; vec4$1.set = function(out, x, y, z, w) { out[0] = x; out[1] = y; out[2] = z; out[3] = w; return out; }; vec4$1.add = function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; out[2] = a[2] + b[2]; out[3] = a[3] + b[3]; return out; }; vec4$1.subtract = function(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; out[2] = a[2] - b[2]; out[3] = a[3] - b[3]; return out; }; vec4$1.sub = vec4$1.subtract; vec4$1.multiply = function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; out[2] = a[2] * b[2]; out[3] = a[3] * b[3]; return out; }; vec4$1.mul = vec4$1.multiply; vec4$1.divide = function(out, a, b) { out[0] = a[0] / b[0]; out[1] = a[1] / b[1]; out[2] = a[2] / b[2]; out[3] = a[3] / b[3]; return out; }; vec4$1.div = vec4$1.divide; vec4$1.min = function(out, a, b) { out[0] = Math.min(a[0], b[0]); out[1] = Math.min(a[1], b[1]); out[2] = Math.min(a[2], b[2]); out[3] = Math.min(a[3], b[3]); return out; }; vec4$1.max = function(out, a, b) { out[0] = Math.max(a[0], b[0]); out[1] = Math.max(a[1], b[1]); out[2] = Math.max(a[2], b[2]); out[3] = Math.max(a[3], b[3]); return out; }; vec4$1.scale = function(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; out[2] = a[2] * b; out[3] = a[3] * b; return out; }; vec4$1.scaleAndAdd = function(out, a, b, scale) { out[0] = a[0] + b[0] * scale; out[1] = a[1] + b[1] * scale; out[2] = a[2] + b[2] * scale; out[3] = a[3] + b[3] * scale; return out; }; vec4$1.distance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1], z = b[2] - a[2], w = b[3] - a[3]; return Math.sqrt(x * x + y * y + z * z + w * w); }; vec4$1.dist = vec4$1.distance; vec4$1.squaredDistance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1], z = b[2] - a[2], w = b[3] - a[3]; return x * x + y * y + z * z + w * w; }; vec4$1.sqrDist = vec4$1.squaredDistance; vec4$1.length = function(a) { var x = a[0], y = a[1], z = a[2], w = a[3]; return Math.sqrt(x * x + y * y + z * z + w * w); }; vec4$1.len = vec4$1.length; vec4$1.squaredLength = function(a) { var x = a[0], y = a[1], z = a[2], w = a[3]; return x * x + y * y + z * z + w * w; }; vec4$1.sqrLen = vec4$1.squaredLength; vec4$1.negate = function(out, a) { out[0] = -a[0]; out[1] = -a[1]; out[2] = -a[2]; out[3] = -a[3]; return out; }; vec4$1.inverse = function(out, a) { out[0] = 1 / a[0]; out[1] = 1 / a[1]; out[2] = 1 / a[2]; out[3] = 1 / a[3]; return out; }; vec4$1.normalize = function(out, a) { var x = a[0], y = a[1], z = a[2], w = a[3]; var len = x * x + y * y + z * z + w * w; if (len > 0) { len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; out[2] = a[2] * len; out[3] = a[3] * len; } return out; }; vec4$1.dot = function(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; }; vec4$1.lerp = function(out, a, b, t) { var ax = a[0], ay = a[1], az = a[2], aw = a[3]; out[0] = ax + t * (b[0] - ax); out[1] = ay + t * (b[1] - ay); out[2] = az + t * (b[2] - az); out[3] = aw + t * (b[3] - aw); return out; }; vec4$1.random = function(out, scale) { scale = scale || 1; out[0] = GLMAT_RANDOM$1(); out[1] = GLMAT_RANDOM$1(); out[2] = GLMAT_RANDOM$1(); out[3] = GLMAT_RANDOM$1(); vec4$1.normalize(out, out); vec4$1.scale(out, out, scale); return out; }; vec4$1.transformMat4 = function(out, a, m) { var x = a[0], y = a[1], z = a[2], w = a[3]; out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; return out; }; vec4$1.transformQuat = function(out, a, q) { var x = a[0], y = a[1], z = a[2], qx = q[0], qy = q[1], qz = q[2], qw = q[3], ix = qw * x + qy * z - qz * y, iy = qw * y + qz * x - qx * z, iz = qw * z + qx * y - qy * x, iw = -qx * x - qy * y - qz * z; out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy; out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz; out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx; return out; }; vec4$1.forEach = function() { var vec = vec4$1.create(); return function(a, stride, offset, count, fn, arg) { var i, l; if (!stride) { stride = 4; } if (!offset) { offset = 0; } if (count) { l = Math.min(count * stride + offset, a.length); } else { l = a.length; } for (i = offset; i < l; i += stride) { vec[0] = a[i]; vec[1] = a[i + 1]; vec[2] = a[i + 2]; vec[3] = a[i + 3]; fn(vec, vec, arg); a[i] = vec[0]; a[i + 1] = vec[1]; a[i + 2] = vec[2]; a[i + 3] = vec[3]; } return a; }; }(); var mat3$1 = {}; mat3$1.create = function() { var out = new GLMAT_ARRAY_TYPE(9); out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 1; out[5] = 0; out[6] = 0; out[7] = 0; out[8] = 1; return out; }; mat3$1.fromMat4 = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[4]; out[4] = a[5]; out[5] = a[6]; out[6] = a[8]; out[7] = a[9]; out[8] = a[10]; return out; }; mat3$1.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(9); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; return out; }; mat3$1.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; return out; }; mat3$1.identity = function(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 1; out[5] = 0; out[6] = 0; out[7] = 0; out[8] = 1; return out; }; mat3$1.transpose = function(out, a) { if (out === a) { var a01 = a[1], a02 = a[2], a12 = a[5]; out[1] = a[3]; out[2] = a[6]; out[3] = a01; out[5] = a[7]; out[6] = a02; out[7] = a12; } else { out[0] = a[0]; out[1] = a[3]; out[2] = a[6]; out[3] = a[1]; out[4] = a[4]; out[5] = a[7]; out[6] = a[2]; out[7] = a[5]; out[8] = a[8]; } return out; }; mat3$1.invert = function(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[3], a11 = a[4], a12 = a[5], a20 = a[6], a21 = a[7], a22 = a[8], b01 = a22 * a11 - a12 * a21, b11 = -a22 * a10 + a12 * a20, b21 = a21 * a10 - a11 * a20, det = a00 * b01 + a01 * b11 + a02 * b21; if (!det) { return null; } det = 1 / det; out[0] = b01 * det; out[1] = (-a22 * a01 + a02 * a21) * det; out[2] = (a12 * a01 - a02 * a11) * det; out[3] = b11 * det; out[4] = (a22 * a00 - a02 * a20) * det; out[5] = (-a12 * a00 + a02 * a10) * det; out[6] = b21 * det; out[7] = (-a21 * a00 + a01 * a20) * det; out[8] = (a11 * a00 - a01 * a10) * det; return out; }; mat3$1.adjoint = function(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[3], a11 = a[4], a12 = a[5], a20 = a[6], a21 = a[7], a22 = a[8]; out[0] = a11 * a22 - a12 * a21; out[1] = a02 * a21 - a01 * a22; out[2] = a01 * a12 - a02 * a11; out[3] = a12 * a20 - a10 * a22; out[4] = a00 * a22 - a02 * a20; out[5] = a02 * a10 - a00 * a12; out[6] = a10 * a21 - a11 * a20; out[7] = a01 * a20 - a00 * a21; out[8] = a00 * a11 - a01 * a10; return out; }; mat3$1.determinant = function(a) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[3], a11 = a[4], a12 = a[5], a20 = a[6], a21 = a[7], a22 = a[8]; return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20); }; mat3$1.multiply = function(out, a, b) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[3], a11 = a[4], a12 = a[5], a20 = a[6], a21 = a[7], a22 = a[8], b00 = b[0], b01 = b[1], b02 = b[2], b10 = b[3], b11 = b[4], b12 = b[5], b20 = b[6], b21 = b[7], b22 = b[8]; out[0] = b00 * a00 + b01 * a10 + b02 * a20; out[1] = b00 * a01 + b01 * a11 + b02 * a21; out[2] = b00 * a02 + b01 * a12 + b02 * a22; out[3] = b10 * a00 + b11 * a10 + b12 * a20; out[4] = b10 * a01 + b11 * a11 + b12 * a21; out[5] = b10 * a02 + b11 * a12 + b12 * a22; out[6] = b20 * a00 + b21 * a10 + b22 * a20; out[7] = b20 * a01 + b21 * a11 + b22 * a21; out[8] = b20 * a02 + b21 * a12 + b22 * a22; return out; }; mat3$1.mul = mat3$1.multiply; mat3$1.translate = function(out, a, v) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[3], a11 = a[4], a12 = a[5], a20 = a[6], a21 = a[7], a22 = a[8], x = v[0], y = v[1]; out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a10; out[4] = a11; out[5] = a12; out[6] = x * a00 + y * a10 + a20; out[7] = x * a01 + y * a11 + a21; out[8] = x * a02 + y * a12 + a22; return out; }; mat3$1.rotate = function(out, a, rad2) { var a00 = a[0], a01 = a[1], a02 = a[2], a10 = a[3], a11 = a[4], a12 = a[5], a20 = a[6], a21 = a[7], a22 = a[8], s = Math.sin(rad2), c = Math.cos(rad2); out[0] = c * a00 + s * a10; out[1] = c * a01 + s * a11; out[2] = c * a02 + s * a12; out[3] = c * a10 - s * a00; out[4] = c * a11 - s * a01; out[5] = c * a12 - s * a02; out[6] = a20; out[7] = a21; out[8] = a22; return out; }; mat3$1.scale = function(out, a, v) { var x = v[0], y = v[1]; out[0] = x * a[0]; out[1] = x * a[1]; out[2] = x * a[2]; out[3] = y * a[3]; out[4] = y * a[4]; out[5] = y * a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; return out; }; mat3$1.fromMat2d = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = 0; out[3] = a[2]; out[4] = a[3]; out[5] = 0; out[6] = a[4]; out[7] = a[5]; out[8] = 1; return out; }; mat3$1.fromQuat = function(out, q) { var x = q[0], y = q[1], z = q[2], w = q[3], x2 = x + x, y2 = y + y, z2 = z + z, xx = x * x2, yx = y * x2, yy = y * y2, zx = z * x2, zy = z * y2, zz = z * z2, wx2 = w * x2, wy = w * y2, wz = w * z2; out[0] = 1 - yy - zz; out[3] = yx - wz; out[6] = zx + wy; out[1] = yx + wz; out[4] = 1 - xx - zz; out[7] = zy - wx2; out[2] = zx - wy; out[5] = zy + wx2; out[8] = 1 - xx - yy; return out; }; mat3$1.normalFromMat4 = function(out, a) { var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], b00 = a00 * a11 - a01 * a10, b01 = a00 * a12 - a02 * a10, b02 = a00 * a13 - a03 * a10, b03 = a01 * a12 - a02 * a11, b04 = a01 * a13 - a03 * a11, b05 = a02 * a13 - a03 * a12, b06 = a20 * a31 - a21 * a30, b07 = a20 * a32 - a22 * a30, b08 = a20 * a33 - a23 * a30, b09 = a21 * a32 - a22 * a31, b10 = a21 * a33 - a23 * a31, b11 = a22 * a33 - a23 * a32, det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1 / det; out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det; out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det; out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det; out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det; out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det; out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det; out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det; out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det; return out; }; mat3$1.frob = function(a) { return Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + Math.pow(a[6], 2) + Math.pow(a[7], 2) + Math.pow(a[8], 2)); }; var quat = {}; quat.create = function() { var out = new GLMAT_ARRAY_TYPE(4); out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; }; quat.rotationTo = function() { var tmpvec3 = vec3$f.create(); var xUnitVec3 = vec3$f.fromValues(1, 0, 0); var yUnitVec3 = vec3$f.fromValues(0, 1, 0); return function(out, a, b) { var dot = vec3$f.dot(a, b); if (dot < -0.999999) { vec3$f.cross(tmpvec3, xUnitVec3, a); if (vec3$f.length(tmpvec3) < 1e-6) vec3$f.cross(tmpvec3, yUnitVec3, a); vec3$f.normalize(tmpvec3, tmpvec3); quat.setAxisAngle(out, tmpvec3, Math.PI); return out; } else if (dot > 0.999999) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; } else { vec3$f.cross(tmpvec3, a, b); out[0] = tmpvec3[0]; out[1] = tmpvec3[1]; out[2] = tmpvec3[2]; out[3] = 1 + dot; return quat.normalize(out, out); } }; }(); quat.setAxes = function() { var matr = mat3$1.create(); return function(out, view, right, up) { matr[0] = right[0]; matr[3] = right[1]; matr[6] = right[2]; matr[1] = up[0]; matr[4] = up[1]; matr[7] = up[2]; matr[2] = -view[0]; matr[5] = -view[1]; matr[8] = -view[2]; return quat.normalize(out, quat.fromMat3(out, matr)); }; }(); quat.clone = vec4$1.clone; quat.fromValues = vec4$1.fromValues; quat.copy = vec4$1.copy; quat.set = vec4$1.set; quat.identity = function(out) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 1; return out; }; quat.setAxisAngle = function(out, axis, rad2) { rad2 = rad2 * 0.5; var s = Math.sin(rad2); out[0] = s * axis[0]; out[1] = s * axis[1]; out[2] = s * axis[2]; out[3] = Math.cos(rad2); return out; }; quat.add = vec4$1.add; quat.multiply = function(out, a, b) { var ax = a[0], ay = a[1], az = a[2], aw = a[3], bx = b[0], by = b[1], bz = b[2], bw = b[3]; out[0] = ax * bw + aw * bx + ay * bz - az * by; out[1] = ay * bw + aw * by + az * bx - ax * bz; out[2] = az * bw + aw * bz + ax * by - ay * bx; out[3] = aw * bw - ax * bx - ay * by - az * bz; return out; }; quat.mul = quat.multiply; quat.scale = vec4$1.scale; quat.rotateX = function(out, a, rad2) { rad2 *= 0.5; var ax = a[0], ay = a[1], az = a[2], aw = a[3], bx = Math.sin(rad2), bw = Math.cos(rad2); out[0] = ax * bw + aw * bx; out[1] = ay * bw + az * bx; out[2] = az * bw - ay * bx; out[3] = aw * bw - ax * bx; return out; }; quat.rotateY = function(out, a, rad2) { rad2 *= 0.5; var ax = a[0], ay = a[1], az = a[2], aw = a[3], by = Math.sin(rad2), bw = Math.cos(rad2); out[0] = ax * bw - az * by; out[1] = ay * bw + aw * by; out[2] = az * bw + ax * by; out[3] = aw * bw - ay * by; return out; }; quat.rotateZ = function(out, a, rad2) { rad2 *= 0.5; var ax = a[0], ay = a[1], az = a[2], aw = a[3], bz = Math.sin(rad2), bw = Math.cos(rad2); out[0] = ax * bw + ay * bz; out[1] = ay * bw - ax * bz; out[2] = az * bw + aw * bz; out[3] = aw * bw - az * bz; return out; }; quat.calculateW = function(out, a) { var x = a[0], y = a[1], z = a[2]; out[0] = x; out[1] = y; out[2] = z; out[3] = Math.sqrt(Math.abs(1 - x * x - y * y - z * z)); return out; }; quat.dot = vec4$1.dot; quat.lerp = vec4$1.lerp; quat.slerp = function(out, a, b, t) { var ax = a[0], ay = a[1], az = a[2], aw = a[3], bx = b[0], by = b[1], bz = b[2], bw = b[3]; var omega, cosom, sinom, scale0, scale1; cosom = ax * bx + ay * by + az * bz + aw * bw; if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw; } if (1 - cosom > 1e-6) { omega = Math.acos(cosom); sinom = Math.sin(omega); scale0 = Math.sin((1 - t) * omega) / sinom; scale1 = Math.sin(t * omega) / sinom; } else { scale0 = 1 - t; scale1 = t; } out[0] = scale0 * ax + scale1 * bx; out[1] = scale0 * ay + scale1 * by; out[2] = scale0 * az + scale1 * bz; out[3] = scale0 * aw + scale1 * bw; return out; }; quat.invert = function(out, a) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3, invDot = dot ? 1 / dot : 0; out[0] = -a0 * invDot; out[1] = -a1 * invDot; out[2] = -a2 * invDot; out[3] = a3 * invDot; return out; }; quat.conjugate = function(out, a) { out[0] = -a[0]; out[1] = -a[1]; out[2] = -a[2]; out[3] = a[3]; return out; }; quat.length = vec4$1.length; quat.len = quat.length; quat.squaredLength = vec4$1.squaredLength; quat.sqrLen = quat.squaredLength; quat.normalize = vec4$1.normalize; quat.fromMat3 = function(out, m) { var fTrace = m[0] + m[4] + m[8]; var fRoot; if (fTrace > 0) { fRoot = Math.sqrt(fTrace + 1); out[3] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[0] = (m[5] - m[7]) * fRoot; out[1] = (m[6] - m[2]) * fRoot; out[2] = (m[1] - m[3]) * fRoot; } else { var i = 0; if (m[4] > m[0]) i = 1; if (m[8] > m[i * 3 + i]) i = 2; var j = (i + 1) % 3; var k = (i + 2) % 3; fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1); out[i] = 0.5 * fRoot; fRoot = 0.5 / fRoot; out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot; out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot; out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot; } return out; }; var Matrix4 = function() { this._axisX = new Vector3(); this._axisY = new Vector3(); this._axisZ = new Vector3(); this.array = mat4$2.create(); this._dirty = true; }; Matrix4.prototype = { constructor: Matrix4, /** * Set components from array * @param {Float32Array|number[]} arr */ setArray: function(arr) { for (var i = 0; i < this.array.length; i++) { this.array[i] = arr[i]; } this._dirty = true; return this; }, /** * Calculate the adjugate of self, in-place * @return {clay.Matrix4} */ adjoint: function() { mat4$2.adjoint(this.array, this.array); this._dirty = true; return this; }, /** * Clone a new Matrix4 * @return {clay.Matrix4} */ clone: function() { return new Matrix4().copy(this); }, /** * Copy from b * @param {clay.Matrix4} b * @return {clay.Matrix4} */ copy: function(a) { mat4$2.copy(this.array, a.array); this._dirty = true; return this; }, /** * Calculate matrix determinant * @return {number} */ determinant: function() { return mat4$2.determinant(this.array); }, /** * Set upper 3x3 part from quaternion * @param {clay.Quaternion} q * @return {clay.Matrix4} */ fromQuat: function(q) { mat4$2.fromQuat(this.array, q.array); this._dirty = true; return this; }, /** * Set from a quaternion rotation and a vector translation * @param {clay.Quaternion} q * @param {clay.Vector3} v * @return {clay.Matrix4} */ fromRotationTranslation: function(q, v) { mat4$2.fromRotationTranslation(this.array, q.array, v.array); this._dirty = true; return this; }, /** * Set from Matrix2d, it is used when converting a 2d shape to 3d space. * In 3d space it is equivalent to ranslate on xy plane and rotate about z axis * @param {clay.Matrix2d} m2d * @return {clay.Matrix4} */ fromMat2d: function(m2d) { Matrix4.fromMat2d(this, m2d); return this; }, /** * Set from frustum bounds * @param {number} left * @param {number} right * @param {number} bottom * @param {number} top * @param {number} near * @param {number} far * @return {clay.Matrix4} */ frustum: function(left, right, bottom, top, near, far) { mat4$2.frustum(this.array, left, right, bottom, top, near, far); this._dirty = true; return this; }, /** * Set to a identity matrix * @return {clay.Matrix4} */ identity: function() { mat4$2.identity(this.array); this._dirty = true; return this; }, /** * Invert self * @return {clay.Matrix4} */ invert: function() { mat4$2.invert(this.array, this.array); this._dirty = true; return this; }, /** * Set as a matrix with the given eye position, focal point, and up axis * @param {clay.Vector3} eye * @param {clay.Vector3} center * @param {clay.Vector3} up * @return {clay.Matrix4} */ lookAt: function(eye, center, up) { mat4$2.lookAt(this.array, eye.array, center.array, up.array); this._dirty = true; return this; }, /** * Alias for mutiply * @param {clay.Matrix4} b * @return {clay.Matrix4} */ mul: function(b) { mat4$2.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiplyLeft * @param {clay.Matrix4} a * @return {clay.Matrix4} */ mulLeft: function(a) { mat4$2.mul(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Multiply self and b * @param {clay.Matrix4} b * @return {clay.Matrix4} */ multiply: function(b) { mat4$2.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Multiply a and self, a is on the left * @param {clay.Matrix3} a * @return {clay.Matrix3} */ multiplyLeft: function(a) { mat4$2.multiply(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Set as a orthographic projection matrix * @param {number} left * @param {number} right * @param {number} bottom * @param {number} top * @param {number} near * @param {number} far * @return {clay.Matrix4} */ ortho: function(left, right, bottom, top, near, far) { mat4$2.ortho(this.array, left, right, bottom, top, near, far); this._dirty = true; return this; }, /** * Set as a perspective projection matrix * @param {number} fovy * @param {number} aspect * @param {number} near * @param {number} far * @return {clay.Matrix4} */ perspective: function(fovy, aspect, near, far) { mat4$2.perspective(this.array, fovy, aspect, near, far); this._dirty = true; return this; }, /** * Rotate self by rad about axis. * Equal to right-multiply a rotaion matrix * @param {number} rad * @param {clay.Vector3} axis * @return {clay.Matrix4} */ rotate: function(rad2, axis) { mat4$2.rotate(this.array, this.array, rad2, axis.array); this._dirty = true; return this; }, /** * Rotate self by a given radian about X axis. * Equal to right-multiply a rotaion matrix * @param {number} rad * @return {clay.Matrix4} */ rotateX: function(rad2) { mat4$2.rotateX(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Rotate self by a given radian about Y axis. * Equal to right-multiply a rotaion matrix * @param {number} rad * @return {clay.Matrix4} */ rotateY: function(rad2) { mat4$2.rotateY(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Rotate self by a given radian about Z axis. * Equal to right-multiply a rotaion matrix * @param {number} rad * @return {clay.Matrix4} */ rotateZ: function(rad2) { mat4$2.rotateZ(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Scale self by s * Equal to right-multiply a scale matrix * @param {clay.Vector3} s * @return {clay.Matrix4} */ scale: function(v) { mat4$2.scale(this.array, this.array, v.array); this._dirty = true; return this; }, /** * Translate self by v. * Equal to right-multiply a translate matrix * @param {clay.Vector3} v * @return {clay.Matrix4} */ translate: function(v) { mat4$2.translate(this.array, this.array, v.array); this._dirty = true; return this; }, /** * Transpose self, in-place. * @return {clay.Matrix2} */ transpose: function() { mat4$2.transpose(this.array, this.array); this._dirty = true; return this; }, /** * Decompose a matrix to SRT * @param {clay.Vector3} [scale] * @param {clay.Quaternion} rotation * @param {clay.Vector} position * @see http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.matrix.decompose.aspx */ decomposeMatrix: function() { var x = vec3$f.create(); var y = vec3$f.create(); var z = vec3$f.create(); var m3 = mat3$1.create(); return function(scale, rotation, position) { var el = this.array; vec3$f.set(x, el[0], el[1], el[2]); vec3$f.set(y, el[4], el[5], el[6]); vec3$f.set(z, el[8], el[9], el[10]); var sx = vec3$f.length(x); var sy = vec3$f.length(y); var sz = vec3$f.length(z); var det = this.determinant(); if (det < 0) { sx = -sx; } if (scale) { scale.set(sx, sy, sz); } position.set(el[12], el[13], el[14]); mat3$1.fromMat4(m3, el); m3[0] /= sx; m3[1] /= sx; m3[2] /= sx; m3[3] /= sy; m3[4] /= sy; m3[5] /= sy; m3[6] /= sz; m3[7] /= sz; m3[8] /= sz; quat.fromMat3(rotation.array, m3); quat.normalize(rotation.array, rotation.array); rotation._dirty = true; position._dirty = true; }; }(), toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; var defineProperty$2 = Object.defineProperty; if (defineProperty$2) { var proto$2 = Matrix4.prototype; defineProperty$2(proto$2, "z", { get: function() { var el = this.array; this._axisZ.set(el[8], el[9], el[10]); return this._axisZ; }, set: function(v) { var el = this.array; v = v.array; el[8] = v[0]; el[9] = v[1]; el[10] = v[2]; this._dirty = true; } }); defineProperty$2(proto$2, "y", { get: function() { var el = this.array; this._axisY.set(el[4], el[5], el[6]); return this._axisY; }, set: function(v) { var el = this.array; v = v.array; el[4] = v[0]; el[5] = v[1]; el[6] = v[2]; this._dirty = true; } }); defineProperty$2(proto$2, "x", { get: function() { var el = this.array; this._axisX.set(el[0], el[1], el[2]); return this._axisX; }, set: function(v) { var el = this.array; v = v.array; el[0] = v[0]; el[1] = v[1]; el[2] = v[2]; this._dirty = true; } }); } Matrix4.adjoint = function(out, a) { mat4$2.adjoint(out.array, a.array); out._dirty = true; return out; }; Matrix4.copy = function(out, a) { mat4$2.copy(out.array, a.array); out._dirty = true; return out; }; Matrix4.determinant = function(a) { return mat4$2.determinant(a.array); }; Matrix4.identity = function(out) { mat4$2.identity(out.array); out._dirty = true; return out; }; Matrix4.ortho = function(out, left, right, bottom, top, near, far) { mat4$2.ortho(out.array, left, right, bottom, top, near, far); out._dirty = true; return out; }; Matrix4.perspective = function(out, fovy, aspect, near, far) { mat4$2.perspective(out.array, fovy, aspect, near, far); out._dirty = true; return out; }; Matrix4.lookAt = function(out, eye, center, up) { mat4$2.lookAt(out.array, eye.array, center.array, up.array); out._dirty = true; return out; }; Matrix4.invert = function(out, a) { mat4$2.invert(out.array, a.array); out._dirty = true; return out; }; Matrix4.mul = function(out, a, b) { mat4$2.mul(out.array, a.array, b.array); out._dirty = true; return out; }; Matrix4.multiply = Matrix4.mul; Matrix4.fromQuat = function(out, q) { mat4$2.fromQuat(out.array, q.array); out._dirty = true; return out; }; Matrix4.fromRotationTranslation = function(out, q, v) { mat4$2.fromRotationTranslation(out.array, q.array, v.array); out._dirty = true; return out; }; Matrix4.fromMat2d = function(m4, m2d) { m4._dirty = true; var m2d = m2d.array; var m4 = m4.array; m4[0] = m2d[0]; m4[4] = m2d[2]; m4[12] = m2d[4]; m4[1] = m2d[1]; m4[5] = m2d[3]; m4[13] = m2d[5]; return m4; }; Matrix4.rotate = function(out, a, rad2, axis) { mat4$2.rotate(out.array, a.array, rad2, axis.array); out._dirty = true; return out; }; Matrix4.rotateX = function(out, a, rad2) { mat4$2.rotateX(out.array, a.array, rad2); out._dirty = true; return out; }; Matrix4.rotateY = function(out, a, rad2) { mat4$2.rotateY(out.array, a.array, rad2); out._dirty = true; return out; }; Matrix4.rotateZ = function(out, a, rad2) { mat4$2.rotateZ(out.array, a.array, rad2); out._dirty = true; return out; }; Matrix4.scale = function(out, a, v) { mat4$2.scale(out.array, a.array, v.array); out._dirty = true; return out; }; Matrix4.transpose = function(out, a) { mat4$2.transpose(out.array, a.array); out._dirty = true; return out; }; Matrix4.translate = function(out, a, v) { mat4$2.translate(out.array, a.array, v.array); out._dirty = true; return out; }; var Quaternion = function(x, y, z, w) { x = x || 0; y = y || 0; z = z || 0; w = w === void 0 ? 1 : w; this.array = quat.fromValues(x, y, z, w); this._dirty = true; }; Quaternion.prototype = { constructor: Quaternion, /** * Add b to self * @param {clay.Quaternion} b * @return {clay.Quaternion} */ add: function(b) { quat.add(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Calculate the w component from x, y, z component * @return {clay.Quaternion} */ calculateW: function() { quat.calculateW(this.array, this.array); this._dirty = true; return this; }, /** * Set x, y and z components * @param {number} x * @param {number} y * @param {number} z * @param {number} w * @return {clay.Quaternion} */ set: function(x, y, z, w) { this.array[0] = x; this.array[1] = y; this.array[2] = z; this.array[3] = w; this._dirty = true; return this; }, /** * Set x, y, z and w components from array * @param {Float32Array|number[]} arr * @return {clay.Quaternion} */ setArray: function(arr) { this.array[0] = arr[0]; this.array[1] = arr[1]; this.array[2] = arr[2]; this.array[3] = arr[3]; this._dirty = true; return this; }, /** * Clone a new Quaternion * @return {clay.Quaternion} */ clone: function() { return new Quaternion(this.x, this.y, this.z, this.w); }, /** * Calculates the conjugate of self If the quaternion is normalized, * this function is faster than invert and produces the same result. * * @return {clay.Quaternion} */ conjugate: function() { quat.conjugate(this.array, this.array); this._dirty = true; return this; }, /** * Copy from b * @param {clay.Quaternion} b * @return {clay.Quaternion} */ copy: function(b) { quat.copy(this.array, b.array); this._dirty = true; return this; }, /** * Dot product of self and b * @param {clay.Quaternion} b * @return {number} */ dot: function(b) { return quat.dot(this.array, b.array); }, /** * Set from the given 3x3 rotation matrix * @param {clay.Matrix3} m * @return {clay.Quaternion} */ fromMat3: function(m) { quat.fromMat3(this.array, m.array); this._dirty = true; return this; }, /** * Set from the given 4x4 rotation matrix * The 4th column and 4th row will be droped * @param {clay.Matrix4} m * @return {clay.Quaternion} */ fromMat4: function() { var m3 = mat3$1.create(); return function(m) { mat3$1.fromMat4(m3, m.array); mat3$1.transpose(m3, m3); quat.fromMat3(this.array, m3); this._dirty = true; return this; }; }(), /** * Set to identity quaternion * @return {clay.Quaternion} */ identity: function() { quat.identity(this.array); this._dirty = true; return this; }, /** * Invert self * @return {clay.Quaternion} */ invert: function() { quat.invert(this.array, this.array); this._dirty = true; return this; }, /** * Alias of length * @return {number} */ len: function() { return quat.len(this.array); }, /** * Calculate the length * @return {number} */ length: function() { return quat.length(this.array); }, /** * Linear interpolation between a and b * @param {clay.Quaternion} a * @param {clay.Quaternion} b * @param {number} t * @return {clay.Quaternion} */ lerp: function(a, b, t) { quat.lerp(this.array, a.array, b.array, t); this._dirty = true; return this; }, /** * Alias for multiply * @param {clay.Quaternion} b * @return {clay.Quaternion} */ mul: function(b) { quat.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiplyLeft * @param {clay.Quaternion} a * @return {clay.Quaternion} */ mulLeft: function(a) { quat.multiply(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Mutiply self and b * @param {clay.Quaternion} b * @return {clay.Quaternion} */ multiply: function(b) { quat.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Mutiply a and self * Quaternion mutiply is not commutative, so the result of mutiplyLeft is different with multiply. * @param {clay.Quaternion} a * @return {clay.Quaternion} */ multiplyLeft: function(a) { quat.multiply(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Normalize self * @return {clay.Quaternion} */ normalize: function() { quat.normalize(this.array, this.array); this._dirty = true; return this; }, /** * Rotate self by a given radian about X axis * @param {number} rad * @return {clay.Quaternion} */ rotateX: function(rad2) { quat.rotateX(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Rotate self by a given radian about Y axis * @param {number} rad * @return {clay.Quaternion} */ rotateY: function(rad2) { quat.rotateY(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Rotate self by a given radian about Z axis * @param {number} rad * @return {clay.Quaternion} */ rotateZ: function(rad2) { quat.rotateZ(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Sets self to represent the shortest rotation from Vector3 a to Vector3 b. * a and b needs to be normalized * @param {clay.Vector3} a * @param {clay.Vector3} b * @return {clay.Quaternion} */ rotationTo: function(a, b) { quat.rotationTo(this.array, a.array, b.array); this._dirty = true; return this; }, /** * Sets self with values corresponding to the given axes * @param {clay.Vector3} view * @param {clay.Vector3} right * @param {clay.Vector3} up * @return {clay.Quaternion} */ setAxes: function(view, right, up) { quat.setAxes(this.array, view.array, right.array, up.array); this._dirty = true; return this; }, /** * Sets self with a rotation axis and rotation angle * @param {clay.Vector3} axis * @param {number} rad * @return {clay.Quaternion} */ setAxisAngle: function(axis, rad2) { quat.setAxisAngle(this.array, axis.array, rad2); this._dirty = true; return this; }, /** * Perform spherical linear interpolation between a and b * @param {clay.Quaternion} a * @param {clay.Quaternion} b * @param {number} t * @return {clay.Quaternion} */ slerp: function(a, b, t) { quat.slerp(this.array, a.array, b.array, t); this._dirty = true; return this; }, /** * Alias for squaredLength * @return {number} */ sqrLen: function() { return quat.sqrLen(this.array); }, /** * Squared length of self * @return {number} */ squaredLength: function() { return quat.squaredLength(this.array); }, /** * Set from euler * @param {clay.Vector3} v * @param {String} order */ fromEuler: function(v, order) { return Quaternion.fromEuler(this, v, order); }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; var defineProperty$1 = Object.defineProperty; if (defineProperty$1) { var proto$1 = Quaternion.prototype; defineProperty$1(proto$1, "x", { get: function() { return this.array[0]; }, set: function(value) { this.array[0] = value; this._dirty = true; } }); defineProperty$1(proto$1, "y", { get: function() { return this.array[1]; }, set: function(value) { this.array[1] = value; this._dirty = true; } }); defineProperty$1(proto$1, "z", { get: function() { return this.array[2]; }, set: function(value) { this.array[2] = value; this._dirty = true; } }); defineProperty$1(proto$1, "w", { get: function() { return this.array[3]; }, set: function(value) { this.array[3] = value; this._dirty = true; } }); } Quaternion.add = function(out, a, b) { quat.add(out.array, a.array, b.array); out._dirty = true; return out; }; Quaternion.set = function(out, x, y, z, w) { quat.set(out.array, x, y, z, w); out._dirty = true; }; Quaternion.copy = function(out, b) { quat.copy(out.array, b.array); out._dirty = true; return out; }; Quaternion.calculateW = function(out, a) { quat.calculateW(out.array, a.array); out._dirty = true; return out; }; Quaternion.conjugate = function(out, a) { quat.conjugate(out.array, a.array); out._dirty = true; return out; }; Quaternion.identity = function(out) { quat.identity(out.array); out._dirty = true; return out; }; Quaternion.invert = function(out, a) { quat.invert(out.array, a.array); out._dirty = true; return out; }; Quaternion.dot = function(a, b) { return quat.dot(a.array, b.array); }; Quaternion.len = function(a) { return quat.length(a.array); }; Quaternion.lerp = function(out, a, b, t) { quat.lerp(out.array, a.array, b.array, t); out._dirty = true; return out; }; Quaternion.slerp = function(out, a, b, t) { quat.slerp(out.array, a.array, b.array, t); out._dirty = true; return out; }; Quaternion.mul = function(out, a, b) { quat.multiply(out.array, a.array, b.array); out._dirty = true; return out; }; Quaternion.multiply = Quaternion.mul; Quaternion.rotateX = function(out, a, rad2) { quat.rotateX(out.array, a.array, rad2); out._dirty = true; return out; }; Quaternion.rotateY = function(out, a, rad2) { quat.rotateY(out.array, a.array, rad2); out._dirty = true; return out; }; Quaternion.rotateZ = function(out, a, rad2) { quat.rotateZ(out.array, a.array, rad2); out._dirty = true; return out; }; Quaternion.setAxisAngle = function(out, axis, rad2) { quat.setAxisAngle(out.array, axis.array, rad2); out._dirty = true; return out; }; Quaternion.normalize = function(out, a) { quat.normalize(out.array, a.array); out._dirty = true; return out; }; Quaternion.sqrLen = function(a) { return quat.sqrLen(a.array); }; Quaternion.squaredLength = Quaternion.sqrLen; Quaternion.fromMat3 = function(out, m) { quat.fromMat3(out.array, m.array); out._dirty = true; return out; }; Quaternion.setAxes = function(out, view, right, up) { quat.setAxes(out.array, view.array, right.array, up.array); out._dirty = true; return out; }; Quaternion.rotationTo = function(out, a, b) { quat.rotationTo(out.array, a.array, b.array); out._dirty = true; return out; }; Quaternion.fromEuler = function(out, v, order) { out._dirty = true; v = v.array; var target = out.array; var c1 = Math.cos(v[0] / 2); var c2 = Math.cos(v[1] / 2); var c3 = Math.cos(v[2] / 2); var s1 = Math.sin(v[0] / 2); var s2 = Math.sin(v[1] / 2); var s3 = Math.sin(v[2] / 2); var order = (order || "XYZ").toUpperCase(); switch (order) { case "XYZ": target[0] = s1 * c2 * c3 + c1 * s2 * s3; target[1] = c1 * s2 * c3 - s1 * c2 * s3; target[2] = c1 * c2 * s3 + s1 * s2 * c3; target[3] = c1 * c2 * c3 - s1 * s2 * s3; break; case "YXZ": target[0] = s1 * c2 * c3 + c1 * s2 * s3; target[1] = c1 * s2 * c3 - s1 * c2 * s3; target[2] = c1 * c2 * s3 - s1 * s2 * c3; target[3] = c1 * c2 * c3 + s1 * s2 * s3; break; case "ZXY": target[0] = s1 * c2 * c3 - c1 * s2 * s3; target[1] = c1 * s2 * c3 + s1 * c2 * s3; target[2] = c1 * c2 * s3 + s1 * s2 * c3; target[3] = c1 * c2 * c3 - s1 * s2 * s3; break; case "ZYX": target[0] = s1 * c2 * c3 - c1 * s2 * s3; target[1] = c1 * s2 * c3 + s1 * c2 * s3; target[2] = c1 * c2 * s3 - s1 * s2 * c3; target[3] = c1 * c2 * c3 + s1 * s2 * s3; break; case "YZX": target[0] = s1 * c2 * c3 + c1 * s2 * s3; target[1] = c1 * s2 * c3 + s1 * c2 * s3; target[2] = c1 * c2 * s3 - s1 * s2 * c3; target[3] = c1 * c2 * c3 - s1 * s2 * s3; break; case "XZY": target[0] = s1 * c2 * c3 - c1 * s2 * s3; target[1] = c1 * s2 * c3 - s1 * c2 * s3; target[2] = c1 * c2 * s3 + s1 * s2 * c3; target[3] = c1 * c2 * c3 + s1 * s2 * s3; break; } }; var vec3Set$2 = vec3$f.set; var vec3Copy$1 = vec3$f.copy; var BoundingBox = function(min, max) { this.min = min || new Vector3(Infinity, Infinity, Infinity); this.max = max || new Vector3(-Infinity, -Infinity, -Infinity); this.vertices = null; }; BoundingBox.prototype = { constructor: BoundingBox, /** * Update min and max coords from a vertices array * @param {array} vertices */ updateFromVertices: function(vertices) { if (vertices.length > 0) { var min = this.min; var max = this.max; var minArr = min.array; var maxArr = max.array; vec3Copy$1(minArr, vertices[0]); vec3Copy$1(maxArr, vertices[0]); for (var i = 1; i < vertices.length; i++) { var vertex = vertices[i]; if (vertex[0] < minArr[0]) { minArr[0] = vertex[0]; } if (vertex[1] < minArr[1]) { minArr[1] = vertex[1]; } if (vertex[2] < minArr[2]) { minArr[2] = vertex[2]; } if (vertex[0] > maxArr[0]) { maxArr[0] = vertex[0]; } if (vertex[1] > maxArr[1]) { maxArr[1] = vertex[1]; } if (vertex[2] > maxArr[2]) { maxArr[2] = vertex[2]; } } min._dirty = true; max._dirty = true; } }, /** * Union operation with another bounding box * @param {clay.BoundingBox} bbox */ union: function(bbox) { var min = this.min; var max = this.max; vec3$f.min(min.array, min.array, bbox.min.array); vec3$f.max(max.array, max.array, bbox.max.array); min._dirty = true; max._dirty = true; return this; }, /** * Intersection operation with another bounding box * @param {clay.BoundingBox} bbox */ intersection: function(bbox) { var min = this.min; var max = this.max; vec3$f.max(min.array, min.array, bbox.min.array); vec3$f.min(max.array, max.array, bbox.max.array); min._dirty = true; max._dirty = true; return this; }, /** * If intersect with another bounding box * @param {clay.BoundingBox} bbox * @return {boolean} */ intersectBoundingBox: function(bbox) { var _min = this.min.array; var _max = this.max.array; var _min2 = bbox.min.array; var _max2 = bbox.max.array; return !(_min[0] > _max2[0] || _min[1] > _max2[1] || _min[2] > _max2[2] || _max[0] < _min2[0] || _max[1] < _min2[1] || _max[2] < _min2[2]); }, /** * If contain another bounding box entirely * @param {clay.BoundingBox} bbox * @return {boolean} */ containBoundingBox: function(bbox) { var _min = this.min.array; var _max = this.max.array; var _min2 = bbox.min.array; var _max2 = bbox.max.array; return _min[0] <= _min2[0] && _min[1] <= _min2[1] && _min[2] <= _min2[2] && _max[0] >= _max2[0] && _max[1] >= _max2[1] && _max[2] >= _max2[2]; }, /** * If contain point entirely * @param {clay.Vector3} point * @return {boolean} */ containPoint: function(p) { var _min = this.min.array; var _max = this.max.array; var _p = p.array; return _min[0] <= _p[0] && _min[1] <= _p[1] && _min[2] <= _p[2] && _max[0] >= _p[0] && _max[1] >= _p[1] && _max[2] >= _p[2]; }, /** * If bounding box is finite */ isFinite: function() { var _min = this.min.array; var _max = this.max.array; return isFinite(_min[0]) && isFinite(_min[1]) && isFinite(_min[2]) && isFinite(_max[0]) && isFinite(_max[1]) && isFinite(_max[2]); }, /** * Apply an affine transform matrix to the bounding box * @param {clay.Matrix4} matrix */ applyTransform: function(matrix) { this.transformFrom(this, matrix); }, /** * Get from another bounding box and an affine transform matrix. * @param {clay.BoundingBox} source * @param {clay.Matrix4} matrix */ transformFrom: function() { var xa = vec3$f.create(); var xb = vec3$f.create(); var ya = vec3$f.create(); var yb = vec3$f.create(); var za = vec3$f.create(); var zb = vec3$f.create(); return function(source, matrix) { var min = source.min.array; var max = source.max.array; var m = matrix.array; xa[0] = m[0] * min[0]; xa[1] = m[1] * min[0]; xa[2] = m[2] * min[0]; xb[0] = m[0] * max[0]; xb[1] = m[1] * max[0]; xb[2] = m[2] * max[0]; ya[0] = m[4] * min[1]; ya[1] = m[5] * min[1]; ya[2] = m[6] * min[1]; yb[0] = m[4] * max[1]; yb[1] = m[5] * max[1]; yb[2] = m[6] * max[1]; za[0] = m[8] * min[2]; za[1] = m[9] * min[2]; za[2] = m[10] * min[2]; zb[0] = m[8] * max[2]; zb[1] = m[9] * max[2]; zb[2] = m[10] * max[2]; min = this.min.array; max = this.max.array; min[0] = Math.min(xa[0], xb[0]) + Math.min(ya[0], yb[0]) + Math.min(za[0], zb[0]) + m[12]; min[1] = Math.min(xa[1], xb[1]) + Math.min(ya[1], yb[1]) + Math.min(za[1], zb[1]) + m[13]; min[2] = Math.min(xa[2], xb[2]) + Math.min(ya[2], yb[2]) + Math.min(za[2], zb[2]) + m[14]; max[0] = Math.max(xa[0], xb[0]) + Math.max(ya[0], yb[0]) + Math.max(za[0], zb[0]) + m[12]; max[1] = Math.max(xa[1], xb[1]) + Math.max(ya[1], yb[1]) + Math.max(za[1], zb[1]) + m[13]; max[2] = Math.max(xa[2], xb[2]) + Math.max(ya[2], yb[2]) + Math.max(za[2], zb[2]) + m[14]; this.min._dirty = true; this.max._dirty = true; return this; }; }(), /** * Apply a projection matrix to the bounding box * @param {clay.Matrix4} matrix */ applyProjection: function(matrix) { var min = this.min.array; var max = this.max.array; var m = matrix.array; var v10 = min[0]; var v11 = min[1]; var v12 = min[2]; var v20 = max[0]; var v21 = max[1]; var v22 = min[2]; var v30 = max[0]; var v31 = max[1]; var v32 = max[2]; if (m[15] === 1) { min[0] = m[0] * v10 + m[12]; min[1] = m[5] * v11 + m[13]; max[2] = m[10] * v12 + m[14]; max[0] = m[0] * v30 + m[12]; max[1] = m[5] * v31 + m[13]; min[2] = m[10] * v32 + m[14]; } else { var w = -1 / v12; min[0] = m[0] * v10 * w; min[1] = m[5] * v11 * w; max[2] = (m[10] * v12 + m[14]) * w; w = -1 / v22; max[0] = m[0] * v20 * w; max[1] = m[5] * v21 * w; w = -1 / v32; min[2] = (m[10] * v32 + m[14]) * w; } this.min._dirty = true; this.max._dirty = true; return this; }, updateVertices: function() { var vertices = this.vertices; if (!vertices) { vertices = []; for (var i = 0; i < 8; i++) { vertices[i] = vec3$f.fromValues(0, 0, 0); } this.vertices = vertices; } var min = this.min.array; var max = this.max.array; vec3Set$2(vertices[0], min[0], min[1], min[2]); vec3Set$2(vertices[1], min[0], max[1], min[2]); vec3Set$2(vertices[2], max[0], min[1], min[2]); vec3Set$2(vertices[3], max[0], max[1], min[2]); vec3Set$2(vertices[4], min[0], min[1], max[2]); vec3Set$2(vertices[5], min[0], max[1], max[2]); vec3Set$2(vertices[6], max[0], min[1], max[2]); vec3Set$2(vertices[7], max[0], max[1], max[2]); return this; }, /** * Copy values from another bounding box * @param {clay.BoundingBox} bbox */ copy: function(bbox) { var min = this.min; var max = this.max; vec3Copy$1(min.array, bbox.min.array); vec3Copy$1(max.array, bbox.max.array); min._dirty = true; max._dirty = true; return this; }, /** * Clone a new bounding box * @return {clay.BoundingBox} */ clone: function() { var boundingBox = new BoundingBox(); boundingBox.copy(this); return boundingBox; } }; var nameId = 0; var Node$1 = Base.extend( /** @lends clay.Node# */ { /** * Scene node name * @type {string} */ name: "", /** * Position relative to its parent node. aka translation. * @type {clay.Vector3} */ position: null, /** * Rotation relative to its parent node. Represented by a quaternion * @type {clay.Quaternion} */ rotation: null, /** * Scale relative to its parent node * @type {clay.Vector3} */ scale: null, /** * Affine transform matrix relative to its root scene. * @type {clay.Matrix4} */ worldTransform: null, /** * Affine transform matrix relative to its parent node. * Composited with position, rotation and scale. * @type {clay.Matrix4} */ localTransform: null, /** * If the local transform is update from SRT(scale, rotation, translation, which is position here) each frame * @type {boolean} */ autoUpdateLocalTransform: true, /** * Parent of current scene node * @type {?clay.Node} * @private */ _parent: null, /** * The root scene mounted. Null if it is a isolated node * @type {?clay.Scene} * @private */ _scene: null, /** * @type {boolean} * @private */ _needsUpdateWorldTransform: true, /** * @type {boolean} * @private */ _inIterating: false, // Depth for transparent list sorting __depth: 0 }, function() { if (!this.name) { this.name = (this.type || "NODE") + "_" + nameId++; } if (!this.position) { this.position = new Vector3(); } if (!this.rotation) { this.rotation = new Quaternion(); } if (!this.scale) { this.scale = new Vector3(1, 1, 1); } this.worldTransform = new Matrix4(); this.localTransform = new Matrix4(); this._children = []; }, /**@lends clay.Node.prototype. */ { /** * @type {?clay.Vector3} * @instance */ target: null, /** * If node and its chilren invisible * @type {boolean} * @instance */ invisible: false, /** * If Node is a skinned mesh * @return {boolean} */ isSkinnedMesh: function() { return false; }, /** * Return true if it is a renderable scene node, like Mesh and ParticleSystem * @return {boolean} */ isRenderable: function() { return false; }, /** * Set the name of the scene node * @param {string} name */ setName: function(name) { var scene = this._scene; if (scene) { var nodeRepository = scene._nodeRepository; delete nodeRepository[this.name]; nodeRepository[name] = this; } this.name = name; }, /** * Add a child node * @param {clay.Node} node */ add: function(node) { var originalParent = node._parent; if (originalParent === this) { return; } if (originalParent) { originalParent.remove(node); } node._parent = this; this._children.push(node); var scene = this._scene; if (scene && scene !== node.scene) { node.traverse(this._addSelfToScene, this); } node._needsUpdateWorldTransform = true; }, /** * Remove the given child scene node * @param {clay.Node} node */ remove: function(node) { var children = this._children; var idx = children.indexOf(node); if (idx < 0) { return; } children.splice(idx, 1); node._parent = null; if (this._scene) { node.traverse(this._removeSelfFromScene, this); } }, /** * Remove all children */ removeAll: function() { var children = this._children; for (var idx = 0; idx < children.length; idx++) { children[idx]._parent = null; if (this._scene) { children[idx].traverse(this._removeSelfFromScene, this); } } this._children = []; }, /** * Get the scene mounted * @return {clay.Scene} */ getScene: function() { return this._scene; }, /** * Get parent node * @return {clay.Scene} */ getParent: function() { return this._parent; }, _removeSelfFromScene: function(descendant) { descendant._scene.removeFromScene(descendant); descendant._scene = null; }, _addSelfToScene: function(descendant) { this._scene.addToScene(descendant); descendant._scene = this._scene; }, /** * Return true if it is ancestor of the given scene node * @param {clay.Node} node */ isAncestor: function(node) { var parent = node._parent; while (parent) { if (parent === this) { return true; } parent = parent._parent; } return false; }, /** * Get a new created array of all children nodes * @return {clay.Node[]} */ children: function() { return this._children.slice(); }, /** * Get child scene node at given index. * @param {number} idx * @return {clay.Node} */ childAt: function(idx) { return this._children[idx]; }, /** * Get first child with the given name * @param {string} name * @return {clay.Node} */ getChildByName: function(name) { var children = this._children; for (var i = 0; i < children.length; i++) { if (children[i].name === name) { return children[i]; } } }, /** * Get first descendant have the given name * @param {string} name * @return {clay.Node} */ getDescendantByName: function(name) { var children = this._children; for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.name === name) { return child; } else { var res = child.getDescendantByName(name); if (res) { return res; } } } }, /** * Query descendant node by path * @param {string} path * @return {clay.Node} * @example * node.queryNode('root/parent/child'); */ queryNode: function(path) { if (!path) { return; } var pathArr = path.split("/"); var current = this; for (var i = 0; i < pathArr.length; i++) { var name = pathArr[i]; if (!name) { continue; } var found = false; var children = current._children; for (var j = 0; j < children.length; j++) { var child = children[j]; if (child.name === name) { current = child; found = true; break; } } if (!found) { return; } } return current; }, /** * Get query path, relative to rootNode(default is scene) * @param {clay.Node} [rootNode] * @return {string} */ getPath: function(rootNode) { if (!this._parent) { return "/"; } var current = this._parent; var path = this.name; while (current._parent) { path = current.name + "/" + path; if (current._parent == rootNode) { break; } current = current._parent; } if (!current._parent && rootNode) { return null; } return path; }, /** * Depth first traverse all its descendant scene nodes. * * **WARN** Don't do `add`, `remove` operation in the callback during traverse. * @param {Function} callback * @param {Node} [context] */ traverse: function(callback, context) { callback.call(context, this); var _children = this._children; for (var i = 0, len = _children.length; i < len; i++) { _children[i].traverse(callback, context); } }, /** * Traverse all children nodes. * * **WARN** DON'T do `add`, `remove` operation in the callback during iteration. * * @param {Function} callback * @param {Node} [context] */ eachChild: function(callback, context) { var _children = this._children; for (var i = 0, len = _children.length; i < len; i++) { var child = _children[i]; callback.call(context, child, i); } }, /** * Set the local transform and decompose to SRT * @param {clay.Matrix4} matrix */ setLocalTransform: function(matrix) { mat4$2.copy(this.localTransform.array, matrix.array); this.decomposeLocalTransform(); }, /** * Decompose the local transform to SRT */ decomposeLocalTransform: function(keepScale) { var scale = !keepScale ? this.scale : null; this.localTransform.decomposeMatrix(scale, this.rotation, this.position); }, /** * Set the world transform and decompose to SRT * @param {clay.Matrix4} matrix */ setWorldTransform: function(matrix) { mat4$2.copy(this.worldTransform.array, matrix.array); this.decomposeWorldTransform(); }, /** * Decompose the world transform to SRT * @function */ decomposeWorldTransform: function() { var tmp = mat4$2.create(); return function(keepScale) { var localTransform = this.localTransform; var worldTransform = this.worldTransform; if (this._parent) { mat4$2.invert(tmp, this._parent.worldTransform.array); mat4$2.multiply(localTransform.array, tmp, worldTransform.array); } else { mat4$2.copy(localTransform.array, worldTransform.array); } var scale = !keepScale ? this.scale : null; localTransform.decomposeMatrix(scale, this.rotation, this.position); }; }(), transformNeedsUpdate: function() { return this.position._dirty || this.rotation._dirty || this.scale._dirty; }, /** * Update local transform from SRT * Notice that local transform will not be updated if _dirty mark of position, rotation, scale is all false */ updateLocalTransform: function() { var position = this.position; var rotation = this.rotation; var scale = this.scale; if (this.transformNeedsUpdate()) { var m = this.localTransform.array; mat4$2.fromRotationTranslation(m, rotation.array, position.array); mat4$2.scale(m, m, scale.array); rotation._dirty = false; scale._dirty = false; position._dirty = false; this._needsUpdateWorldTransform = true; } }, /** * Update world transform, assume its parent world transform have been updated * @private */ _updateWorldTransformTopDown: function() { var localTransform = this.localTransform.array; var worldTransform = this.worldTransform.array; if (this._parent) { mat4$2.multiplyAffine( worldTransform, this._parent.worldTransform.array, localTransform ); } else { mat4$2.copy(worldTransform, localTransform); } }, /** * Update world transform before whole scene is updated. */ updateWorldTransform: function() { var rootNodeIsDirty = this; while (rootNodeIsDirty && rootNodeIsDirty.getParent() && rootNodeIsDirty.getParent().transformNeedsUpdate()) { rootNodeIsDirty = rootNodeIsDirty.getParent(); } rootNodeIsDirty.update(); }, /** * Update local transform and world transform recursively * @param {boolean} forceUpdateWorld */ update: function(forceUpdateWorld) { if (this.autoUpdateLocalTransform) { this.updateLocalTransform(); } else { forceUpdateWorld = true; } if (forceUpdateWorld || this._needsUpdateWorldTransform) { this._updateWorldTransformTopDown(); forceUpdateWorld = true; this._needsUpdateWorldTransform = false; } var children = this._children; for (var i = 0, len = children.length; i < len; i++) { children[i].update(forceUpdateWorld); } }, /** * Get bounding box of node * @param {Function} [filter] * @param {clay.BoundingBox} [out] * @return {clay.BoundingBox} */ // TODO Skinning getBoundingBox: function() { function defaultFilter(el) { return !el.invisible && el.geometry; } var tmpBBox = new BoundingBox(); var tmpMat4 = new Matrix4(); var invWorldTransform = new Matrix4(); return function(filter, out) { out = out || new BoundingBox(); if (this._parent) { Matrix4.invert(invWorldTransform, this._parent.worldTransform); } else { Matrix4.identity(invWorldTransform); } this.traverse(function(mesh2) { if (mesh2.geometry && mesh2.geometry.boundingBox) { tmpBBox.copy(mesh2.geometry.boundingBox); Matrix4.multiply(tmpMat4, invWorldTransform, mesh2.worldTransform); tmpBBox.applyTransform(tmpMat4); out.union(tmpBBox); } }, this, defaultFilter); return out; }; }(), /** * Get world position, extracted from world transform * @param {clay.Vector3} [out] * @return {clay.Vector3} */ getWorldPosition: function(out) { if (this.transformNeedsUpdate()) { this.updateWorldTransform(); } var m = this.worldTransform.array; if (out) { var arr = out.array; arr[0] = m[12]; arr[1] = m[13]; arr[2] = m[14]; return out; } else { return new Vector3(m[12], m[13], m[14]); } }, /** * Clone a new node * @return {Node} */ clone: function() { var node = new this.constructor(); var children = this._children; node.setName(this.name); node.position.copy(this.position); node.rotation.copy(this.rotation); node.scale.copy(this.scale); for (var i = 0; i < children.length; i++) { node.add(children[i].clone()); } return node; }, /** * Rotate the node around a axis by angle degrees, axis passes through point * @param {clay.Vector3} point Center point * @param {clay.Vector3} axis Center axis * @param {number} angle Rotation angle * @see http://docs.unity3d.com/Documentation/ScriptReference/Transform.RotateAround.html * @function */ rotateAround: function() { var v = new Vector3(); var RTMatrix = new Matrix4(); return function(point, axis, angle) { v.copy(this.position).subtract(point); var localTransform = this.localTransform; localTransform.identity(); localTransform.translate(point); localTransform.rotate(angle, axis); RTMatrix.fromRotationTranslation(this.rotation, v); localTransform.multiply(RTMatrix); localTransform.scale(this.scale); this.decomposeLocalTransform(); this._needsUpdateWorldTransform = true; }; }(), /** * @param {clay.Vector3} target * @param {clay.Vector3} [up] * @see http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml * @function */ lookAt: function() { var m = new Matrix4(); return function(target, up) { m.lookAt(this.position, target, up || this.localTransform.y).invert(); this.setLocalTransform(m); this.target = target; }; }() } ); var Renderable = Node$1.extend( /** @lends clay.Renderable# */ { /** * @type {clay.Material} */ material: null, /** * @type {clay.Geometry} */ geometry: null, /** * @type {number} */ mode: glenum.TRIANGLES, _renderInfo: null }, /** @lends clay.Renderable.prototype */ { __program: null, /** * Group of received light. */ lightGroup: 0, /** * Render order, Nodes with smaller value renders before nodes with larger values. * @type {Number} */ renderOrder: 0, /** * Used when mode is LINES, LINE_STRIP or LINE_LOOP * @type {number} */ // lineWidth: 1, /** * If enable culling * @type {boolean} */ culling: true, /** * Specify which side of polygon will be culled. * Possible values: * + {@link clay.Renderable.BACK} * + {@link clay.Renderable.FRONT} * + {@link clay.Renderable.FRONT_AND_BACK} * @see https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/cullFace * @type {number} */ cullFace: glenum.BACK, /** * Specify which side is front face. * Possible values: * + {@link clay.Renderable.CW} * + {@link clay.Renderable.CCW} * @see https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/frontFace * @type {number} */ frontFace: glenum.CCW, /** * If enable software frustum culling * @type {boolean} */ frustumCulling: true, /** * @type {boolean} */ receiveShadow: true, /** * @type {boolean} */ castShadow: true, /** * @type {boolean} */ ignorePicking: false, /** * @type {boolean} */ ignorePreZ: false, /** * @type {boolean} */ ignoreGBuffer: false, /** * @return {boolean} */ isRenderable: function() { return this.geometry && this.material && this.material.shader && !this.invisible && this.geometry.vertexCount > 0; }, /** * Before render hook * @type {Function} */ beforeRender: function(_gl) { }, /** * Before render hook * @type {Function} */ afterRender: function(_gl, renderStat) { }, getBoundingBox: function(filter, out) { out = Node$1.prototype.getBoundingBox.call(this, filter, out); if (this.geometry && this.geometry.boundingBox) { out.union(this.geometry.boundingBox); } return out; }, /** * Clone a new renderable * @function * @return {clay.Renderable} */ clone: /* @__PURE__ */ function() { var properties = [ "castShadow", "receiveShadow", "mode", "culling", "cullFace", "frontFace", "frustumCulling", "renderOrder", "lineWidth", "ignorePicking", "ignorePreZ", "ignoreGBuffer" ]; return function() { var renderable = Node$1.prototype.clone.call(this); renderable.geometry = this.geometry; renderable.material = this.material; for (var i = 0; i < properties.length; i++) { var name = properties[i]; if (renderable[name] !== this[name]) { renderable[name] = this[name]; } } return renderable; }; }() } ); Renderable.POINTS = glenum.POINTS; Renderable.LINES = glenum.LINES; Renderable.LINE_LOOP = glenum.LINE_LOOP; Renderable.LINE_STRIP = glenum.LINE_STRIP; Renderable.TRIANGLES = glenum.TRIANGLES; Renderable.TRIANGLE_STRIP = glenum.TRIANGLE_STRIP; Renderable.TRIANGLE_FAN = glenum.TRIANGLE_FAN; Renderable.BACK = glenum.BACK; Renderable.FRONT = glenum.FRONT; Renderable.FRONT_AND_BACK = glenum.FRONT_AND_BACK; Renderable.CW = glenum.CW; Renderable.CCW = glenum.CCW; var RayPicking = Base.extend( /** @lends clay.picking.RayPicking# */ { /** * Target scene * @type {clay.Scene} */ scene: null, /** * Target camera * @type {clay.Camera} */ camera: null, /** * Target renderer * @type {clay.Renderer} */ renderer: null }, function() { this._ray = new Ray(); this._ndc = new Vector2(); }, /** @lends clay.picking.RayPicking.prototype */ { /** * Pick the nearest intersection object in the scene * @param {number} x Mouse position x * @param {number} y Mouse position y * @param {boolean} [forcePickAll=false] ignore ignorePicking * @return {clay.picking.RayPicking~Intersection} */ pick: function(x, y, forcePickAll) { var out = this.pickAll(x, y, [], forcePickAll); return out[0] || null; }, /** * Pick all intersection objects, wich will be sorted from near to far * @param {number} x Mouse position x * @param {number} y Mouse position y * @param {Array} [output] * @param {boolean} [forcePickAll=false] ignore ignorePicking * @return {Array.} */ pickAll: function(x, y, output, forcePickAll) { this.renderer.screenToNDC(x, y, this._ndc); this.camera.castRay(this._ndc, this._ray); output = output || []; this._intersectNode(this.scene, output, forcePickAll || false); output.sort(this._intersectionCompareFunc); return output; }, _intersectNode: function(node, out, forcePickAll) { if (node instanceof Renderable && node.isRenderable()) { if ((!node.ignorePicking || forcePickAll) && // Only triangle mesh support ray picking (node.mode === glenum.TRIANGLES && node.geometry.isUseIndices() || node.geometry.pickByRay || node.geometry.pick)) { this._intersectRenderable(node, out); } } for (var i = 0; i < node._children.length; i++) { this._intersectNode(node._children[i], out, forcePickAll); } }, _intersectRenderable: function() { var v1 = new Vector3(); var v2 = new Vector3(); var v3 = new Vector3(); var ray = new Ray(); var worldInverse = new Matrix4(); return function(renderable, out) { var isSkinnedMesh = renderable.isSkinnedMesh(); ray.copy(this._ray); Matrix4.invert(worldInverse, renderable.worldTransform); if (!isSkinnedMesh) { ray.applyTransform(worldInverse); } var geometry = renderable.geometry; var bbox = isSkinnedMesh ? renderable.skeleton.boundingBox : geometry.boundingBox; if (bbox && !ray.intersectBoundingBox(bbox)) { return; } if (geometry.pick) { geometry.pick( this._ndc.x, this._ndc.y, this.renderer, this.camera, renderable, out ); return; } else if (geometry.pickByRay) { geometry.pickByRay(ray, renderable, out); return; } var cullBack = renderable.cullFace === glenum.BACK && renderable.frontFace === glenum.CCW || renderable.cullFace === glenum.FRONT && renderable.frontFace === glenum.CW; var point; var indices = geometry.indices; var positionAttr = geometry.attributes.position; var weightAttr = geometry.attributes.weight; var jointAttr = geometry.attributes.joint; var skinMatricesArray; var skinMatrices = []; if (!positionAttr || !positionAttr.value || !indices) { return; } if (isSkinnedMesh) { skinMatricesArray = renderable.skeleton.getSubSkinMatrices(renderable.__uid__, renderable.joints); for (var i = 0; i < renderable.joints.length; i++) { skinMatrices[i] = skinMatrices[i] || []; for (var k = 0; k < 16; k++) { skinMatrices[i][k] = skinMatricesArray[i * 16 + k]; } } var pos = []; var weight = []; var joint = []; var skinnedPos = []; var tmp = []; var skinnedPositionAttr = geometry.attributes.skinnedPosition; if (!skinnedPositionAttr || !skinnedPositionAttr.value) { geometry.createAttribute("skinnedPosition", "f", 3); skinnedPositionAttr = geometry.attributes.skinnedPosition; skinnedPositionAttr.init(geometry.vertexCount); } for (var i = 0; i < geometry.vertexCount; i++) { positionAttr.get(i, pos); weightAttr.get(i, weight); jointAttr.get(i, joint); weight[3] = 1 - weight[0] - weight[1] - weight[2]; vec3$f.set(skinnedPos, 0, 0, 0); for (var k = 0; k < 4; k++) { if (joint[k] >= 0 && weight[k] > 1e-4) { vec3$f.transformMat4(tmp, pos, skinMatrices[joint[k]]); vec3$f.scaleAndAdd(skinnedPos, skinnedPos, tmp, weight[k]); } } skinnedPositionAttr.set(i, skinnedPos); } } for (var i = 0; i < indices.length; i += 3) { var i1 = indices[i]; var i2 = indices[i + 1]; var i3 = indices[i + 2]; var finalPosAttr = isSkinnedMesh ? geometry.attributes.skinnedPosition : positionAttr; finalPosAttr.get(i1, v1.array); finalPosAttr.get(i2, v2.array); finalPosAttr.get(i3, v3.array); if (cullBack) { point = ray.intersectTriangle(v1, v2, v3, renderable.culling); } else { point = ray.intersectTriangle(v1, v3, v2, renderable.culling); } if (point) { var pointW = new Vector3(); if (!isSkinnedMesh) { Vector3.transformMat4(pointW, point, renderable.worldTransform); } else { Vector3.copy(pointW, point); } out.push(new RayPicking.Intersection( point, pointW, renderable, [i1, i2, i3], i / 3, Vector3.dist(pointW, this._ray.origin) )); } } }; }(), _intersectionCompareFunc: function(a, b) { return a.distance - b.distance; } } ); RayPicking.Intersection = function(point, pointWorld, target, triangle, triangleIndex, distance) { this.point = point; this.pointWorld = pointWorld; this.target = target; this.triangle = triangle; this.triangleIndex = triangleIndex; this.distance = distance; }; var DIRTY_PREFIX = "__dt__"; var Cache = function() { this._contextId = 0; this._caches = []; this._context = {}; }; Cache.prototype = { use: function(contextId, documentSchema) { var caches = this._caches; if (!caches[contextId]) { caches[contextId] = {}; if (documentSchema) { caches[contextId] = documentSchema(); } } this._contextId = contextId; this._context = caches[contextId]; }, put: function(key, value) { this._context[key] = value; }, get: function(key) { return this._context[key]; }, dirty: function(field) { field = field || ""; var key = DIRTY_PREFIX + field; this.put(key, true); }, dirtyAll: function(field) { field = field || ""; var key = DIRTY_PREFIX + field; var caches = this._caches; for (var i = 0; i < caches.length; i++) { if (caches[i]) { caches[i][key] = true; } } }, fresh: function(field) { field = field || ""; var key = DIRTY_PREFIX + field; this.put(key, false); }, freshAll: function(field) { field = field || ""; var key = DIRTY_PREFIX + field; var caches = this._caches; for (var i = 0; i < caches.length; i++) { if (caches[i]) { caches[i][key] = false; } } }, isDirty: function(field) { field = field || ""; var key = DIRTY_PREFIX + field; var context = this._context; return !context.hasOwnProperty(key) || context[key] === true; }, deleteContext: function(contextId) { delete this._caches[contextId]; this._context = {}; }, delete: function(key) { delete this._context[key]; }, clearAll: function() { this._caches = {}; }, getContext: function() { return this._context; }, eachContext: function(cb, context) { var keys2 = Object.keys(this._caches); keys2.forEach(function(key) { cb && cb.call(context, key); }); }, miss: function(key) { return !this._context.hasOwnProperty(key); } }; Cache.prototype.constructor = Cache; var Texture = Base.extend( /** @lends clay.Texture# */ { /** * Texture width, readonly when the texture source is image * @type {number} */ width: 512, /** * Texture height, readonly when the texture source is image * @type {number} */ height: 512, /** * Texel data type. * Possible values: * + {@link clay.Texture.UNSIGNED_BYTE} * + {@link clay.Texture.HALF_FLOAT} * + {@link clay.Texture.FLOAT} * + {@link clay.Texture.UNSIGNED_INT_24_8_WEBGL} * + {@link clay.Texture.UNSIGNED_INT} * @type {number} */ type: glenum.UNSIGNED_BYTE, /** * Format of texel data * Possible values: * + {@link clay.Texture.RGBA} * + {@link clay.Texture.DEPTH_COMPONENT} * + {@link clay.Texture.DEPTH_STENCIL} * @type {number} */ format: glenum.RGBA, /** * Texture wrap. Default to be REPEAT. * Possible values: * + {@link clay.Texture.CLAMP_TO_EDGE} * + {@link clay.Texture.REPEAT} * + {@link clay.Texture.MIRRORED_REPEAT} * @type {number} */ wrapS: glenum.REPEAT, /** * Texture wrap. Default to be REPEAT. * Possible values: * + {@link clay.Texture.CLAMP_TO_EDGE} * + {@link clay.Texture.REPEAT} * + {@link clay.Texture.MIRRORED_REPEAT} * @type {number} */ wrapT: glenum.REPEAT, /** * Possible values: * + {@link clay.Texture.NEAREST} * + {@link clay.Texture.LINEAR} * + {@link clay.Texture.NEAREST_MIPMAP_NEAREST} * + {@link clay.Texture.LINEAR_MIPMAP_NEAREST} * + {@link clay.Texture.NEAREST_MIPMAP_LINEAR} * + {@link clay.Texture.LINEAR_MIPMAP_LINEAR} * @type {number} */ minFilter: glenum.LINEAR_MIPMAP_LINEAR, /** * Possible values: * + {@link clay.Texture.NEAREST} * + {@link clay.Texture.LINEAR} * @type {number} */ magFilter: glenum.LINEAR, /** * If enable mimap. * @type {boolean} */ useMipmap: true, /** * Anisotropic filtering, enabled if value is larger than 1 * @see https://developer.mozilla.org/en-US/docs/Web/API/EXT_texture_filter_anisotropic * @type {number} */ anisotropic: 1, // pixelStorei parameters, not available when texture is used as render target // http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml /** * If flip in y axis for given image source * @type {boolean} * @default true */ flipY: true, /** * A flag to indicate if texture source is sRGB */ sRGB: true, /** * @type {number} * @default 4 */ unpackAlignment: 4, /** * @type {boolean} * @default false */ premultiplyAlpha: false, /** * Dynamic option for texture like video * @type {boolean} */ dynamic: false, NPOT: false, // PENDING // Init it here to avoid deoptimization when it's assigned in application dynamically __used: 0 }, function() { this._cache = new Cache(); }, /** @lends clay.Texture.prototype */ { getWebGLTexture: function(renderer) { var _gl = renderer.gl; var cache = this._cache; cache.use(renderer.__uid__); if (cache.miss("webgl_texture")) { cache.put("webgl_texture", _gl.createTexture()); } if (this.dynamic) { this.update(renderer); } else if (cache.isDirty()) { this.update(renderer); cache.fresh(); } return cache.get("webgl_texture"); }, bind: function() { }, unbind: function() { }, /** * Mark texture is dirty and update in the next frame */ dirty: function() { if (this._cache) { this._cache.dirtyAll(); } }, update: function(renderer) { }, // Update the common parameters of texture updateCommon: function(renderer) { var _gl = renderer.gl; _gl.pixelStorei(_gl.UNPACK_FLIP_Y_WEBGL, this.flipY); _gl.pixelStorei(_gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha); _gl.pixelStorei(_gl.UNPACK_ALIGNMENT, this.unpackAlignment); if (this.format === glenum.DEPTH_COMPONENT) { this.useMipmap = false; } var sRGBExt = renderer.getGLExtension("EXT_sRGB"); if (this.format === Texture.SRGB && !sRGBExt) { this.format = Texture.RGB; } if (this.format === Texture.SRGB_ALPHA && !sRGBExt) { this.format = Texture.RGBA; } this.NPOT = !this.isPowerOfTwo(); }, getAvailableWrapS: function() { if (this.NPOT) { return glenum.CLAMP_TO_EDGE; } return this.wrapS; }, getAvailableWrapT: function() { if (this.NPOT) { return glenum.CLAMP_TO_EDGE; } return this.wrapT; }, getAvailableMinFilter: function() { var minFilter = this.minFilter; if (this.NPOT || !this.useMipmap) { if (minFilter === glenum.NEAREST_MIPMAP_NEAREST || minFilter === glenum.NEAREST_MIPMAP_LINEAR) { return glenum.NEAREST; } else if (minFilter === glenum.LINEAR_MIPMAP_LINEAR || minFilter === glenum.LINEAR_MIPMAP_NEAREST) { return glenum.LINEAR; } else { return minFilter; } } else { return minFilter; } }, getAvailableMagFilter: function() { return this.magFilter; }, nextHighestPowerOfTwo: function(x) { --x; for (var i = 1; i < 32; i <<= 1) { x = x | x >> i; } return x + 1; }, /** * @param {clay.Renderer} renderer */ dispose: function(renderer) { var cache = this._cache; cache.use(renderer.__uid__); var webglTexture = cache.get("webgl_texture"); if (webglTexture) { renderer.gl.deleteTexture(webglTexture); } cache.deleteContext(renderer.__uid__); }, /** * Test if image of texture is valid and loaded. * @return {boolean} */ isRenderable: function() { }, /** * Test if texture size is power of two * @return {boolean} */ isPowerOfTwo: function() { } } ); Object.defineProperty(Texture.prototype, "width", { get: function() { return this._width; }, set: function(value) { this._width = value; } }); Object.defineProperty(Texture.prototype, "height", { get: function() { return this._height; }, set: function(value) { this._height = value; } }); Texture.BYTE = glenum.BYTE; Texture.UNSIGNED_BYTE = glenum.UNSIGNED_BYTE; Texture.SHORT = glenum.SHORT; Texture.UNSIGNED_SHORT = glenum.UNSIGNED_SHORT; Texture.INT = glenum.INT; Texture.UNSIGNED_INT = glenum.UNSIGNED_INT; Texture.FLOAT = glenum.FLOAT; Texture.HALF_FLOAT = 36193; Texture.UNSIGNED_INT_24_8_WEBGL = 34042; Texture.DEPTH_COMPONENT = glenum.DEPTH_COMPONENT; Texture.DEPTH_STENCIL = glenum.DEPTH_STENCIL; Texture.ALPHA = glenum.ALPHA; Texture.RGB = glenum.RGB; Texture.RGBA = glenum.RGBA; Texture.LUMINANCE = glenum.LUMINANCE; Texture.LUMINANCE_ALPHA = glenum.LUMINANCE_ALPHA; Texture.SRGB = 35904; Texture.SRGB_ALPHA = 35906; Texture.COMPRESSED_RGB_S3TC_DXT1_EXT = 33776; Texture.COMPRESSED_RGBA_S3TC_DXT1_EXT = 33777; Texture.COMPRESSED_RGBA_S3TC_DXT3_EXT = 33778; Texture.COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779; Texture.NEAREST = glenum.NEAREST; Texture.LINEAR = glenum.LINEAR; Texture.NEAREST_MIPMAP_NEAREST = glenum.NEAREST_MIPMAP_NEAREST; Texture.LINEAR_MIPMAP_NEAREST = glenum.LINEAR_MIPMAP_NEAREST; Texture.NEAREST_MIPMAP_LINEAR = glenum.NEAREST_MIPMAP_LINEAR; Texture.LINEAR_MIPMAP_LINEAR = glenum.LINEAR_MIPMAP_LINEAR; Texture.REPEAT = glenum.REPEAT; Texture.CLAMP_TO_EDGE = glenum.CLAMP_TO_EDGE; Texture.MIRRORED_REPEAT = glenum.MIRRORED_REPEAT; var Mesh = Renderable.extend( /** @lends clay.Mesh# */ { /** * Used when it is a skinned mesh * @type {clay.Skeleton} */ skeleton: null, /** * Joints indices Meshes can share the one skeleton instance and each mesh can use one part of joints. Joints indices indicate the index of joint in the skeleton instance * @type {number[]} */ joints: null }, function() { if (!this.joints) { this.joints = []; } }, { /** * Offset matrix used for multiple skinned mesh clone sharing one skeleton * @type {clay.Matrix4} */ offsetMatrix: null, isInstancedMesh: function() { return false; }, isSkinnedMesh: function() { return !!(this.skeleton && this.joints && this.joints.length > 0); }, clone: function() { var mesh2 = Renderable.prototype.clone.call(this); mesh2.skeleton = this.skeleton; if (this.joints) { mesh2.joints = this.joints.slice(); } return mesh2; } } ); Mesh.POINTS = glenum.POINTS; Mesh.LINES = glenum.LINES; Mesh.LINE_LOOP = glenum.LINE_LOOP; Mesh.LINE_STRIP = glenum.LINE_STRIP; Mesh.TRIANGLES = glenum.TRIANGLES; Mesh.TRIANGLE_STRIP = glenum.TRIANGLE_STRIP; Mesh.TRIANGLE_FAN = glenum.TRIANGLE_FAN; Mesh.BACK = glenum.BACK; Mesh.FRONT = glenum.FRONT; Mesh.FRONT_AND_BACK = glenum.FRONT_AND_BACK; Mesh.CW = glenum.CW; Mesh.CCW = glenum.CCW; var mathUtil = {}; mathUtil.isPowerOfTwo = function(value) { return (value & value - 1) === 0; }; mathUtil.nextPowerOfTwo = function(value) { value--; value |= value >> 1; value |= value >> 2; value |= value >> 4; value |= value >> 8; value |= value >> 16; value++; return value; }; mathUtil.nearestPowerOfTwo = function(value) { return Math.pow(2, Math.round(Math.log(value) / Math.LN2)); }; var isPowerOfTwo$2 = mathUtil.isPowerOfTwo; function nearestPowerOfTwo$1(val) { return Math.pow(2, Math.round(Math.log(val) / Math.LN2)); } function convertTextureToPowerOfTwo$1(texture, canvas) { var width = nearestPowerOfTwo$1(texture.width); var height = nearestPowerOfTwo$1(texture.height); canvas = canvas || document.createElement("canvas"); canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(texture.image, 0, 0, width, height); return canvas; } var Texture2D = Texture.extend(function() { return ( /** @lends clay.Texture2D# */ { /** * @type {?HTMLImageElement|HTMLCanvasElemnet} */ // TODO mark dirty when assigned. image: null, /** * Pixels data. Will be ignored if image is set. * @type {?Uint8Array|Float32Array} */ pixels: null, /** * @type {Array.} * @example * [{ * image: mipmap0, * pixels: null * }, { * image: mipmap1, * pixels: null * }, ....] */ mipmaps: [], /** * If convert texture to power-of-two * @type {boolean} */ convertToPOT: false } ); }, { textureType: "texture2D", update: function(renderer) { var _gl = renderer.gl; _gl.bindTexture(_gl.TEXTURE_2D, this._cache.get("webgl_texture")); this.updateCommon(renderer); var glFormat = this.format; var glType = this.type; var convertToPOT = !!(this.convertToPOT && !this.mipmaps.length && this.image && (this.wrapS === Texture.REPEAT || this.wrapT === Texture.REPEAT) && this.NPOT); _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, convertToPOT ? this.wrapS : this.getAvailableWrapS()); _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, convertToPOT ? this.wrapT : this.getAvailableWrapT()); _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, convertToPOT ? this.magFilter : this.getAvailableMagFilter()); _gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, convertToPOT ? this.minFilter : this.getAvailableMinFilter()); var anisotropicExt = renderer.getGLExtension("EXT_texture_filter_anisotropic"); if (anisotropicExt && this.anisotropic > 1) { _gl.texParameterf(_gl.TEXTURE_2D, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic); } if (glType === 36193) { var halfFloatExt = renderer.getGLExtension("OES_texture_half_float"); if (!halfFloatExt) { glType = glenum.FLOAT; } } if (this.mipmaps.length) { var width = this.width; var height = this.height; for (var i = 0; i < this.mipmaps.length; i++) { var mipmap = this.mipmaps[i]; this._updateTextureData(_gl, mipmap, i, width, height, glFormat, glType, false); width /= 2; height /= 2; } } else { this._updateTextureData(_gl, this, 0, this.width, this.height, glFormat, glType, convertToPOT); if (this.useMipmap && (!this.NPOT || convertToPOT)) { _gl.generateMipmap(_gl.TEXTURE_2D); } } _gl.bindTexture(_gl.TEXTURE_2D, null); }, _updateTextureData: function(_gl, data, level, width, height, glFormat, glType, convertToPOT) { if (data.image) { var imgData = data.image; if (convertToPOT) { this._potCanvas = convertTextureToPowerOfTwo$1(this, this._potCanvas); imgData = this._potCanvas; } _gl.texImage2D(_gl.TEXTURE_2D, level, glFormat, glFormat, glType, imgData); } else { if (glFormat <= Texture.COMPRESSED_RGBA_S3TC_DXT5_EXT && glFormat >= Texture.COMPRESSED_RGB_S3TC_DXT1_EXT) { _gl.compressedTexImage2D(_gl.TEXTURE_2D, level, glFormat, width, height, 0, data.pixels); } else { _gl.texImage2D(_gl.TEXTURE_2D, level, glFormat, width, height, 0, glFormat, glType, data.pixels); } } }, /** * @param {clay.Renderer} renderer * @memberOf clay.Texture2D.prototype */ generateMipmap: function(renderer) { var _gl = renderer.gl; if (this.useMipmap && !this.NPOT) { _gl.bindTexture(_gl.TEXTURE_2D, this._cache.get("webgl_texture")); _gl.generateMipmap(_gl.TEXTURE_2D); } }, isPowerOfTwo: function() { return isPowerOfTwo$2(this.width) && isPowerOfTwo$2(this.height); }, isRenderable: function() { if (this.image) { return this.image.width > 0 && this.image.height > 0; } else { return !!(this.width && this.height); } }, bind: function(renderer) { renderer.gl.bindTexture(renderer.gl.TEXTURE_2D, this.getWebGLTexture(renderer)); }, unbind: function(renderer) { renderer.gl.bindTexture(renderer.gl.TEXTURE_2D, null); }, load: function(src, crossOrigin) { var image = vendor.createImage(); if (crossOrigin) { image.crossOrigin = crossOrigin; } var self2 = this; image.onload = function() { self2.dirty(); self2.trigger("success", self2); }; image.onerror = function() { self2.trigger("error", self2); }; image.src = src; this.image = image; return this; } }); Object.defineProperty(Texture2D.prototype, "width", { get: function() { if (this.image) { return this.image.width; } return this._width; }, set: function(value) { if (this.image) { console.warn("Texture from image can't set width"); } else { if (this._width !== value) { this.dirty(); } this._width = value; } } }); Object.defineProperty(Texture2D.prototype, "height", { get: function() { if (this.image) { return this.image.height; } return this._height; }, set: function(value) { if (this.image) { console.warn("Texture from image can't set height"); } else { if (this._height !== value) { this.dirty(); } this._height = value; } } }); function getArrayCtorByType(type) { return { "byte": vendor.Int8Array, "ubyte": vendor.Uint8Array, "short": vendor.Int16Array, "ushort": vendor.Uint16Array }[type] || vendor.Float32Array; } function makeAttrKey(attrName) { return "attr_" + attrName; } function Attribute$1(name, type, size, semantic) { this.name = name; this.type = type; this.size = size; this.semantic = semantic || ""; this.value = null; switch (size) { case 1: this.get = function(idx) { return this.value[idx]; }; this.set = function(idx, value) { this.value[idx] = value; }; this.copy = function(target, source) { this.value[target] = this.value[target]; }; break; case 2: this.get = function(idx, out) { var arr = this.value; out[0] = arr[idx * 2]; out[1] = arr[idx * 2 + 1]; return out; }; this.set = function(idx, val) { var arr = this.value; arr[idx * 2] = val[0]; arr[idx * 2 + 1] = val[1]; }; this.copy = function(target, source) { var arr = this.value; source *= 2; target *= 2; arr[target] = arr[source]; arr[target + 1] = arr[source + 1]; }; break; case 3: this.get = function(idx, out) { var idx3 = idx * 3; var arr = this.value; out[0] = arr[idx3]; out[1] = arr[idx3 + 1]; out[2] = arr[idx3 + 2]; return out; }; this.set = function(idx, val) { var idx3 = idx * 3; var arr = this.value; arr[idx3] = val[0]; arr[idx3 + 1] = val[1]; arr[idx3 + 2] = val[2]; }; this.copy = function(target, source) { var arr = this.value; source *= 3; target *= 3; arr[target] = arr[source]; arr[target + 1] = arr[source + 1]; arr[target + 2] = arr[source + 2]; }; break; case 4: this.get = function(idx, out) { var arr = this.value; var idx4 = idx * 4; out[0] = arr[idx4]; out[1] = arr[idx4 + 1]; out[2] = arr[idx4 + 2]; out[3] = arr[idx4 + 3]; return out; }; this.set = function(idx, val) { var arr = this.value; var idx4 = idx * 4; arr[idx4] = val[0]; arr[idx4 + 1] = val[1]; arr[idx4 + 2] = val[2]; arr[idx4 + 3] = val[3]; }; this.copy = function(target, source) { var arr = this.value; source *= 4; target *= 4; arr[target] = arr[source]; arr[target + 1] = arr[source + 1]; arr[target + 2] = arr[source + 2]; arr[target + 3] = arr[source + 3]; }; } } Attribute$1.prototype.init = function(nVertex) { if (!this.value || this.value.length !== nVertex * this.size) { var ArrayConstructor = getArrayCtorByType(this.type); this.value = new ArrayConstructor(nVertex * this.size); } }; Attribute$1.prototype.fromArray = function(array) { var ArrayConstructor = getArrayCtorByType(this.type); var value; if (array[0] && array[0].length) { var n = 0; var size = this.size; value = new ArrayConstructor(array.length * size); for (var i = 0; i < array.length; i++) { for (var j = 0; j < size; j++) { value[n++] = array[i][j]; } } } else { value = new ArrayConstructor(array); } this.value = value; }; Attribute$1.prototype.clone = function(copyValue) { var ret2 = new Attribute$1(this.name, this.type, this.size, this.semantic); if (copyValue) { console.warn("todo"); } return ret2; }; function AttributeBuffer(name, type, buffer, size, semantic) { this.name = name; this.type = type; this.buffer = buffer; this.size = size; this.semantic = semantic; this.symbol = ""; this.needsRemove = false; } function IndicesBuffer(buffer) { this.buffer = buffer; this.count = 0; } var GeometryBase = Base.extend( function() { return ( /** @lends clay.GeometryBase# */ { /** * Attributes of geometry. * @type {Object.} */ attributes: {}, /** * Indices of geometry. * @type {Uint16Array|Uint32Array} */ indices: null, /** * Is vertices data dynamically updated. * Attributes value can't be changed after first render if dyanmic is false. * @type {boolean} */ dynamic: true, _enabledAttributes: null, // PENDING // Init it here to avoid deoptimization when it's assigned in application dynamically __used: 0 } ); }, function() { this._cache = new Cache(); this._attributeList = Object.keys(this.attributes); this.__vaoCache = {}; }, /** @lends clay.GeometryBase.prototype */ { /** * Main attribute will be used to count vertex number * @type {string} */ mainAttribute: "", /** * User defined picking algorithm instead of default * triangle ray intersection * x, y are NDC. * ```typescript * (x, y, renderer, camera, renderable, out) => boolean * ``` * @type {?Function} */ pick: null, /** * User defined ray picking algorithm instead of default * triangle ray intersection * ```typescript * (ray: clay.Ray, renderable: clay.Renderable, out: Array) => boolean * ``` * @type {?Function} */ pickByRay: null, /** * Mark attributes and indices in geometry needs to update. * Usually called after you change the data in attributes. */ dirty: function() { var enabledAttributes = this.getEnabledAttributes(); for (var i = 0; i < enabledAttributes.length; i++) { this.dirtyAttribute(enabledAttributes[i]); } this.dirtyIndices(); this._enabledAttributes = null; this._cache.dirty("any"); }, /** * Mark the indices needs to update. */ dirtyIndices: function() { this._cache.dirtyAll("indices"); }, /** * Mark the attributes needs to update. * @param {string} [attrName] */ dirtyAttribute: function(attrName) { this._cache.dirtyAll(makeAttrKey(attrName)); this._cache.dirtyAll("attributes"); }, /** * Get indices of triangle at given index. * @param {number} idx * @param {Array.} out * @return {Array.} */ getTriangleIndices: function(idx, out) { if (idx < this.triangleCount && idx >= 0) { if (!out) { out = []; } var indices = this.indices; out[0] = indices[idx * 3]; out[1] = indices[idx * 3 + 1]; out[2] = indices[idx * 3 + 2]; return out; } }, /** * Set indices of triangle at given index. * @param {number} idx * @param {Array.} arr */ setTriangleIndices: function(idx, arr) { var indices = this.indices; indices[idx * 3] = arr[0]; indices[idx * 3 + 1] = arr[1]; indices[idx * 3 + 2] = arr[2]; }, isUseIndices: function() { return !!this.indices; }, /** * Initialize indices from an array. * @param {Array} array */ initIndicesFromArray: function(array) { var value; var ArrayConstructor = this.vertexCount > 65535 ? vendor.Uint32Array : vendor.Uint16Array; if (array[0] && array[0].length) { var n = 0; var size = 3; value = new ArrayConstructor(array.length * size); for (var i = 0; i < array.length; i++) { for (var j = 0; j < size; j++) { value[n++] = array[i][j]; } } } else { value = new ArrayConstructor(array); } this.indices = value; }, /** * Create a new attribute * @param {string} name * @param {string} type * @param {number} size * @param {string} [semantic] */ createAttribute: function(name, type, size, semantic) { var attrib = new Attribute$1(name, type, size, semantic); if (this.attributes[name]) { this.removeAttribute(name); } this.attributes[name] = attrib; this._attributeList.push(name); return attrib; }, /** * Remove attribute * @param {string} name */ removeAttribute: function(name) { var attributeList = this._attributeList; var idx = attributeList.indexOf(name); if (idx >= 0) { attributeList.splice(idx, 1); delete this.attributes[name]; return true; } return false; }, /** * Get attribute * @param {string} name * @return {clay.GeometryBase.Attribute} */ getAttribute: function(name) { return this.attributes[name]; }, /** * Get enabled attributes name list * Attribute which has the same vertex number with position is treated as a enabled attribute * @return {string[]} */ getEnabledAttributes: function() { var enabledAttributes = this._enabledAttributes; var attributeList = this._attributeList; if (enabledAttributes) { return enabledAttributes; } var result = []; var nVertex = this.vertexCount; for (var i = 0; i < attributeList.length; i++) { var name = attributeList[i]; var attrib = this.attributes[name]; if (attrib.value) { if (attrib.value.length === nVertex * attrib.size) { result.push(name); } } } this._enabledAttributes = result; return result; }, getBufferChunks: function(renderer) { var cache = this._cache; cache.use(renderer.__uid__); var isAttributesDirty = cache.isDirty("attributes"); var isIndicesDirty = cache.isDirty("indices"); if (isAttributesDirty || isIndicesDirty) { this._updateBuffer(renderer.gl, isAttributesDirty, isIndicesDirty); var enabledAttributes = this.getEnabledAttributes(); for (var i = 0; i < enabledAttributes.length; i++) { cache.fresh(makeAttrKey(enabledAttributes[i])); } cache.fresh("attributes"); cache.fresh("indices"); } cache.fresh("any"); return cache.get("chunks"); }, _updateBuffer: function(_gl, isAttributesDirty, isIndicesDirty) { var cache = this._cache; var chunks = cache.get("chunks"); var firstUpdate = false; if (!chunks) { chunks = []; chunks[0] = { attributeBuffers: [], indicesBuffer: null }; cache.put("chunks", chunks); firstUpdate = true; } var chunk = chunks[0]; var attributeBuffers = chunk.attributeBuffers; var indicesBuffer = chunk.indicesBuffer; if (isAttributesDirty || firstUpdate) { var attributeList = this.getEnabledAttributes(); var attributeBufferMap = {}; if (!firstUpdate) { for (var i = 0; i < attributeBuffers.length; i++) { attributeBufferMap[attributeBuffers[i].name] = attributeBuffers[i]; } } for (var k = 0; k < attributeList.length; k++) { var name = attributeList[k]; var attribute = this.attributes[name]; var bufferInfo; if (!firstUpdate) { bufferInfo = attributeBufferMap[name]; } var buffer; if (bufferInfo) { buffer = bufferInfo.buffer; } else { buffer = _gl.createBuffer(); } if (cache.isDirty(makeAttrKey(name))) { _gl.bindBuffer(_gl.ARRAY_BUFFER, buffer); _gl.bufferData(_gl.ARRAY_BUFFER, attribute.value, this.dynamic ? _gl.DYNAMIC_DRAW : _gl.STATIC_DRAW); } attributeBuffers[k] = new AttributeBuffer(name, attribute.type, buffer, attribute.size, attribute.semantic); } for (var i = k; i < attributeBuffers.length; i++) { _gl.deleteBuffer(attributeBuffers[i].buffer); } attributeBuffers.length = k; } if (this.isUseIndices() && (isIndicesDirty || firstUpdate)) { if (!indicesBuffer) { indicesBuffer = new IndicesBuffer(_gl.createBuffer()); chunk.indicesBuffer = indicesBuffer; } indicesBuffer.count = this.indices.length; _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, indicesBuffer.buffer); _gl.bufferData(_gl.ELEMENT_ARRAY_BUFFER, this.indices, this.dynamic ? _gl.DYNAMIC_DRAW : _gl.STATIC_DRAW); } }, /** * Dispose geometry data in GL context. * @param {clay.Renderer} renderer */ dispose: function(renderer) { var cache = this._cache; cache.use(renderer.__uid__); var chunks = cache.get("chunks"); if (chunks) { for (var c = 0; c < chunks.length; c++) { var chunk = chunks[c]; for (var k = 0; k < chunk.attributeBuffers.length; k++) { var attribs = chunk.attributeBuffers[k]; renderer.gl.deleteBuffer(attribs.buffer); } if (chunk.indicesBuffer) { renderer.gl.deleteBuffer(chunk.indicesBuffer.buffer); } } } if (this.__vaoCache) { var vaoExt = renderer.getGLExtension("OES_vertex_array_object"); for (var id in this.__vaoCache) { var vao = this.__vaoCache[id].vao; if (vao) { vaoExt.deleteVertexArrayOES(vao); } } } this.__vaoCache = {}; cache.deleteContext(renderer.__uid__); } } ); if (Object.defineProperty) { Object.defineProperty(GeometryBase.prototype, "vertexCount", { enumerable: false, get: function() { var mainAttribute = this.attributes[this.mainAttribute]; if (!mainAttribute) { mainAttribute = this.attributes[this._attributeList[0]]; } if (!mainAttribute || !mainAttribute.value) { return 0; } return mainAttribute.value.length / mainAttribute.size; } }); Object.defineProperty(GeometryBase.prototype, "triangleCount", { enumerable: false, get: function() { var indices = this.indices; if (!indices) { return 0; } else { return indices.length / 3; } } }); } GeometryBase.STATIC_DRAW = glenum.STATIC_DRAW; GeometryBase.DYNAMIC_DRAW = glenum.DYNAMIC_DRAW; GeometryBase.STREAM_DRAW = glenum.STREAM_DRAW; GeometryBase.AttributeBuffer = AttributeBuffer; GeometryBase.IndicesBuffer = IndicesBuffer; GeometryBase.Attribute = Attribute$1; var vec3Create = vec3$f.create; var vec3Add = vec3$f.add; var vec3Set$1 = vec3$f.set; var Attribute = GeometryBase.Attribute; var Geometry = GeometryBase.extend( function() { return ( /** @lends clay.Geometry# */ { /** * Attributes of geometry. Including: * + `position` * + `texcoord0` * + `texcoord1` * + `normal` * + `tangent` * + `color` * + `weight` * + `joint` * + `barycentric` * * @type {Object.} */ attributes: { position: new Attribute("position", "float", 3, "POSITION"), texcoord0: new Attribute("texcoord0", "float", 2, "TEXCOORD_0"), texcoord1: new Attribute("texcoord1", "float", 2, "TEXCOORD_1"), normal: new Attribute("normal", "float", 3, "NORMAL"), tangent: new Attribute("tangent", "float", 4, "TANGENT"), color: new Attribute("color", "float", 4, "COLOR"), // Skinning attributes // Each vertex can be bind to 4 bones, because the // sum of weights is 1, so the weights is stored in vec3 and the last // can be calculated by 1-w.x-w.y-w.z weight: new Attribute("weight", "float", 3, "WEIGHT"), joint: new Attribute("joint", "float", 4, "JOINT"), // For wireframe display // http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/ barycentric: new Attribute("barycentric", "float", 3, null) }, /** * Calculated bounding box of geometry. * @type {clay.BoundingBox} */ boundingBox: null } ); }, /** @lends clay.Geometry.prototype */ { mainAttribute: "position", /** * Update boundingBox of Geometry */ updateBoundingBox: function() { var bbox = this.boundingBox; if (!bbox) { bbox = this.boundingBox = new BoundingBox(); } var posArr = this.attributes.position.value; if (posArr && posArr.length) { var min = bbox.min; var max = bbox.max; var minArr = min.array; var maxArr = max.array; vec3$f.set(minArr, posArr[0], posArr[1], posArr[2]); vec3$f.set(maxArr, posArr[0], posArr[1], posArr[2]); for (var i = 3; i < posArr.length; ) { var x = posArr[i++]; var y = posArr[i++]; var z = posArr[i++]; if (x < minArr[0]) { minArr[0] = x; } if (y < minArr[1]) { minArr[1] = y; } if (z < minArr[2]) { minArr[2] = z; } if (x > maxArr[0]) { maxArr[0] = x; } if (y > maxArr[1]) { maxArr[1] = y; } if (z > maxArr[2]) { maxArr[2] = z; } } min._dirty = true; max._dirty = true; } }, /** * Generate normals per vertex. */ generateVertexNormals: function() { if (!this.vertexCount) { return; } var indices = this.indices; var attributes = this.attributes; var positions = attributes.position.value; var normals = attributes.normal.value; if (!normals || normals.length !== positions.length) { normals = attributes.normal.value = new vendor.Float32Array(positions.length); } else { for (var i = 0; i < normals.length; i++) { normals[i] = 0; } } var p12 = vec3Create(); var p22 = vec3Create(); var p3 = vec3Create(); var v21 = vec3Create(); var v32 = vec3Create(); var n = vec3Create(); var len = indices ? indices.length : this.vertexCount; var i1, i2, i3; for (var f = 0; f < len; ) { if (indices) { i1 = indices[f++]; i2 = indices[f++]; i3 = indices[f++]; } else { i1 = f++; i2 = f++; i3 = f++; } vec3Set$1(p12, positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]); vec3Set$1(p22, positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]); vec3Set$1(p3, positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]); vec3$f.sub(v21, p12, p22); vec3$f.sub(v32, p22, p3); vec3$f.cross(n, v21, v32); for (var i = 0; i < 3; i++) { normals[i1 * 3 + i] = normals[i1 * 3 + i] + n[i]; normals[i2 * 3 + i] = normals[i2 * 3 + i] + n[i]; normals[i3 * 3 + i] = normals[i3 * 3 + i] + n[i]; } } for (var i = 0; i < normals.length; ) { vec3Set$1(n, normals[i], normals[i + 1], normals[i + 2]); vec3$f.normalize(n, n); normals[i++] = n[0]; normals[i++] = n[1]; normals[i++] = n[2]; } this.dirty(); }, /** * Generate normals per face. */ generateFaceNormals: function() { if (!this.vertexCount) { return; } if (!this.isUniqueVertex()) { this.generateUniqueVertex(); } var indices = this.indices; var attributes = this.attributes; var positions = attributes.position.value; var normals = attributes.normal.value; var p12 = vec3Create(); var p22 = vec3Create(); var p3 = vec3Create(); var v21 = vec3Create(); var v32 = vec3Create(); var n = vec3Create(); if (!normals) { normals = attributes.normal.value = new Float32Array(positions.length); } var len = indices ? indices.length : this.vertexCount; var i1, i2, i3; for (var f = 0; f < len; ) { if (indices) { i1 = indices[f++]; i2 = indices[f++]; i3 = indices[f++]; } else { i1 = f++; i2 = f++; i3 = f++; } vec3Set$1(p12, positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]); vec3Set$1(p22, positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]); vec3Set$1(p3, positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2]); vec3$f.sub(v21, p12, p22); vec3$f.sub(v32, p22, p3); vec3$f.cross(n, v21, v32); vec3$f.normalize(n, n); for (var i = 0; i < 3; i++) { normals[i1 * 3 + i] = n[i]; normals[i2 * 3 + i] = n[i]; normals[i3 * 3 + i] = n[i]; } } this.dirty(); }, /** * Generate tangents attributes. */ generateTangents: function() { if (!this.vertexCount) { return; } var nVertex = this.vertexCount; var attributes = this.attributes; if (!attributes.tangent.value) { attributes.tangent.value = new Float32Array(nVertex * 4); } var texcoords = attributes.texcoord0.value; var positions = attributes.position.value; var tangents = attributes.tangent.value; var normals = attributes.normal.value; if (!texcoords) { console.warn("Geometry without texcoords can't generate tangents."); return; } var tan1 = []; var tan2 = []; for (var i = 0; i < nVertex; i++) { tan1[i] = [0, 0, 0]; tan2[i] = [0, 0, 0]; } var sdir = [0, 0, 0]; var tdir = [0, 0, 0]; var indices = this.indices; var len = indices ? indices.length : this.vertexCount; var i1, i2, i3; for (var i = 0; i < len; ) { if (indices) { i1 = indices[i++]; i2 = indices[i++]; i3 = indices[i++]; } else { i1 = i++; i2 = i++; i3 = i++; } var st1s = texcoords[i1 * 2], st2s = texcoords[i2 * 2], st3s = texcoords[i3 * 2], st1t = texcoords[i1 * 2 + 1], st2t = texcoords[i2 * 2 + 1], st3t = texcoords[i3 * 2 + 1], p1x = positions[i1 * 3], p2x = positions[i2 * 3], p3x = positions[i3 * 3], p1y = positions[i1 * 3 + 1], p2y = positions[i2 * 3 + 1], p3y = positions[i3 * 3 + 1], p1z = positions[i1 * 3 + 2], p2z = positions[i2 * 3 + 2], p3z = positions[i3 * 3 + 2]; var x1 = p2x - p1x, x2 = p3x - p1x, y1 = p2y - p1y, y2 = p3y - p1y, z1 = p2z - p1z, z2 = p3z - p1z; var s1 = st2s - st1s, s2 = st3s - st1s, t1 = st2t - st1t, t2 = st3t - st1t; var r = 1 / (s1 * t2 - t1 * s2); sdir[0] = (t2 * x1 - t1 * x2) * r; sdir[1] = (t2 * y1 - t1 * y2) * r; sdir[2] = (t2 * z1 - t1 * z2) * r; tdir[0] = (s1 * x2 - s2 * x1) * r; tdir[1] = (s1 * y2 - s2 * y1) * r; tdir[2] = (s1 * z2 - s2 * z1) * r; vec3Add(tan1[i1], tan1[i1], sdir); vec3Add(tan1[i2], tan1[i2], sdir); vec3Add(tan1[i3], tan1[i3], sdir); vec3Add(tan2[i1], tan2[i1], tdir); vec3Add(tan2[i2], tan2[i2], tdir); vec3Add(tan2[i3], tan2[i3], tdir); } var tmp = vec3Create(); var nCrossT = vec3Create(); var n = vec3Create(); for (var i = 0; i < nVertex; i++) { n[0] = normals[i * 3]; n[1] = normals[i * 3 + 1]; n[2] = normals[i * 3 + 2]; var t = tan1[i]; vec3$f.scale(tmp, n, vec3$f.dot(n, t)); vec3$f.sub(tmp, t, tmp); vec3$f.normalize(tmp, tmp); vec3$f.cross(nCrossT, n, t); tangents[i * 4] = tmp[0]; tangents[i * 4 + 1] = tmp[1]; tangents[i * 4 + 2] = tmp[2]; tangents[i * 4 + 3] = vec3$f.dot(nCrossT, tan2[i]) < 0 ? -1 : 1; } this.dirty(); }, /** * If vertices are not shared by different indices. */ isUniqueVertex: function() { if (this.isUseIndices()) { return this.vertexCount === this.indices.length; } else { return true; } }, /** * Create a unique vertex for each index. */ generateUniqueVertex: function() { if (!this.vertexCount || !this.indices) { return; } if (this.indices.length > 65535) { this.indices = new vendor.Uint32Array(this.indices); } var attributes = this.attributes; var indices = this.indices; var attributeNameList = this.getEnabledAttributes(); var oldAttrValues = {}; for (var a = 0; a < attributeNameList.length; a++) { var name = attributeNameList[a]; oldAttrValues[name] = attributes[name].value; attributes[name].init(this.indices.length); } var cursor = 0; for (var i = 0; i < indices.length; i++) { var ii = indices[i]; for (var a = 0; a < attributeNameList.length; a++) { var name = attributeNameList[a]; var array = attributes[name].value; var size = attributes[name].size; for (var k = 0; k < size; k++) { array[cursor * size + k] = oldAttrValues[name][ii * size + k]; } } indices[i] = cursor; cursor++; } this.dirty(); }, /** * Generate barycentric coordinates for wireframe draw. */ generateBarycentric: function() { if (!this.vertexCount) { return; } if (!this.isUniqueVertex()) { this.generateUniqueVertex(); } var attributes = this.attributes; var array = attributes.barycentric.value; var indices = this.indices; if (array && array.length === indices.length * 3) { return; } array = attributes.barycentric.value = new Float32Array(indices.length * 3); for (var i = 0; i < (indices ? indices.length : this.vertexCount / 3); ) { for (var j = 0; j < 3; j++) { var ii = indices ? indices[i++] : i * 3 + j; array[ii * 3 + j] = 1; } } this.dirty(); }, /** * Apply transform to geometry attributes. * @param {clay.Matrix4} matrix */ applyTransform: function(matrix) { var attributes = this.attributes; var positions = attributes.position.value; var normals = attributes.normal.value; var tangents = attributes.tangent.value; matrix = matrix.array; var inverseTransposeMatrix = mat4$2.create(); mat4$2.invert(inverseTransposeMatrix, matrix); mat4$2.transpose(inverseTransposeMatrix, inverseTransposeMatrix); var vec3TransformMat4 = vec3$f.transformMat4; var vec3ForEach = vec3$f.forEach; vec3ForEach(positions, 3, 0, null, vec3TransformMat4, matrix); if (normals) { vec3ForEach(normals, 3, 0, null, vec3TransformMat4, inverseTransposeMatrix); } if (tangents) { vec3ForEach(tangents, 4, 0, null, vec3TransformMat4, inverseTransposeMatrix); } if (this.boundingBox) { this.updateBoundingBox(); } }, /** * Dispose geometry data in GL context. * @param {clay.Renderer} renderer */ dispose: function(renderer) { var cache = this._cache; cache.use(renderer.__uid__); var chunks = cache.get("chunks"); if (chunks) { for (var c = 0; c < chunks.length; c++) { var chunk = chunks[c]; for (var k = 0; k < chunk.attributeBuffers.length; k++) { var attribs = chunk.attributeBuffers[k]; renderer.gl.deleteBuffer(attribs.buffer); } if (chunk.indicesBuffer) { renderer.gl.deleteBuffer(chunk.indicesBuffer.buffer); } } } if (this.__vaoCache) { var vaoExt = renderer.getGLExtension("OES_vertex_array_object"); for (var id in this.__vaoCache) { var vao = this.__vaoCache[id].vao; if (vao) { vaoExt.deleteVertexArrayOES(vao); } } } this.__vaoCache = {}; cache.deleteContext(renderer.__uid__); } } ); Geometry.STATIC_DRAW = GeometryBase.STATIC_DRAW; Geometry.DYNAMIC_DRAW = GeometryBase.DYNAMIC_DRAW; Geometry.STREAM_DRAW = GeometryBase.STREAM_DRAW; Geometry.AttributeBuffer = GeometryBase.AttributeBuffer; Geometry.IndicesBuffer = GeometryBase.IndicesBuffer; Geometry.Attribute = Attribute; const calcAmbientSHLightEssl = "vec3 calcAmbientSHLight(int idx, vec3 N) {\n int offset = 9 * idx;\n return ambientSHLightCoefficients[0]\n + ambientSHLightCoefficients[1] * N.x\n + ambientSHLightCoefficients[2] * N.y\n + ambientSHLightCoefficients[3] * N.z\n + ambientSHLightCoefficients[4] * N.x * N.z\n + ambientSHLightCoefficients[5] * N.z * N.y\n + ambientSHLightCoefficients[6] * N.y * N.x\n + ambientSHLightCoefficients[7] * (3.0 * N.z * N.z - 1.0)\n + ambientSHLightCoefficients[8] * (N.x * N.x - N.y * N.y);\n}"; var uniformVec3Prefix = "uniform vec3 "; var uniformFloatPrefix = "uniform float "; var exportHeaderPrefix = "@export clay.header."; var exportEnd = "@end"; var unconfigurable = ":unconfigurable;"; const lightShader = [ exportHeaderPrefix + "directional_light", uniformVec3Prefix + "directionalLightDirection[DIRECTIONAL_LIGHT_COUNT]" + unconfigurable, uniformVec3Prefix + "directionalLightColor[DIRECTIONAL_LIGHT_COUNT]" + unconfigurable, exportEnd, exportHeaderPrefix + "ambient_light", uniformVec3Prefix + "ambientLightColor[AMBIENT_LIGHT_COUNT]" + unconfigurable, exportEnd, exportHeaderPrefix + "ambient_sh_light", uniformVec3Prefix + "ambientSHLightColor[AMBIENT_SH_LIGHT_COUNT]" + unconfigurable, uniformVec3Prefix + "ambientSHLightCoefficients[AMBIENT_SH_LIGHT_COUNT * 9]" + unconfigurable, calcAmbientSHLightEssl, exportEnd, exportHeaderPrefix + "ambient_cubemap_light", uniformVec3Prefix + "ambientCubemapLightColor[AMBIENT_CUBEMAP_LIGHT_COUNT]" + unconfigurable, "uniform samplerCube ambientCubemapLightCubemap[AMBIENT_CUBEMAP_LIGHT_COUNT]" + unconfigurable, "uniform sampler2D ambientCubemapLightBRDFLookup[AMBIENT_CUBEMAP_LIGHT_COUNT]" + unconfigurable, exportEnd, exportHeaderPrefix + "point_light", uniformVec3Prefix + "pointLightPosition[POINT_LIGHT_COUNT]" + unconfigurable, uniformFloatPrefix + "pointLightRange[POINT_LIGHT_COUNT]" + unconfigurable, uniformVec3Prefix + "pointLightColor[POINT_LIGHT_COUNT]" + unconfigurable, exportEnd, exportHeaderPrefix + "spot_light", uniformVec3Prefix + "spotLightPosition[SPOT_LIGHT_COUNT]" + unconfigurable, uniformVec3Prefix + "spotLightDirection[SPOT_LIGHT_COUNT]" + unconfigurable, uniformFloatPrefix + "spotLightRange[SPOT_LIGHT_COUNT]" + unconfigurable, uniformFloatPrefix + "spotLightUmbraAngleCosine[SPOT_LIGHT_COUNT]" + unconfigurable, uniformFloatPrefix + "spotLightPenumbraAngleCosine[SPOT_LIGHT_COUNT]" + unconfigurable, uniformFloatPrefix + "spotLightFalloffFactor[SPOT_LIGHT_COUNT]" + unconfigurable, uniformVec3Prefix + "spotLightColor[SPOT_LIGHT_COUNT]" + unconfigurable, exportEnd ].join("\n"); Shader["import"](lightShader); var Light = Node$1.extend( function() { return ( /** @lends clay.Light# */ { /** * Light RGB color * @type {number[]} */ color: [1, 1, 1], /** * Light intensity * @type {number} */ intensity: 1, // Config for shadow map /** * If light cast shadow * @type {boolean} */ castShadow: true, /** * Shadow map size * @type {number} */ shadowResolution: 512, /** * Light group, shader with same `lightGroup` will be affected * * Only useful in forward rendering * @type {number} */ group: 0 } ); }, /** @lends clay.Light.prototype. */ { /** * Light type * @type {string} * @memberOf clay.Light# */ type: "", /** * @return {clay.Light} * @memberOf clay.Light.prototype */ clone: function() { var light = Node$1.prototype.clone.call(this); light.color = Array.prototype.slice.call(this.color); light.intensity = this.intensity; light.castShadow = this.castShadow; light.shadowResolution = this.shadowResolution; return light; } } ); var Plane$1 = function(normal2, distance) { this.normal = normal2 || new Vector3(0, 1, 0); this.distance = distance || 0; }; Plane$1.prototype = { constructor: Plane$1, /** * Distance from a given point to the plane * @param {clay.Vector3} point * @return {number} */ distanceToPoint: function(point) { return vec3$f.dot(point.array, this.normal.array) - this.distance; }, /** * Calculate the projection point on the plane * @param {clay.Vector3} point * @param {clay.Vector3} out * @return {clay.Vector3} */ projectPoint: function(point, out) { if (!out) { out = new Vector3(); } var d = this.distanceToPoint(point); vec3$f.scaleAndAdd(out.array, point.array, this.normal.array, -d); out._dirty = true; return out; }, /** * Normalize the plane's normal and calculate the distance */ normalize: function() { var invLen = 1 / vec3$f.len(this.normal.array); vec3$f.scale(this.normal.array, invLen); this.distance *= invLen; }, /** * If the plane intersect a frustum * @param {clay.Frustum} Frustum * @return {boolean} */ intersectFrustum: function(frustum) { var coords = frustum.vertices; var normal2 = this.normal.array; var onPlane = vec3$f.dot(coords[0].array, normal2) > this.distance; for (var i = 1; i < 8; i++) { if (vec3$f.dot(coords[i].array, normal2) > this.distance != onPlane) { return true; } } }, /** * Calculate the intersection point between plane and a given line * @function * @param {clay.Vector3} start start point of line * @param {clay.Vector3} end end point of line * @param {clay.Vector3} [out] * @return {clay.Vector3} */ intersectLine: function() { var rd = vec3$f.create(); return function(start, end, out) { var d0 = this.distanceToPoint(start); var d1 = this.distanceToPoint(end); if (d0 > 0 && d1 > 0 || d0 < 0 && d1 < 0) { return null; } var pn = this.normal.array; var d = this.distance; var ro = start.array; vec3$f.sub(rd, end.array, start.array); vec3$f.normalize(rd, rd); var divider = vec3$f.dot(pn, rd); if (divider === 0) { return null; } if (!out) { out = new Vector3(); } var t = (vec3$f.dot(pn, ro) - d) / divider; vec3$f.scaleAndAdd(out.array, ro, rd, -t); out._dirty = true; return out; }; }(), /** * Apply an affine transform matrix to plane * @function * @return {clay.Matrix4} */ applyTransform: function() { var inverseTranspose = mat4$2.create(); var normalv4 = vec4$1.create(); var pointv4 = vec4$1.create(); pointv4[3] = 1; return function(m4) { m4 = m4.array; vec3$f.scale(pointv4, this.normal.array, this.distance); vec4$1.transformMat4(pointv4, pointv4, m4); this.distance = vec3$f.dot(pointv4, this.normal.array); mat4$2.invert(inverseTranspose, m4); mat4$2.transpose(inverseTranspose, inverseTranspose); normalv4[3] = 0; vec3$f.copy(normalv4, this.normal.array); vec4$1.transformMat4(normalv4, normalv4, inverseTranspose); vec3$f.copy(this.normal.array, normalv4); }; }(), /** * Copy from another plane * @param {clay.Vector3} plane */ copy: function(plane) { vec3$f.copy(this.normal.array, plane.normal.array); this.normal._dirty = true; this.distance = plane.distance; }, /** * Clone a new plane * @return {clay.Plane} */ clone: function() { var plane = new Plane$1(); plane.copy(this); return plane; } }; var vec3Set = vec3$f.set; var vec3Copy = vec3$f.copy; var vec3TranformMat4 = vec3$f.transformMat4; var mathMin = Math.min; var mathMax = Math.max; var Frustum = function() { this.planes = []; for (var i = 0; i < 6; i++) { this.planes.push(new Plane$1()); } this.boundingBox = new BoundingBox(); this.vertices = []; for (var i = 0; i < 8; i++) { this.vertices[i] = vec3$f.fromValues(0, 0, 0); } }; Frustum.prototype = { // http://web.archive.org/web/20120531231005/http://crazyjoke.free.fr/doc/3D/plane%20extraction.pdf /** * Set frustum from a projection matrix * @param {clay.Matrix4} projectionMatrix */ setFromProjection: function(projectionMatrix) { var planes = this.planes; var m = projectionMatrix.array; var m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3]; var m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7]; var m8 = m[8], m9 = m[9], m10 = m[10], m11 = m[11]; var m12 = m[12], m13 = m[13], m14 = m[14], m15 = m[15]; vec3Set(planes[0].normal.array, m3 - m0, m7 - m4, m11 - m8); planes[0].distance = -(m15 - m12); planes[0].normalize(); vec3Set(planes[1].normal.array, m3 + m0, m7 + m4, m11 + m8); planes[1].distance = -(m15 + m12); planes[1].normalize(); vec3Set(planes[2].normal.array, m3 + m1, m7 + m5, m11 + m9); planes[2].distance = -(m15 + m13); planes[2].normalize(); vec3Set(planes[3].normal.array, m3 - m1, m7 - m5, m11 - m9); planes[3].distance = -(m15 - m13); planes[3].normalize(); vec3Set(planes[4].normal.array, m3 - m2, m7 - m6, m11 - m10); planes[4].distance = -(m15 - m14); planes[4].normalize(); vec3Set(planes[5].normal.array, m3 + m2, m7 + m6, m11 + m10); planes[5].distance = -(m15 + m14); planes[5].normalize(); var boundingBox = this.boundingBox; var vertices = this.vertices; if (m15 === 0) { var aspect = m5 / m0; var zNear = -m14 / (m10 - 1); var zFar = -m14 / (m10 + 1); var farY = -zFar / m5; var nearY = -zNear / m5; boundingBox.min.set(-farY * aspect, -farY, zFar); boundingBox.max.set(farY * aspect, farY, zNear); vec3Set(vertices[0], -farY * aspect, -farY, zFar); vec3Set(vertices[1], -farY * aspect, farY, zFar); vec3Set(vertices[2], farY * aspect, -farY, zFar); vec3Set(vertices[3], farY * aspect, farY, zFar); vec3Set(vertices[4], -nearY * aspect, -nearY, zNear); vec3Set(vertices[5], -nearY * aspect, nearY, zNear); vec3Set(vertices[6], nearY * aspect, -nearY, zNear); vec3Set(vertices[7], nearY * aspect, nearY, zNear); } else { var left = (-1 - m12) / m0; var right = (1 - m12) / m0; var top = (1 - m13) / m5; var bottom = (-1 - m13) / m5; var near = (-1 - m14) / m10; var far = (1 - m14) / m10; boundingBox.min.set(Math.min(left, right), Math.min(bottom, top), Math.min(far, near)); boundingBox.max.set(Math.max(right, left), Math.max(top, bottom), Math.max(near, far)); var min = boundingBox.min.array; var max = boundingBox.max.array; vec3Set(vertices[0], min[0], min[1], min[2]); vec3Set(vertices[1], min[0], max[1], min[2]); vec3Set(vertices[2], max[0], min[1], min[2]); vec3Set(vertices[3], max[0], max[1], min[2]); vec3Set(vertices[4], min[0], min[1], max[2]); vec3Set(vertices[5], min[0], max[1], max[2]); vec3Set(vertices[6], max[0], min[1], max[2]); vec3Set(vertices[7], max[0], max[1], max[2]); } }, /** * Apply a affine transform matrix and set to the given bounding box * @function * @param {clay.BoundingBox} * @param {clay.Matrix4} * @return {clay.BoundingBox} */ getTransformedBoundingBox: function() { var tmpVec3 = vec3$f.create(); return function(bbox, matrix) { var vertices = this.vertices; var m4 = matrix.array; var min = bbox.min; var max = bbox.max; var minArr = min.array; var maxArr = max.array; var v = vertices[0]; vec3TranformMat4(tmpVec3, v, m4); vec3Copy(minArr, tmpVec3); vec3Copy(maxArr, tmpVec3); for (var i = 1; i < 8; i++) { v = vertices[i]; vec3TranformMat4(tmpVec3, v, m4); minArr[0] = mathMin(tmpVec3[0], minArr[0]); minArr[1] = mathMin(tmpVec3[1], minArr[1]); minArr[2] = mathMin(tmpVec3[2], minArr[2]); maxArr[0] = mathMax(tmpVec3[0], maxArr[0]); maxArr[1] = mathMax(tmpVec3[1], maxArr[1]); maxArr[2] = mathMax(tmpVec3[2], maxArr[2]); } min._dirty = true; max._dirty = true; return bbox; }; }() }; var Camera = Node$1.extend( function() { return ( /** @lends clay.Camera# */ { /** * Camera projection matrix * @type {clay.Matrix4} */ projectionMatrix: new Matrix4(), /** * Inverse of camera projection matrix * @type {clay.Matrix4} */ invProjectionMatrix: new Matrix4(), /** * View matrix, equal to inverse of camera's world matrix * @type {clay.Matrix4} */ viewMatrix: new Matrix4(), /** * Camera frustum in view space * @type {clay.Frustum} */ frustum: new Frustum() } ); }, function() { this.update(true); }, /** @lends clay.Camera.prototype */ { update: function(force) { Node$1.prototype.update.call(this, force); Matrix4.invert(this.viewMatrix, this.worldTransform); this.updateProjectionMatrix(); Matrix4.invert(this.invProjectionMatrix, this.projectionMatrix); this.frustum.setFromProjection(this.projectionMatrix); }, /** * Set camera view matrix */ setViewMatrix: function(viewMatrix) { Matrix4.copy(this.viewMatrix, viewMatrix); Matrix4.invert(this.worldTransform, viewMatrix); this.decomposeWorldTransform(); }, /** * Decompose camera projection matrix */ decomposeProjectionMatrix: function() { }, /** * Set camera projection matrix * @param {clay.Matrix4} projectionMatrix */ setProjectionMatrix: function(projectionMatrix) { Matrix4.copy(this.projectionMatrix, projectionMatrix); Matrix4.invert(this.invProjectionMatrix, projectionMatrix); this.decomposeProjectionMatrix(); }, /** * Update projection matrix, called after update */ updateProjectionMatrix: function() { }, /** * Cast a picking ray from camera near plane to far plane * @function * @param {clay.Vector2} ndc * @param {clay.Ray} [out] * @return {clay.Ray} */ castRay: function() { var v4 = vec4$1.create(); return function(ndc2, out) { var ray = out !== void 0 ? out : new Ray(); var x = ndc2.array[0]; var y = ndc2.array[1]; vec4$1.set(v4, x, y, -1, 1); vec4$1.transformMat4(v4, v4, this.invProjectionMatrix.array); vec4$1.transformMat4(v4, v4, this.worldTransform.array); vec3$f.scale(ray.origin.array, v4, 1 / v4[3]); vec4$1.set(v4, x, y, 1, 1); vec4$1.transformMat4(v4, v4, this.invProjectionMatrix.array); vec4$1.transformMat4(v4, v4, this.worldTransform.array); vec3$f.scale(v4, v4, 1 / v4[3]); vec3$f.sub(ray.direction.array, v4, ray.origin.array); vec3$f.normalize(ray.direction.array, ray.direction.array); ray.direction._dirty = true; ray.origin._dirty = true; return ray; }; }() /** * @function * @name clone * @return {clay.Camera} * @memberOf clay.Camera.prototype */ } ); var IDENTITY = mat4$2.create(); var WORLDVIEW = mat4$2.create(); var programKeyCache = {}; function getProgramKey(lightNumbers) { var defineStr = []; var lightTypes = Object.keys(lightNumbers); lightTypes.sort(); for (var i = 0; i < lightTypes.length; i++) { var lightType = lightTypes[i]; defineStr.push(lightType + " " + lightNumbers[lightType]); } var key = defineStr.join("\n"); if (programKeyCache[key]) { return programKeyCache[key]; } var id = util.genGUID(); programKeyCache[key] = id; return id; } function RenderList() { this.opaque = []; this.transparent = []; this._opaqueCount = 0; this._transparentCount = 0; } RenderList.prototype.startCount = function() { this._opaqueCount = 0; this._transparentCount = 0; }; RenderList.prototype.add = function(object, isTransparent) { if (isTransparent) { this.transparent[this._transparentCount++] = object; } else { this.opaque[this._opaqueCount++] = object; } }; RenderList.prototype.endCount = function() { this.transparent.length = this._transparentCount; this.opaque.length = this._opaqueCount; }; var Scene = Node$1.extend( function() { return ( /** @lends clay.Scene# */ { /** * Global material of scene * @type {clay.Material} */ material: null, lights: [], /** * Scene bounding box in view space. * Used when camera needs to adujst the near and far plane automatically * so that the view frustum contains the visible objects as tightly as possible. * Notice: * It is updated after rendering (in the step of frustum culling passingly). So may be not so accurate, but saves a lot of calculation * * @type {clay.BoundingBox} */ viewBoundingBoxLastFrame: new BoundingBox(), // Uniforms for shadow map. shadowUniforms: {}, _cameraList: [], // Properties to save the light information in the scene // Will be set in the render function _lightUniforms: {}, _previousLightNumber: {}, _lightNumber: { // groupId: { // POINT_LIGHT: 0, // DIRECTIONAL_LIGHT: 0, // SPOT_LIGHT: 0, // AMBIENT_LIGHT: 0, // AMBIENT_SH_LIGHT: 0 // } }, _lightProgramKeys: {}, _nodeRepository: {}, _renderLists: new LRU$1(20) } ); }, function() { this._scene = this; }, /** @lends clay.Scene.prototype. */ { // Add node to scene addToScene: function(node) { if (node instanceof Camera) { if (this._cameraList.length > 0) { console.warn("Found multiple camera in one scene. Use the fist one."); } this._cameraList.push(node); } else if (node instanceof Light) { this.lights.push(node); } if (node.name) { this._nodeRepository[node.name] = node; } }, // Remove node from scene removeFromScene: function(node) { var idx; if (node instanceof Camera) { idx = this._cameraList.indexOf(node); if (idx >= 0) { this._cameraList.splice(idx, 1); } } else if (node instanceof Light) { idx = this.lights.indexOf(node); if (idx >= 0) { this.lights.splice(idx, 1); } } if (node.name) { delete this._nodeRepository[node.name]; } }, /** * Get node by name * @param {string} name * @return {Node} * @DEPRECATED */ getNode: function(name) { return this._nodeRepository[name]; }, /** * Set main camera of the scene. * @param {claygl.Camera} camera */ setMainCamera: function(camera2) { var idx = this._cameraList.indexOf(camera2); if (idx >= 0) { this._cameraList.splice(idx, 1); } this._cameraList.unshift(camera2); }, /** * Get main camera of the scene. */ getMainCamera: function() { return this._cameraList[0]; }, getLights: function() { return this.lights; }, updateLights: function() { var lights = this.lights; this._previousLightNumber = this._lightNumber; var lightNumber = {}; for (var i = 0; i < lights.length; i++) { var light = lights[i]; if (light.invisible) { continue; } var group = light.group; if (!lightNumber[group]) { lightNumber[group] = {}; } lightNumber[group][light.type] = lightNumber[group][light.type] || 0; lightNumber[group][light.type]++; } this._lightNumber = lightNumber; for (var groupId in lightNumber) { this._lightProgramKeys[groupId] = getProgramKey(lightNumber[groupId]); } this._updateLightUniforms(); }, /** * Clone a node and it's children, including mesh, camera, light, etc. * Unlike using `Node#clone`. It will clone skeleton and remap the joints. Material will also be cloned. * * @param {clay.Node} node * @return {clay.Node} */ cloneNode: function(node) { var newNode = node.clone(); var clonedNodesMap = {}; function buildNodesMap(sNode, tNode) { clonedNodesMap[sNode.__uid__] = tNode; for (var i = 0; i < sNode._children.length; i++) { var sChild = sNode._children[i]; var tChild = tNode._children[i]; buildNodesMap(sChild, tChild); } } buildNodesMap(node, newNode); newNode.traverse(function(newChild) { if (newChild.skeleton) { newChild.skeleton = newChild.skeleton.clone(clonedNodesMap); } if (newChild.material) { newChild.material = newChild.material.clone(); } }); return newNode; }, /** * Traverse the scene and add the renderable object to the render list. * It needs camera for the frustum culling. * * @param {clay.Camera} camera * @param {boolean} updateSceneBoundingBox * @return {clay.Scene.RenderList} */ updateRenderList: function(camera2, updateSceneBoundingBox) { var id = camera2.__uid__; var renderList = this._renderLists.get(id); if (!renderList) { renderList = new RenderList(); this._renderLists.put(id, renderList); } renderList.startCount(); if (updateSceneBoundingBox) { this.viewBoundingBoxLastFrame.min.set(Infinity, Infinity, Infinity); this.viewBoundingBoxLastFrame.max.set(-Infinity, -Infinity, -Infinity); } var sceneMaterialTransparent = this.material && this.material.transparent || false; this._doUpdateRenderList(this, camera2, sceneMaterialTransparent, renderList, updateSceneBoundingBox); renderList.endCount(); return renderList; }, /** * Get render list. Used after {@link clay.Scene#updateRenderList} * @param {clay.Camera} camera * @return {clay.Scene.RenderList} */ getRenderList: function(camera2) { return this._renderLists.get(camera2.__uid__); }, _doUpdateRenderList: function(parent, camera2, sceneMaterialTransparent, renderList, updateSceneBoundingBox) { if (parent.invisible) { return; } for (var i = 0; i < parent._children.length; i++) { var child = parent._children[i]; if (child.isRenderable()) { var worldM = child.isSkinnedMesh() ? IDENTITY : child.worldTransform.array; var geometry = child.geometry; mat4$2.multiplyAffine(WORLDVIEW, camera2.viewMatrix.array, worldM); if (updateSceneBoundingBox && !geometry.boundingBox || !this.isFrustumCulled(child, camera2, WORLDVIEW)) { renderList.add(child, child.material.transparent || sceneMaterialTransparent); } } if (child._children.length > 0) { this._doUpdateRenderList(child, camera2, sceneMaterialTransparent, renderList, updateSceneBoundingBox); } } }, /** * If an scene object is culled by camera frustum * * Object can be a renderable or a light * * @param {clay.Node} object * @param {clay.Camera} camera * @param {Array.} worldViewMat represented with array * @param {Array.} projectionMat represented with array */ isFrustumCulled: function() { var cullingBoundingBox = new BoundingBox(); var cullingMatrix = new Matrix4(); return function(object, camera2, worldViewMat) { var geoBBox = object.boundingBox; if (!geoBBox) { if (object.skeleton && object.skeleton.boundingBox) { geoBBox = object.skeleton.boundingBox; } else { geoBBox = object.geometry.boundingBox; } } if (!geoBBox) { return false; } cullingMatrix.array = worldViewMat; cullingBoundingBox.transformFrom(geoBBox, cullingMatrix); if (object.castShadow) { this.viewBoundingBoxLastFrame.union(cullingBoundingBox); } if (object.frustumCulling) { if (!cullingBoundingBox.intersectBoundingBox(camera2.frustum.boundingBox)) { return true; } cullingMatrix.array = camera2.projectionMatrix.array; if (cullingBoundingBox.max.array[2] > 0 && cullingBoundingBox.min.array[2] < 0) { cullingBoundingBox.max.array[2] = -1e-20; } cullingBoundingBox.applyProjection(cullingMatrix); var min = cullingBoundingBox.min.array; var max = cullingBoundingBox.max.array; if (max[0] < -1 || min[0] > 1 || max[1] < -1 || min[1] > 1 || max[2] < -1 || min[2] > 1) { return true; } } return false; }; }(), _updateLightUniforms: function() { var lights = this.lights; lights.sort(lightSortFunc); var lightUniforms = this._lightUniforms; for (var group in lightUniforms) { for (var symbol in lightUniforms[group]) { lightUniforms[group][symbol].value.length = 0; } } for (var i = 0; i < lights.length; i++) { var light = lights[i]; if (light.invisible) { continue; } var group = light.group; for (var symbol in light.uniformTemplates) { var uniformTpl = light.uniformTemplates[symbol]; var value = uniformTpl.value(light); if (value == null) { continue; } if (!lightUniforms[group]) { lightUniforms[group] = {}; } if (!lightUniforms[group][symbol]) { lightUniforms[group][symbol] = { type: "", value: [] }; } var lu = lightUniforms[group][symbol]; lu.type = uniformTpl.type + "v"; switch (uniformTpl.type) { case "1i": case "1f": case "t": lu.value.push(value); break; case "2f": case "3f": case "4f": for (var j = 0; j < value.length; j++) { lu.value.push(value[j]); } break; default: console.error("Unkown light uniform type " + uniformTpl.type); } } } }, getLightGroups: function() { var lightGroups = []; for (var groupId in this._lightNumber) { lightGroups.push(groupId); } return lightGroups; }, getNumberChangedLightGroups: function() { var lightGroups = []; for (var groupId in this._lightNumber) { if (this.isLightNumberChanged(groupId)) { lightGroups.push(groupId); } } return lightGroups; }, // Determine if light group is different with since last frame // Used to determine whether to update shader and scene's uniforms in Renderer.render isLightNumberChanged: function(lightGroup) { var prevLightNumber = this._previousLightNumber; var currentLightNumber = this._lightNumber; for (var type in currentLightNumber[lightGroup]) { if (!prevLightNumber[lightGroup]) { return true; } if (currentLightNumber[lightGroup][type] !== prevLightNumber[lightGroup][type]) { return true; } } for (var type in prevLightNumber[lightGroup]) { if (!currentLightNumber[lightGroup]) { return true; } if (currentLightNumber[lightGroup][type] !== prevLightNumber[lightGroup][type]) { return true; } } return false; }, getLightsNumbers: function(lightGroup) { return this._lightNumber[lightGroup]; }, getProgramKey: function(lightGroup) { return this._lightProgramKeys[lightGroup]; }, setLightUniforms: /* @__PURE__ */ function() { function setUniforms(uniforms, program, renderer) { for (var symbol in uniforms) { var lu = uniforms[symbol]; if (lu.type === "tv") { if (!program.hasUniform(symbol)) { continue; } var texSlots = []; for (var i = 0; i < lu.value.length; i++) { var texture = lu.value[i]; var slot = program.takeCurrentTextureSlot(renderer, texture); texSlots.push(slot); } program.setUniform(renderer.gl, "1iv", symbol, texSlots); } else { program.setUniform(renderer.gl, lu.type, symbol, lu.value); } } } return function(program, lightGroup, renderer) { setUniforms(this._lightUniforms[lightGroup], program, renderer); setUniforms(this.shadowUniforms, program, renderer); }; }(), /** * Dispose self, clear all the scene objects * But resources of gl like texuture, shader will not be disposed. * Mostly you should use disposeScene method in Renderer to do dispose. */ dispose: function() { this.material = null; this._opaqueList = []; this._transparentList = []; this.lights = []; this._lightUniforms = {}; this._lightNumber = {}; this._nodeRepository = {}; } } ); function lightSortFunc(a, b) { if (b.castShadow && !a.castShadow) { return true; } } var Entry = /* @__PURE__ */ function() { function Entry2(val) { this.value = val; } return Entry2; }(); var LinkedList = function() { function LinkedList2() { this._len = 0; } LinkedList2.prototype.insert = function(val) { var entry = new Entry(val); this.insertEntry(entry); return entry; }; LinkedList2.prototype.insertEntry = function(entry) { if (!this.head) { this.head = this.tail = entry; } else { this.tail.next = entry; entry.prev = this.tail; entry.next = null; this.tail = entry; } this._len++; }; LinkedList2.prototype.remove = function(entry) { var prev = entry.prev; var next = entry.next; if (prev) { prev.next = next; } else { this.head = next; } if (next) { next.prev = prev; } else { this.tail = prev; } entry.next = entry.prev = null; this._len--; }; LinkedList2.prototype.len = function() { return this._len; }; LinkedList2.prototype.clear = function() { this.head = this.tail = null; this._len = 0; }; return LinkedList2; }(); var LRU = function() { function LRU2(maxSize) { this._list = new LinkedList(); this._maxSize = 10; this._map = {}; this._maxSize = maxSize; } LRU2.prototype.put = function(key, value) { var list = this._list; var map2 = this._map; var removed = null; if (map2[key] == null) { var len = list.len(); var entry = this._lastRemovedEntry; if (len >= this._maxSize && len > 0) { var leastUsedEntry = list.head; list.remove(leastUsedEntry); delete map2[leastUsedEntry.key]; removed = leastUsedEntry.value; this._lastRemovedEntry = leastUsedEntry; } if (entry) { entry.value = value; } else { entry = new Entry(value); } entry.key = key; list.insertEntry(entry); map2[key] = entry; } return removed; }; LRU2.prototype.get = function(key) { var entry = this._map[key]; var list = this._list; if (entry != null) { if (entry !== list.tail) { list.remove(entry); list.insertEntry(entry); } return entry.value; } }; LRU2.prototype.clear = function() { this._list.clear(); this._map = {}; }; LRU2.prototype.len = function() { return this._list.len(); }; return LRU2; }(); var isPowerOfTwo$1 = mathUtil.isPowerOfTwo; var targetList = ["px", "nx", "py", "ny", "pz", "nz"]; var TextureCube = Texture.extend(function() { return ( /** @lends clay.TextureCube# */ { /** * @type {boolean} * @default false */ // PENDING cubemap should not flipY in default. // flipY: false, /** * @type {Object} * @property {?HTMLImageElement|HTMLCanvasElemnet} px * @property {?HTMLImageElement|HTMLCanvasElemnet} nx * @property {?HTMLImageElement|HTMLCanvasElemnet} py * @property {?HTMLImageElement|HTMLCanvasElemnet} ny * @property {?HTMLImageElement|HTMLCanvasElemnet} pz * @property {?HTMLImageElement|HTMLCanvasElemnet} nz */ image: { px: null, nx: null, py: null, ny: null, pz: null, nz: null }, /** * Pixels data of each side. Will be ignored if images are set. * @type {Object} * @property {?Uint8Array} px * @property {?Uint8Array} nx * @property {?Uint8Array} py * @property {?Uint8Array} ny * @property {?Uint8Array} pz * @property {?Uint8Array} nz */ pixels: { px: null, nx: null, py: null, ny: null, pz: null, nz: null }, /** * @type {Array.} */ mipmaps: [] } ); }, { textureType: "textureCube", update: function(renderer) { var _gl = renderer.gl; _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this._cache.get("webgl_texture")); this.updateCommon(renderer); var glFormat = this.format; var glType = this.type; _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_S, this.getAvailableWrapS()); _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_T, this.getAvailableWrapT()); _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_MAG_FILTER, this.getAvailableMagFilter()); _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_MIN_FILTER, this.getAvailableMinFilter()); var anisotropicExt = renderer.getGLExtension("EXT_texture_filter_anisotropic"); if (anisotropicExt && this.anisotropic > 1) { _gl.texParameterf(_gl.TEXTURE_CUBE_MAP, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic); } if (glType === 36193) { var halfFloatExt = renderer.getGLExtension("OES_texture_half_float"); if (!halfFloatExt) { glType = glenum.FLOAT; } } if (this.mipmaps.length) { var width = this.width; var height = this.height; for (var i = 0; i < this.mipmaps.length; i++) { var mipmap = this.mipmaps[i]; this._updateTextureData(_gl, mipmap, i, width, height, glFormat, glType); width /= 2; height /= 2; } } else { this._updateTextureData(_gl, this, 0, this.width, this.height, glFormat, glType); if (!this.NPOT && this.useMipmap) { _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP); } } _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, null); }, _updateTextureData: function(_gl, data, level, width, height, glFormat, glType) { for (var i = 0; i < 6; i++) { var target = targetList[i]; var img = data.image && data.image[target]; if (img) { _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, glFormat, glFormat, glType, img); } else { _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, glFormat, width, height, 0, glFormat, glType, data.pixels && data.pixels[target]); } } }, /** * @param {clay.Renderer} renderer * @memberOf clay.TextureCube.prototype */ generateMipmap: function(renderer) { var _gl = renderer.gl; if (this.useMipmap && !this.NPOT) { _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this._cache.get("webgl_texture")); _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP); } }, bind: function(renderer) { renderer.gl.bindTexture(renderer.gl.TEXTURE_CUBE_MAP, this.getWebGLTexture(renderer)); }, unbind: function(renderer) { renderer.gl.bindTexture(renderer.gl.TEXTURE_CUBE_MAP, null); }, // Overwrite the isPowerOfTwo method isPowerOfTwo: function() { if (this.image.px) { return isPowerOfTwo$1(this.image.px.width) && isPowerOfTwo$1(this.image.px.height); } else { return isPowerOfTwo$1(this.width) && isPowerOfTwo$1(this.height); } }, isRenderable: function() { if (this.image.px) { return isImageRenderable(this.image.px) && isImageRenderable(this.image.nx) && isImageRenderable(this.image.py) && isImageRenderable(this.image.ny) && isImageRenderable(this.image.pz) && isImageRenderable(this.image.nz); } else { return !!(this.width && this.height); } }, load: function(imageList, crossOrigin) { var loading = 0; var self2 = this; util.each(imageList, function(src, target) { var image = vendor.createImage(); if (crossOrigin) { image.crossOrigin = crossOrigin; } image.onload = function() { loading--; if (loading === 0) { self2.dirty(); self2.trigger("success", self2); } }; image.onerror = function() { loading--; }; loading++; image.src = src; self2.image[target] = image; }); return this; } }); Object.defineProperty(TextureCube.prototype, "width", { get: function() { if (this.image && this.image.px) { return this.image.px.width; } return this._width; }, set: function(value) { if (this.image && this.image.px) { console.warn("Texture from image can't set width"); } else { if (this._width !== value) { this.dirty(); } this._width = value; } } }); Object.defineProperty(TextureCube.prototype, "height", { get: function() { if (this.image && this.image.px) { return this.image.px.height; } return this._height; }, set: function(value) { if (this.image && this.image.px) { console.warn("Texture from image can't set height"); } else { if (this._height !== value) { this.dirty(); } this._height = value; } } }); function isImageRenderable(image) { return image.width > 0 && image.height > 0; } var Perspective = Camera.extend( /** @lends clay.camera.Perspective# */ { /** * Vertical field of view in degrees * @type {number} */ fov: 50, /** * Aspect ratio, typically viewport width / height * @type {number} */ aspect: 1, /** * Near bound of the frustum * @type {number} */ near: 0.1, /** * Far bound of the frustum * @type {number} */ far: 2e3 }, /** @lends clay.camera.Perspective.prototype */ { updateProjectionMatrix: function() { var rad2 = this.fov / 180 * Math.PI; this.projectionMatrix.perspective(rad2, this.aspect, this.near, this.far); }, decomposeProjectionMatrix: function() { var m = this.projectionMatrix.array; var rad2 = Math.atan(1 / m[5]) * 2; this.fov = rad2 / Math.PI * 180; this.aspect = m[5] / m[0]; this.near = m[14] / (m[10] - 1); this.far = m[14] / (m[10] + 1); }, /** * @return {clay.camera.Perspective} */ clone: function() { var camera2 = Camera.prototype.clone.call(this); camera2.fov = this.fov; camera2.aspect = this.aspect; camera2.near = this.near; camera2.far = this.far; return camera2; } } ); var KEY_FRAMEBUFFER = "framebuffer"; var KEY_RENDERBUFFER = "renderbuffer"; var KEY_RENDERBUFFER_WIDTH = KEY_RENDERBUFFER + "_width"; var KEY_RENDERBUFFER_HEIGHT = KEY_RENDERBUFFER + "_height"; var KEY_RENDERBUFFER_ATTACHED = KEY_RENDERBUFFER + "_attached"; var KEY_DEPTHTEXTURE_ATTACHED = "depthtexture_attached"; var GL_FRAMEBUFFER = glenum.FRAMEBUFFER; var GL_RENDERBUFFER = glenum.RENDERBUFFER; var GL_DEPTH_ATTACHMENT = glenum.DEPTH_ATTACHMENT; var GL_COLOR_ATTACHMENT0 = glenum.COLOR_ATTACHMENT0; var FrameBuffer = Base.extend( /** @lends clay.FrameBuffer# */ { /** * If use depth buffer * @type {boolean} */ depthBuffer: true, /** * @type {Object} */ viewport: null, _width: 0, _height: 0, _textures: null, _boundRenderer: null }, function() { this._cache = new Cache(); this._textures = {}; }, /**@lends clay.FrameBuffer.prototype. */ { /** * Get attached texture width * {number} */ // FIXME Can't use before #bind getTextureWidth: function() { return this._width; }, /** * Get attached texture height * {number} */ getTextureHeight: function() { return this._height; }, /** * Bind the framebuffer to given renderer before rendering * @param {clay.Renderer} renderer */ bind: function(renderer) { if (renderer.__currentFrameBuffer) { if (renderer.__currentFrameBuffer === this) { return; } console.warn("Renderer already bound with another framebuffer. Unbind it first"); } renderer.__currentFrameBuffer = this; var _gl = renderer.gl; _gl.bindFramebuffer(GL_FRAMEBUFFER, this._getFrameBufferGL(renderer)); this._boundRenderer = renderer; var cache = this._cache; cache.put("viewport", renderer.viewport); var hasTextureAttached = false; var width; var height; for (var attachment in this._textures) { hasTextureAttached = true; var obj = this._textures[attachment]; if (obj) { width = obj.texture.width; height = obj.texture.height; this._doAttach(renderer, obj.texture, attachment, obj.target); } } this._width = width; this._height = height; if (!hasTextureAttached && this.depthBuffer) { console.error("Must attach texture before bind, or renderbuffer may have incorrect width and height."); } if (this.viewport) { renderer.setViewport(this.viewport); } else { renderer.setViewport(0, 0, width, height, 1); } var attachedTextures = cache.get("attached_textures"); if (attachedTextures) { for (var attachment in attachedTextures) { if (!this._textures[attachment]) { var target = attachedTextures[attachment]; this._doDetach(_gl, attachment, target); } } } if (!cache.get(KEY_DEPTHTEXTURE_ATTACHED) && this.depthBuffer) { if (cache.miss(KEY_RENDERBUFFER)) { cache.put(KEY_RENDERBUFFER, _gl.createRenderbuffer()); } var renderbuffer = cache.get(KEY_RENDERBUFFER); if (width !== cache.get(KEY_RENDERBUFFER_WIDTH) || height !== cache.get(KEY_RENDERBUFFER_HEIGHT)) { _gl.bindRenderbuffer(GL_RENDERBUFFER, renderbuffer); _gl.renderbufferStorage(GL_RENDERBUFFER, _gl.DEPTH_COMPONENT16, width, height); cache.put(KEY_RENDERBUFFER_WIDTH, width); cache.put(KEY_RENDERBUFFER_HEIGHT, height); _gl.bindRenderbuffer(GL_RENDERBUFFER, null); } if (!cache.get(KEY_RENDERBUFFER_ATTACHED)) { _gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer); cache.put(KEY_RENDERBUFFER_ATTACHED, true); } } }, /** * Unbind the frame buffer after rendering * @param {clay.Renderer} renderer */ unbind: function(renderer) { renderer.__currentFrameBuffer = null; var _gl = renderer.gl; _gl.bindFramebuffer(GL_FRAMEBUFFER, null); this._boundRenderer = null; this._cache.use(renderer.__uid__); var viewport = this._cache.get("viewport"); if (viewport) { renderer.setViewport(viewport); } this.updateMipmap(renderer); }, // Because the data of texture is changed over time, // Here update the mipmaps of texture each time after rendered; updateMipmap: function(renderer) { var _gl = renderer.gl; for (var attachment in this._textures) { var obj = this._textures[attachment]; if (obj) { var texture = obj.texture; if (!texture.NPOT && texture.useMipmap && texture.minFilter === Texture.LINEAR_MIPMAP_LINEAR) { var target = texture.textureType === "textureCube" ? glenum.TEXTURE_CUBE_MAP : glenum.TEXTURE_2D; _gl.bindTexture(target, texture.getWebGLTexture(renderer)); _gl.generateMipmap(target); _gl.bindTexture(target, null); } } } }, // 0x8CD5, 36053, FRAMEBUFFER_COMPLETE // 0x8CD6, 36054, FRAMEBUFFER_INCOMPLETE_ATTACHMENT // 0x8CD7, 36055, FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT // 0x8CD9, 36057, FRAMEBUFFER_INCOMPLETE_DIMENSIONS // 0x8CDD, 36061, FRAMEBUFFER_UNSUPPORTED checkStatus: function(_gl) { return _gl.checkFramebufferStatus(GL_FRAMEBUFFER); }, _getFrameBufferGL: function(renderer) { var cache = this._cache; cache.use(renderer.__uid__); if (cache.miss(KEY_FRAMEBUFFER)) { cache.put(KEY_FRAMEBUFFER, renderer.gl.createFramebuffer()); } return cache.get(KEY_FRAMEBUFFER); }, /** * Attach a texture(RTT) to the framebuffer * @param {clay.Texture} texture * @param {number} [attachment=gl.COLOR_ATTACHMENT0] * @param {number} [target=gl.TEXTURE_2D] */ attach: function(texture, attachment, target) { if (!texture.width) { throw new Error("The texture attached to color buffer is not a valid."); } attachment = attachment || GL_COLOR_ATTACHMENT0; target = target || glenum.TEXTURE_2D; var boundRenderer = this._boundRenderer; var _gl = boundRenderer && boundRenderer.gl; var attachedTextures; if (_gl) { var cache = this._cache; cache.use(boundRenderer.__uid__); attachedTextures = cache.get("attached_textures"); } var previous = this._textures[attachment]; if (previous && previous.target === target && previous.texture === texture && (attachedTextures && attachedTextures[attachment] != null)) { return; } var canAttach = true; if (boundRenderer) { canAttach = this._doAttach(boundRenderer, texture, attachment, target); if (!this.viewport) { boundRenderer.setViewport(0, 0, texture.width, texture.height, 1); } } if (canAttach) { this._textures[attachment] = this._textures[attachment] || {}; this._textures[attachment].texture = texture; this._textures[attachment].target = target; } }, _doAttach: function(renderer, texture, attachment, target) { var _gl = renderer.gl; var webglTexture = texture.getWebGLTexture(renderer); var attachedTextures = this._cache.get("attached_textures"); if (attachedTextures && attachedTextures[attachment]) { var obj = attachedTextures[attachment]; if (obj.texture === texture && obj.target === target) { return; } } attachment = +attachment; var canAttach = true; if (attachment === GL_DEPTH_ATTACHMENT || attachment === glenum.DEPTH_STENCIL_ATTACHMENT) { var extension = renderer.getGLExtension("WEBGL_depth_texture"); if (!extension) { console.error("Depth texture is not supported by the browser"); canAttach = false; } if (texture.format !== glenum.DEPTH_COMPONENT && texture.format !== glenum.DEPTH_STENCIL) { console.error("The texture attached to depth buffer is not a valid."); canAttach = false; } if (canAttach) { var renderbuffer = this._cache.get(KEY_RENDERBUFFER); if (renderbuffer) { _gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, null); _gl.deleteRenderbuffer(renderbuffer); this._cache.put(KEY_RENDERBUFFER, false); } this._cache.put(KEY_RENDERBUFFER_ATTACHED, false); this._cache.put(KEY_DEPTHTEXTURE_ATTACHED, true); } } _gl.framebufferTexture2D(GL_FRAMEBUFFER, attachment, target, webglTexture, 0); if (!attachedTextures) { attachedTextures = {}; this._cache.put("attached_textures", attachedTextures); } attachedTextures[attachment] = attachedTextures[attachment] || {}; attachedTextures[attachment].texture = texture; attachedTextures[attachment].target = target; return canAttach; }, _doDetach: function(_gl, attachment, target) { _gl.framebufferTexture2D(GL_FRAMEBUFFER, attachment, target, null, 0); var attachedTextures = this._cache.get("attached_textures"); if (attachedTextures && attachedTextures[attachment]) { attachedTextures[attachment] = null; } if (attachment === GL_DEPTH_ATTACHMENT || attachment === glenum.DEPTH_STENCIL_ATTACHMENT) { this._cache.put(KEY_DEPTHTEXTURE_ATTACHED, false); } }, /** * Detach a texture * @param {number} [attachment=gl.COLOR_ATTACHMENT0] * @param {number} [target=gl.TEXTURE_2D] */ detach: function(attachment, target) { this._textures[attachment] = null; if (this._boundRenderer) { var cache = this._cache; cache.use(this._boundRenderer.__uid__); this._doDetach(this._boundRenderer.gl, attachment, target); } }, /** * Dispose * @param {WebGLRenderingContext} _gl */ dispose: function(renderer) { var _gl = renderer.gl; var cache = this._cache; cache.use(renderer.__uid__); var renderBuffer = cache.get(KEY_RENDERBUFFER); if (renderBuffer) { _gl.deleteRenderbuffer(renderBuffer); } var frameBuffer = cache.get(KEY_FRAMEBUFFER); if (frameBuffer) { _gl.deleteFramebuffer(frameBuffer); } cache.deleteContext(renderer.__uid__); this._textures = {}; } } ); FrameBuffer.DEPTH_ATTACHMENT = GL_DEPTH_ATTACHMENT; FrameBuffer.COLOR_ATTACHMENT0 = GL_COLOR_ATTACHMENT0; FrameBuffer.STENCIL_ATTACHMENT = glenum.STENCIL_ATTACHMENT; FrameBuffer.DEPTH_STENCIL_ATTACHMENT = glenum.DEPTH_STENCIL_ATTACHMENT; var targets$3 = ["px", "nx", "py", "ny", "pz", "nz"]; var EnvironmentMapPass = Base.extend( function() { var ret2 = ( /** @lends clay.prePass.EnvironmentMap# */ { /** * Camera position * @type {clay.Vector3} * @memberOf clay.prePass.EnvironmentMap# */ position: new Vector3(), /** * Camera far plane * @type {number} * @memberOf clay.prePass.EnvironmentMap# */ far: 1e3, /** * Camera near plane * @type {number} * @memberOf clay.prePass.EnvironmentMap# */ near: 0.1, /** * Environment cube map * @type {clay.TextureCube} * @memberOf clay.prePass.EnvironmentMap# */ texture: null, /** * Used if you wan't have shadow in environment map * @type {clay.prePass.ShadowMap} */ shadowMapPass: null } ); var cameras = ret2._cameras = { px: new Perspective({ fov: 90 }), nx: new Perspective({ fov: 90 }), py: new Perspective({ fov: 90 }), ny: new Perspective({ fov: 90 }), pz: new Perspective({ fov: 90 }), nz: new Perspective({ fov: 90 }) }; cameras.px.lookAt(Vector3.POSITIVE_X, Vector3.NEGATIVE_Y); cameras.nx.lookAt(Vector3.NEGATIVE_X, Vector3.NEGATIVE_Y); cameras.py.lookAt(Vector3.POSITIVE_Y, Vector3.POSITIVE_Z); cameras.ny.lookAt(Vector3.NEGATIVE_Y, Vector3.NEGATIVE_Z); cameras.pz.lookAt(Vector3.POSITIVE_Z, Vector3.NEGATIVE_Y); cameras.nz.lookAt(Vector3.NEGATIVE_Z, Vector3.NEGATIVE_Y); ret2._frameBuffer = new FrameBuffer(); return ret2; }, /** @lends clay.prePass.EnvironmentMap# */ { /** * @param {string} target * @return {clay.Camera} */ getCamera: function(target) { return this._cameras[target]; }, /** * @param {clay.Renderer} renderer * @param {clay.Scene} scene * @param {boolean} [notUpdateScene=false] */ render: function(renderer, scene, notUpdateScene) { var _gl = renderer.gl; if (!notUpdateScene) { scene.update(); } var n = this.texture.width; var fov = 2 * Math.atan(n / (n - 0.5)) / Math.PI * 180; for (var i = 0; i < 6; i++) { var target = targets$3[i]; var camera2 = this._cameras[target]; Vector3.copy(camera2.position, this.position); camera2.far = this.far; camera2.near = this.near; camera2.fov = fov; if (this.shadowMapPass) { camera2.update(); var bbox = scene.getBoundingBox(); bbox.applyTransform(camera2.viewMatrix); scene.viewBoundingBoxLastFrame.copy(bbox); this.shadowMapPass.render(renderer, scene, camera2, true); } this._frameBuffer.attach( this.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); this._frameBuffer.bind(renderer); renderer.render(scene, camera2, true); this._frameBuffer.unbind(renderer); } }, /** * @param {clay.Renderer} renderer */ dispose: function(renderer) { this._frameBuffer.dispose(renderer); } } ); var Plane = Geometry.extend( /** @lends clay.geometry.Plane# */ { dynamic: false, /** * @type {number} */ widthSegments: 1, /** * @type {number} */ heightSegments: 1 }, function() { this.build(); }, /** @lends clay.geometry.Plane.prototype */ { /** * Build plane geometry */ build: function() { var heightSegments = this.heightSegments; var widthSegments = this.widthSegments; var attributes = this.attributes; var positions = []; var texcoords = []; var normals = []; var faces = []; for (var y = 0; y <= heightSegments; y++) { var t = y / heightSegments; for (var x = 0; x <= widthSegments; x++) { var s = x / widthSegments; positions.push([2 * s - 1, 2 * t - 1, 0]); if (texcoords) { texcoords.push([s, t]); } if (normals) { normals.push([0, 0, 1]); } if (x < widthSegments && y < heightSegments) { var i = x + y * (widthSegments + 1); faces.push([i, i + 1, i + widthSegments + 1]); faces.push([i + widthSegments + 1, i + 1, i + widthSegments + 2]); } } } attributes.position.fromArray(positions); attributes.texcoord0.fromArray(texcoords); attributes.normal.fromArray(normals); this.initIndicesFromArray(faces); this.boundingBox = new BoundingBox(); this.boundingBox.min.set(-1, -1, 0); this.boundingBox.max.set(1, 1, 0); } } ); var planeMatrix = new Matrix4(); var Cube = Geometry.extend( /**@lends clay.geometry.Cube# */ { dynamic: false, /** * @type {number} */ widthSegments: 1, /** * @type {number} */ heightSegments: 1, /** * @type {number} */ depthSegments: 1, /** * @type {boolean} */ inside: false }, function() { this.build(); }, /** @lends clay.geometry.Cube.prototype */ { /** * Build cube geometry */ build: function() { var planes = { "px": createPlane("px", this.depthSegments, this.heightSegments), "nx": createPlane("nx", this.depthSegments, this.heightSegments), "py": createPlane("py", this.widthSegments, this.depthSegments), "ny": createPlane("ny", this.widthSegments, this.depthSegments), "pz": createPlane("pz", this.widthSegments, this.heightSegments), "nz": createPlane("nz", this.widthSegments, this.heightSegments) }; var attrList = ["position", "texcoord0", "normal"]; var vertexNumber = 0; var faceNumber = 0; for (var pos in planes) { vertexNumber += planes[pos].vertexCount; faceNumber += planes[pos].indices.length; } for (var k = 0; k < attrList.length; k++) { this.attributes[attrList[k]].init(vertexNumber); } this.indices = new vendor.Uint16Array(faceNumber); var faceOffset = 0; var vertexOffset = 0; for (var pos in planes) { var plane = planes[pos]; for (var k = 0; k < attrList.length; k++) { var attrName = attrList[k]; var attrArray = plane.attributes[attrName].value; var attrSize = plane.attributes[attrName].size; var isNormal = attrName === "normal"; for (var i = 0; i < attrArray.length; i++) { var value = attrArray[i]; if (this.inside && isNormal) { value = -value; } this.attributes[attrName].value[i + attrSize * vertexOffset] = value; } } var len = plane.indices.length; for (var i = 0; i < plane.indices.length; i++) { this.indices[i + faceOffset] = vertexOffset + plane.indices[this.inside ? len - i - 1 : i]; } faceOffset += plane.indices.length; vertexOffset += plane.vertexCount; } this.boundingBox = new BoundingBox(); this.boundingBox.max.set(1, 1, 1); this.boundingBox.min.set(-1, -1, -1); } } ); function createPlane(pos, widthSegments, heightSegments) { planeMatrix.identity(); var plane = new Plane({ widthSegments, heightSegments }); switch (pos) { case "px": Matrix4.translate(planeMatrix, planeMatrix, Vector3.POSITIVE_X); Matrix4.rotateY(planeMatrix, planeMatrix, Math.PI / 2); break; case "nx": Matrix4.translate(planeMatrix, planeMatrix, Vector3.NEGATIVE_X); Matrix4.rotateY(planeMatrix, planeMatrix, -Math.PI / 2); break; case "py": Matrix4.translate(planeMatrix, planeMatrix, Vector3.POSITIVE_Y); Matrix4.rotateX(planeMatrix, planeMatrix, -Math.PI / 2); break; case "ny": Matrix4.translate(planeMatrix, planeMatrix, Vector3.NEGATIVE_Y); Matrix4.rotateX(planeMatrix, planeMatrix, Math.PI / 2); break; case "pz": Matrix4.translate(planeMatrix, planeMatrix, Vector3.POSITIVE_Z); break; case "nz": Matrix4.translate(planeMatrix, planeMatrix, Vector3.NEGATIVE_Z); Matrix4.rotateY(planeMatrix, planeMatrix, Math.PI); break; } plane.applyTransform(planeMatrix); return plane; } const skyboxEssl = "@export clay.skybox.vertex\n#define SHADER_NAME skybox\nuniform mat4 world : WORLD;\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nattribute vec3 position : POSITION;\nvarying vec3 v_WorldPosition;\nvoid main()\n{\n v_WorldPosition = (world * vec4(position, 1.0)).xyz;\n gl_Position = worldViewProjection * vec4(position, 1.0);\n}\n@end\n@export clay.skybox.fragment\n#define PI 3.1415926\nuniform mat4 viewInverse : VIEWINVERSE;\n#ifdef EQUIRECTANGULAR\nuniform sampler2D environmentMap;\n#else\nuniform samplerCube environmentMap;\n#endif\nuniform float lod: 0.0;\nvarying vec3 v_WorldPosition;\n@import clay.util.rgbm\n@import clay.util.srgb\n@import clay.util.ACES\nvoid main()\n{\n vec3 eyePos = viewInverse[3].xyz;\n vec3 V = normalize(v_WorldPosition - eyePos);\n#ifdef EQUIRECTANGULAR\n float phi = acos(V.y);\n float theta = atan(-V.x, V.z) + PI * 0.5;\n vec2 uv = vec2(theta / 2.0 / PI, phi / PI);\n vec4 texel = decodeHDR(texture2D(environmentMap, fract(uv)));\n#else\n #if defined(LOD) || defined(SUPPORT_TEXTURE_LOD)\n vec4 texel = decodeHDR(textureCubeLodEXT(environmentMap, V, lod));\n #else\n vec4 texel = decodeHDR(textureCube(environmentMap, V));\n #endif\n#endif\n#ifdef SRGB_DECODE\n texel = sRGBToLinear(texel);\n#endif\n#ifdef TONEMAPPING\n texel.rgb = ACESToneMapping(texel.rgb);\n#endif\n#ifdef SRGB_ENCODE\n texel = linearTosRGB(texel);\n#endif\n gl_FragColor = encodeHDR(vec4(texel.rgb, 1.0));\n}\n@end"; Shader.import(skyboxEssl); var Skybox = Mesh.extend( function() { var skyboxShader = new Shader({ vertex: Shader.source("clay.skybox.vertex"), fragment: Shader.source("clay.skybox.fragment") }); var material = new Material({ shader: skyboxShader, depthMask: false }); return { /** * @type {clay.Scene} * @memberOf clay.plugin.Skybox.prototype */ scene: null, geometry: new Cube(), material, environmentMap: null, culling: false, _dummyCamera: new Perspective() }; }, function() { var scene = this.scene; if (scene) { this.attachScene(scene); } if (this.environmentMap) { this.setEnvironmentMap(this.environmentMap); } }, /** @lends clay.plugin.Skybox# */ { /** * Attach the skybox to the scene * @param {clay.Scene} scene */ attachScene: function(scene) { if (this.scene) { this.detachScene(); } scene.skybox = this; this.scene = scene; scene.on("beforerender", this._beforeRenderScene, this); }, /** * Detach from scene */ detachScene: function() { if (this.scene) { this.scene.off("beforerender", this._beforeRenderScene); this.scene.skybox = null; } this.scene = null; }, /** * Dispose skybox * @param {clay.Renderer} renderer */ dispose: function(renderer) { this.detachScene(); this.geometry.dispose(renderer); }, /** * Set environment map * @param {clay.TextureCube} envMap */ setEnvironmentMap: function(envMap) { if (envMap.textureType === "texture2D") { this.material.define("EQUIRECTANGULAR"); envMap.minFilter = Texture.LINEAR; } else { this.material.undefine("EQUIRECTANGULAR"); } this.material.set("environmentMap", envMap); }, /** * Get environment map * @return {clay.TextureCube} */ getEnvironmentMap: function() { return this.material.get("environmentMap"); }, _beforeRenderScene: function(renderer, scene, camera2) { this.renderSkybox(renderer, camera2); }, renderSkybox: function(renderer, camera2) { var dummyCamera = this._dummyCamera; dummyCamera.aspect = renderer.getViewportAspect(); dummyCamera.fov = camera2.fov || 50; dummyCamera.updateProjectionMatrix(); Matrix4.invert(dummyCamera.invProjectionMatrix, dummyCamera.projectionMatrix); dummyCamera.worldTransform.copy(camera2.worldTransform); dummyCamera.viewMatrix.copy(camera2.viewMatrix); this.position.copy(camera2.getWorldPosition()); this.update(); renderer.gl.disable(renderer.gl.BLEND); if (this.material.get("lod") > 0) { this.material.define("fragment", "LOD"); } else { this.material.undefine("fragment", "LOD"); } renderer.renderPass([this], dummyCamera); } } ); var DDS_MAGIC = 542327876; var DDSD_MIPMAPCOUNT = 131072; var DDSCAPS2_CUBEMAP = 512; var DDPF_FOURCC = 4; function fourCCToInt32(value) { return value.charCodeAt(0) + (value.charCodeAt(1) << 8) + (value.charCodeAt(2) << 16) + (value.charCodeAt(3) << 24); } var headerLengthInt = 31; var FOURCC_DXT1 = fourCCToInt32("DXT1"); var FOURCC_DXT3 = fourCCToInt32("DXT3"); var FOURCC_DXT5 = fourCCToInt32("DXT5"); var off_magic = 0; var off_size = 1; var off_flags = 2; var off_height = 3; var off_width = 4; var off_mipmapCount = 7; var off_pfFlags = 20; var off_pfFourCC = 21; var off_caps2 = 28; var ret$1 = { parse: function(arrayBuffer, out) { var header = new Int32Array(arrayBuffer, 0, headerLengthInt); if (header[off_magic] !== DDS_MAGIC) { return null; } if (!header(off_pfFlags) & DDPF_FOURCC) { return null; } var fourCC = header(off_pfFourCC); var width = header[off_width]; var height = header[off_height]; var isCubeMap = header[off_caps2] & DDSCAPS2_CUBEMAP; var hasMipmap = header[off_flags] & DDSD_MIPMAPCOUNT; var blockBytes, internalFormat; switch (fourCC) { case FOURCC_DXT1: blockBytes = 8; internalFormat = Texture.COMPRESSED_RGB_S3TC_DXT1_EXT; break; case FOURCC_DXT3: blockBytes = 16; internalFormat = Texture.COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case FOURCC_DXT5: blockBytes = 16; internalFormat = Texture.COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: return null; } var dataOffset = header[off_size] + 4; var faceNumber = isCubeMap ? 6 : 1; var mipmapCount = 1; if (hasMipmap) { mipmapCount = Math.max(1, header[off_mipmapCount]); } var textures = []; for (var f = 0; f < faceNumber; f++) { var _width = width; var _height = height; textures[f] = new Texture2D({ width: _width, height: _height, format: internalFormat }); var mipmaps = []; for (var i = 0; i < mipmapCount; i++) { var dataLength = Math.max(4, _width) / 4 * Math.max(4, _height) / 4 * blockBytes; var byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength); dataOffset += dataLength; _width *= 0.5; _height *= 0.5; mipmaps[i] = byteArray; } textures[f].pixels = mipmaps[0]; if (hasMipmap) { textures[f].mipmaps = mipmaps; } } if (out) { out.width = textures[0].width; out.height = textures[0].height; out.format = textures[0].format; out.pixels = textures[0].pixels; out.mipmaps = textures[0].mipmaps; } else { return textures[0]; } } }; var toChar = String.fromCharCode; var MINELEN = 8; var MAXELEN = 32767; function rgbe2float(rgbe, buffer, offset, exposure) { if (rgbe[3] > 0) { var f = Math.pow(2, rgbe[3] - 128 - 8 + exposure); buffer[offset + 0] = rgbe[0] * f; buffer[offset + 1] = rgbe[1] * f; buffer[offset + 2] = rgbe[2] * f; } else { buffer[offset + 0] = 0; buffer[offset + 1] = 0; buffer[offset + 2] = 0; } buffer[offset + 3] = 1; return buffer; } function uint82string(array, offset, size) { var str = ""; for (var i = offset; i < size; i++) { str += toChar(array[i]); } return str; } function copyrgbe(s, t) { t[0] = s[0]; t[1] = s[1]; t[2] = s[2]; t[3] = s[3]; } function oldReadColors(scan, buffer, offset, xmax) { var rshift = 0, x = 0, len = xmax; while (len > 0) { scan[x][0] = buffer[offset++]; scan[x][1] = buffer[offset++]; scan[x][2] = buffer[offset++]; scan[x][3] = buffer[offset++]; if (scan[x][0] === 1 && scan[x][1] === 1 && scan[x][2] === 1) { for (var i = scan[x][3] << rshift >>> 0; i > 0; i--) { copyrgbe(scan[x - 1], scan[x]); x++; len--; } rshift += 8; } else { x++; len--; rshift = 0; } } return offset; } function readColors(scan, buffer, offset, xmax) { if (xmax < MINELEN | xmax > MAXELEN) { return oldReadColors(scan, buffer, offset, xmax); } var i = buffer[offset++]; if (i != 2) { return oldReadColors(scan, buffer, offset - 1, xmax); } scan[0][1] = buffer[offset++]; scan[0][2] = buffer[offset++]; i = buffer[offset++]; if ((scan[0][2] << 8 >>> 0 | i) >>> 0 !== xmax) { return null; } for (var i = 0; i < 4; i++) { for (var x = 0; x < xmax; ) { var code = buffer[offset++]; if (code > 128) { code = (code & 127) >>> 0; var val = buffer[offset++]; while (code--) { scan[x++][i] = val; } } else { while (code--) { scan[x++][i] = buffer[offset++]; } } } } return offset; } var ret = { // http://www.graphics.cornell.edu/~bjw/rgbe.html // Blender source // http://radsite.lbl.gov/radiance/refer/Notes/picture_format.html parseRGBE: function(arrayBuffer, texture, exposure) { if (exposure == null) { exposure = 0; } var data = new Uint8Array(arrayBuffer); var size = data.length; if (uint82string(data, 0, 2) !== "#?") { return; } for (var i = 2; i < size; i++) { if (toChar(data[i]) === "\n" && toChar(data[i + 1]) === "\n") { break; } } if (i >= size) { return; } i += 2; var str = ""; for (; i < size; i++) { var _char = toChar(data[i]); if (_char === "\n") { break; } str += _char; } var tmp = str.split(" "); var height = parseInt(tmp[1]); var width = parseInt(tmp[3]); if (!width || !height) { return; } var offset = i + 1; var scanline = []; for (var x = 0; x < width; x++) { scanline[x] = []; for (var j = 0; j < 4; j++) { scanline[x][j] = 0; } } var pixels = new Float32Array(width * height * 4); var offset2 = 0; for (var y = 0; y < height; y++) { var offset = readColors(scanline, data, offset, width); if (!offset) { return null; } for (var x = 0; x < width; x++) { rgbe2float(scanline[x], pixels, offset2, exposure); offset2 += 4; } } if (!texture) { texture = new Texture2D(); } texture.width = width; texture.height = height; texture.pixels = pixels; texture.type = Texture.FLOAT; return texture; }, parseRGBEFromPNG: function(png) { } }; var textureUtil = { /** * @param {string|object} path * @param {object} [option] * @param {Function} [onsuccess] * @param {Function} [onerror] * @return {clay.Texture} */ loadTexture: function(path, option, onsuccess, onerror) { var texture; if (typeof option === "function") { onsuccess = option; onerror = onsuccess; option = {}; } else { option = option || {}; } if (typeof path === "string") { if (path.match(/.hdr$/) || option.fileType === "hdr") { texture = new Texture2D({ width: 0, height: 0, sRGB: false }); textureUtil._fetchTexture( path, function(data) { ret.parseRGBE(data, texture, option.exposure); texture.dirty(); onsuccess && onsuccess(texture); }, onerror ); return texture; } else if (path.match(/.dds$/) || option.fileType === "dds") { texture = new Texture2D({ width: 0, height: 0 }); textureUtil._fetchTexture( path, function(data) { ret$1.parse(data, texture); texture.dirty(); onsuccess && onsuccess(texture); }, onerror ); } else { texture = new Texture2D(); texture.load(path); texture.success(onsuccess); texture.error(onerror); } } else if (typeof path === "object" && typeof path.px !== "undefined") { texture = new TextureCube(); texture.load(path); texture.success(onsuccess); texture.error(onerror); } return texture; }, /** * Load a panorama texture and render it to a cube map * @param {clay.Renderer} renderer * @param {string} path * @param {clay.TextureCube} cubeMap * @param {object} [option] * @param {boolean} [option.encodeRGBM] * @param {number} [option.exposure] * @param {Function} [onsuccess] * @param {Function} [onerror] */ loadPanorama: function(renderer, path, cubeMap, option, onsuccess, onerror) { var self2 = this; if (typeof option === "function") { onsuccess = option; onerror = onsuccess; option = {}; } else { option = option || {}; } textureUtil.loadTexture(path, option, function(texture) { texture.flipY = option.flipY || false; self2.panoramaToCubeMap(renderer, texture, cubeMap, option); texture.dispose(renderer); onsuccess && onsuccess(cubeMap); }, onerror); }, /** * Render a panorama texture to a cube map * @param {clay.Renderer} renderer * @param {clay.Texture2D} panoramaMap * @param {clay.TextureCube} cubeMap * @param {Object} option * @param {boolean} [option.encodeRGBM] */ panoramaToCubeMap: function(renderer, panoramaMap, cubeMap, option) { var environmentMapPass = new EnvironmentMapPass(); var skydome = new Skybox({ scene: new Scene() }); skydome.setEnvironmentMap(panoramaMap); option = option || {}; if (option.encodeRGBM) { skydome.material.define("fragment", "RGBM_ENCODE"); } cubeMap.sRGB = panoramaMap.sRGB; environmentMapPass.texture = cubeMap; environmentMapPass.render(renderer, skydome.scene); environmentMapPass.texture = null; environmentMapPass.dispose(renderer); return cubeMap; }, /** * Convert height map to normal map * @param {HTMLImageElement|HTMLCanvasElement} image * @param {boolean} [checkBump=false] * @return {HTMLCanvasElement} */ heightToNormal: function(image, checkBump) { var canvas = document.createElement("canvas"); var width = canvas.width = image.width; var height = canvas.height = image.height; var ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, width, height); checkBump = checkBump || false; var srcData = ctx.getImageData(0, 0, width, height); var dstData = ctx.createImageData(width, height); for (var i = 0; i < srcData.data.length; i += 4) { if (checkBump) { var r = srcData.data[i]; var g2 = srcData.data[i + 1]; var b = srcData.data[i + 2]; var diff = Math.abs(r - g2) + Math.abs(g2 - b); if (diff > 20) { console.warn("Given image is not a height map"); return image; } } var x1, y1, x2, y2; if (i % (width * 4) === 0) { x1 = srcData.data[i]; x2 = srcData.data[i + 4]; } else if (i % (width * 4) === (width - 1) * 4) { x1 = srcData.data[i - 4]; x2 = srcData.data[i]; } else { x1 = srcData.data[i - 4]; x2 = srcData.data[i + 4]; } if (i < width * 4) { y1 = srcData.data[i]; y2 = srcData.data[i + width * 4]; } else if (i > width * (height - 1) * 4) { y1 = srcData.data[i - width * 4]; y2 = srcData.data[i]; } else { y1 = srcData.data[i - width * 4]; y2 = srcData.data[i + width * 4]; } dstData.data[i] = x1 - x2 + 127; dstData.data[i + 1] = y1 - y2 + 127; dstData.data[i + 2] = 255; dstData.data[i + 3] = 255; } ctx.putImageData(dstData, 0, 0); return canvas; }, /** * Convert height map to normal map * @param {HTMLImageElement|HTMLCanvasElement} image * @param {boolean} [checkBump=false] * @param {number} [threshold=20] * @return {HTMLCanvasElement} */ isHeightImage: function(img, downScaleSize, threshold) { if (!img || !img.width || !img.height) { return false; } var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); var size = downScaleSize || 32; threshold = threshold || 20; canvas.width = canvas.height = size; ctx.drawImage(img, 0, 0, size, size); var srcData = ctx.getImageData(0, 0, size, size); for (var i = 0; i < srcData.data.length; i += 4) { var r = srcData.data[i]; var g2 = srcData.data[i + 1]; var b = srcData.data[i + 2]; var diff = Math.abs(r - g2) + Math.abs(g2 - b); if (diff > threshold) { return false; } } return true; }, _fetchTexture: function(path, onsuccess, onerror) { vendor.request.get({ url: path, responseType: "arraybuffer", onload: onsuccess, onerror }); }, /** * Create a chessboard texture * @param {number} [size] * @param {number} [unitSize] * @param {string} [color1] * @param {string} [color2] * @return {clay.Texture2D} */ createChessboard: function(size, unitSize, color1, color2) { size = size || 512; unitSize = unitSize || 64; color1 = color1 || "black"; color2 = color2 || "white"; var repeat = Math.ceil(size / unitSize); var canvas = document.createElement("canvas"); canvas.width = size; canvas.height = size; var ctx = canvas.getContext("2d"); ctx.fillStyle = color2; ctx.fillRect(0, 0, size, size); ctx.fillStyle = color1; for (var i = 0; i < repeat; i++) { for (var j = 0; j < repeat; j++) { var isFill = j % 2 ? i % 2 : i % 2 - 1; if (isFill) { ctx.fillRect(i * unitSize, j * unitSize, unitSize, unitSize); } } } var texture = new Texture2D({ image: canvas, anisotropic: 8 }); return texture; }, /** * Create a blank pure color 1x1 texture * @param {string} color * @return {clay.Texture2D} */ createBlank: function(color) { var canvas = document.createElement("canvas"); canvas.width = 1; canvas.height = 1; var ctx = canvas.getContext("2d"); ctx.fillStyle = color; ctx.fillRect(0, 0, 1, 1); var texture = new Texture2D({ image: canvas }); return texture; } }; var events = ["mousedown", "mouseup", "mousemove", "mouseover", "mouseout", "click", "dblclick", "contextmenu"]; function makeHandlerName(eventName) { return "_on" + eventName; } var EChartsSurface = function(chart) { var self2 = this; this._texture = new Texture2D({ anisotropic: 32, flipY: false, surface: this, dispose: function(renderer) { self2.dispose(); Texture2D.prototype.dispose.call(this, renderer); } }); events.forEach(function(eventName) { this[makeHandlerName(eventName)] = function(eveObj) { if (!eveObj.triangle) { return; } this._meshes.forEach(function(mesh2) { this.dispatchEvent(eventName, mesh2, eveObj.triangle, eveObj.point); }, this); }; }, this); this._meshes = []; if (chart) { this.setECharts(chart); } this.onupdate = null; }; EChartsSurface.prototype = { constructor: EChartsSurface, getTexture: function() { return this._texture; }, setECharts: function(chart) { this._chart = chart; var canvas = chart.getDom(); if (!(canvas instanceof HTMLCanvasElement)) { console.error("ECharts must init on canvas if it is used as texture."); canvas = document.createElement("canvas"); } else { var self2 = this; var zr = chart.getZr(); var oldRefreshImmediately = zr.__oldRefreshImmediately || zr.refreshImmediately; zr.refreshImmediately = function() { oldRefreshImmediately.call(this); self2._texture.dirty(); self2.onupdate && self2.onupdate(); }; zr.__oldRefreshImmediately = oldRefreshImmediately; } this._texture.image = canvas; this._texture.dirty(); this.onupdate && this.onupdate(); }, /** * @method * @param {clay.Mesh} attachedMesh * @param {Array.} triangle Triangle indices * @param {clay.math.Vector3} point */ dispatchEvent: function() { var p02 = new Vector3(); var p12 = new Vector3(); var p22 = new Vector3(); var uv0 = new Vector2(); var uv1 = new Vector2(); var uv2 = new Vector2(); var uv = new Vector2(); var vCross = new Vector3(); return function(eventName, attachedMesh, triangle, point) { var geo = attachedMesh.geometry; var position = geo.attributes.position; var texcoord = geo.attributes.texcoord0; var dot = Vector3.dot; var cross2 = Vector3.cross; position.get(triangle[0], p02.array); position.get(triangle[1], p12.array); position.get(triangle[2], p22.array); texcoord.get(triangle[0], uv0.array); texcoord.get(triangle[1], uv1.array); texcoord.get(triangle[2], uv2.array); cross2(vCross, p12, p22); var det = dot(p02, vCross); var t = dot(point, vCross) / det; cross2(vCross, p22, p02); var u = dot(point, vCross) / det; cross2(vCross, p02, p12); var v = dot(point, vCross) / det; Vector2.scale(uv, uv0, t); Vector2.scaleAndAdd(uv, uv, uv1, u); Vector2.scaleAndAdd(uv, uv, uv2, v); var x = uv.x * this._chart.getWidth(); var y = uv.y * this._chart.getHeight(); this._chart.getZr().handler.dispatch(eventName, { zrX: x, zrY: y }); }; }(), attachToMesh: function(mesh2) { if (this._meshes.indexOf(mesh2) >= 0) { return; } events.forEach(function(eventName) { mesh2.on(eventName, this[makeHandlerName(eventName)], this); }, this); this._meshes.push(mesh2); }, detachFromMesh: function(mesh2) { var idx = this._meshes.indexOf(mesh2); if (idx >= 0) { this._meshes.splice(idx, 1); } events.forEach(function(eventName) { mesh2.off(eventName, this[makeHandlerName(eventName)]); }, this); }, dispose: function() { this._meshes.forEach(function(mesh2) { this.detachFromMesh(mesh2); }, this); } }; var Orthographic = Camera.extend( /** @lends clay.camera.Orthographic# */ { /** * @type {number} */ left: -1, /** * @type {number} */ right: 1, /** * @type {number} */ near: -1, /** * @type {number} */ far: 1, /** * @type {number} */ top: 1, /** * @type {number} */ bottom: -1 }, /** @lends clay.camera.Orthographic.prototype */ { updateProjectionMatrix: function() { this.projectionMatrix.ortho(this.left, this.right, this.bottom, this.top, this.near, this.far); }, decomposeProjectionMatrix: function() { var m = this.projectionMatrix.array; this.left = (-1 - m[12]) / m[0]; this.right = (1 - m[12]) / m[0]; this.top = (1 - m[13]) / m[5]; this.bottom = (-1 - m[13]) / m[5]; this.near = -(-1 - m[14]) / m[10]; this.far = -(1 - m[14]) / m[10]; }, /** * @return {clay.camera.Orthographic} */ clone: function() { var camera2 = Camera.prototype.clone.call(this); camera2.left = this.left; camera2.right = this.right; camera2.near = this.near; camera2.far = this.far; camera2.top = this.top; camera2.bottom = this.bottom; return camera2; } } ); const vertexGlsl = "\n@export clay.compositor.vertex\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nattribute vec3 position : POSITION;\nattribute vec2 texcoord : TEXCOORD_0;\nvarying vec2 v_Texcoord;\nvoid main()\n{\n v_Texcoord = texcoord;\n gl_Position = worldViewProjection * vec4(position, 1.0);\n}\n@end"; Shader["import"](vertexGlsl); var planeGeo = new Plane(); var mesh = new Mesh({ geometry: planeGeo, frustumCulling: false }); var camera = new Orthographic(); var Pass = Base.extend( function() { return ( /** @lends clay.compositor.Pass# */ { /** * Fragment shader string * @type {string} */ // PENDING shader or fragment ? fragment: "", /** * @type {Object} */ outputs: null, /** * @type {clay.Material} */ material: null, /** * @type {Boolean} */ blendWithPrevious: false, /** * @type {Boolean} */ clearColor: false, /** * @type {Boolean} */ clearDepth: true } ); }, function() { var shader = new Shader(Shader.source("clay.compositor.vertex"), this.fragment); var material = new Material({ shader }); material.enableTexturesAll(); this.material = material; }, /** @lends clay.compositor.Pass.prototype */ { /** * @param {string} name * @param {} value */ setUniform: function(name, value) { this.material.setUniform(name, value); }, /** * @param {string} name * @return {} */ getUniform: function(name) { var uniform = this.material.uniforms[name]; if (uniform) { return uniform.value; } }, /** * @param {clay.Texture} texture * @param {number} attachment */ attachOutput: function(texture, attachment) { if (!this.outputs) { this.outputs = {}; } attachment = attachment || glenum.COLOR_ATTACHMENT0; this.outputs[attachment] = texture; }, /** * @param {clay.Texture} texture */ detachOutput: function(texture) { for (var attachment in this.outputs) { if (this.outputs[attachment] === texture) { this.outputs[attachment] = null; } } }, bind: function(renderer, frameBuffer) { if (this.outputs) { for (var attachment in this.outputs) { var texture = this.outputs[attachment]; if (texture) { frameBuffer.attach(texture, attachment); } } } if (frameBuffer) { frameBuffer.bind(renderer); } }, unbind: function(renderer, frameBuffer) { frameBuffer.unbind(renderer); }, /** * @param {clay.Renderer} renderer * @param {clay.FrameBuffer} [frameBuffer] */ render: function(renderer, frameBuffer) { var _gl = renderer.gl; if (frameBuffer) { this.bind(renderer, frameBuffer); var ext = renderer.getGLExtension("EXT_draw_buffers"); if (ext && this.outputs) { var bufs = []; for (var attachment in this.outputs) { attachment = +attachment; if (attachment >= _gl.COLOR_ATTACHMENT0 && attachment <= _gl.COLOR_ATTACHMENT0 + 8) { bufs.push(attachment); } } ext.drawBuffersEXT(bufs); } } this.trigger("beforerender", this, renderer); var clearBit = this.clearDepth ? _gl.DEPTH_BUFFER_BIT : 0; _gl.depthMask(true); if (this.clearColor) { clearBit = clearBit | _gl.COLOR_BUFFER_BIT; _gl.colorMask(true, true, true, true); var cc = this.clearColor; if (Array.isArray(cc)) { _gl.clearColor(cc[0], cc[1], cc[2], cc[3]); } } _gl.clear(clearBit); if (this.blendWithPrevious) { _gl.enable(_gl.BLEND); this.material.transparent = true; } else { _gl.disable(_gl.BLEND); this.material.transparent = false; } this.renderQuad(renderer); this.trigger("afterrender", this, renderer); if (frameBuffer) { this.unbind(renderer, frameBuffer); } }, /** * Simply do quad rendering */ renderQuad: function(renderer) { mesh.material = this.material; renderer.renderPass([mesh], camera); }, /** * @param {clay.Renderer} renderer */ dispose: function(renderer) { } } ); const integrateBRDFShaderCode = "#define SAMPLE_NUMBER 1024\n#define PI 3.14159265358979\nuniform sampler2D normalDistribution;\nuniform vec2 viewportSize : [512, 256];\nconst vec3 N = vec3(0.0, 0.0, 1.0);\nconst float fSampleNumber = float(SAMPLE_NUMBER);\nvec3 importanceSampleNormal(float i, float roughness, vec3 N) {\n vec3 H = texture2D(normalDistribution, vec2(roughness, i)).rgb;\n vec3 upVector = abs(N.y) > 0.999 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);\n vec3 tangentX = normalize(cross(N, upVector));\n vec3 tangentZ = cross(N, tangentX);\n return normalize(tangentX * H.x + N * H.y + tangentZ * H.z);\n}\nfloat G_Smith(float roughness, float NoV, float NoL) {\n float k = roughness * roughness / 2.0;\n float G1V = NoV / (NoV * (1.0 - k) + k);\n float G1L = NoL / (NoL * (1.0 - k) + k);\n return G1L * G1V;\n}\nvoid main() {\n vec2 uv = gl_FragCoord.xy / viewportSize;\n float NoV = uv.x;\n float roughness = uv.y;\n vec3 V;\n V.x = sqrt(1.0 - NoV * NoV);\n V.y = 0.0;\n V.z = NoV;\n float A = 0.0;\n float B = 0.0;\n for (int i = 0; i < SAMPLE_NUMBER; i++) {\n vec3 H = importanceSampleNormal(float(i) / fSampleNumber, roughness, N);\n vec3 L = reflect(-V, H);\n float NoL = clamp(L.z, 0.0, 1.0);\n float NoH = clamp(H.z, 0.0, 1.0);\n float VoH = clamp(dot(V, H), 0.0, 1.0);\n if (NoL > 0.0) {\n float G = G_Smith(roughness, NoV, NoL);\n float G_Vis = G * VoH / (NoH * NoV);\n float Fc = pow(1.0 - VoH, 5.0);\n A += (1.0 - Fc) * G_Vis;\n B += Fc * G_Vis;\n }\n }\n gl_FragColor = vec4(vec2(A, B) / fSampleNumber, 0.0, 1.0);\n}\n"; const prefilterFragCode = "#define SHADER_NAME prefilter\n#define SAMPLE_NUMBER 1024\n#define PI 3.14159265358979\nuniform mat4 viewInverse : VIEWINVERSE;\nuniform samplerCube environmentMap;\nuniform sampler2D normalDistribution;\nuniform float roughness : 0.5;\nvarying vec2 v_Texcoord;\nvarying vec3 v_WorldPosition;\n@import clay.util.rgbm\nvec3 importanceSampleNormal(float i, float roughness, vec3 N) {\n vec3 H = texture2D(normalDistribution, vec2(roughness, i)).rgb;\n vec3 upVector = abs(N.y) > 0.999 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);\n vec3 tangentX = normalize(cross(N, upVector));\n vec3 tangentZ = cross(N, tangentX);\n return normalize(tangentX * H.x + N * H.y + tangentZ * H.z);\n}\nvoid main() {\n vec3 eyePos = viewInverse[3].xyz;\n vec3 V = normalize(v_WorldPosition - eyePos);\n vec3 N = V;\n vec3 prefilteredColor = vec3(0.0);\n float totalWeight = 0.0;\n float fMaxSampleNumber = float(SAMPLE_NUMBER);\n for (int i = 0; i < SAMPLE_NUMBER; i++) {\n vec3 H = importanceSampleNormal(float(i) / fMaxSampleNumber, roughness, N);\n vec3 L = reflect(-V, H);\n float NoL = clamp(dot(N, L), 0.0, 1.0);\n if (NoL > 0.0) {\n prefilteredColor += decodeHDR(textureCube(environmentMap, L)).rgb * NoL;\n totalWeight += NoL;\n }\n }\n gl_FragColor = encodeHDR(vec4(prefilteredColor / totalWeight, 1.0));\n}\n"; var cubemapUtil = {}; var targets$2 = ["px", "nx", "py", "ny", "pz", "nz"]; cubemapUtil.prefilterEnvironmentMap = function(renderer, envMap, textureOpts, normalDistribution, brdfLookup) { if (!brdfLookup || !normalDistribution) { normalDistribution = cubemapUtil.generateNormalDistribution(); brdfLookup = cubemapUtil.integrateBRDF(renderer, normalDistribution); } textureOpts = textureOpts || {}; var width = textureOpts.width || 64; var height = textureOpts.height || 64; var textureType = textureOpts.type || envMap.type; var prefilteredCubeMap = new TextureCube({ width, height, type: textureType, flipY: false, mipmaps: [] }); if (!prefilteredCubeMap.isPowerOfTwo()) { console.warn("Width and height must be power of two to enable mipmap."); } var size = Math.min(width, height); var mipmapNum = Math.log(size) / Math.log(2) + 1; var prefilterMaterial = new Material({ shader: new Shader({ vertex: Shader.source("clay.skybox.vertex"), fragment: prefilterFragCode }) }); prefilterMaterial.set("normalDistribution", normalDistribution); textureOpts.encodeRGBM && prefilterMaterial.define("fragment", "RGBM_ENCODE"); textureOpts.decodeRGBM && prefilterMaterial.define("fragment", "RGBM_DECODE"); var dummyScene = new Scene(); var skyEnv; if (envMap.textureType === "texture2D") { var envCubemap = new TextureCube({ width, height, // FIXME FLOAT type will cause GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT error on iOS type: textureType === Texture.FLOAT ? Texture.HALF_FLOAT : textureType }); textureUtil.panoramaToCubeMap(renderer, envMap, envCubemap, { // PENDING encodeRGBM so it can be decoded as RGBM encodeRGBM: textureOpts.decodeRGBM }); envMap = envCubemap; } skyEnv = new Skybox({ scene: dummyScene, material: prefilterMaterial }); skyEnv.material.set("environmentMap", envMap); var envMapPass = new EnvironmentMapPass({ texture: prefilteredCubeMap }); if (textureOpts.encodeRGBM) { textureType = prefilteredCubeMap.type = Texture.UNSIGNED_BYTE; } var renderTargetTmp = new Texture2D({ width, height, type: textureType }); var frameBuffer = new FrameBuffer({ depthBuffer: false }); var ArrayCtor = vendor[textureType === Texture.UNSIGNED_BYTE ? "Uint8Array" : "Float32Array"]; for (var i = 0; i < mipmapNum; i++) { prefilteredCubeMap.mipmaps[i] = { pixels: {} }; skyEnv.material.set("roughness", i / (mipmapNum - 1)); var n = renderTargetTmp.width; var fov = 2 * Math.atan(n / (n - 0.5)) / Math.PI * 180; for (var j = 0; j < targets$2.length; j++) { var pixels = new ArrayCtor(renderTargetTmp.width * renderTargetTmp.height * 4); frameBuffer.attach(renderTargetTmp); frameBuffer.bind(renderer); var camera2 = envMapPass.getCamera(targets$2[j]); camera2.fov = fov; renderer.render(dummyScene, camera2); renderer.gl.readPixels( 0, 0, renderTargetTmp.width, renderTargetTmp.height, Texture.RGBA, textureType, pixels ); frameBuffer.unbind(renderer); prefilteredCubeMap.mipmaps[i].pixels[targets$2[j]] = pixels; } renderTargetTmp.width /= 2; renderTargetTmp.height /= 2; renderTargetTmp.dirty(); } frameBuffer.dispose(renderer); renderTargetTmp.dispose(renderer); skyEnv.dispose(renderer); normalDistribution.dispose(renderer); return { environmentMap: prefilteredCubeMap, brdfLookup, normalDistribution, maxMipmapLevel: mipmapNum }; }; cubemapUtil.integrateBRDF = function(renderer, normalDistribution) { normalDistribution = normalDistribution || cubemapUtil.generateNormalDistribution(); var framebuffer = new FrameBuffer({ depthBuffer: false }); var pass = new Pass({ fragment: integrateBRDFShaderCode }); var texture = new Texture2D({ width: 512, height: 256, type: Texture.HALF_FLOAT, wrapS: Texture.CLAMP_TO_EDGE, wrapT: Texture.CLAMP_TO_EDGE, minFilter: Texture.NEAREST, magFilter: Texture.NEAREST, useMipmap: false }); pass.setUniform("normalDistribution", normalDistribution); pass.setUniform("viewportSize", [512, 256]); pass.attachOutput(texture); pass.render(renderer, framebuffer); framebuffer.dispose(renderer); return texture; }; cubemapUtil.generateNormalDistribution = function(roughnessLevels, sampleSize) { var roughnessLevels = roughnessLevels || 256; var sampleSize = sampleSize || 1024; var normalDistribution = new Texture2D({ width: roughnessLevels, height: sampleSize, type: Texture.FLOAT, minFilter: Texture.NEAREST, magFilter: Texture.NEAREST, wrapS: Texture.CLAMP_TO_EDGE, wrapT: Texture.CLAMP_TO_EDGE, useMipmap: false }); var pixels = new Float32Array(sampleSize * roughnessLevels * 4); var tmp = []; for (var j = 0; j < roughnessLevels; j++) { var roughness = j / roughnessLevels; var a = roughness * roughness; for (var i = 0; i < sampleSize; i++) { var y = (i << 16 | i >>> 16) >>> 0; y = ((y & 1431655765) << 1 | (y & 2863311530) >>> 1) >>> 0; y = ((y & 858993459) << 2 | (y & 3435973836) >>> 2) >>> 0; y = ((y & 252645135) << 4 | (y & 4042322160) >>> 4) >>> 0; y = (((y & 16711935) << 8 | (y & 4278255360) >>> 8) >>> 0) / 4294967296; var cosTheta = Math.sqrt((1 - y) / (1 + (a * a - 1) * y)); tmp[i] = cosTheta; } for (var i = 0; i < sampleSize; i++) { var offset = (i * roughnessLevels + j) * 4; var cosTheta = tmp[i]; var sinTheta = Math.sqrt(1 - cosTheta * cosTheta); var x = i / sampleSize; var phi = 2 * Math.PI * x; pixels[offset] = sinTheta * Math.cos(phi); pixels[offset + 1] = cosTheta; pixels[offset + 2] = sinTheta * Math.sin(phi); pixels[offset + 3] = 1; } } normalDistribution.pixels = pixels; return normalDistribution; }; var AmbientCubemapLight = Light.extend( { /** * @type {clay.TextureCube} * @memberOf clay.light.AmbientCubemap# */ cubemap: null, // TODO // range: 100, castShadow: false, _normalDistribution: null, _brdfLookup: null }, /** @lends clay.light.AmbientCubemap# */ { type: "AMBIENT_CUBEMAP_LIGHT", /** * Do prefitering the cubemap * @param {clay.Renderer} renderer * @param {number} [size=32] */ prefilter: function(renderer, size) { if (!renderer.getGLExtension("EXT_shader_texture_lod")) { console.warn("Device not support textureCubeLodEXT"); return; } if (!this._brdfLookup) { this._normalDistribution = cubemapUtil.generateNormalDistribution(); this._brdfLookup = cubemapUtil.integrateBRDF(renderer, this._normalDistribution); } var cubemap = this.cubemap; if (cubemap.__prefiltered) { return; } var result = cubemapUtil.prefilterEnvironmentMap( renderer, cubemap, { encodeRGBM: true, width: size, height: size }, this._normalDistribution, this._brdfLookup ); this.cubemap = result.environmentMap; this.cubemap.__prefiltered = true; cubemap.dispose(renderer); }, getBRDFLookup: function() { return this._brdfLookup; }, uniformTemplates: { ambientCubemapLightColor: { type: "3f", value: function(instance) { var color = instance.color; var intensity = instance.intensity; return [color[0] * intensity, color[1] * intensity, color[2] * intensity]; } }, ambientCubemapLightCubemap: { type: "t", value: function(instance) { return instance.cubemap; } }, ambientCubemapLightBRDFLookup: { type: "t", value: function(instance) { return instance._brdfLookup; } } } /** * @function * @name clone * @return {clay.light.AmbientCubemap} * @memberOf clay.light.AmbientCubemap.prototype */ } ); var AmbientSHLight = Light.extend({ castShadow: false, /** * Spherical Harmonic Coefficients * @type {Array.} * @memberOf clay.light.AmbientSH# */ coefficients: [] }, function() { this._coefficientsTmpArr = new vendor.Float32Array(9 * 3); }, { type: "AMBIENT_SH_LIGHT", uniformTemplates: { ambientSHLightColor: { type: "3f", value: function(instance) { var color = instance.color; var intensity = instance.intensity; return [color[0] * intensity, color[1] * intensity, color[2] * intensity]; } }, ambientSHLightCoefficients: { type: "3f", value: function(instance) { var coefficientsTmpArr = instance._coefficientsTmpArr; for (var i = 0; i < instance.coefficients.length; i++) { coefficientsTmpArr[i] = instance.coefficients[i]; } return coefficientsTmpArr; } } } /** * @function * @name clone * @return {clay.light.Ambient} * @memberOf clay.light.Ambient.prototype */ }); var sh = {}; var targets$1 = ["px", "nx", "py", "ny", "pz", "nz"]; function harmonics(normal2, index) { var x = normal2[0]; var y = normal2[1]; var z = normal2[2]; if (index === 0) { return 1; } else if (index === 1) { return x; } else if (index === 2) { return y; } else if (index === 3) { return z; } else if (index === 4) { return x * z; } else if (index === 5) { return y * z; } else if (index === 6) { return x * y; } else if (index === 7) { return 3 * z * z - 1; } else { return x * x - y * y; } } var normalTransform = { px: [2, 1, 0, -1, -1, 1], nx: [2, 1, 0, 1, -1, -1], py: [0, 2, 1, 1, -1, -1], ny: [0, 2, 1, 1, 1, 1], pz: [0, 1, 2, -1, -1, -1], nz: [0, 1, 2, 1, -1, 1] }; function projectEnvironmentMapCPU(renderer, cubePixels, width, height) { var coeff = new vendor.Float32Array(9 * 3); var normal2 = vec3$f.create(); var texel = vec3$f.create(); var fetchNormal = vec3$f.create(); for (var m = 0; m < 9; m++) { var result = vec3$f.create(); for (var k = 0; k < targets$1.length; k++) { var pixels = cubePixels[targets$1[k]]; var sideResult = vec3$f.create(); var divider = 0; var i = 0; var transform = normalTransform[targets$1[k]]; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { normal2[0] = x / (width - 1) * 2 - 1; normal2[1] = y / (height - 1) * 2 - 1; normal2[2] = -1; vec3$f.normalize(normal2, normal2); fetchNormal[0] = normal2[transform[0]] * transform[3]; fetchNormal[1] = normal2[transform[1]] * transform[4]; fetchNormal[2] = normal2[transform[2]] * transform[5]; texel[0] = pixels[i++] / 255; texel[1] = pixels[i++] / 255; texel[2] = pixels[i++] / 255; var scale = pixels[i++] / 255 * 8.12; texel[0] *= scale; texel[1] *= scale; texel[2] *= scale; vec3$f.scaleAndAdd(sideResult, sideResult, texel, harmonics(fetchNormal, m) * -normal2[2]); divider += -normal2[2]; } } vec3$f.scaleAndAdd(result, result, sideResult, 1 / divider); } coeff[m * 3] = result[0] / 6; coeff[m * 3 + 1] = result[1] / 6; coeff[m * 3 + 2] = result[2] / 6; } return coeff; } sh.projectEnvironmentMap = function(renderer, envMap, opts) { opts = opts || {}; opts.lod = opts.lod || 0; var skybox; var dummyScene = new Scene(); var size = 64; if (envMap.textureType === "texture2D") { skybox = new Skybox({ scene: dummyScene, environmentMap: envMap }); } else { size = envMap.image && envMap.image.px ? envMap.image.px.width : envMap.width; skybox = new Skybox({ scene: dummyScene, environmentMap: envMap }); } var width = Math.ceil(size / Math.pow(2, opts.lod)); var height = Math.ceil(size / Math.pow(2, opts.lod)); var rgbmTexture = new Texture2D({ width, height }); var framebuffer = new FrameBuffer(); skybox.material.define("fragment", "RGBM_ENCODE"); if (opts.decodeRGBM) { skybox.material.define("fragment", "RGBM_DECODE"); } skybox.material.set("lod", opts.lod); var envMapPass = new EnvironmentMapPass({ texture: rgbmTexture }); var cubePixels = {}; for (var i = 0; i < targets$1.length; i++) { cubePixels[targets$1[i]] = new Uint8Array(width * height * 4); var camera2 = envMapPass.getCamera(targets$1[i]); camera2.fov = 90; framebuffer.attach(rgbmTexture); framebuffer.bind(renderer); renderer.render(dummyScene, camera2); renderer.gl.readPixels( 0, 0, width, height, Texture.RGBA, Texture.UNSIGNED_BYTE, cubePixels[targets$1[i]] ); framebuffer.unbind(renderer); } skybox.dispose(renderer); framebuffer.dispose(renderer); rgbmTexture.dispose(renderer); return projectEnvironmentMapCPU(renderer, cubePixels, width, height); }; var retrieve = { firstNotNull: function() { for (var i = 0, len = arguments.length; i < len; i++) { if (arguments[i] != null) { return arguments[i]; } } }, /** * @param {module:echarts/data/List} data * @param {Object} payload Contains dataIndex (means rawIndex) / dataIndexInside / name * each of which can be Array or primary type. * @return {number|Array.} dataIndex If not found, return undefined/null. */ queryDataIndex: function(data, payload) { if (payload.dataIndexInside != null) { return payload.dataIndexInside; } else if (payload.dataIndex != null) { return isArray(payload.dataIndex) ? map$1(payload.dataIndex, function(value) { return data.indexOfRawIndex(value); }) : data.indexOfRawIndex(payload.dataIndex); } else if (payload.name != null) { return isArray(payload.name) ? map$1(payload.name, function(value) { return data.indexOfName(value); }) : data.indexOfName(payload.name); } } }; var Sphere = Geometry.extend( /** @lends clay.geometry.Sphere# */ { dynamic: false, /** * @type {number} */ widthSegments: 40, /** * @type {number} */ heightSegments: 20, /** * @type {number} */ phiStart: 0, /** * @type {number} */ phiLength: Math.PI * 2, /** * @type {number} */ thetaStart: 0, /** * @type {number} */ thetaLength: Math.PI, /** * @type {number} */ radius: 1 }, function() { this.build(); }, /** @lends clay.geometry.Sphere.prototype */ { /** * Build sphere geometry */ build: function() { var heightSegments = this.heightSegments; var widthSegments = this.widthSegments; var positionAttr = this.attributes.position; var texcoordAttr = this.attributes.texcoord0; var normalAttr = this.attributes.normal; var vertexCount = (widthSegments + 1) * (heightSegments + 1); positionAttr.init(vertexCount); texcoordAttr.init(vertexCount); normalAttr.init(vertexCount); var IndicesCtor = vertexCount > 65535 ? Uint32Array : Uint16Array; var indices = this.indices = new IndicesCtor(widthSegments * heightSegments * 6); var x, y, z, u, v, i, j; var radius = this.radius; var phiStart = this.phiStart; var phiLength = this.phiLength; var thetaStart = this.thetaStart; var thetaLength = this.thetaLength; var radius = this.radius; var pos = []; var uv = []; var offset = 0; var divider = 1 / radius; for (j = 0; j <= heightSegments; j++) { for (i = 0; i <= widthSegments; i++) { u = i / widthSegments; v = j / heightSegments; x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); y = radius * Math.cos(thetaStart + v * thetaLength); z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength); pos[0] = x; pos[1] = y; pos[2] = z; uv[0] = u; uv[1] = v; positionAttr.set(offset, pos); texcoordAttr.set(offset, uv); pos[0] *= divider; pos[1] *= divider; pos[2] *= divider; normalAttr.set(offset, pos); offset++; } } var i1, i2, i3, i4; var len = widthSegments + 1; var n = 0; for (j = 0; j < heightSegments; j++) { for (i = 0; i < widthSegments; i++) { i2 = j * len + i; i1 = j * len + i + 1; i4 = (j + 1) * len + i + 1; i3 = (j + 1) * len + i; indices[n++] = i1; indices[n++] = i2; indices[n++] = i4; indices[n++] = i2; indices[n++] = i3; indices[n++] = i4; } } this.boundingBox = new BoundingBox(); this.boundingBox.max.set(radius, radius, radius); this.boundingBox.min.set(-radius, -radius, -radius); } } ); var AmbientLight = Light.extend({ castShadow: false }, { type: "AMBIENT_LIGHT", uniformTemplates: { ambientLightColor: { type: "3f", value: function(instance) { var color = instance.color; var intensity = instance.intensity; return [color[0] * intensity, color[1] * intensity, color[2] * intensity]; } } } /** * @function * @name clone * @return {clay.light.Ambient} * @memberOf clay.light.Ambient.prototype */ }); var DirectionalLight = Light.extend( /** @lends clay.light.Directional# */ { /** * @type {number} */ shadowBias: 1e-3, /** * @type {number} */ shadowSlopeScale: 2, /** * Shadow cascade. * Use PSSM technique when it is larger than 1 and have a unique directional light in scene. * @type {number} */ shadowCascade: 1, /** * Available when shadowCascade is larger than 1 and have a unique directional light in scene. * @type {number} */ cascadeSplitLogFactor: 0.2 }, { type: "DIRECTIONAL_LIGHT", uniformTemplates: { directionalLightDirection: { type: "3f", value: function(instance) { instance.__dir = instance.__dir || new Vector3(); return instance.__dir.copy(instance.worldTransform.z).normalize().negate().array; } }, directionalLightColor: { type: "3f", value: function(instance) { var color = instance.color; var intensity = instance.intensity; return [color[0] * intensity, color[1] * intensity, color[2] * intensity]; } } }, /** * @return {clay.light.Directional} * @memberOf clay.light.Directional.prototype */ clone: function() { var light = Light.prototype.clone.call(this); light.shadowBias = this.shadowBias; light.shadowSlopeScale = this.shadowSlopeScale; return light; } } ); var PointLight = Light.extend( /** @lends clay.light.Point# */ { /** * @type {number} */ range: 100, /** * @type {number} */ castShadow: false }, { type: "POINT_LIGHT", uniformTemplates: { pointLightPosition: { type: "3f", value: function(instance) { return instance.getWorldPosition().array; } }, pointLightRange: { type: "1f", value: function(instance) { return instance.range; } }, pointLightColor: { type: "3f", value: function(instance) { var color = instance.color; var intensity = instance.intensity; return [color[0] * intensity, color[1] * intensity, color[2] * intensity]; } } }, /** * @return {clay.light.Point} * @memberOf clay.light.Point.prototype */ clone: function() { var light = Light.prototype.clone.call(this); light.range = this.range; return light; } } ); var SpotLight = Light.extend( /**@lends clay.light.Spot */ { /** * @type {number} */ range: 20, /** * @type {number} */ umbraAngle: 30, /** * @type {number} */ penumbraAngle: 45, /** * @type {number} */ falloffFactor: 2, /** * @type {number} */ shadowBias: 1e-3, /** * @type {number} */ shadowSlopeScale: 2 }, { type: "SPOT_LIGHT", uniformTemplates: { spotLightPosition: { type: "3f", value: function(instance) { return instance.getWorldPosition().array; } }, spotLightRange: { type: "1f", value: function(instance) { return instance.range; } }, spotLightUmbraAngleCosine: { type: "1f", value: function(instance) { return Math.cos(instance.umbraAngle * Math.PI / 180); } }, spotLightPenumbraAngleCosine: { type: "1f", value: function(instance) { return Math.cos(instance.penumbraAngle * Math.PI / 180); } }, spotLightFalloffFactor: { type: "1f", value: function(instance) { return instance.falloffFactor; } }, spotLightDirection: { type: "3f", value: function(instance) { instance.__dir = instance.__dir || new Vector3(); return instance.__dir.copy(instance.worldTransform.z).negate().array; } }, spotLightColor: { type: "3f", value: function(instance) { var color = instance.color; var intensity = instance.intensity; return [color[0] * intensity, color[1] * intensity, color[2] * intensity]; } } }, /** * @return {clay.light.Spot} * @memberOf clay.light.Spot.prototype */ clone: function() { var light = Light.prototype.clone.call(this); light.range = this.range; light.umbraAngle = this.umbraAngle; light.penumbraAngle = this.penumbraAngle; light.falloffFactor = this.falloffFactor; light.shadowBias = this.shadowBias; light.shadowSlopeScale = this.shadowSlopeScale; return light; } } ); var Vector4 = function(x, y, z, w) { x = x || 0; y = y || 0; z = z || 0; w = w || 0; this.array = vec4$1.fromValues(x, y, z, w); this._dirty = true; }; Vector4.prototype = { constructor: Vector4, /** * Add b to self * @param {clay.Vector4} b * @return {clay.Vector4} */ add: function(b) { vec4$1.add(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Set x, y and z components * @param {number} x * @param {number} y * @param {number} z * @param {number} w * @return {clay.Vector4} */ set: function(x, y, z, w) { this.array[0] = x; this.array[1] = y; this.array[2] = z; this.array[3] = w; this._dirty = true; return this; }, /** * Set x, y, z and w components from array * @param {Float32Array|number[]} arr * @return {clay.Vector4} */ setArray: function(arr) { this.array[0] = arr[0]; this.array[1] = arr[1]; this.array[2] = arr[2]; this.array[3] = arr[3]; this._dirty = true; return this; }, /** * Clone a new Vector4 * @return {clay.Vector4} */ clone: function() { return new Vector4(this.x, this.y, this.z, this.w); }, /** * Copy from b * @param {clay.Vector4} b * @return {clay.Vector4} */ copy: function(b) { vec4$1.copy(this.array, b.array); this._dirty = true; return this; }, /** * Alias for distance * @param {clay.Vector4} b * @return {number} */ dist: function(b) { return vec4$1.dist(this.array, b.array); }, /** * Distance between self and b * @param {clay.Vector4} b * @return {number} */ distance: function(b) { return vec4$1.distance(this.array, b.array); }, /** * Alias for divide * @param {clay.Vector4} b * @return {clay.Vector4} */ div: function(b) { vec4$1.div(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Divide self by b * @param {clay.Vector4} b * @return {clay.Vector4} */ divide: function(b) { vec4$1.divide(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Dot product of self and b * @param {clay.Vector4} b * @return {number} */ dot: function(b) { return vec4$1.dot(this.array, b.array); }, /** * Alias of length * @return {number} */ len: function() { return vec4$1.len(this.array); }, /** * Calculate the length * @return {number} */ length: function() { return vec4$1.length(this.array); }, /** * Linear interpolation between a and b * @param {clay.Vector4} a * @param {clay.Vector4} b * @param {number} t * @return {clay.Vector4} */ lerp: function(a, b, t) { vec4$1.lerp(this.array, a.array, b.array, t); this._dirty = true; return this; }, /** * Minimum of self and b * @param {clay.Vector4} b * @return {clay.Vector4} */ min: function(b) { vec4$1.min(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Maximum of self and b * @param {clay.Vector4} b * @return {clay.Vector4} */ max: function(b) { vec4$1.max(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiply * @param {clay.Vector4} b * @return {clay.Vector4} */ mul: function(b) { vec4$1.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Mutiply self and b * @param {clay.Vector4} b * @return {clay.Vector4} */ multiply: function(b) { vec4$1.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Negate self * @return {clay.Vector4} */ negate: function() { vec4$1.negate(this.array, this.array); this._dirty = true; return this; }, /** * Normalize self * @return {clay.Vector4} */ normalize: function() { vec4$1.normalize(this.array, this.array); this._dirty = true; return this; }, /** * Generate random x, y, z, w components with a given scale * @param {number} scale * @return {clay.Vector4} */ random: function(scale) { vec4$1.random(this.array, scale); this._dirty = true; return this; }, /** * Scale self * @param {number} scale * @return {clay.Vector4} */ scale: function(s) { vec4$1.scale(this.array, this.array, s); this._dirty = true; return this; }, /** * Scale b and add to self * @param {clay.Vector4} b * @param {number} scale * @return {clay.Vector4} */ scaleAndAdd: function(b, s) { vec4$1.scaleAndAdd(this.array, this.array, b.array, s); this._dirty = true; return this; }, /** * Alias for squaredDistance * @param {clay.Vector4} b * @return {number} */ sqrDist: function(b) { return vec4$1.sqrDist(this.array, b.array); }, /** * Squared distance between self and b * @param {clay.Vector4} b * @return {number} */ squaredDistance: function(b) { return vec4$1.squaredDistance(this.array, b.array); }, /** * Alias for squaredLength * @return {number} */ sqrLen: function() { return vec4$1.sqrLen(this.array); }, /** * Squared length of self * @return {number} */ squaredLength: function() { return vec4$1.squaredLength(this.array); }, /** * Alias for subtract * @param {clay.Vector4} b * @return {clay.Vector4} */ sub: function(b) { vec4$1.sub(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Subtract b from self * @param {clay.Vector4} b * @return {clay.Vector4} */ subtract: function(b) { vec4$1.subtract(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Transform self with a Matrix4 m * @param {clay.Matrix4} m * @return {clay.Vector4} */ transformMat4: function(m) { vec4$1.transformMat4(this.array, this.array, m.array); this._dirty = true; return this; }, /** * Transform self with a Quaternion q * @param {clay.Quaternion} q * @return {clay.Vector4} */ transformQuat: function(q) { vec4$1.transformQuat(this.array, this.array, q.array); this._dirty = true; return this; }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; var defineProperty = Object.defineProperty; if (defineProperty) { var proto = Vector4.prototype; defineProperty(proto, "x", { get: function() { return this.array[0]; }, set: function(value) { this.array[0] = value; this._dirty = true; } }); defineProperty(proto, "y", { get: function() { return this.array[1]; }, set: function(value) { this.array[1] = value; this._dirty = true; } }); defineProperty(proto, "z", { get: function() { return this.array[2]; }, set: function(value) { this.array[2] = value; this._dirty = true; } }); defineProperty(proto, "w", { get: function() { return this.array[3]; }, set: function(value) { this.array[3] = value; this._dirty = true; } }); } Vector4.add = function(out, a, b) { vec4$1.add(out.array, a.array, b.array); out._dirty = true; return out; }; Vector4.set = function(out, x, y, z, w) { vec4$1.set(out.array, x, y, z, w); out._dirty = true; }; Vector4.copy = function(out, b) { vec4$1.copy(out.array, b.array); out._dirty = true; return out; }; Vector4.dist = function(a, b) { return vec4$1.distance(a.array, b.array); }; Vector4.distance = Vector4.dist; Vector4.div = function(out, a, b) { vec4$1.divide(out.array, a.array, b.array); out._dirty = true; return out; }; Vector4.divide = Vector4.div; Vector4.dot = function(a, b) { return vec4$1.dot(a.array, b.array); }; Vector4.len = function(b) { return vec4$1.length(b.array); }; Vector4.lerp = function(out, a, b, t) { vec4$1.lerp(out.array, a.array, b.array, t); out._dirty = true; return out; }; Vector4.min = function(out, a, b) { vec4$1.min(out.array, a.array, b.array); out._dirty = true; return out; }; Vector4.max = function(out, a, b) { vec4$1.max(out.array, a.array, b.array); out._dirty = true; return out; }; Vector4.mul = function(out, a, b) { vec4$1.multiply(out.array, a.array, b.array); out._dirty = true; return out; }; Vector4.multiply = Vector4.mul; Vector4.negate = function(out, a) { vec4$1.negate(out.array, a.array); out._dirty = true; return out; }; Vector4.normalize = function(out, a) { vec4$1.normalize(out.array, a.array); out._dirty = true; return out; }; Vector4.random = function(out, scale) { vec4$1.random(out.array, scale); out._dirty = true; return out; }; Vector4.scale = function(out, a, scale) { vec4$1.scale(out.array, a.array, scale); out._dirty = true; return out; }; Vector4.scaleAndAdd = function(out, a, b, scale) { vec4$1.scaleAndAdd(out.array, a.array, b.array, scale); out._dirty = true; return out; }; Vector4.sqrDist = function(a, b) { return vec4$1.sqrDist(a.array, b.array); }; Vector4.squaredDistance = Vector4.sqrDist; Vector4.sqrLen = function(a) { return vec4$1.sqrLen(a.array); }; Vector4.squaredLength = Vector4.sqrLen; Vector4.sub = function(out, a, b) { vec4$1.subtract(out.array, a.array, b.array); out._dirty = true; return out; }; Vector4.subtract = Vector4.sub; Vector4.transformMat4 = function(out, a, m) { vec4$1.transformMat4(out.array, a.array, m.array); out._dirty = true; return out; }; Vector4.transformQuat = function(out, a, q) { vec4$1.transformQuat(out.array, a.array, q.array); out._dirty = true; return out; }; var mat2 = {}; mat2.create = function() { var out = new GLMAT_ARRAY_TYPE(4); out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 1; return out; }; mat2.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(4); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; }; mat2.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; }; mat2.identity = function(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 1; return out; }; mat2.transpose = function(out, a) { if (out === a) { var a1 = a[1]; out[1] = a[2]; out[2] = a1; } else { out[0] = a[0]; out[1] = a[2]; out[2] = a[1]; out[3] = a[3]; } return out; }; mat2.invert = function(out, a) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], det = a0 * a3 - a2 * a1; if (!det) { return null; } det = 1 / det; out[0] = a3 * det; out[1] = -a1 * det; out[2] = -a2 * det; out[3] = a0 * det; return out; }; mat2.adjoint = function(out, a) { var a0 = a[0]; out[0] = a[3]; out[1] = -a[1]; out[2] = -a[2]; out[3] = a0; return out; }; mat2.determinant = function(a) { return a[0] * a[3] - a[2] * a[1]; }; mat2.multiply = function(out, a, b) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; out[0] = a0 * b0 + a2 * b1; out[1] = a1 * b0 + a3 * b1; out[2] = a0 * b2 + a2 * b3; out[3] = a1 * b2 + a3 * b3; return out; }; mat2.mul = mat2.multiply; mat2.rotate = function(out, a, rad2) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], s = Math.sin(rad2), c = Math.cos(rad2); out[0] = a0 * c + a2 * s; out[1] = a1 * c + a3 * s; out[2] = a0 * -s + a2 * c; out[3] = a1 * -s + a3 * c; return out; }; mat2.scale = function(out, a, v) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], v0 = v[0], v1 = v[1]; out[0] = a0 * v0; out[1] = a1 * v0; out[2] = a2 * v1; out[3] = a3 * v1; return out; }; mat2.frob = function(a) { return Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2)); }; mat2.LDU = function(L, D, U, a) { L[2] = a[2] / a[0]; U[0] = a[0]; U[1] = a[1]; U[3] = a[3] - L[2] * U[1]; return [L, D, U]; }; var Matrix2 = function() { this.array = mat2.create(); this._dirty = true; }; Matrix2.prototype = { constructor: Matrix2, /** * Set components from array * @param {Float32Array|number[]} arr */ setArray: function(arr) { for (var i = 0; i < this.array.length; i++) { this.array[i] = arr[i]; } this._dirty = true; return this; }, /** * Clone a new Matrix2 * @return {clay.Matrix2} */ clone: function() { return new Matrix2().copy(this); }, /** * Copy from b * @param {clay.Matrix2} b * @return {clay.Matrix2} */ copy: function(b) { mat2.copy(this.array, b.array); this._dirty = true; return this; }, /** * Calculate the adjugate of self, in-place * @return {clay.Matrix2} */ adjoint: function() { mat2.adjoint(this.array, this.array); this._dirty = true; return this; }, /** * Calculate matrix determinant * @return {number} */ determinant: function() { return mat2.determinant(this.array); }, /** * Set to a identity matrix * @return {clay.Matrix2} */ identity: function() { mat2.identity(this.array); this._dirty = true; return this; }, /** * Invert self * @return {clay.Matrix2} */ invert: function() { mat2.invert(this.array, this.array); this._dirty = true; return this; }, /** * Alias for mutiply * @param {clay.Matrix2} b * @return {clay.Matrix2} */ mul: function(b) { mat2.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiplyLeft * @param {clay.Matrix2} a * @return {clay.Matrix2} */ mulLeft: function(a) { mat2.mul(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Multiply self and b * @param {clay.Matrix2} b * @return {clay.Matrix2} */ multiply: function(b) { mat2.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Multiply a and self, a is on the left * @param {clay.Matrix2} a * @return {clay.Matrix2} */ multiplyLeft: function(a) { mat2.multiply(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Rotate self by a given radian * @param {number} rad * @return {clay.Matrix2} */ rotate: function(rad2) { mat2.rotate(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Scale self by s * @param {clay.Vector2} s * @return {clay.Matrix2} */ scale: function(v) { mat2.scale(this.array, this.array, v.array); this._dirty = true; return this; }, /** * Transpose self, in-place. * @return {clay.Matrix2} */ transpose: function() { mat2.transpose(this.array, this.array); this._dirty = true; return this; }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; Matrix2.adjoint = function(out, a) { mat2.adjoint(out.array, a.array); out._dirty = true; return out; }; Matrix2.copy = function(out, a) { mat2.copy(out.array, a.array); out._dirty = true; return out; }; Matrix2.determinant = function(a) { return mat2.determinant(a.array); }; Matrix2.identity = function(out) { mat2.identity(out.array); out._dirty = true; return out; }; Matrix2.invert = function(out, a) { mat2.invert(out.array, a.array); out._dirty = true; return out; }; Matrix2.mul = function(out, a, b) { mat2.mul(out.array, a.array, b.array); out._dirty = true; return out; }; Matrix2.multiply = Matrix2.mul; Matrix2.rotate = function(out, a, rad2) { mat2.rotate(out.array, a.array, rad2); out._dirty = true; return out; }; Matrix2.scale = function(out, a, v) { mat2.scale(out.array, a.array, v.array); out._dirty = true; return out; }; Matrix2.transpose = function(out, a) { mat2.transpose(out.array, a.array); out._dirty = true; return out; }; var mat2d = {}; mat2d.create = function() { var out = new GLMAT_ARRAY_TYPE(6); out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 1; out[4] = 0; out[5] = 0; return out; }; mat2d.clone = function(a) { var out = new GLMAT_ARRAY_TYPE(6); out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; return out; }; mat2d.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; return out; }; mat2d.identity = function(out) { out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 1; out[4] = 0; out[5] = 0; return out; }; mat2d.invert = function(out, a) { var aa = a[0], ab = a[1], ac = a[2], ad = a[3], atx = a[4], aty = a[5]; var det = aa * ad - ab * ac; if (!det) { return null; } det = 1 / det; out[0] = ad * det; out[1] = -ab * det; out[2] = -ac * det; out[3] = aa * det; out[4] = (ac * aty - ad * atx) * det; out[5] = (ab * atx - aa * aty) * det; return out; }; mat2d.determinant = function(a) { return a[0] * a[3] - a[1] * a[2]; }; mat2d.multiply = function(out, a, b) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5]; out[0] = a0 * b0 + a2 * b1; out[1] = a1 * b0 + a3 * b1; out[2] = a0 * b2 + a2 * b3; out[3] = a1 * b2 + a3 * b3; out[4] = a0 * b4 + a2 * b5 + a4; out[5] = a1 * b4 + a3 * b5 + a5; return out; }; mat2d.mul = mat2d.multiply; mat2d.rotate = function(out, a, rad2) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], s = Math.sin(rad2), c = Math.cos(rad2); out[0] = a0 * c + a2 * s; out[1] = a1 * c + a3 * s; out[2] = a0 * -s + a2 * c; out[3] = a1 * -s + a3 * c; out[4] = a4; out[5] = a5; return out; }; mat2d.scale = function(out, a, v) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], v0 = v[0], v1 = v[1]; out[0] = a0 * v0; out[1] = a1 * v0; out[2] = a2 * v1; out[3] = a3 * v1; out[4] = a4; out[5] = a5; return out; }; mat2d.translate = function(out, a, v) { var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], v0 = v[0], v1 = v[1]; out[0] = a0; out[1] = a1; out[2] = a2; out[3] = a3; out[4] = a0 * v0 + a2 * v1 + a4; out[5] = a1 * v0 + a3 * v1 + a5; return out; }; mat2d.frob = function(a) { return Math.sqrt(Math.pow(a[0], 2) + Math.pow(a[1], 2) + Math.pow(a[2], 2) + Math.pow(a[3], 2) + Math.pow(a[4], 2) + Math.pow(a[5], 2) + 1); }; var Matrix2d = function() { this.array = mat2d.create(); this._dirty = true; }; Matrix2d.prototype = { constructor: Matrix2d, /** * Set components from array * @param {Float32Array|number[]} arr */ setArray: function(arr) { for (var i = 0; i < this.array.length; i++) { this.array[i] = arr[i]; } this._dirty = true; return this; }, /** * Clone a new Matrix2d * @return {clay.Matrix2d} */ clone: function() { return new Matrix2d().copy(this); }, /** * Copy from b * @param {clay.Matrix2d} b * @return {clay.Matrix2d} */ copy: function(b) { mat2d.copy(this.array, b.array); this._dirty = true; return this; }, /** * Calculate matrix determinant * @return {number} */ determinant: function() { return mat2d.determinant(this.array); }, /** * Set to a identity matrix * @return {clay.Matrix2d} */ identity: function() { mat2d.identity(this.array); this._dirty = true; return this; }, /** * Invert self * @return {clay.Matrix2d} */ invert: function() { mat2d.invert(this.array, this.array); this._dirty = true; return this; }, /** * Alias for mutiply * @param {clay.Matrix2d} b * @return {clay.Matrix2d} */ mul: function(b) { mat2d.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiplyLeft * @param {clay.Matrix2d} a * @return {clay.Matrix2d} */ mulLeft: function(b) { mat2d.mul(this.array, b.array, this.array); this._dirty = true; return this; }, /** * Multiply self and b * @param {clay.Matrix2d} b * @return {clay.Matrix2d} */ multiply: function(b) { mat2d.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Multiply a and self, a is on the left * @param {clay.Matrix2d} a * @return {clay.Matrix2d} */ multiplyLeft: function(b) { mat2d.multiply(this.array, b.array, this.array); this._dirty = true; return this; }, /** * Rotate self by a given radian * @param {number} rad * @return {clay.Matrix2d} */ rotate: function(rad2) { mat2d.rotate(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Scale self by s * @param {clay.Vector2} s * @return {clay.Matrix2d} */ scale: function(s) { mat2d.scale(this.array, this.array, s.array); this._dirty = true; return this; }, /** * Translate self by v * @param {clay.Vector2} v * @return {clay.Matrix2d} */ translate: function(v) { mat2d.translate(this.array, this.array, v.array); this._dirty = true; return this; }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; Matrix2d.copy = function(out, a) { mat2d.copy(out.array, a.array); out._dirty = true; return out; }; Matrix2d.determinant = function(a) { return mat2d.determinant(a.array); }; Matrix2d.identity = function(out) { mat2d.identity(out.array); out._dirty = true; return out; }; Matrix2d.invert = function(out, a) { mat2d.invert(out.array, a.array); out._dirty = true; return out; }; Matrix2d.mul = function(out, a, b) { mat2d.mul(out.array, a.array, b.array); out._dirty = true; return out; }; Matrix2d.multiply = Matrix2d.mul; Matrix2d.rotate = function(out, a, rad2) { mat2d.rotate(out.array, a.array, rad2); out._dirty = true; return out; }; Matrix2d.scale = function(out, a, v) { mat2d.scale(out.array, a.array, v.array); out._dirty = true; return out; }; Matrix2d.translate = function(out, a, v) { mat2d.translate(out.array, a.array, v.array); out._dirty = true; return out; }; var Matrix3 = function() { this.array = mat3$1.create(); this._dirty = true; }; Matrix3.prototype = { constructor: Matrix3, /** * Set components from array * @param {Float32Array|number[]} arr */ setArray: function(arr) { for (var i = 0; i < this.array.length; i++) { this.array[i] = arr[i]; } this._dirty = true; return this; }, /** * Calculate the adjugate of self, in-place * @return {clay.Matrix3} */ adjoint: function() { mat3$1.adjoint(this.array, this.array); this._dirty = true; return this; }, /** * Clone a new Matrix3 * @return {clay.Matrix3} */ clone: function() { return new Matrix3().copy(this); }, /** * Copy from b * @param {clay.Matrix3} b * @return {clay.Matrix3} */ copy: function(b) { mat3$1.copy(this.array, b.array); this._dirty = true; return this; }, /** * Calculate matrix determinant * @return {number} */ determinant: function() { return mat3$1.determinant(this.array); }, /** * Copy the values from Matrix2d a * @param {clay.Matrix2d} a * @return {clay.Matrix3} */ fromMat2d: function(a) { mat3$1.fromMat2d(this.array, a.array); this._dirty = true; return this; }, /** * Copies the upper-left 3x3 values of Matrix4 * @param {clay.Matrix4} a * @return {clay.Matrix3} */ fromMat4: function(a) { mat3$1.fromMat4(this.array, a.array); this._dirty = true; return this; }, /** * Calculates a rotation matrix from the given quaternion * @param {clay.Quaternion} q * @return {clay.Matrix3} */ fromQuat: function(q) { mat3$1.fromQuat(this.array, q.array); this._dirty = true; return this; }, /** * Set to a identity matrix * @return {clay.Matrix3} */ identity: function() { mat3$1.identity(this.array); this._dirty = true; return this; }, /** * Invert self * @return {clay.Matrix3} */ invert: function() { mat3$1.invert(this.array, this.array); this._dirty = true; return this; }, /** * Alias for mutiply * @param {clay.Matrix3} b * @return {clay.Matrix3} */ mul: function(b) { mat3$1.mul(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Alias for multiplyLeft * @param {clay.Matrix3} a * @return {clay.Matrix3} */ mulLeft: function(a) { mat3$1.mul(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Multiply self and b * @param {clay.Matrix3} b * @return {clay.Matrix3} */ multiply: function(b) { mat3$1.multiply(this.array, this.array, b.array); this._dirty = true; return this; }, /** * Multiply a and self, a is on the left * @param {clay.Matrix3} a * @return {clay.Matrix3} */ multiplyLeft: function(a) { mat3$1.multiply(this.array, a.array, this.array); this._dirty = true; return this; }, /** * Rotate self by a given radian * @param {number} rad * @return {clay.Matrix3} */ rotate: function(rad2) { mat3$1.rotate(this.array, this.array, rad2); this._dirty = true; return this; }, /** * Scale self by s * @param {clay.Vector2} s * @return {clay.Matrix3} */ scale: function(v) { mat3$1.scale(this.array, this.array, v.array); this._dirty = true; return this; }, /** * Translate self by v * @param {clay.Vector2} v * @return {clay.Matrix3} */ translate: function(v) { mat3$1.translate(this.array, this.array, v.array); this._dirty = true; return this; }, /** * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix * @param {clay.Matrix4} a */ normalFromMat4: function(a) { mat3$1.normalFromMat4(this.array, a.array); this._dirty = true; return this; }, /** * Transpose self, in-place. * @return {clay.Matrix2} */ transpose: function() { mat3$1.transpose(this.array, this.array); this._dirty = true; return this; }, toString: function() { return "[" + Array.prototype.join.call(this.array, ",") + "]"; }, toArray: function() { return Array.prototype.slice.call(this.array); } }; Matrix3.adjoint = function(out, a) { mat3$1.adjoint(out.array, a.array); out._dirty = true; return out; }; Matrix3.copy = function(out, a) { mat3$1.copy(out.array, a.array); out._dirty = true; return out; }; Matrix3.determinant = function(a) { return mat3$1.determinant(a.array); }; Matrix3.identity = function(out) { mat3$1.identity(out.array); out._dirty = true; return out; }; Matrix3.invert = function(out, a) { mat3$1.invert(out.array, a.array); return out; }; Matrix3.mul = function(out, a, b) { mat3$1.mul(out.array, a.array, b.array); out._dirty = true; return out; }; Matrix3.multiply = Matrix3.mul; Matrix3.fromMat2d = function(out, a) { mat3$1.fromMat2d(out.array, a.array); out._dirty = true; return out; }; Matrix3.fromMat4 = function(out, a) { mat3$1.fromMat4(out.array, a.array); out._dirty = true; return out; }; Matrix3.fromQuat = function(out, q) { mat3$1.fromQuat(out.array, q.array); out._dirty = true; return out; }; Matrix3.normalFromMat4 = function(out, a) { mat3$1.normalFromMat4(out.array, a.array); out._dirty = true; return out; }; Matrix3.rotate = function(out, a, rad2) { mat3$1.rotate(out.array, a.array, rad2); out._dirty = true; return out; }; Matrix3.scale = function(out, a, v) { mat3$1.scale(out.array, a.array, v.array); out._dirty = true; return out; }; Matrix3.transpose = function(out, a) { mat3$1.transpose(out.array, a.array); out._dirty = true; return out; }; Matrix3.translate = function(out, a, v) { mat3$1.translate(out.array, a.array, v.array); out._dirty = true; return out; }; var easingFuncs = { linear: function(k) { return k; }, quadraticIn: function(k) { return k * k; }, quadraticOut: function(k) { return k * (2 - k); }, quadraticInOut: function(k) { if ((k *= 2) < 1) { return 0.5 * k * k; } return -0.5 * (--k * (k - 2) - 1); }, cubicIn: function(k) { return k * k * k; }, cubicOut: function(k) { return --k * k * k + 1; }, cubicInOut: function(k) { if ((k *= 2) < 1) { return 0.5 * k * k * k; } return 0.5 * ((k -= 2) * k * k + 2); }, quarticIn: function(k) { return k * k * k * k; }, quarticOut: function(k) { return 1 - --k * k * k * k; }, quarticInOut: function(k) { if ((k *= 2) < 1) { return 0.5 * k * k * k * k; } return -0.5 * ((k -= 2) * k * k * k - 2); }, quinticIn: function(k) { return k * k * k * k * k; }, quinticOut: function(k) { return --k * k * k * k * k + 1; }, quinticInOut: function(k) { if ((k *= 2) < 1) { return 0.5 * k * k * k * k * k; } return 0.5 * ((k -= 2) * k * k * k * k + 2); }, sinusoidalIn: function(k) { return 1 - Math.cos(k * Math.PI / 2); }, sinusoidalOut: function(k) { return Math.sin(k * Math.PI / 2); }, sinusoidalInOut: function(k) { return 0.5 * (1 - Math.cos(Math.PI * k)); }, exponentialIn: function(k) { return k === 0 ? 0 : Math.pow(1024, k - 1); }, exponentialOut: function(k) { return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); }, exponentialInOut: function(k) { if (k === 0) { return 0; } if (k === 1) { return 1; } if ((k *= 2) < 1) { return 0.5 * Math.pow(1024, k - 1); } return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2); }, circularIn: function(k) { return 1 - Math.sqrt(1 - k * k); }, circularOut: function(k) { return Math.sqrt(1 - --k * k); }, circularInOut: function(k) { if ((k *= 2) < 1) { return -0.5 * (Math.sqrt(1 - k * k) - 1); } return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); }, elasticIn: function(k) { var s; var a = 0.1; var p = 0.4; if (k === 0) { return 0; } if (k === 1) { return 1; } if (!a || a < 1) { a = 1; s = p / 4; } else { s = p * Math.asin(1 / a) / (2 * Math.PI); } return -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p)); }, elasticOut: function(k) { var s; var a = 0.1; var p = 0.4; if (k === 0) { return 0; } if (k === 1) { return 1; } if (!a || a < 1) { a = 1; s = p / 4; } else { s = p * Math.asin(1 / a) / (2 * Math.PI); } return a * Math.pow(2, -10 * k) * Math.sin((k - s) * (2 * Math.PI) / p) + 1; }, elasticInOut: function(k) { var s; var a = 0.1; var p = 0.4; if (k === 0) { return 0; } if (k === 1) { return 1; } if (!a || a < 1) { a = 1; s = p / 4; } else { s = p * Math.asin(1 / a) / (2 * Math.PI); } if ((k *= 2) < 1) { return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p)); } return a * Math.pow(2, -10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1; }, backIn: function(k) { var s = 1.70158; return k * k * ((s + 1) * k - s); }, backOut: function(k) { var s = 1.70158; return --k * k * ((s + 1) * k + s) + 1; }, backInOut: function(k) { var s = 1.70158 * 1.525; if ((k *= 2) < 1) { return 0.5 * (k * k * ((s + 1) * k - s)); } return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); }, bounceIn: function(k) { return 1 - easingFuncs.bounceOut(1 - k); }, bounceOut: function(k) { if (k < 1 / 2.75) { return 7.5625 * k * k; } else if (k < 2 / 2.75) { return 7.5625 * (k -= 1.5 / 2.75) * k + 0.75; } else if (k < 2.5 / 2.75) { return 7.5625 * (k -= 2.25 / 2.75) * k + 0.9375; } else { return 7.5625 * (k -= 2.625 / 2.75) * k + 0.984375; } }, bounceInOut: function(k) { if (k < 0.5) { return easingFuncs.bounceIn(k * 2) * 0.5; } return easingFuncs.bounceOut(k * 2 - 1) * 0.5 + 0.5; } }; reduce([ "Function", "RegExp", "Date", "Error", "CanvasGradient", "CanvasPattern", "Image", "Canvas" ], function(obj, val) { obj["[object " + val + "]"] = true; return obj; }, {}); reduce([ "Int8", "Uint8", "Uint8Clamped", "Int16", "Uint16", "Int32", "Uint32", "Float32", "Float64" ], function(obj, val) { obj["[object " + val + "Array]"] = true; return obj; }, {}); var arrayProto = Array.prototype; var nativeSlice = arrayProto.slice; var nativeMap = arrayProto.map; var ctorFunction = function() { }.constructor; var protoFunction = ctorFunction ? ctorFunction.prototype : null; var protoKey = "__proto__"; function logError() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (typeof console !== "undefined") { console.error.apply(console, args); } } function extend(target, source) { if (Object.assign) { Object.assign(target, source); } else { for (var key in source) { if (source.hasOwnProperty(key) && key !== protoKey) { target[key] = source[key]; } } } return target; } function isArrayLike(data) { if (!data) { return false; } if (typeof data === "string") { return false; } return typeof data.length === "number"; } function map(arr, cb, context) { if (!arr) { return []; } if (!cb) { return slice(arr); } if (arr.map && arr.map === nativeMap) { return arr.map(cb, context); } else { var result = []; for (var i = 0, len = arr.length; i < len; i++) { result.push(cb.call(context, arr[i], i, arr)); } return result; } } function reduce(arr, cb, memo, context) { if (!(arr && cb)) { return; } for (var i = 0, len = arr.length; i < len; i++) { memo = cb.call(context, memo, arr[i], i, arr); } return memo; } function keys(obj) { if (!obj) { return []; } if (Object.keys) { return Object.keys(obj); } var keyList = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { keyList.push(key); } } return keyList; } function bindPolyfill(func, context) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } return function() { return func.apply(context, args.concat(nativeSlice.call(arguments))); }; } protoFunction && isFunction(protoFunction.bind) ? protoFunction.call.bind(protoFunction.bind) : bindPolyfill; function isFunction(value) { return typeof value === "function"; } function isString(value) { return typeof value === "string"; } function isNumber(value) { return typeof value === "number"; } function isGradientObject(value) { return value.colorStops != null; } function eqNaN(value) { return value !== value; } function slice(arr) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return nativeSlice.apply(arr, args); } function trim(str) { if (str == null) { return null; } else if (typeof str.trim === "function") { return str.trim(); } else { return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""); } } function concatArray(a, b) { var newArray = new a.constructor(a.length + b.length); for (var i = 0; i < a.length; i++) { newArray[i] = a[i]; } var offset = a.length; for (var i = 0; i < b.length; i++) { newArray[i + offset] = b[i]; } return newArray; } function noop() { } function applyTransform(out, v, m) { var x = v[0]; var y = v[1]; out[0] = m[0] * x + m[2] * y + m[4]; out[1] = m[1] * x + m[3] * y + m[5]; return out; } var mathPow = Math.pow; var mathSqrt = Math.sqrt; var EPSILON = 1e-8; var THREE_SQRT = mathSqrt(3); var ONE_THIRD = 1 / 3; function isAroundZero(val) { return val > -EPSILON && val < EPSILON; } function cubicAt(p02, p12, p22, p3, t) { var onet = 1 - t; return onet * onet * (onet * p02 + 3 * t * p12) + t * t * (t * p3 + 3 * onet * p22); } function cubicRootAt(p02, p12, p22, p3, val, roots) { var a = p3 + 3 * (p12 - p22) - p02; var b = 3 * (p22 - p12 * 2 + p02); var c = 3 * (p12 - p02); var d = p02 - val; var A = b * b - 3 * a * c; var B = b * c - 9 * a * d; var C = c * c - 3 * b * d; var n = 0; if (isAroundZero(A) && isAroundZero(B)) { if (isAroundZero(b)) { roots[0] = 0; } else { var t1 = -c / b; if (t1 >= 0 && t1 <= 1) { roots[n++] = t1; } } } else { var disc = B * B - 4 * A * C; if (isAroundZero(disc)) { var K = B / A; var t1 = -b / a + K; var t2 = -K / 2; if (t1 >= 0 && t1 <= 1) { roots[n++] = t1; } if (t2 >= 0 && t2 <= 1) { roots[n++] = t2; } } else if (disc > 0) { var discSqrt = mathSqrt(disc); var Y1 = A * b + 1.5 * a * (-B + discSqrt); var Y2 = A * b + 1.5 * a * (-B - discSqrt); if (Y1 < 0) { Y1 = -mathPow(-Y1, ONE_THIRD); } else { Y1 = mathPow(Y1, ONE_THIRD); } if (Y2 < 0) { Y2 = -mathPow(-Y2, ONE_THIRD); } else { Y2 = mathPow(Y2, ONE_THIRD); } var t1 = (-b - (Y1 + Y2)) / (3 * a); if (t1 >= 0 && t1 <= 1) { roots[n++] = t1; } } else { var T = (2 * A * b - 3 * a * B) / (2 * mathSqrt(A * A * A)); var theta = Math.acos(T) / 3; var ASqrt = mathSqrt(A); var tmp = Math.cos(theta); var t1 = (-b - 2 * ASqrt * tmp) / (3 * a); var t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a); var t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a); if (t1 >= 0 && t1 <= 1) { roots[n++] = t1; } if (t2 >= 0 && t2 <= 1) { roots[n++] = t2; } if (t3 >= 0 && t3 <= 1) { roots[n++] = t3; } } } return n; } var regexp = /cubic-bezier\(([0-9,\.e ]+)\)/; function createCubicEasingFunc(cubicEasingStr) { var cubic = cubicEasingStr && regexp.exec(cubicEasingStr); if (cubic) { var points = cubic[1].split(","); var a_1 = +trim(points[0]); var b_1 = +trim(points[1]); var c_1 = +trim(points[2]); var d_1 = +trim(points[3]); if (isNaN(a_1 + b_1 + c_1 + d_1)) { return; } var roots_1 = []; return function(p) { return p <= 0 ? 0 : p >= 1 ? 1 : cubicRootAt(0, a_1, c_1, 1, p, roots_1) && cubicAt(0, b_1, d_1, 1, roots_1[0]); }; } } var Clip = function() { function Clip2(opts) { this._inited = false; this._startTime = 0; this._pausedTime = 0; this._paused = false; this._life = opts.life || 1e3; this._delay = opts.delay || 0; this.loop = opts.loop || false; this.onframe = opts.onframe || noop; this.ondestroy = opts.ondestroy || noop; this.onrestart = opts.onrestart || noop; opts.easing && this.setEasing(opts.easing); } Clip2.prototype.step = function(globalTime, deltaTime) { if (!this._inited) { this._startTime = globalTime + this._delay; this._inited = true; } if (this._paused) { this._pausedTime += deltaTime; return; } var life = this._life; var elapsedTime = globalTime - this._startTime - this._pausedTime; var percent = elapsedTime / life; if (percent < 0) { percent = 0; } percent = Math.min(percent, 1); var easingFunc = this.easingFunc; var schedule = easingFunc ? easingFunc(percent) : percent; this.onframe(schedule); if (percent === 1) { if (this.loop) { var remainder = elapsedTime % life; this._startTime = globalTime - remainder; this._pausedTime = 0; this.onrestart(); } else { return true; } } return false; }; Clip2.prototype.pause = function() { this._paused = true; }; Clip2.prototype.resume = function() { this._paused = false; }; Clip2.prototype.setEasing = function(easing) { this.easing = easing; this.easingFunc = isFunction(easing) ? easing : easingFuncs[easing] || createCubicEasingFunc(easing); }; return Clip2; }(); var kCSSColorTable = { "transparent": [0, 0, 0, 0], "aliceblue": [240, 248, 255, 1], "antiquewhite": [250, 235, 215, 1], "aqua": [0, 255, 255, 1], "aquamarine": [127, 255, 212, 1], "azure": [240, 255, 255, 1], "beige": [245, 245, 220, 1], "bisque": [255, 228, 196, 1], "black": [0, 0, 0, 1], "blanchedalmond": [255, 235, 205, 1], "blue": [0, 0, 255, 1], "blueviolet": [138, 43, 226, 1], "brown": [165, 42, 42, 1], "burlywood": [222, 184, 135, 1], "cadetblue": [95, 158, 160, 1], "chartreuse": [127, 255, 0, 1], "chocolate": [210, 105, 30, 1], "coral": [255, 127, 80, 1], "cornflowerblue": [100, 149, 237, 1], "cornsilk": [255, 248, 220, 1], "crimson": [220, 20, 60, 1], "cyan": [0, 255, 255, 1], "darkblue": [0, 0, 139, 1], "darkcyan": [0, 139, 139, 1], "darkgoldenrod": [184, 134, 11, 1], "darkgray": [169, 169, 169, 1], "darkgreen": [0, 100, 0, 1], "darkgrey": [169, 169, 169, 1], "darkkhaki": [189, 183, 107, 1], "darkmagenta": [139, 0, 139, 1], "darkolivegreen": [85, 107, 47, 1], "darkorange": [255, 140, 0, 1], "darkorchid": [153, 50, 204, 1], "darkred": [139, 0, 0, 1], "darksalmon": [233, 150, 122, 1], "darkseagreen": [143, 188, 143, 1], "darkslateblue": [72, 61, 139, 1], "darkslategray": [47, 79, 79, 1], "darkslategrey": [47, 79, 79, 1], "darkturquoise": [0, 206, 209, 1], "darkviolet": [148, 0, 211, 1], "deeppink": [255, 20, 147, 1], "deepskyblue": [0, 191, 255, 1], "dimgray": [105, 105, 105, 1], "dimgrey": [105, 105, 105, 1], "dodgerblue": [30, 144, 255, 1], "firebrick": [178, 34, 34, 1], "floralwhite": [255, 250, 240, 1], "forestgreen": [34, 139, 34, 1], "fuchsia": [255, 0, 255, 1], "gainsboro": [220, 220, 220, 1], "ghostwhite": [248, 248, 255, 1], "gold": [255, 215, 0, 1], "goldenrod": [218, 165, 32, 1], "gray": [128, 128, 128, 1], "green": [0, 128, 0, 1], "greenyellow": [173, 255, 47, 1], "grey": [128, 128, 128, 1], "honeydew": [240, 255, 240, 1], "hotpink": [255, 105, 180, 1], "indianred": [205, 92, 92, 1], "indigo": [75, 0, 130, 1], "ivory": [255, 255, 240, 1], "khaki": [240, 230, 140, 1], "lavender": [230, 230, 250, 1], "lavenderblush": [255, 240, 245, 1], "lawngreen": [124, 252, 0, 1], "lemonchiffon": [255, 250, 205, 1], "lightblue": [173, 216, 230, 1], "lightcoral": [240, 128, 128, 1], "lightcyan": [224, 255, 255, 1], "lightgoldenrodyellow": [250, 250, 210, 1], "lightgray": [211, 211, 211, 1], "lightgreen": [144, 238, 144, 1], "lightgrey": [211, 211, 211, 1], "lightpink": [255, 182, 193, 1], "lightsalmon": [255, 160, 122, 1], "lightseagreen": [32, 178, 170, 1], "lightskyblue": [135, 206, 250, 1], "lightslategray": [119, 136, 153, 1], "lightslategrey": [119, 136, 153, 1], "lightsteelblue": [176, 196, 222, 1], "lightyellow": [255, 255, 224, 1], "lime": [0, 255, 0, 1], "limegreen": [50, 205, 50, 1], "linen": [250, 240, 230, 1], "magenta": [255, 0, 255, 1], "maroon": [128, 0, 0, 1], "mediumaquamarine": [102, 205, 170, 1], "mediumblue": [0, 0, 205, 1], "mediumorchid": [186, 85, 211, 1], "mediumpurple": [147, 112, 219, 1], "mediumseagreen": [60, 179, 113, 1], "mediumslateblue": [123, 104, 238, 1], "mediumspringgreen": [0, 250, 154, 1], "mediumturquoise": [72, 209, 204, 1], "mediumvioletred": [199, 21, 133, 1], "midnightblue": [25, 25, 112, 1], "mintcream": [245, 255, 250, 1], "mistyrose": [255, 228, 225, 1], "moccasin": [255, 228, 181, 1], "navajowhite": [255, 222, 173, 1], "navy": [0, 0, 128, 1], "oldlace": [253, 245, 230, 1], "olive": [128, 128, 0, 1], "olivedrab": [107, 142, 35, 1], "orange": [255, 165, 0, 1], "orangered": [255, 69, 0, 1], "orchid": [218, 112, 214, 1], "palegoldenrod": [238, 232, 170, 1], "palegreen": [152, 251, 152, 1], "paleturquoise": [175, 238, 238, 1], "palevioletred": [219, 112, 147, 1], "papayawhip": [255, 239, 213, 1], "peachpuff": [255, 218, 185, 1], "peru": [205, 133, 63, 1], "pink": [255, 192, 203, 1], "plum": [221, 160, 221, 1], "powderblue": [176, 224, 230, 1], "purple": [128, 0, 128, 1], "red": [255, 0, 0, 1], "rosybrown": [188, 143, 143, 1], "royalblue": [65, 105, 225, 1], "saddlebrown": [139, 69, 19, 1], "salmon": [250, 128, 114, 1], "sandybrown": [244, 164, 96, 1], "seagreen": [46, 139, 87, 1], "seashell": [255, 245, 238, 1], "sienna": [160, 82, 45, 1], "silver": [192, 192, 192, 1], "skyblue": [135, 206, 235, 1], "slateblue": [106, 90, 205, 1], "slategray": [112, 128, 144, 1], "slategrey": [112, 128, 144, 1], "snow": [255, 250, 250, 1], "springgreen": [0, 255, 127, 1], "steelblue": [70, 130, 180, 1], "tan": [210, 180, 140, 1], "teal": [0, 128, 128, 1], "thistle": [216, 191, 216, 1], "tomato": [255, 99, 71, 1], "turquoise": [64, 224, 208, 1], "violet": [238, 130, 238, 1], "wheat": [245, 222, 179, 1], "white": [255, 255, 255, 1], "whitesmoke": [245, 245, 245, 1], "yellow": [255, 255, 0, 1], "yellowgreen": [154, 205, 50, 1] }; function clampCssByte(i) { i = Math.round(i); return i < 0 ? 0 : i > 255 ? 255 : i; } function clampCssFloat(f) { return f < 0 ? 0 : f > 1 ? 1 : f; } function parseCssInt(val) { var str = val; if (str.length && str.charAt(str.length - 1) === "%") { return clampCssByte(parseFloat(str) / 100 * 255); } return clampCssByte(parseInt(str, 10)); } function parseCssFloat(val) { var str = val; if (str.length && str.charAt(str.length - 1) === "%") { return clampCssFloat(parseFloat(str) / 100); } return clampCssFloat(parseFloat(str)); } function cssHueToRgb(m1, m2, h) { if (h < 0) { h += 1; } else if (h > 1) { h -= 1; } if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; } if (h * 2 < 1) { return m2; } if (h * 3 < 2) { return m1 + (m2 - m1) * (2 / 3 - h) * 6; } return m1; } function setRgba(out, r, g2, b, a) { out[0] = r; out[1] = g2; out[2] = b; out[3] = a; return out; } function copyRgba(out, a) { out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out; } var colorCache = new LRU(20); var lastRemovedArr = null; function putToCache(colorStr, rgbaArr) { if (lastRemovedArr) { copyRgba(lastRemovedArr, rgbaArr); } lastRemovedArr = colorCache.put(colorStr, lastRemovedArr || rgbaArr.slice()); } function parse(colorStr, rgbaArr) { if (!colorStr) { return; } rgbaArr = rgbaArr || []; var cached = colorCache.get(colorStr); if (cached) { return copyRgba(rgbaArr, cached); } colorStr = colorStr + ""; var str = colorStr.replace(/ /g, "").toLowerCase(); if (str in kCSSColorTable) { copyRgba(rgbaArr, kCSSColorTable[str]); putToCache(colorStr, rgbaArr); return rgbaArr; } var strLen = str.length; if (str.charAt(0) === "#") { if (strLen === 4 || strLen === 5) { var iv = parseInt(str.slice(1, 4), 16); if (!(iv >= 0 && iv <= 4095)) { setRgba(rgbaArr, 0, 0, 0, 1); return; } setRgba(rgbaArr, (iv & 3840) >> 4 | (iv & 3840) >> 8, iv & 240 | (iv & 240) >> 4, iv & 15 | (iv & 15) << 4, strLen === 5 ? parseInt(str.slice(4), 16) / 15 : 1); putToCache(colorStr, rgbaArr); return rgbaArr; } else if (strLen === 7 || strLen === 9) { var iv = parseInt(str.slice(1, 7), 16); if (!(iv >= 0 && iv <= 16777215)) { setRgba(rgbaArr, 0, 0, 0, 1); return; } setRgba(rgbaArr, (iv & 16711680) >> 16, (iv & 65280) >> 8, iv & 255, strLen === 9 ? parseInt(str.slice(7), 16) / 255 : 1); putToCache(colorStr, rgbaArr); return rgbaArr; } return; } var op = str.indexOf("("); var ep = str.indexOf(")"); if (op !== -1 && ep + 1 === strLen) { var fname = str.substr(0, op); var params = str.substr(op + 1, ep - (op + 1)).split(","); var alpha = 1; switch (fname) { case "rgba": if (params.length !== 4) { return params.length === 3 ? setRgba(rgbaArr, +params[0], +params[1], +params[2], 1) : setRgba(rgbaArr, 0, 0, 0, 1); } alpha = parseCssFloat(params.pop()); case "rgb": if (params.length >= 3) { setRgba(rgbaArr, parseCssInt(params[0]), parseCssInt(params[1]), parseCssInt(params[2]), params.length === 3 ? alpha : parseCssFloat(params[3])); putToCache(colorStr, rgbaArr); return rgbaArr; } else { setRgba(rgbaArr, 0, 0, 0, 1); return; } case "hsla": if (params.length !== 4) { setRgba(rgbaArr, 0, 0, 0, 1); return; } params[3] = parseCssFloat(params[3]); hsla2rgba(params, rgbaArr); putToCache(colorStr, rgbaArr); return rgbaArr; case "hsl": if (params.length !== 3) { setRgba(rgbaArr, 0, 0, 0, 1); return; } hsla2rgba(params, rgbaArr); putToCache(colorStr, rgbaArr); return rgbaArr; default: return; } } setRgba(rgbaArr, 0, 0, 0, 1); return; } function hsla2rgba(hsla, rgba) { var h = (parseFloat(hsla[0]) % 360 + 360) % 360 / 360; var s = parseCssFloat(hsla[1]); var l = parseCssFloat(hsla[2]); var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; var m1 = l * 2 - m2; rgba = rgba || []; setRgba(rgba, clampCssByte(cssHueToRgb(m1, m2, h + 1 / 3) * 255), clampCssByte(cssHueToRgb(m1, m2, h) * 255), clampCssByte(cssHueToRgb(m1, m2, h - 1 / 3) * 255), 1); if (hsla.length === 4) { rgba[3] = hsla[3]; } return rgba; } var Browser = /* @__PURE__ */ function() { function Browser2() { this.firefox = false; this.ie = false; this.edge = false; this.newEdge = false; this.weChat = false; } return Browser2; }(); var Env = /* @__PURE__ */ function() { function Env2() { this.browser = new Browser(); this.node = false; this.wxa = false; this.worker = false; this.svgSupported = false; this.touchEventsSupported = false; this.pointerEventsSupported = false; this.domSupported = false; this.transformSupported = false; this.transform3dSupported = false; this.hasGlobalWindow = typeof window !== "undefined"; } return Env2; }(); var env = new Env(); if (typeof wx === "object" && typeof wx.getSystemInfoSync === "function") { env.wxa = true; env.touchEventsSupported = true; } else if (typeof document === "undefined" && typeof self !== "undefined") { env.worker = true; } else if (!env.hasGlobalWindow || "Deno" in window) { env.node = true; env.svgSupported = true; } else { detect(navigator.userAgent, env); } function detect(ua, env2) { var browser = env2.browser; var firefox = ua.match(/Firefox\/([\d.]+)/); var ie = ua.match(/MSIE\s([\d.]+)/) || ua.match(/Trident\/.+?rv:(([\d.]+))/); var edge = ua.match(/Edge?\/([\d.]+)/); var weChat = /micromessenger/i.test(ua); if (firefox) { browser.firefox = true; browser.version = firefox[1]; } if (ie) { browser.ie = true; browser.version = ie[1]; } if (edge) { browser.edge = true; browser.version = edge[1]; browser.newEdge = +edge[1].split(".")[0] > 18; } if (weChat) { browser.weChat = true; } env2.svgSupported = typeof SVGRect !== "undefined"; env2.touchEventsSupported = "ontouchstart" in window && !browser.ie && !browser.edge; env2.pointerEventsSupported = "onpointerdown" in window && (browser.edge || browser.ie && +browser.version >= 11); env2.domSupported = typeof document !== "undefined"; var style = document.documentElement.style; env2.transform3dSupported = (browser.ie && "transition" in style || browser.edge || "WebKitCSSMatrix" in window && "m11" in new WebKitCSSMatrix() || "MozPerspective" in style) && !("OTransition" in style); env2.transformSupported = env2.transform3dSupported || browser.ie && +browser.version >= 9; } function isLinearGradient(val) { return val.type === "linear"; } function isRadialGradient(val) { return val.type === "radial"; } (function() { if (env.hasGlobalWindow && isFunction(window.btoa)) { return function(str) { return window.btoa(unescape(encodeURIComponent(str))); }; } if (typeof Buffer !== "undefined") { return function(str) { return Buffer.from(str).toString("base64"); }; } return function(str) { return null; }; })(); var arraySlice = Array.prototype.slice; function interpolateNumber(p02, p12, percent) { return (p12 - p02) * percent + p02; } function interpolate1DArray(out, p02, p12, percent) { var len = p02.length; for (var i = 0; i < len; i++) { out[i] = interpolateNumber(p02[i], p12[i], percent); } return out; } function interpolate2DArray(out, p02, p12, percent) { var len = p02.length; var len2 = len && p02[0].length; for (var i = 0; i < len; i++) { if (!out[i]) { out[i] = []; } for (var j = 0; j < len2; j++) { out[i][j] = interpolateNumber(p02[i][j], p12[i][j], percent); } } return out; } function add1DArray(out, p02, p12, sign2) { var len = p02.length; for (var i = 0; i < len; i++) { out[i] = p02[i] + p12[i] * sign2; } return out; } function add2DArray(out, p02, p12, sign2) { var len = p02.length; var len2 = len && p02[0].length; for (var i = 0; i < len; i++) { if (!out[i]) { out[i] = []; } for (var j = 0; j < len2; j++) { out[i][j] = p02[i][j] + p12[i][j] * sign2; } } return out; } function fillColorStops(val0, val1) { var len0 = val0.length; var len1 = val1.length; var shorterArr = len0 > len1 ? val1 : val0; var shorterLen = Math.min(len0, len1); var last = shorterArr[shorterLen - 1] || { color: [0, 0, 0, 0], offset: 0 }; for (var i = shorterLen; i < Math.max(len0, len1); i++) { shorterArr.push({ offset: last.offset, color: last.color.slice() }); } } function fillArray(val0, val1, arrDim) { var arr0 = val0; var arr1 = val1; if (!arr0.push || !arr1.push) { return; } var arr0Len = arr0.length; var arr1Len = arr1.length; if (arr0Len !== arr1Len) { var isPreviousLarger = arr0Len > arr1Len; if (isPreviousLarger) { arr0.length = arr1Len; } else { for (var i = arr0Len; i < arr1Len; i++) { arr0.push(arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i])); } } } var len2 = arr0[0] && arr0[0].length; for (var i = 0; i < arr0.length; i++) { if (arrDim === 1) { if (isNaN(arr0[i])) { arr0[i] = arr1[i]; } } else { for (var j = 0; j < len2; j++) { if (isNaN(arr0[i][j])) { arr0[i][j] = arr1[i][j]; } } } } } function cloneValue(value) { if (isArrayLike(value)) { var len = value.length; if (isArrayLike(value[0])) { var ret2 = []; for (var i = 0; i < len; i++) { ret2.push(arraySlice.call(value[i])); } return ret2; } return arraySlice.call(value); } return value; } function rgba2String(rgba) { rgba[0] = Math.floor(rgba[0]) || 0; rgba[1] = Math.floor(rgba[1]) || 0; rgba[2] = Math.floor(rgba[2]) || 0; rgba[3] = rgba[3] == null ? 1 : rgba[3]; return "rgba(" + rgba.join(",") + ")"; } function guessArrayDim(value) { return isArrayLike(value && value[0]) ? 2 : 1; } var VALUE_TYPE_NUMBER = 0; var VALUE_TYPE_1D_ARRAY = 1; var VALUE_TYPE_2D_ARRAY = 2; var VALUE_TYPE_COLOR = 3; var VALUE_TYPE_LINEAR_GRADIENT = 4; var VALUE_TYPE_RADIAL_GRADIENT = 5; var VALUE_TYPE_UNKOWN = 6; function isGradientValueType(valType) { return valType === VALUE_TYPE_LINEAR_GRADIENT || valType === VALUE_TYPE_RADIAL_GRADIENT; } function isArrayValueType(valType) { return valType === VALUE_TYPE_1D_ARRAY || valType === VALUE_TYPE_2D_ARRAY; } var tmpRgba = [0, 0, 0, 0]; var Track = function() { function Track2(propName) { this.keyframes = []; this.discrete = false; this._invalid = false; this._needsSort = false; this._lastFr = 0; this._lastFrP = 0; this.propName = propName; } Track2.prototype.isFinished = function() { return this._finished; }; Track2.prototype.setFinished = function() { this._finished = true; if (this._additiveTrack) { this._additiveTrack.setFinished(); } }; Track2.prototype.needsAnimate = function() { return this.keyframes.length >= 1; }; Track2.prototype.getAdditiveTrack = function() { return this._additiveTrack; }; Track2.prototype.addKeyframe = function(time, rawValue, easing) { this._needsSort = true; var keyframes = this.keyframes; var len = keyframes.length; var discrete = false; var valType = VALUE_TYPE_UNKOWN; var value = rawValue; if (isArrayLike(rawValue)) { var arrayDim = guessArrayDim(rawValue); valType = arrayDim; if (arrayDim === 1 && !isNumber(rawValue[0]) || arrayDim === 2 && !isNumber(rawValue[0][0])) { discrete = true; } } else { if (isNumber(rawValue) && !eqNaN(rawValue)) { valType = VALUE_TYPE_NUMBER; } else if (isString(rawValue)) { if (!isNaN(+rawValue)) { valType = VALUE_TYPE_NUMBER; } else { var colorArray = parse(rawValue); if (colorArray) { value = colorArray; valType = VALUE_TYPE_COLOR; } } } else if (isGradientObject(rawValue)) { var parsedGradient = extend({}, value); parsedGradient.colorStops = map(rawValue.colorStops, function(colorStop) { return { offset: colorStop.offset, color: parse(colorStop.color) }; }); if (isLinearGradient(rawValue)) { valType = VALUE_TYPE_LINEAR_GRADIENT; } else if (isRadialGradient(rawValue)) { valType = VALUE_TYPE_RADIAL_GRADIENT; } value = parsedGradient; } } if (len === 0) { this.valType = valType; } else if (valType !== this.valType || valType === VALUE_TYPE_UNKOWN) { discrete = true; } this.discrete = this.discrete || discrete; var kf = { time, value, rawValue, percent: 0 }; if (easing) { kf.easing = easing; kf.easingFunc = isFunction(easing) ? easing : easingFuncs[easing] || createCubicEasingFunc(easing); } keyframes.push(kf); return kf; }; Track2.prototype.prepare = function(maxTime, additiveTrack) { var kfs = this.keyframes; if (this._needsSort) { kfs.sort(function(a, b) { return a.time - b.time; }); } var valType = this.valType; var kfsLen = kfs.length; var lastKf = kfs[kfsLen - 1]; var isDiscrete = this.discrete; var isArr = isArrayValueType(valType); var isGradient = isGradientValueType(valType); for (var i = 0; i < kfsLen; i++) { var kf = kfs[i]; var value = kf.value; var lastValue = lastKf.value; kf.percent = kf.time / maxTime; if (!isDiscrete) { if (isArr && i !== kfsLen - 1) { fillArray(value, lastValue, valType); } else if (isGradient) { fillColorStops(value.colorStops, lastValue.colorStops); } } } if (!isDiscrete && valType !== VALUE_TYPE_RADIAL_GRADIENT && additiveTrack && this.needsAnimate() && additiveTrack.needsAnimate() && valType === additiveTrack.valType && !additiveTrack._finished) { this._additiveTrack = additiveTrack; var startValue = kfs[0].value; for (var i = 0; i < kfsLen; i++) { if (valType === VALUE_TYPE_NUMBER) { kfs[i].additiveValue = kfs[i].value - startValue; } else if (valType === VALUE_TYPE_COLOR) { kfs[i].additiveValue = add1DArray([], kfs[i].value, startValue, -1); } else if (isArrayValueType(valType)) { kfs[i].additiveValue = valType === VALUE_TYPE_1D_ARRAY ? add1DArray([], kfs[i].value, startValue, -1) : add2DArray([], kfs[i].value, startValue, -1); } } } }; Track2.prototype.step = function(target, percent) { if (this._finished) { return; } if (this._additiveTrack && this._additiveTrack._finished) { this._additiveTrack = null; } var isAdditive = this._additiveTrack != null; var valueKey = isAdditive ? "additiveValue" : "value"; var valType = this.valType; var keyframes = this.keyframes; var kfsNum = keyframes.length; var propName = this.propName; var isValueColor = valType === VALUE_TYPE_COLOR; var frameIdx; var lastFrame = this._lastFr; var mathMin2 = Math.min; var frame; var nextFrame; if (kfsNum === 1) { frame = nextFrame = keyframes[0]; } else { if (percent < 0) { frameIdx = 0; } else if (percent < this._lastFrP) { var start = mathMin2(lastFrame + 1, kfsNum - 1); for (frameIdx = start; frameIdx >= 0; frameIdx--) { if (keyframes[frameIdx].percent <= percent) { break; } } frameIdx = mathMin2(frameIdx, kfsNum - 2); } else { for (frameIdx = lastFrame; frameIdx < kfsNum; frameIdx++) { if (keyframes[frameIdx].percent > percent) { break; } } frameIdx = mathMin2(frameIdx - 1, kfsNum - 2); } nextFrame = keyframes[frameIdx + 1]; frame = keyframes[frameIdx]; } if (!(frame && nextFrame)) { return; } this._lastFr = frameIdx; this._lastFrP = percent; var interval = nextFrame.percent - frame.percent; var w = interval === 0 ? 1 : mathMin2((percent - frame.percent) / interval, 1); if (nextFrame.easingFunc) { w = nextFrame.easingFunc(w); } var targetArr = isAdditive ? this._additiveValue : isValueColor ? tmpRgba : target[propName]; if ((isArrayValueType(valType) || isValueColor) && !targetArr) { targetArr = this._additiveValue = []; } if (this.discrete) { target[propName] = w < 1 ? frame.rawValue : nextFrame.rawValue; } else if (isArrayValueType(valType)) { valType === VALUE_TYPE_1D_ARRAY ? interpolate1DArray(targetArr, frame[valueKey], nextFrame[valueKey], w) : interpolate2DArray(targetArr, frame[valueKey], nextFrame[valueKey], w); } else if (isGradientValueType(valType)) { var val = frame[valueKey]; var nextVal_1 = nextFrame[valueKey]; var isLinearGradient_1 = valType === VALUE_TYPE_LINEAR_GRADIENT; target[propName] = { type: isLinearGradient_1 ? "linear" : "radial", x: interpolateNumber(val.x, nextVal_1.x, w), y: interpolateNumber(val.y, nextVal_1.y, w), colorStops: map(val.colorStops, function(colorStop, idx) { var nextColorStop = nextVal_1.colorStops[idx]; return { offset: interpolateNumber(colorStop.offset, nextColorStop.offset, w), color: rgba2String(interpolate1DArray([], colorStop.color, nextColorStop.color, w)) }; }), global: nextVal_1.global }; if (isLinearGradient_1) { target[propName].x2 = interpolateNumber(val.x2, nextVal_1.x2, w); target[propName].y2 = interpolateNumber(val.y2, nextVal_1.y2, w); } else { target[propName].r = interpolateNumber(val.r, nextVal_1.r, w); } } else if (isValueColor) { interpolate1DArray(targetArr, frame[valueKey], nextFrame[valueKey], w); if (!isAdditive) { target[propName] = rgba2String(targetArr); } } else { var value = interpolateNumber(frame[valueKey], nextFrame[valueKey], w); if (isAdditive) { this._additiveValue = value; } else { target[propName] = value; } } if (isAdditive) { this._addToTarget(target); } }; Track2.prototype._addToTarget = function(target) { var valType = this.valType; var propName = this.propName; var additiveValue = this._additiveValue; if (valType === VALUE_TYPE_NUMBER) { target[propName] = target[propName] + additiveValue; } else if (valType === VALUE_TYPE_COLOR) { parse(target[propName], tmpRgba); add1DArray(tmpRgba, tmpRgba, additiveValue, 1); target[propName] = rgba2String(tmpRgba); } else if (valType === VALUE_TYPE_1D_ARRAY) { add1DArray(target[propName], target[propName], additiveValue, 1); } else if (valType === VALUE_TYPE_2D_ARRAY) { add2DArray(target[propName], target[propName], additiveValue, 1); } }; return Track2; }(); var Animator = function() { function Animator2(target, loop, allowDiscreteAnimation, additiveTo) { this._tracks = {}; this._trackKeys = []; this._maxTime = 0; this._started = 0; this._clip = null; this._target = target; this._loop = loop; if (loop && additiveTo) { logError("Can' use additive animation on looped animation."); return; } this._additiveAnimators = additiveTo; this._allowDiscrete = allowDiscreteAnimation; } Animator2.prototype.getMaxTime = function() { return this._maxTime; }; Animator2.prototype.getDelay = function() { return this._delay; }; Animator2.prototype.getLoop = function() { return this._loop; }; Animator2.prototype.getTarget = function() { return this._target; }; Animator2.prototype.changeTarget = function(target) { this._target = target; }; Animator2.prototype.when = function(time, props, easing) { return this.whenWithKeys(time, props, keys(props), easing); }; Animator2.prototype.whenWithKeys = function(time, props, propNames, easing) { var tracks = this._tracks; for (var i = 0; i < propNames.length; i++) { var propName = propNames[i]; var track = tracks[propName]; if (!track) { track = tracks[propName] = new Track(propName); var initialValue = void 0; var additiveTrack = this._getAdditiveTrack(propName); if (additiveTrack) { var addtiveTrackKfs = additiveTrack.keyframes; var lastFinalKf = addtiveTrackKfs[addtiveTrackKfs.length - 1]; initialValue = lastFinalKf && lastFinalKf.value; if (additiveTrack.valType === VALUE_TYPE_COLOR && initialValue) { initialValue = rgba2String(initialValue); } } else { initialValue = this._target[propName]; } if (initialValue == null) { continue; } if (time > 0) { track.addKeyframe(0, cloneValue(initialValue), easing); } this._trackKeys.push(propName); } track.addKeyframe(time, cloneValue(props[propName]), easing); } this._maxTime = Math.max(this._maxTime, time); return this; }; Animator2.prototype.pause = function() { this._clip.pause(); this._paused = true; }; Animator2.prototype.resume = function() { this._clip.resume(); this._paused = false; }; Animator2.prototype.isPaused = function() { return !!this._paused; }; Animator2.prototype.duration = function(duration) { this._maxTime = duration; this._force = true; return this; }; Animator2.prototype._doneCallback = function() { this._setTracksFinished(); this._clip = null; var doneList = this._doneCbs; if (doneList) { var len = doneList.length; for (var i = 0; i < len; i++) { doneList[i].call(this); } } }; Animator2.prototype._abortedCallback = function() { this._setTracksFinished(); var animation = this.animation; var abortedList = this._abortedCbs; if (animation) { animation.removeClip(this._clip); } this._clip = null; if (abortedList) { for (var i = 0; i < abortedList.length; i++) { abortedList[i].call(this); } } }; Animator2.prototype._setTracksFinished = function() { var tracks = this._tracks; var tracksKeys = this._trackKeys; for (var i = 0; i < tracksKeys.length; i++) { tracks[tracksKeys[i]].setFinished(); } }; Animator2.prototype._getAdditiveTrack = function(trackName) { var additiveTrack; var additiveAnimators = this._additiveAnimators; if (additiveAnimators) { for (var i = 0; i < additiveAnimators.length; i++) { var track = additiveAnimators[i].getTrack(trackName); if (track) { additiveTrack = track; } } } return additiveTrack; }; Animator2.prototype.start = function(easing) { if (this._started > 0) { return; } this._started = 1; var self2 = this; var tracks = []; var maxTime = this._maxTime || 0; for (var i = 0; i < this._trackKeys.length; i++) { var propName = this._trackKeys[i]; var track = this._tracks[propName]; var additiveTrack = this._getAdditiveTrack(propName); var kfs = track.keyframes; var kfsNum = kfs.length; track.prepare(maxTime, additiveTrack); if (track.needsAnimate()) { if (!this._allowDiscrete && track.discrete) { var lastKf = kfs[kfsNum - 1]; if (lastKf) { self2._target[track.propName] = lastKf.rawValue; } track.setFinished(); } else { tracks.push(track); } } } if (tracks.length || this._force) { var clip = new Clip({ life: maxTime, loop: this._loop, delay: this._delay || 0, onframe: function(percent) { self2._started = 2; var additiveAnimators = self2._additiveAnimators; if (additiveAnimators) { var stillHasAdditiveAnimator = false; for (var i2 = 0; i2 < additiveAnimators.length; i2++) { if (additiveAnimators[i2]._clip) { stillHasAdditiveAnimator = true; break; } } if (!stillHasAdditiveAnimator) { self2._additiveAnimators = null; } } for (var i2 = 0; i2 < tracks.length; i2++) { tracks[i2].step(self2._target, percent); } var onframeList = self2._onframeCbs; if (onframeList) { for (var i2 = 0; i2 < onframeList.length; i2++) { onframeList[i2](self2._target, percent); } } }, ondestroy: function() { self2._doneCallback(); } }); this._clip = clip; if (this.animation) { this.animation.addClip(clip); } if (easing) { clip.setEasing(easing); } } else { this._doneCallback(); } return this; }; Animator2.prototype.stop = function(forwardToLast) { if (!this._clip) { return; } var clip = this._clip; if (forwardToLast) { clip.onframe(1); } this._abortedCallback(); }; Animator2.prototype.delay = function(time) { this._delay = time; return this; }; Animator2.prototype.during = function(cb) { if (cb) { if (!this._onframeCbs) { this._onframeCbs = []; } this._onframeCbs.push(cb); } return this; }; Animator2.prototype.done = function(cb) { if (cb) { if (!this._doneCbs) { this._doneCbs = []; } this._doneCbs.push(cb); } return this; }; Animator2.prototype.aborted = function(cb) { if (cb) { if (!this._abortedCbs) { this._abortedCbs = []; } this._abortedCbs.push(cb); } return this; }; Animator2.prototype.getClip = function() { return this._clip; }; Animator2.prototype.getTrack = function(propName) { return this._tracks[propName]; }; Animator2.prototype.getTracks = function() { var _this = this; return map(this._trackKeys, function(key) { return _this._tracks[key]; }); }; Animator2.prototype.stopTracks = function(propNames, forwardToLast) { if (!propNames.length || !this._clip) { return true; } var tracks = this._tracks; var tracksKeys = this._trackKeys; for (var i = 0; i < propNames.length; i++) { var track = tracks[propNames[i]]; if (track && !track.isFinished()) { if (forwardToLast) { track.step(this._target, 1); } else if (this._started === 1) { track.step(this._target, 0); } track.setFinished(); } } var allAborted = true; for (var i = 0; i < tracksKeys.length; i++) { if (!tracks[tracksKeys[i]].isFinished()) { allAborted = false; break; } } if (allAborted) { this._abortedCallback(); } return allAborted; }; Animator2.prototype.saveTo = function(target, trackKeys, firstOrLast) { if (!target) { return; } trackKeys = trackKeys || this._trackKeys; for (var i = 0; i < trackKeys.length; i++) { var propName = trackKeys[i]; var track = this._tracks[propName]; if (!track || track.isFinished()) { continue; } var kfs = track.keyframes; var kf = kfs[firstOrLast ? 0 : kfs.length - 1]; if (kf) { target[propName] = cloneValue(kf.rawValue); } } }; Animator2.prototype.__changeFinalValue = function(finalProps, trackKeys) { trackKeys = trackKeys || keys(finalProps); for (var i = 0; i < trackKeys.length; i++) { var propName = trackKeys[i]; var track = this._tracks[propName]; if (!track) { continue; } var kfs = track.keyframes; if (kfs.length > 1) { var lastKf = kfs.pop(); track.addKeyframe(lastKf.time, finalProps[propName]); track.prepare(this._maxTime, track.getAdditiveTrack()); } } }; return Animator2; }(); var animatableMixin = { _animators: null, getAnimators: function() { this._animators = this._animators || []; return this._animators; }, animate: function(path, opts) { this._animators = this._animators || []; var el = this; var target; if (path) { var pathSplitted = path.split("."); var prop = el; for (var i = 0, l = pathSplitted.length; i < l; i++) { if (!prop) { continue; } prop = prop[pathSplitted[i]]; } if (prop) { target = prop; } } else { target = el; } if (target == null) { throw new Error("Target " + path + " not exists"); } var animators = this._animators; var animator = new Animator(target, opts); var self2 = this; animator.during(function() { if (self2.__zr) { self2.__zr.refresh(); } }).done(function() { var idx = animators.indexOf(animator); if (idx >= 0) { animators.splice(idx, 1); } }); animators.push(animator); if (this.__zr) { this.__zr.animation.addAnimator(animator); } return animator; }, stopAnimation: function(forwardToLast) { this._animators = this._animators || []; var animators = this._animators; var len = animators.length; for (var i = 0; i < len; i++) { animators[i].stop(forwardToLast); } animators.length = 0; return this; }, addAnimatorsToZr: function(zr) { if (this._animators) { for (var i = 0; i < this._animators.length; i++) { zr.animation.addAnimator(this._animators[i]); } } }, removeAnimatorsFromZr: function(zr) { if (this._animators) { for (var i = 0; i < this._animators.length; i++) { zr.animation.removeAnimator(this._animators[i]); } } } }; const utilShaderCode = "\n@export clay.util.rand\nhighp float rand(vec2 uv) {\n const highp float a = 12.9898, b = 78.233, c = 43758.5453;\n highp float dt = dot(uv.xy, vec2(a,b)), sn = mod(dt, 3.141592653589793);\n return fract(sin(sn) * c);\n}\n@end\n@export clay.util.calculate_attenuation\nuniform float attenuationFactor : 5.0;\nfloat lightAttenuation(float dist, float range)\n{\n float attenuation = 1.0;\n attenuation = dist*dist/(range*range+1.0);\n float att_s = attenuationFactor;\n attenuation = 1.0/(attenuation*att_s+1.0);\n att_s = 1.0/(att_s+1.0);\n attenuation = attenuation - att_s;\n attenuation /= 1.0 - att_s;\n return clamp(attenuation, 0.0, 1.0);\n}\n@end\n@export clay.util.edge_factor\n#ifdef SUPPORT_STANDARD_DERIVATIVES\nfloat edgeFactor(float width)\n{\n vec3 d = fwidth(v_Barycentric);\n vec3 a3 = smoothstep(vec3(0.0), d * width, v_Barycentric);\n return min(min(a3.x, a3.y), a3.z);\n}\n#else\nfloat edgeFactor(float width)\n{\n return 1.0;\n}\n#endif\n@end\n@export clay.util.encode_float\nvec4 encodeFloat(const in float depth)\n{\n const vec4 bitShifts = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);\n const vec4 bit_mask = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);\n vec4 res = fract(depth * bitShifts);\n res -= res.xxyz * bit_mask;\n return res;\n}\n@end\n@export clay.util.decode_float\nfloat decodeFloat(const in vec4 color)\n{\n const vec4 bitShifts = vec4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1.0);\n return dot(color, bitShifts);\n}\n@end\n@export clay.util.float\n@import clay.util.encode_float\n@import clay.util.decode_float\n@end\n@export clay.util.rgbm_decode\nvec3 RGBMDecode(vec4 rgbm, float range) {\n return range * rgbm.rgb * rgbm.a;\n}\n@end\n@export clay.util.rgbm_encode\nvec4 RGBMEncode(vec3 color, float range) {\n if (dot(color, color) == 0.0) {\n return vec4(0.0);\n }\n vec4 rgbm;\n color /= range;\n rgbm.a = clamp(max(max(color.r, color.g), max(color.b, 1e-6)), 0.0, 1.0);\n rgbm.a = ceil(rgbm.a * 255.0) / 255.0;\n rgbm.rgb = color / rgbm.a;\n return rgbm;\n}\n@end\n@export clay.util.rgbm\n@import clay.util.rgbm_decode\n@import clay.util.rgbm_encode\nvec4 decodeHDR(vec4 color)\n{\n#if defined(RGBM_DECODE) || defined(RGBM)\n return vec4(RGBMDecode(color, 8.12), 1.0);\n#else\n return color;\n#endif\n}\nvec4 encodeHDR(vec4 color)\n{\n#if defined(RGBM_ENCODE) || defined(RGBM)\n return RGBMEncode(color.xyz, 8.12);\n#else\n return color;\n#endif\n}\n@end\n@export clay.util.srgb\nvec4 sRGBToLinear(in vec4 value) {\n return vec4(mix(pow(value.rgb * 0.9478672986 + vec3(0.0521327014), vec3(2.4)), value.rgb * 0.0773993808, vec3(lessThanEqual(value.rgb, vec3(0.04045)))), value.w);\n}\nvec4 linearTosRGB(in vec4 value) {\n return vec4(mix(pow(value.rgb, vec3(0.41666)) * 1.055 - vec3(0.055), value.rgb * 12.92, vec3(lessThanEqual(value.rgb, vec3(0.0031308)))), value.w);\n}\n@end\n@export clay.chunk.skinning_header\n#ifdef SKINNING\nattribute vec3 weight : WEIGHT;\nattribute vec4 joint : JOINT;\n#ifdef USE_SKIN_MATRICES_TEXTURE\nuniform sampler2D skinMatricesTexture : ignore;\nuniform float skinMatricesTextureSize: ignore;\nmat4 getSkinMatrix(sampler2D tex, float idx) {\n float j = idx * 4.0;\n float x = mod(j, skinMatricesTextureSize);\n float y = floor(j / skinMatricesTextureSize) + 0.5;\n vec2 scale = vec2(skinMatricesTextureSize);\n return mat4(\n texture2D(tex, vec2(x + 0.5, y) / scale),\n texture2D(tex, vec2(x + 1.5, y) / scale),\n texture2D(tex, vec2(x + 2.5, y) / scale),\n texture2D(tex, vec2(x + 3.5, y) / scale)\n );\n}\nmat4 getSkinMatrix(float idx) {\n return getSkinMatrix(skinMatricesTexture, idx);\n}\n#else\nuniform mat4 skinMatrix[JOINT_COUNT] : SKIN_MATRIX;\nmat4 getSkinMatrix(float idx) {\n return skinMatrix[int(idx)];\n}\n#endif\n#endif\n@end\n@export clay.chunk.skin_matrix\nmat4 skinMatrixWS = getSkinMatrix(joint.x) * weight.x;\nif (weight.y > 1e-4)\n{\n skinMatrixWS += getSkinMatrix(joint.y) * weight.y;\n}\nif (weight.z > 1e-4)\n{\n skinMatrixWS += getSkinMatrix(joint.z) * weight.z;\n}\nfloat weightW = 1.0-weight.x-weight.y-weight.z;\nif (weightW > 1e-4)\n{\n skinMatrixWS += getSkinMatrix(joint.w) * weightW;\n}\n@end\n@export clay.chunk.instancing_header\n#ifdef INSTANCING\nattribute vec4 instanceMat1;\nattribute vec4 instanceMat2;\nattribute vec4 instanceMat3;\n#endif\n@end\n@export clay.chunk.instancing_matrix\nmat4 instanceMat = mat4(\n vec4(instanceMat1.xyz, 0.0),\n vec4(instanceMat2.xyz, 0.0),\n vec4(instanceMat3.xyz, 0.0),\n vec4(instanceMat1.w, instanceMat2.w, instanceMat3.w, 1.0)\n);\n@end\n@export clay.util.parallax_correct\nvec3 parallaxCorrect(in vec3 dir, in vec3 pos, in vec3 boxMin, in vec3 boxMax) {\n vec3 first = (boxMax - pos) / dir;\n vec3 second = (boxMin - pos) / dir;\n vec3 further = max(first, second);\n float dist = min(further.x, min(further.y, further.z));\n vec3 fixedPos = pos + dir * dist;\n vec3 boxCenter = (boxMax + boxMin) * 0.5;\n return normalize(fixedPos - boxCenter);\n}\n@end\n@export clay.util.clamp_sample\nvec4 clampSample(const in sampler2D texture, const in vec2 coord)\n{\n#ifdef STEREO\n float eye = step(0.5, coord.x) * 0.5;\n vec2 coordClamped = clamp(coord, vec2(eye, 0.0), vec2(0.5 + eye, 1.0));\n#else\n vec2 coordClamped = clamp(coord, vec2(0.0), vec2(1.0));\n#endif\n return texture2D(texture, coordClamped);\n}\n@end\n@export clay.util.ACES\nvec3 ACESToneMapping(vec3 color)\n{\n const float A = 2.51;\n const float B = 0.03;\n const float C = 2.43;\n const float D = 0.59;\n const float E = 0.14;\n return (color * (A * color + B)) / (color * (C * color + D) + E);\n}\n@end"; const commonGLSL = "\n@export ecgl.common.transformUniforms\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform mat4 worldInverseTranspose : WORLDINVERSETRANSPOSE;\nuniform mat4 world : WORLD;\n@end\n\n@export ecgl.common.attributes\nattribute vec3 position : POSITION;\nattribute vec2 texcoord : TEXCOORD_0;\nattribute vec3 normal : NORMAL;\n@end\n\n@export ecgl.common.uv.header\nuniform vec2 uvRepeat : [1.0, 1.0];\nuniform vec2 uvOffset : [0.0, 0.0];\nuniform vec2 detailUvRepeat : [1.0, 1.0];\nuniform vec2 detailUvOffset : [0.0, 0.0];\n\nvarying vec2 v_Texcoord;\nvarying vec2 v_DetailTexcoord;\n@end\n\n@export ecgl.common.uv.main\nv_Texcoord = texcoord * uvRepeat + uvOffset;\nv_DetailTexcoord = texcoord * detailUvRepeat + detailUvOffset;\n@end\n\n@export ecgl.common.uv.fragmentHeader\nvarying vec2 v_Texcoord;\nvarying vec2 v_DetailTexcoord;\n@end\n\n\n@export ecgl.common.albedo.main\n\n vec4 albedoTexel = vec4(1.0);\n#ifdef DIFFUSEMAP_ENABLED\n albedoTexel = texture2D(diffuseMap, v_Texcoord);\n #ifdef SRGB_DECODE\n albedoTexel = sRGBToLinear(albedoTexel);\n #endif\n#endif\n\n#ifdef DETAILMAP_ENABLED\n vec4 detailTexel = texture2D(detailMap, v_DetailTexcoord);\n #ifdef SRGB_DECODE\n detailTexel = sRGBToLinear(detailTexel);\n #endif\n albedoTexel.rgb = mix(albedoTexel.rgb, detailTexel.rgb, detailTexel.a);\n albedoTexel.a = detailTexel.a + (1.0 - detailTexel.a) * albedoTexel.a;\n#endif\n\n@end\n\n@export ecgl.common.wireframe.vertexHeader\n\n#ifdef WIREFRAME_QUAD\nattribute vec4 barycentric;\nvarying vec4 v_Barycentric;\n#elif defined(WIREFRAME_TRIANGLE)\nattribute vec3 barycentric;\nvarying vec3 v_Barycentric;\n#endif\n\n@end\n\n@export ecgl.common.wireframe.vertexMain\n\n#if defined(WIREFRAME_QUAD) || defined(WIREFRAME_TRIANGLE)\n v_Barycentric = barycentric;\n#endif\n\n@end\n\n\n@export ecgl.common.wireframe.fragmentHeader\n\nuniform float wireframeLineWidth : 1;\nuniform vec4 wireframeLineColor: [0, 0, 0, 0.5];\n\n#ifdef WIREFRAME_QUAD\nvarying vec4 v_Barycentric;\nfloat edgeFactor () {\n vec4 d = fwidth(v_Barycentric);\n vec4 a4 = smoothstep(vec4(0.0), d * wireframeLineWidth, v_Barycentric);\n return min(min(min(a4.x, a4.y), a4.z), a4.w);\n}\n#elif defined(WIREFRAME_TRIANGLE)\nvarying vec3 v_Barycentric;\nfloat edgeFactor () {\n vec3 d = fwidth(v_Barycentric);\n vec3 a3 = smoothstep(vec3(0.0), d * wireframeLineWidth, v_Barycentric);\n return min(min(a3.x, a3.y), a3.z);\n}\n#endif\n\n@end\n\n\n@export ecgl.common.wireframe.fragmentMain\n\n#if defined(WIREFRAME_QUAD) || defined(WIREFRAME_TRIANGLE)\n if (wireframeLineWidth > 0.) {\n vec4 lineColor = wireframeLineColor;\n#ifdef SRGB_DECODE\n lineColor = sRGBToLinear(lineColor);\n#endif\n\n gl_FragColor.rgb = mix(gl_FragColor.rgb, lineColor.rgb, (1.0 - edgeFactor()) * lineColor.a);\n }\n#endif\n@end\n\n\n\n\n@export ecgl.common.bumpMap.header\n\n#ifdef BUMPMAP_ENABLED\nuniform sampler2D bumpMap;\nuniform float bumpScale : 1.0;\n\n\nvec3 bumpNormal(vec3 surfPos, vec3 surfNormal, vec3 baseNormal)\n{\n vec2 dSTdx = dFdx(v_Texcoord);\n vec2 dSTdy = dFdy(v_Texcoord);\n\n float Hll = bumpScale * texture2D(bumpMap, v_Texcoord).x;\n float dHx = bumpScale * texture2D(bumpMap, v_Texcoord + dSTdx).x - Hll;\n float dHy = bumpScale * texture2D(bumpMap, v_Texcoord + dSTdy).x - Hll;\n\n vec3 vSigmaX = dFdx(surfPos);\n vec3 vSigmaY = dFdy(surfPos);\n vec3 vN = surfNormal;\n\n vec3 R1 = cross(vSigmaY, vN);\n vec3 R2 = cross(vN, vSigmaX);\n\n float fDet = dot(vSigmaX, R1);\n\n vec3 vGrad = sign(fDet) * (dHx * R1 + dHy * R2);\n return normalize(abs(fDet) * baseNormal - vGrad);\n\n}\n#endif\n\n@end\n\n@export ecgl.common.normalMap.vertexHeader\n\n#ifdef NORMALMAP_ENABLED\nattribute vec4 tangent : TANGENT;\nvarying vec3 v_Tangent;\nvarying vec3 v_Bitangent;\n#endif\n\n@end\n\n@export ecgl.common.normalMap.vertexMain\n\n#ifdef NORMALMAP_ENABLED\n if (dot(tangent, tangent) > 0.0) {\n v_Tangent = normalize((worldInverseTranspose * vec4(tangent.xyz, 0.0)).xyz);\n v_Bitangent = normalize(cross(v_Normal, v_Tangent) * tangent.w);\n }\n#endif\n\n@end\n\n\n@export ecgl.common.normalMap.fragmentHeader\n\n#ifdef NORMALMAP_ENABLED\nuniform sampler2D normalMap;\nvarying vec3 v_Tangent;\nvarying vec3 v_Bitangent;\n#endif\n\n@end\n\n@export ecgl.common.normalMap.fragmentMain\n#ifdef NORMALMAP_ENABLED\n if (dot(v_Tangent, v_Tangent) > 0.0) {\n vec3 normalTexel = texture2D(normalMap, v_DetailTexcoord).xyz;\n if (dot(normalTexel, normalTexel) > 0.0) { N = normalTexel * 2.0 - 1.0;\n mat3 tbn = mat3(v_Tangent, v_Bitangent, v_Normal);\n N = normalize(tbn * N);\n }\n }\n#endif\n@end\n\n\n\n@export ecgl.common.vertexAnimation.header\n\n#ifdef VERTEX_ANIMATION\nattribute vec3 prevPosition;\nattribute vec3 prevNormal;\nuniform float percent;\n#endif\n\n@end\n\n@export ecgl.common.vertexAnimation.main\n\n#ifdef VERTEX_ANIMATION\n vec3 pos = mix(prevPosition, position, percent);\n vec3 norm = mix(prevNormal, normal, percent);\n#else\n vec3 pos = position;\n vec3 norm = normal;\n#endif\n\n@end\n\n\n@export ecgl.common.ssaoMap.header\n#ifdef SSAOMAP_ENABLED\nuniform sampler2D ssaoMap;\nuniform vec4 viewport : VIEWPORT;\n#endif\n@end\n\n@export ecgl.common.ssaoMap.main\n float ao = 1.0;\n#ifdef SSAOMAP_ENABLED\n ao = texture2D(ssaoMap, (gl_FragCoord.xy - viewport.xy) / viewport.zw).r;\n#endif\n@end\n\n\n\n\n@export ecgl.common.diffuseLayer.header\n\n#if (LAYER_DIFFUSEMAP_COUNT > 0)\nuniform float layerDiffuseIntensity[LAYER_DIFFUSEMAP_COUNT];\nuniform sampler2D layerDiffuseMap[LAYER_DIFFUSEMAP_COUNT];\n#endif\n\n@end\n\n@export ecgl.common.emissiveLayer.header\n\n#if (LAYER_EMISSIVEMAP_COUNT > 0)\nuniform float layerEmissionIntensity[LAYER_EMISSIVEMAP_COUNT];\nuniform sampler2D layerEmissiveMap[LAYER_EMISSIVEMAP_COUNT];\n#endif\n\n@end\n\n@export ecgl.common.layers.header\n@import ecgl.common.diffuseLayer.header\n@import ecgl.common.emissiveLayer.header\n@end\n\n@export ecgl.common.diffuseLayer.main\n\n#if (LAYER_DIFFUSEMAP_COUNT > 0)\n for (int _idx_ = 0; _idx_ < LAYER_DIFFUSEMAP_COUNT; _idx_++) {{\n float intensity = layerDiffuseIntensity[_idx_];\n vec4 texel2 = texture2D(layerDiffuseMap[_idx_], v_Texcoord);\n #ifdef SRGB_DECODE\n texel2 = sRGBToLinear(texel2);\n #endif\n albedoTexel.rgb = mix(albedoTexel.rgb, texel2.rgb * intensity, texel2.a);\n albedoTexel.a = texel2.a + (1.0 - texel2.a) * albedoTexel.a;\n }}\n#endif\n\n@end\n\n@export ecgl.common.emissiveLayer.main\n\n#if (LAYER_EMISSIVEMAP_COUNT > 0)\n for (int _idx_ = 0; _idx_ < LAYER_EMISSIVEMAP_COUNT; _idx_++)\n {{\n vec4 texel2 = texture2D(layerEmissiveMap[_idx_], v_Texcoord) * layerEmissionIntensity[_idx_];\n #ifdef SRGB_DECODE\n texel2 = sRGBToLinear(texel2);\n #endif\n float intensity = layerEmissionIntensity[_idx_];\n gl_FragColor.rgb += texel2.rgb * texel2.a * intensity;\n }}\n#endif\n\n@end\n"; const colorGLSL = "@export ecgl.color.vertex\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\n@import ecgl.common.uv.header\n\nattribute vec2 texcoord : TEXCOORD_0;\nattribute vec3 position: POSITION;\n\n@import ecgl.common.wireframe.vertexHeader\n\n#ifdef VERTEX_COLOR\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n#endif\n\n#ifdef VERTEX_ANIMATION\nattribute vec3 prevPosition;\nuniform float percent : 1.0;\n#endif\n\n#ifdef ATMOSPHERE_ENABLED\nattribute vec3 normal: NORMAL;\nuniform mat4 worldInverseTranspose : WORLDINVERSETRANSPOSE;\nvarying vec3 v_Normal;\n#endif\n\nvoid main()\n{\n#ifdef VERTEX_ANIMATION\n vec3 pos = mix(prevPosition, position, percent);\n#else\n vec3 pos = position;\n#endif\n\n gl_Position = worldViewProjection * vec4(pos, 1.0);\n\n @import ecgl.common.uv.main\n\n#ifdef VERTEX_COLOR\n v_Color = a_Color;\n#endif\n\n#ifdef ATMOSPHERE_ENABLED\n v_Normal = normalize((worldInverseTranspose * vec4(normal, 0.0)).xyz);\n#endif\n\n @import ecgl.common.wireframe.vertexMain\n\n}\n\n@end\n\n@export ecgl.color.fragment\n\n#define LAYER_DIFFUSEMAP_COUNT 0\n#define LAYER_EMISSIVEMAP_COUNT 0\n\nuniform sampler2D diffuseMap;\nuniform sampler2D detailMap;\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\n#ifdef ATMOSPHERE_ENABLED\nuniform mat4 viewTranspose: VIEWTRANSPOSE;\nuniform vec3 glowColor;\nuniform float glowPower;\nvarying vec3 v_Normal;\n#endif\n\n#ifdef VERTEX_COLOR\nvarying vec4 v_Color;\n#endif\n\n@import ecgl.common.layers.header\n\n@import ecgl.common.uv.fragmentHeader\n\n@import ecgl.common.wireframe.fragmentHeader\n\n@import clay.util.srgb\n\nvoid main()\n{\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(color);\n#else\n gl_FragColor = color;\n#endif\n\n#ifdef VERTEX_COLOR\n gl_FragColor *= v_Color;\n#endif\n\n @import ecgl.common.albedo.main\n\n @import ecgl.common.diffuseLayer.main\n\n gl_FragColor *= albedoTexel;\n\n#ifdef ATMOSPHERE_ENABLED\n float atmoIntensity = pow(1.0 - dot(v_Normal, (viewTranspose * vec4(0.0, 0.0, 1.0, 0.0)).xyz), glowPower);\n gl_FragColor.rgb += glowColor * atmoIntensity;\n#endif\n\n @import ecgl.common.emissiveLayer.main\n\n @import ecgl.common.wireframe.fragmentMain\n\n}\n@end"; const lambertGLSL = "/**\n * http: */\n\n@export ecgl.lambert.vertex\n\n@import ecgl.common.transformUniforms\n\n@import ecgl.common.uv.header\n\n\n@import ecgl.common.attributes\n\n@import ecgl.common.wireframe.vertexHeader\n\n#ifdef VERTEX_COLOR\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n#endif\n\n\n@import ecgl.common.vertexAnimation.header\n\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nvoid main()\n{\n @import ecgl.common.uv.main\n\n @import ecgl.common.vertexAnimation.main\n\n\n gl_Position = worldViewProjection * vec4(pos, 1.0);\n\n v_Normal = normalize((worldInverseTranspose * vec4(norm, 0.0)).xyz);\n v_WorldPosition = (world * vec4(pos, 1.0)).xyz;\n\n#ifdef VERTEX_COLOR\n v_Color = a_Color;\n#endif\n\n @import ecgl.common.wireframe.vertexMain\n}\n\n@end\n\n\n@export ecgl.lambert.fragment\n\n#define LAYER_DIFFUSEMAP_COUNT 0\n#define LAYER_EMISSIVEMAP_COUNT 0\n\n#define NORMAL_UP_AXIS 1\n#define NORMAL_FRONT_AXIS 2\n\n@import ecgl.common.uv.fragmentHeader\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nuniform sampler2D diffuseMap;\nuniform sampler2D detailMap;\n\n@import ecgl.common.layers.header\n\nuniform float emissionIntensity: 1.0;\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nuniform mat4 viewInverse : VIEWINVERSE;\n\n#ifdef ATMOSPHERE_ENABLED\nuniform mat4 viewTranspose: VIEWTRANSPOSE;\nuniform vec3 glowColor;\nuniform float glowPower;\n#endif\n\n#ifdef AMBIENT_LIGHT_COUNT\n@import clay.header.ambient_light\n#endif\n#ifdef AMBIENT_SH_LIGHT_COUNT\n@import clay.header.ambient_sh_light\n#endif\n\n#ifdef DIRECTIONAL_LIGHT_COUNT\n@import clay.header.directional_light\n#endif\n\n#ifdef VERTEX_COLOR\nvarying vec4 v_Color;\n#endif\n\n\n@import ecgl.common.ssaoMap.header\n\n@import ecgl.common.bumpMap.header\n\n@import clay.util.srgb\n\n@import ecgl.common.wireframe.fragmentHeader\n\n@import clay.plugin.compute_shadow_map\n\nvoid main()\n{\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(color);\n#else\n gl_FragColor = color;\n#endif\n\n#ifdef VERTEX_COLOR\n #ifdef SRGB_DECODE\n gl_FragColor *= sRGBToLinear(v_Color);\n #else\n gl_FragColor *= v_Color;\n #endif\n#endif\n\n @import ecgl.common.albedo.main\n\n @import ecgl.common.diffuseLayer.main\n\n gl_FragColor *= albedoTexel;\n\n vec3 N = v_Normal;\n#ifdef DOUBLE_SIDED\n vec3 eyePos = viewInverse[3].xyz;\n vec3 V = normalize(eyePos - v_WorldPosition);\n\n if (dot(N, V) < 0.0) {\n N = -N;\n }\n#endif\n\n float ambientFactor = 1.0;\n\n#ifdef BUMPMAP_ENABLED\n N = bumpNormal(v_WorldPosition, v_Normal, N);\n ambientFactor = dot(v_Normal, N);\n#endif\n\n vec3 N2 = vec3(N.x, N[NORMAL_UP_AXIS], N[NORMAL_FRONT_AXIS]);\n\n vec3 diffuseColor = vec3(0.0, 0.0, 0.0);\n\n @import ecgl.common.ssaoMap.main\n\n#ifdef AMBIENT_LIGHT_COUNT\n for(int i = 0; i < AMBIENT_LIGHT_COUNT; i++)\n {\n diffuseColor += ambientLightColor[i] * ambientFactor * ao;\n }\n#endif\n#ifdef AMBIENT_SH_LIGHT_COUNT\n for(int _idx_ = 0; _idx_ < AMBIENT_SH_LIGHT_COUNT; _idx_++)\n {{\n diffuseColor += calcAmbientSHLight(_idx_, N2) * ambientSHLightColor[_idx_] * ao;\n }}\n#endif\n#ifdef DIRECTIONAL_LIGHT_COUNT\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n float shadowContribsDir[DIRECTIONAL_LIGHT_COUNT];\n if(shadowEnabled)\n {\n computeShadowOfDirectionalLights(v_WorldPosition, shadowContribsDir);\n }\n#endif\n for(int i = 0; i < DIRECTIONAL_LIGHT_COUNT; i++)\n {\n vec3 lightDirection = -directionalLightDirection[i];\n vec3 lightColor = directionalLightColor[i];\n\n float shadowContrib = 1.0;\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n if (shadowEnabled)\n {\n shadowContrib = shadowContribsDir[i];\n }\n#endif\n\n float ndl = dot(N, normalize(lightDirection)) * shadowContrib;\n\n diffuseColor += lightColor * clamp(ndl, 0.0, 1.0);\n }\n#endif\n\n gl_FragColor.rgb *= diffuseColor;\n\n#ifdef ATMOSPHERE_ENABLED\n float atmoIntensity = pow(1.0 - dot(v_Normal, (viewTranspose * vec4(0.0, 0.0, 1.0, 0.0)).xyz), glowPower);\n gl_FragColor.rgb += glowColor * atmoIntensity;\n#endif\n\n @import ecgl.common.emissiveLayer.main\n\n @import ecgl.common.wireframe.fragmentMain\n}\n\n@end"; const realisticGLSL = "@export ecgl.realistic.vertex\n\n@import ecgl.common.transformUniforms\n\n@import ecgl.common.uv.header\n\n@import ecgl.common.attributes\n\n\n@import ecgl.common.wireframe.vertexHeader\n\n#ifdef VERTEX_COLOR\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n#endif\n\n#ifdef NORMALMAP_ENABLED\nattribute vec4 tangent : TANGENT;\nvarying vec3 v_Tangent;\nvarying vec3 v_Bitangent;\n#endif\n\n@import ecgl.common.vertexAnimation.header\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nvoid main()\n{\n\n @import ecgl.common.uv.main\n\n @import ecgl.common.vertexAnimation.main\n\n gl_Position = worldViewProjection * vec4(pos, 1.0);\n\n v_Normal = normalize((worldInverseTranspose * vec4(norm, 0.0)).xyz);\n v_WorldPosition = (world * vec4(pos, 1.0)).xyz;\n\n#ifdef VERTEX_COLOR\n v_Color = a_Color;\n#endif\n\n#ifdef NORMALMAP_ENABLED\n v_Tangent = normalize((worldInverseTranspose * vec4(tangent.xyz, 0.0)).xyz);\n v_Bitangent = normalize(cross(v_Normal, v_Tangent) * tangent.w);\n#endif\n\n @import ecgl.common.wireframe.vertexMain\n\n}\n\n@end\n\n\n\n@export ecgl.realistic.fragment\n\n#define LAYER_DIFFUSEMAP_COUNT 0\n#define LAYER_EMISSIVEMAP_COUNT 0\n#define PI 3.14159265358979\n#define ROUGHNESS_CHANEL 0\n#define METALNESS_CHANEL 1\n\n#define NORMAL_UP_AXIS 1\n#define NORMAL_FRONT_AXIS 2\n\n#ifdef VERTEX_COLOR\nvarying vec4 v_Color;\n#endif\n\n@import ecgl.common.uv.fragmentHeader\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nuniform sampler2D diffuseMap;\n\nuniform sampler2D detailMap;\nuniform sampler2D metalnessMap;\nuniform sampler2D roughnessMap;\n\n@import ecgl.common.layers.header\n\nuniform float emissionIntensity: 1.0;\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nuniform float metalness : 0.0;\nuniform float roughness : 0.5;\n\nuniform mat4 viewInverse : VIEWINVERSE;\n\n#ifdef ATMOSPHERE_ENABLED\nuniform mat4 viewTranspose: VIEWTRANSPOSE;\nuniform vec3 glowColor;\nuniform float glowPower;\n#endif\n\n#ifdef AMBIENT_LIGHT_COUNT\n@import clay.header.ambient_light\n#endif\n\n#ifdef AMBIENT_SH_LIGHT_COUNT\n@import clay.header.ambient_sh_light\n#endif\n\n#ifdef AMBIENT_CUBEMAP_LIGHT_COUNT\n@import clay.header.ambient_cubemap_light\n#endif\n\n#ifdef DIRECTIONAL_LIGHT_COUNT\n@import clay.header.directional_light\n#endif\n\n@import ecgl.common.normalMap.fragmentHeader\n\n@import ecgl.common.ssaoMap.header\n\n@import ecgl.common.bumpMap.header\n\n@import clay.util.srgb\n\n@import clay.util.rgbm\n\n@import ecgl.common.wireframe.fragmentHeader\n\n@import clay.plugin.compute_shadow_map\n\nvec3 F_Schlick(float ndv, vec3 spec) {\n return spec + (1.0 - spec) * pow(1.0 - ndv, 5.0);\n}\n\nfloat D_Phong(float g, float ndh) {\n float a = pow(8192.0, g);\n return (a + 2.0) / 8.0 * pow(ndh, a);\n}\n\nvoid main()\n{\n vec4 albedoColor = color;\n\n vec3 eyePos = viewInverse[3].xyz;\n vec3 V = normalize(eyePos - v_WorldPosition);\n#ifdef VERTEX_COLOR\n #ifdef SRGB_DECODE\n albedoColor *= sRGBToLinear(v_Color);\n #else\n albedoColor *= v_Color;\n #endif\n#endif\n\n @import ecgl.common.albedo.main\n\n @import ecgl.common.diffuseLayer.main\n\n albedoColor *= albedoTexel;\n\n float m = metalness;\n\n#ifdef METALNESSMAP_ENABLED\n float m2 = texture2D(metalnessMap, v_DetailTexcoord)[METALNESS_CHANEL];\n m = clamp(m2 + (m - 0.5) * 2.0, 0.0, 1.0);\n#endif\n\n vec3 baseColor = albedoColor.rgb;\n albedoColor.rgb = baseColor * (1.0 - m);\n vec3 specFactor = mix(vec3(0.04), baseColor, m);\n\n float g = 1.0 - roughness;\n\n#ifdef ROUGHNESSMAP_ENABLED\n float g2 = 1.0 - texture2D(roughnessMap, v_DetailTexcoord)[ROUGHNESS_CHANEL];\n g = clamp(g2 + (g - 0.5) * 2.0, 0.0, 1.0);\n#endif\n\n vec3 N = v_Normal;\n\n#ifdef DOUBLE_SIDED\n if (dot(N, V) < 0.0) {\n N = -N;\n }\n#endif\n\n float ambientFactor = 1.0;\n\n#ifdef BUMPMAP_ENABLED\n N = bumpNormal(v_WorldPosition, v_Normal, N);\n ambientFactor = dot(v_Normal, N);\n#endif\n\n@import ecgl.common.normalMap.fragmentMain\n\n vec3 N2 = vec3(N.x, N[NORMAL_UP_AXIS], N[NORMAL_FRONT_AXIS]);\n\n vec3 diffuseTerm = vec3(0.0);\n vec3 specularTerm = vec3(0.0);\n\n float ndv = clamp(dot(N, V), 0.0, 1.0);\n vec3 fresnelTerm = F_Schlick(ndv, specFactor);\n\n @import ecgl.common.ssaoMap.main\n\n#ifdef AMBIENT_LIGHT_COUNT\n for(int _idx_ = 0; _idx_ < AMBIENT_LIGHT_COUNT; _idx_++)\n {{\n diffuseTerm += ambientLightColor[_idx_] * ambientFactor * ao;\n }}\n#endif\n\n#ifdef AMBIENT_SH_LIGHT_COUNT\n for(int _idx_ = 0; _idx_ < AMBIENT_SH_LIGHT_COUNT; _idx_++)\n {{\n diffuseTerm += calcAmbientSHLight(_idx_, N2) * ambientSHLightColor[_idx_] * ao;\n }}\n#endif\n\n#ifdef DIRECTIONAL_LIGHT_COUNT\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n float shadowContribsDir[DIRECTIONAL_LIGHT_COUNT];\n if(shadowEnabled)\n {\n computeShadowOfDirectionalLights(v_WorldPosition, shadowContribsDir);\n }\n#endif\n for(int _idx_ = 0; _idx_ < DIRECTIONAL_LIGHT_COUNT; _idx_++)\n {{\n vec3 L = -directionalLightDirection[_idx_];\n vec3 lc = directionalLightColor[_idx_];\n\n vec3 H = normalize(L + V);\n float ndl = clamp(dot(N, normalize(L)), 0.0, 1.0);\n float ndh = clamp(dot(N, H), 0.0, 1.0);\n\n float shadowContrib = 1.0;\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n if (shadowEnabled)\n {\n shadowContrib = shadowContribsDir[_idx_];\n }\n#endif\n\n vec3 li = lc * ndl * shadowContrib;\n\n diffuseTerm += li;\n specularTerm += li * fresnelTerm * D_Phong(g, ndh);\n }}\n#endif\n\n\n#ifdef AMBIENT_CUBEMAP_LIGHT_COUNT\n vec3 L = reflect(-V, N);\n L = vec3(L.x, L[NORMAL_UP_AXIS], L[NORMAL_FRONT_AXIS]);\n float rough2 = clamp(1.0 - g, 0.0, 1.0);\n float bias2 = rough2 * 5.0;\n vec2 brdfParam2 = texture2D(ambientCubemapLightBRDFLookup[0], vec2(rough2, ndv)).xy;\n vec3 envWeight2 = specFactor * brdfParam2.x + brdfParam2.y;\n vec3 envTexel2;\n for(int _idx_ = 0; _idx_ < AMBIENT_CUBEMAP_LIGHT_COUNT; _idx_++)\n {{\n envTexel2 = RGBMDecode(textureCubeLodEXT(ambientCubemapLightCubemap[_idx_], L, bias2), 8.12);\n specularTerm += ambientCubemapLightColor[_idx_] * envTexel2 * envWeight2 * ao;\n }}\n#endif\n\n gl_FragColor.rgb = albedoColor.rgb * diffuseTerm + specularTerm;\n gl_FragColor.a = albedoColor.a;\n\n#ifdef ATMOSPHERE_ENABLED\n float atmoIntensity = pow(1.0 - dot(v_Normal, (viewTranspose * vec4(0.0, 0.0, 1.0, 0.0)).xyz), glowPower);\n gl_FragColor.rgb += glowColor * atmoIntensity;\n#endif\n\n#ifdef SRGB_ENCODE\n gl_FragColor = linearTosRGB(gl_FragColor);\n#endif\n\n @import ecgl.common.emissiveLayer.main\n\n @import ecgl.common.wireframe.fragmentMain\n}\n\n@end"; const hatchingGLSL = "@export ecgl.hatching.vertex\n\n@import ecgl.realistic.vertex\n\n@end\n\n\n@export ecgl.hatching.fragment\n\n#define NORMAL_UP_AXIS 1\n#define NORMAL_FRONT_AXIS 2\n\n@import ecgl.common.uv.fragmentHeader\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nuniform vec4 color : [0.0, 0.0, 0.0, 1.0];\nuniform vec4 paperColor : [1.0, 1.0, 1.0, 1.0];\n\nuniform mat4 viewInverse : VIEWINVERSE;\n\n#ifdef AMBIENT_LIGHT_COUNT\n@import clay.header.ambient_light\n#endif\n#ifdef AMBIENT_SH_LIGHT_COUNT\n@import clay.header.ambient_sh_light\n#endif\n\n#ifdef DIRECTIONAL_LIGHT_COUNT\n@import clay.header.directional_light\n#endif\n\n#ifdef VERTEX_COLOR\nvarying vec4 v_Color;\n#endif\n\n\n@import ecgl.common.ssaoMap.header\n\n@import ecgl.common.bumpMap.header\n\n@import clay.util.srgb\n\n@import ecgl.common.wireframe.fragmentHeader\n\n@import clay.plugin.compute_shadow_map\n\nuniform sampler2D hatch1;\nuniform sampler2D hatch2;\nuniform sampler2D hatch3;\nuniform sampler2D hatch4;\nuniform sampler2D hatch5;\nuniform sampler2D hatch6;\n\nfloat shade(in float tone) {\n vec4 c = vec4(1. ,1., 1., 1.);\n float step = 1. / 6.;\n vec2 uv = v_DetailTexcoord;\n if (tone <= step / 2.0) {\n c = mix(vec4(0.), texture2D(hatch6, uv), 12. * tone);\n }\n else if (tone <= step) {\n c = mix(texture2D(hatch6, uv), texture2D(hatch5, uv), 6. * tone);\n }\n if(tone > step && tone <= 2. * step){\n c = mix(texture2D(hatch5, uv), texture2D(hatch4, uv) , 6. * (tone - step));\n }\n if(tone > 2. * step && tone <= 3. * step){\n c = mix(texture2D(hatch4, uv), texture2D(hatch3, uv), 6. * (tone - 2. * step));\n }\n if(tone > 3. * step && tone <= 4. * step){\n c = mix(texture2D(hatch3, uv), texture2D(hatch2, uv), 6. * (tone - 3. * step));\n }\n if(tone > 4. * step && tone <= 5. * step){\n c = mix(texture2D(hatch2, uv), texture2D(hatch1, uv), 6. * (tone - 4. * step));\n }\n if(tone > 5. * step){\n c = mix(texture2D(hatch1, uv), vec4(1.), 6. * (tone - 5. * step));\n }\n\n return c.r;\n}\n\nconst vec3 w = vec3(0.2125, 0.7154, 0.0721);\n\nvoid main()\n{\n#ifdef SRGB_DECODE\n vec4 inkColor = sRGBToLinear(color);\n#else\n vec4 inkColor = color;\n#endif\n\n#ifdef VERTEX_COLOR\n #ifdef SRGB_DECODE\n inkColor *= sRGBToLinear(v_Color);\n #else\n inkColor *= v_Color;\n #endif\n#endif\n\n vec3 N = v_Normal;\n#ifdef DOUBLE_SIDED\n vec3 eyePos = viewInverse[3].xyz;\n vec3 V = normalize(eyePos - v_WorldPosition);\n\n if (dot(N, V) < 0.0) {\n N = -N;\n }\n#endif\n\n float tone = 0.0;\n\n float ambientFactor = 1.0;\n\n#ifdef BUMPMAP_ENABLED\n N = bumpNormal(v_WorldPosition, v_Normal, N);\n ambientFactor = dot(v_Normal, N);\n#endif\n\n vec3 N2 = vec3(N.x, N[NORMAL_UP_AXIS], N[NORMAL_FRONT_AXIS]);\n\n @import ecgl.common.ssaoMap.main\n\n#ifdef AMBIENT_LIGHT_COUNT\n for(int i = 0; i < AMBIENT_LIGHT_COUNT; i++)\n {\n tone += dot(ambientLightColor[i], w) * ambientFactor * ao;\n }\n#endif\n#ifdef AMBIENT_SH_LIGHT_COUNT\n for(int _idx_ = 0; _idx_ < AMBIENT_SH_LIGHT_COUNT; _idx_++)\n {{\n tone += dot(calcAmbientSHLight(_idx_, N2) * ambientSHLightColor[_idx_], w) * ao;\n }}\n#endif\n#ifdef DIRECTIONAL_LIGHT_COUNT\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n float shadowContribsDir[DIRECTIONAL_LIGHT_COUNT];\n if(shadowEnabled)\n {\n computeShadowOfDirectionalLights(v_WorldPosition, shadowContribsDir);\n }\n#endif\n for(int i = 0; i < DIRECTIONAL_LIGHT_COUNT; i++)\n {\n vec3 lightDirection = -directionalLightDirection[i];\n float lightTone = dot(directionalLightColor[i], w);\n\n float shadowContrib = 1.0;\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n if (shadowEnabled)\n {\n shadowContrib = shadowContribsDir[i];\n }\n#endif\n\n float ndl = dot(N, normalize(lightDirection)) * shadowContrib;\n\n tone += lightTone * clamp(ndl, 0.0, 1.0);\n }\n#endif\n\n gl_FragColor = mix(inkColor, paperColor, shade(clamp(tone, 0.0, 1.0)));\n }\n@end\n"; const shadowGLSL = "@export ecgl.sm.depth.vertex\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\nattribute vec3 position : POSITION;\nattribute vec2 texcoord : TEXCOORD_0;\n\n#ifdef VERTEX_ANIMATION\nattribute vec3 prevPosition;\nuniform float percent : 1.0;\n#endif\n\nvarying vec4 v_ViewPosition;\nvarying vec2 v_Texcoord;\n\nvoid main(){\n\n#ifdef VERTEX_ANIMATION\n vec3 pos = mix(prevPosition, position, percent);\n#else\n vec3 pos = position;\n#endif\n\n v_ViewPosition = worldViewProjection * vec4(pos, 1.0);\n gl_Position = v_ViewPosition;\n\n v_Texcoord = texcoord;\n\n}\n@end\n\n\n\n@export ecgl.sm.depth.fragment\n\n@import clay.sm.depth.fragment\n\n@end"; Object.assign(Node$1.prototype, animatableMixin); Shader.import(utilShaderCode); Shader.import(prezGLSL); Shader.import(commonGLSL); Shader.import(colorGLSL); Shader.import(lambertGLSL); Shader.import(realisticGLSL); Shader.import(hatchingGLSL); Shader.import(shadowGLSL); function isValueNone(value) { return !value || value === "none"; } function isValueImage(value) { return value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof Image; } function isECharts(value) { return value.getZr && value.setOption; } var oldAddToScene = Scene.prototype.addToScene; var oldRemoveFromScene = Scene.prototype.removeFromScene; Scene.prototype.addToScene = function(node) { oldAddToScene.call(this, node); if (this.__zr) { var zr = this.__zr; node.traverse(function(child) { child.__zr = zr; if (child.addAnimatorsToZr) { child.addAnimatorsToZr(zr); } }); } }; Scene.prototype.removeFromScene = function(node) { oldRemoveFromScene.call(this, node); node.traverse(function(child) { var zr = child.__zr; child.__zr = null; if (zr && child.removeAnimatorsFromZr) { child.removeAnimatorsFromZr(zr); } }); }; Material.prototype.setTextureImage = function(textureName, imgValue, api, textureOpts) { if (!this.shader) { return; } var zr = api.getZr(); var material = this; var texture; material.autoUpdateTextureStatus = false; material.disableTexture(textureName); if (!isValueNone(imgValue)) { texture = graphicGL.loadTexture(imgValue, api, textureOpts, function(texture2) { material.enableTexture(textureName); zr && zr.refresh(); }); material.set(textureName, texture); } return texture; }; var graphicGL = {}; graphicGL.Renderer = Renderer; graphicGL.Node = Node$1; graphicGL.Mesh = Mesh; graphicGL.Shader = Shader; graphicGL.Material = Material; graphicGL.Texture = Texture; graphicGL.Texture2D = Texture2D; graphicGL.Geometry = Geometry; graphicGL.SphereGeometry = Sphere; graphicGL.PlaneGeometry = Plane; graphicGL.CubeGeometry = Cube; graphicGL.AmbientLight = AmbientLight; graphicGL.DirectionalLight = DirectionalLight; graphicGL.PointLight = PointLight; graphicGL.SpotLight = SpotLight; graphicGL.PerspectiveCamera = Perspective; graphicGL.OrthographicCamera = Orthographic; graphicGL.Vector2 = Vector2; graphicGL.Vector3 = Vector3; graphicGL.Vector4 = Vector4; graphicGL.Quaternion = Quaternion; graphicGL.Matrix2 = Matrix2; graphicGL.Matrix2d = Matrix2d; graphicGL.Matrix3 = Matrix3; graphicGL.Matrix4 = Matrix4; graphicGL.Plane = Plane$1; graphicGL.Ray = Ray; graphicGL.BoundingBox = BoundingBox; graphicGL.Frustum = Frustum; var blankImage = null; function getBlankImage() { if (blankImage !== null) { return blankImage; } blankImage = textureUtil.createBlank("rgba(255,255,255,0)").image; return blankImage; } function nearestPowerOfTwo(val) { return Math.pow(2, Math.round(Math.log(val) / Math.LN2)); } function convertTextureToPowerOfTwo(texture) { if ((texture.wrapS === Texture.REPEAT || texture.wrapT === Texture.REPEAT) && texture.image) { var width = nearestPowerOfTwo(texture.width); var height = nearestPowerOfTwo(texture.height); if (width !== texture.width || height !== texture.height) { var canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(texture.image, 0, 0, width, height); texture.image = canvas; } } } graphicGL.loadTexture = function(imgValue, api, textureOpts, cb) { if (typeof textureOpts === "function") { cb = textureOpts; textureOpts = {}; } textureOpts = textureOpts || {}; var keys2 = Object.keys(textureOpts).sort(); var prefix = ""; for (var i = 0; i < keys2.length; i++) { prefix += keys2[i] + "_" + textureOpts[keys2[i]] + "_"; } var textureCache = api.__textureCache = api.__textureCache || new LRU(20); if (isECharts(imgValue)) { var id = imgValue.__textureid__; var textureObj = textureCache.get(prefix + id); if (!textureObj) { var surface = new EChartsSurface(imgValue); surface.onupdate = function() { api.getZr().refresh(); }; textureObj = { texture: surface.getTexture() }; for (var i = 0; i < keys2.length; i++) { textureObj.texture[keys2[i]] = textureOpts[keys2[i]]; } id = imgValue.__textureid__ || "__ecgl_ec__" + textureObj.texture.__uid__; imgValue.__textureid__ = id; textureCache.put(prefix + id, textureObj); cb && cb(textureObj.texture); } else { textureObj.texture.surface.setECharts(imgValue); cb && cb(textureObj.texture); } return textureObj.texture; } else if (isValueImage(imgValue)) { var id = imgValue.__textureid__; var textureObj = textureCache.get(prefix + id); if (!textureObj) { textureObj = { texture: new graphicGL.Texture2D({ image: imgValue }) }; for (var i = 0; i < keys2.length; i++) { textureObj.texture[keys2[i]] = textureOpts[keys2[i]]; } id = imgValue.__textureid__ || "__ecgl_image__" + textureObj.texture.__uid__; imgValue.__textureid__ = id; textureCache.put(prefix + id, textureObj); convertTextureToPowerOfTwo(textureObj.texture); cb && cb(textureObj.texture); } return textureObj.texture; } else { var textureObj = textureCache.get(prefix + imgValue); if (textureObj) { if (textureObj.callbacks) { textureObj.callbacks.push(cb); } else { cb && cb(textureObj.texture); } } else { if (imgValue.match(/.hdr$|^data:application\/octet-stream/)) { textureObj = { callbacks: [cb] }; var texture = textureUtil.loadTexture(imgValue, { exposure: textureOpts.exposure, fileType: "hdr" }, function() { texture.dirty(); textureObj.callbacks.forEach(function(cb2) { cb2 && cb2(texture); }); textureObj.callbacks = null; }); textureObj.texture = texture; textureCache.put(prefix + imgValue, textureObj); } else { var texture = new graphicGL.Texture2D({ image: new Image() }); for (var i = 0; i < keys2.length; i++) { texture[keys2[i]] = textureOpts[keys2[i]]; } textureObj = { texture, callbacks: [cb] }; var originalImage = texture.image; originalImage.onload = function() { texture.image = originalImage; convertTextureToPowerOfTwo(texture); texture.dirty(); textureObj.callbacks.forEach(function(cb2) { cb2 && cb2(texture); }); textureObj.callbacks = null; }; originalImage.crossOrigin = "Anonymous"; originalImage.src = imgValue; texture.image = getBlankImage(); textureCache.put(prefix + imgValue, textureObj); } } return textureObj.texture; } }; graphicGL.createAmbientCubemap = function(opt, renderer, api, cb) { opt = opt || {}; var textureUrl = opt.texture; var exposure = retrieve.firstNotNull(opt.exposure, 1); var ambientCubemap = new AmbientCubemapLight({ intensity: retrieve.firstNotNull(opt.specularIntensity, 1) }); var ambientSH = new AmbientSHLight({ intensity: retrieve.firstNotNull(opt.diffuseIntensity, 1), coefficients: [0.844, 0.712, 0.691, -0.037, 0.083, 0.167, 0.343, 0.288, 0.299, -0.041, -0.021, -9e-3, -3e-3, -0.041, -0.064, -0.011, -7e-3, -4e-3, -0.031, 0.034, 0.081, -0.06, -0.049, -0.06, 0.046, 0.056, 0.05] }); ambientCubemap.cubemap = graphicGL.loadTexture(textureUrl, api, { exposure }, function() { ambientCubemap.cubemap.flipY = false; ambientCubemap.prefilter(renderer, 32); ambientSH.coefficients = sh.projectEnvironmentMap(renderer, ambientCubemap.cubemap, { lod: 1 }); cb && cb(); }); return { specular: ambientCubemap, diffuse: ambientSH }; }; graphicGL.createBlankTexture = textureUtil.createBlank; graphicGL.isImage = isValueImage; graphicGL.additiveBlend = function(gl) { gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.SRC_ALPHA, gl.ONE); }; graphicGL.parseColor = function(colorStr, rgba) { if (colorStr instanceof Array) { if (!rgba) { rgba = []; } rgba[0] = colorStr[0]; rgba[1] = colorStr[1]; rgba[2] = colorStr[2]; if (colorStr.length > 3) { rgba[3] = colorStr[3]; } else { rgba[3] = 1; } return rgba; } rgba = parse$1(colorStr || "#000", rgba) || [0, 0, 0, 0]; rgba[0] /= 255; rgba[1] /= 255; rgba[2] /= 255; return rgba; }; graphicGL.directionFromAlphaBeta = function(alpha, beta) { var theta = alpha / 180 * Math.PI + Math.PI / 2; var phi = -beta / 180 * Math.PI + Math.PI / 2; var dir = []; var r = Math.sin(theta); dir[0] = r * Math.cos(phi); dir[1] = -Math.cos(theta); dir[2] = r * Math.sin(phi); return dir; }; graphicGL.getShadowResolution = function(shadowQuality) { var shadowResolution = 1024; switch (shadowQuality) { case "low": shadowResolution = 512; break; case "medium": break; case "high": shadowResolution = 2048; break; case "ultra": shadowResolution = 4096; break; } return shadowResolution; }; graphicGL.COMMON_SHADERS = ["lambert", "color", "realistic", "hatching", "shadow"]; graphicGL.createShader = function(prefix) { if (prefix === "ecgl.shadow") { prefix = "ecgl.displayShadow"; } var vertexShaderStr = Shader.source(prefix + ".vertex"); var fragmentShaderStr = Shader.source(prefix + ".fragment"); if (!vertexShaderStr) { console.error("Vertex shader of '%s' not exits", prefix); } if (!fragmentShaderStr) { console.error("Fragment shader of '%s' not exits", prefix); } var shader = new Shader(vertexShaderStr, fragmentShaderStr); shader.name = prefix; return shader; }; graphicGL.createMaterial = function(prefix, defines) { if (!(defines instanceof Array)) { defines = [defines]; } var shader = graphicGL.createShader(prefix); var material = new Material({ shader }); defines.forEach(function(defineName) { if (typeof defineName === "string") { material.define(defineName); } }); return material; }; graphicGL.setMaterialFromModel = function(shading, material, model, api) { material.autoUpdateTextureStatus = false; var materialModel = model.getModel(shading + "Material"); var detailTexture = materialModel.get("detailTexture"); var uvRepeat = retrieve.firstNotNull(materialModel.get("textureTiling"), 1); var uvOffset = retrieve.firstNotNull(materialModel.get("textureOffset"), 0); if (typeof uvRepeat === "number") { uvRepeat = [uvRepeat, uvRepeat]; } if (typeof uvOffset === "number") { uvOffset = [uvOffset, uvOffset]; } var repeatParam = uvRepeat[0] > 1 || uvRepeat[1] > 1 ? graphicGL.Texture.REPEAT : graphicGL.Texture.CLAMP_TO_EDGE; var textureOpt = { anisotropic: 8, wrapS: repeatParam, wrapT: repeatParam }; if (shading === "realistic") { var roughness = materialModel.get("roughness"); var metalness = materialModel.get("metalness"); if (metalness != null) { if (isNaN(metalness)) { material.setTextureImage("metalnessMap", metalness, api, textureOpt); metalness = retrieve.firstNotNull(materialModel.get("metalnessAdjust"), 0.5); } } else { metalness = 0; } if (roughness != null) { if (isNaN(roughness)) { material.setTextureImage("roughnessMap", roughness, api, textureOpt); roughness = retrieve.firstNotNull(materialModel.get("roughnessAdjust"), 0.5); } } else { roughness = 0.5; } var normalTextureVal = materialModel.get("normalTexture"); material.setTextureImage("detailMap", detailTexture, api, textureOpt); material.setTextureImage("normalMap", normalTextureVal, api, textureOpt); material.set({ roughness, metalness, detailUvRepeat: uvRepeat, detailUvOffset: uvOffset }); } else if (shading === "lambert") { material.setTextureImage("detailMap", detailTexture, api, textureOpt); material.set({ detailUvRepeat: uvRepeat, detailUvOffset: uvOffset }); } else if (shading === "color") { material.setTextureImage("detailMap", detailTexture, api, textureOpt); material.set({ detailUvRepeat: uvRepeat, detailUvOffset: uvOffset }); } else if (shading === "hatching") { var tams = materialModel.get("hatchingTextures") || []; if (tams.length < 6) ; for (var i = 0; i < 6; i++) { material.setTextureImage("hatch" + (i + 1), tams[i], api, { anisotropic: 8, wrapS: graphicGL.Texture.REPEAT, wrapT: graphicGL.Texture.REPEAT }); } material.set({ detailUvRepeat: uvRepeat, detailUvOffset: uvOffset }); } }; graphicGL.updateVertexAnimation = function(mappingAttributes, previousMesh, currentMesh, seriesModel) { var enableAnimation = seriesModel.get("animation"); var duration = seriesModel.get("animationDurationUpdate"); var easing = seriesModel.get("animationEasingUpdate"); var shadowDepthMaterial = currentMesh.shadowDepthMaterial; if (enableAnimation && previousMesh && duration > 0 && previousMesh.geometry.vertexCount === currentMesh.geometry.vertexCount) { currentMesh.material.define("vertex", "VERTEX_ANIMATION"); currentMesh.ignorePreZ = true; if (shadowDepthMaterial) { shadowDepthMaterial.define("vertex", "VERTEX_ANIMATION"); } for (var i = 0; i < mappingAttributes.length; i++) { currentMesh.geometry.attributes[mappingAttributes[i][0]].value = previousMesh.geometry.attributes[mappingAttributes[i][1]].value; } currentMesh.geometry.dirty(); currentMesh.__percent = 0; currentMesh.material.set("percent", 0); currentMesh.stopAnimation(); currentMesh.animate().when(duration, { __percent: 1 }).during(function() { currentMesh.material.set("percent", currentMesh.__percent); if (shadowDepthMaterial) { shadowDepthMaterial.set("percent", currentMesh.__percent); } }).done(function() { currentMesh.ignorePreZ = false; currentMesh.material.undefine("vertex", "VERTEX_ANIMATION"); if (shadowDepthMaterial) { shadowDepthMaterial.undefine("vertex", "VERTEX_ANIMATION"); } }).start(easing); } else { currentMesh.material.undefine("vertex", "VERTEX_ANIMATION"); if (shadowDepthMaterial) { shadowDepthMaterial.undefine("vertex", "VERTEX_ANIMATION"); } } }; var requestAnimationFrame; requestAnimationFrame = env.hasGlobalWindow && (window.requestAnimationFrame && window.requestAnimationFrame.bind(window) || window.msRequestAnimationFrame && window.msRequestAnimationFrame.bind(window) || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame) || function(func) { return setTimeout(func, 16); }; var LayerGL = function(id, zr) { this.id = id; this.zr = zr; try { this.renderer = new Renderer({ clearBit: 0, devicePixelRatio: zr.painter.dpr, preserveDrawingBuffer: true, // PENDING premultipliedAlpha: true }); this.renderer.resize(zr.painter.getWidth(), zr.painter.getHeight()); } catch (e2) { this.renderer = null; this.dom = document.createElement("div"); this.dom.style.cssText = "position:absolute; left: 0; top: 0; right: 0; bottom: 0;"; this.dom.className = "ecgl-nowebgl"; this.dom.innerHTML = "Sorry, your browser does not support WebGL"; console.error(e2); return; } this.onglobalout = this.onglobalout.bind(this); zr.on("globalout", this.onglobalout); this.dom = this.renderer.canvas; var style = this.dom.style; style.position = "absolute"; style.left = "0"; style.top = "0"; this.views = []; this._picking = new RayPicking({ renderer: this.renderer }); this._viewsToDispose = []; this._accumulatingId = 0; this._zrEventProxy = new Rect({ shape: { x: -1, y: -1, width: 2, height: 2 }, // FIXME Better solution. __isGLToZRProxy: true }); this._backgroundColor = null; this._disposed = false; }; LayerGL.prototype.setUnpainted = function() { }; LayerGL.prototype.addView = function(view) { if (view.layer === this) { return; } var idx = this._viewsToDispose.indexOf(view); if (idx >= 0) { this._viewsToDispose.splice(idx, 1); } this.views.push(view); view.layer = this; var zr = this.zr; view.scene.traverse(function(node) { node.__zr = zr; if (node.addAnimatorsToZr) { node.addAnimatorsToZr(zr); } }); }; function removeFromZr(node) { var zr = node.__zr; node.__zr = null; if (zr && node.removeAnimatorsFromZr) { node.removeAnimatorsFromZr(zr); } } LayerGL.prototype.removeView = function(view) { if (view.layer !== this) { return; } var idx = this.views.indexOf(view); if (idx >= 0) { this.views.splice(idx, 1); view.scene.traverse(removeFromZr, this); view.layer = null; this._viewsToDispose.push(view); } }; LayerGL.prototype.removeViewsAll = function() { this.views.forEach(function(view) { view.scene.traverse(removeFromZr, this); view.layer = null; this._viewsToDispose.push(view); }, this); this.views.length = 0; }; LayerGL.prototype.resize = function(width, height) { var renderer = this.renderer; renderer.resize(width, height); }; LayerGL.prototype.clear = function() { var gl = this.renderer.gl; var clearColor = this._backgroundColor || [0, 0, 0, 0]; gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); gl.depthMask(true); gl.colorMask(true, true, true, true); gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT); }; LayerGL.prototype.clearDepth = function() { var gl = this.renderer.gl; gl.clear(gl.DEPTH_BUFFER_BIT); }; LayerGL.prototype.clearColor = function() { var gl = this.renderer.gl; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; LayerGL.prototype.needsRefresh = function() { this.zr.refresh(); }; LayerGL.prototype.refresh = function(bgColor) { this._backgroundColor = bgColor ? graphicGL.parseColor(bgColor) : [0, 0, 0, 0]; this.renderer.clearColor = this._backgroundColor; for (var i = 0; i < this.views.length; i++) { this.views[i].prepareRender(this.renderer); } this._doRender(false); this._trackAndClean(); for (var i = 0; i < this._viewsToDispose.length; i++) { this._viewsToDispose[i].dispose(this.renderer); } this._viewsToDispose.length = 0; this._startAccumulating(); }; LayerGL.prototype.renderToCanvas = function(ctx) { this._startAccumulating(true); ctx.drawImage(this.dom, 0, 0, ctx.canvas.width, ctx.canvas.height); }; LayerGL.prototype._doRender = function(accumulating) { this.clear(); this.renderer.saveViewport(); for (var i = 0; i < this.views.length; i++) { this.views[i].render(this.renderer, accumulating); } this.renderer.restoreViewport(); }; LayerGL.prototype._stopAccumulating = function() { this._accumulatingId = 0; clearTimeout(this._accumulatingTimeout); }; var accumulatingId = 1; LayerGL.prototype._startAccumulating = function(immediate) { var self2 = this; this._stopAccumulating(); var needsAccumulate = false; for (var i = 0; i < this.views.length; i++) { needsAccumulate = this.views[i].needsAccumulate() || needsAccumulate; } if (!needsAccumulate) { return; } function accumulate(id) { if (!self2._accumulatingId || id !== self2._accumulatingId) { return; } var isFinished = true; for (var i2 = 0; i2 < self2.views.length; i2++) { isFinished = self2.views[i2].isAccumulateFinished() && needsAccumulate; } if (!isFinished) { self2._doRender(true); if (immediate) { accumulate(id); } else { requestAnimationFrame(function() { accumulate(id); }); } } } this._accumulatingId = accumulatingId++; if (immediate) { accumulate(self2._accumulatingId); } else { this._accumulatingTimeout = setTimeout(function() { accumulate(self2._accumulatingId); }, 50); } }; LayerGL.prototype._trackAndClean = function() { var textureList = []; var geometriesList = []; if (this._textureList) { markUnused(this._textureList); markUnused(this._geometriesList); } for (var i = 0; i < this.views.length; i++) { collectResources(this.views[i].scene, textureList, geometriesList); } if (this._textureList) { checkAndDispose(this.renderer, this._textureList); checkAndDispose(this.renderer, this._geometriesList); } this._textureList = textureList; this._geometriesList = geometriesList; }; function markUnused(resourceList) { for (var i = 0; i < resourceList.length; i++) { resourceList[i].__used__ = 0; } } function checkAndDispose(renderer, resourceList) { for (var i = 0; i < resourceList.length; i++) { if (!resourceList[i].__used__) { resourceList[i].dispose(renderer); } } } function updateUsed(resource, list) { resource.__used__ = resource.__used__ || 0; resource.__used__++; if (resource.__used__ === 1) { list.push(resource); } } function collectResources(scene, textureResourceList, geometryResourceList) { var prevMaterial; var prevGeometry; scene.traverse(function(renderable) { if (renderable.isRenderable()) { var geometry = renderable.geometry; var material = renderable.material; if (material !== prevMaterial) { var textureUniforms = material.getTextureUniforms(); for (var u = 0; u < textureUniforms.length; u++) { var uniformName = textureUniforms[u]; var val = material.uniforms[uniformName].value; if (!val) { continue; } if (val instanceof Texture) { updateUsed(val, textureResourceList); } else if (val instanceof Array) { for (var k2 = 0; k2 < val.length; k2++) { if (val[k2] instanceof Texture) { updateUsed(val[k2], textureResourceList); } } } } } if (geometry !== prevGeometry) { updateUsed(geometry, geometryResourceList); } prevMaterial = material; prevGeometry = geometry; } }); for (var k = 0; k < scene.lights.length; k++) { if (scene.lights[k].cubemap) { updateUsed(scene.lights[k].cubemap, textureResourceList); } } } LayerGL.prototype.dispose = function() { if (this._disposed) { return; } this._stopAccumulating(); if (this._textureList) { markUnused(this._textureList); markUnused(this._geometriesList); checkAndDispose(this.renderer, this._textureList); checkAndDispose(this.renderer, this._geometriesList); } this.zr.off("globalout", this.onglobalout); this._disposed = true; }; LayerGL.prototype.onmousedown = function(e2) { if (e2.target && e2.target.__isGLToZRProxy) { return; } e2 = e2.event; var obj = this.pickObject(e2.offsetX, e2.offsetY); if (obj) { this._dispatchEvent("mousedown", e2, obj); this._dispatchDataEvent("mousedown", e2, obj); } this._downX = e2.offsetX; this._downY = e2.offsetY; }; LayerGL.prototype.onmousemove = function(e2) { if (e2.target && e2.target.__isGLToZRProxy) { return; } e2 = e2.event; var obj = this.pickObject(e2.offsetX, e2.offsetY); var target = obj && obj.target; var lastHovered = this._hovered; this._hovered = obj; if (lastHovered && target !== lastHovered.target) { lastHovered.relatedTarget = target; this._dispatchEvent("mouseout", e2, lastHovered); this.zr.setCursorStyle("default"); } this._dispatchEvent("mousemove", e2, obj); if (obj) { this.zr.setCursorStyle("pointer"); if (!lastHovered || target !== lastHovered.target) { this._dispatchEvent("mouseover", e2, obj); } } this._dispatchDataEvent("mousemove", e2, obj); }; LayerGL.prototype.onmouseup = function(e2) { if (e2.target && e2.target.__isGLToZRProxy) { return; } e2 = e2.event; var obj = this.pickObject(e2.offsetX, e2.offsetY); if (obj) { this._dispatchEvent("mouseup", e2, obj); this._dispatchDataEvent("mouseup", e2, obj); } this._upX = e2.offsetX; this._upY = e2.offsetY; }; LayerGL.prototype.onclick = LayerGL.prototype.dblclick = function(e2) { if (e2.target && e2.target.__isGLToZRProxy) { return; } var dx = this._upX - this._downX; var dy = this._upY - this._downY; if (Math.sqrt(dx * dx + dy * dy) > 20) { return; } e2 = e2.event; var obj = this.pickObject(e2.offsetX, e2.offsetY); if (obj) { this._dispatchEvent(e2.type, e2, obj); this._dispatchDataEvent(e2.type, e2, obj); } var result = this._clickToSetFocusPoint(e2); if (result) { var success = result.view.setDOFFocusOnPoint(result.distance); if (success) { this.zr.refresh(); } } }; LayerGL.prototype._clickToSetFocusPoint = function(e2) { var renderer = this.renderer; var oldViewport = renderer.viewport; for (var i = this.views.length - 1; i >= 0; i--) { var viewGL = this.views[i]; if (viewGL.hasDOF() && viewGL.containPoint(e2.offsetX, e2.offsetY)) { this._picking.scene = viewGL.scene; this._picking.camera = viewGL.camera; renderer.viewport = viewGL.viewport; var result = this._picking.pick(e2.offsetX, e2.offsetY, true); if (result) { result.view = viewGL; return result; } } } renderer.viewport = oldViewport; }; LayerGL.prototype.onglobalout = function(e2) { var lastHovered = this._hovered; if (lastHovered) { this._dispatchEvent("mouseout", e2, { target: lastHovered.target }); } }; LayerGL.prototype.pickObject = function(x, y) { var output = []; var renderer = this.renderer; var oldViewport = renderer.viewport; for (var i = 0; i < this.views.length; i++) { var viewGL = this.views[i]; if (viewGL.containPoint(x, y)) { this._picking.scene = viewGL.scene; this._picking.camera = viewGL.camera; renderer.viewport = viewGL.viewport; this._picking.pickAll(x, y, output); } } renderer.viewport = oldViewport; output.sort(function(a, b) { return a.distance - b.distance; }); return output[0]; }; LayerGL.prototype._dispatchEvent = function(eveName, originalEvent, newEvent) { if (!newEvent) { newEvent = {}; } var current = newEvent.target; newEvent.cancelBubble = false; newEvent.event = originalEvent; newEvent.type = eveName; newEvent.offsetX = originalEvent.offsetX; newEvent.offsetY = originalEvent.offsetY; while (current) { current.trigger(eveName, newEvent); current = current.getParent(); if (newEvent.cancelBubble) { break; } } this._dispatchToView(eveName, newEvent); }; LayerGL.prototype._dispatchDataEvent = function(eveName, originalEvent, newEvent) { var mesh2 = newEvent && newEvent.target; var dataIndex = mesh2 && mesh2.dataIndex; var seriesIndex = mesh2 && mesh2.seriesIndex; var eventData = mesh2 && mesh2.eventData; var elChangedInMouseMove = false; var eventProxy = this._zrEventProxy; eventProxy.x = originalEvent.offsetX; eventProxy.y = originalEvent.offsetY; eventProxy.update(); var targetInfo = { target: eventProxy }; const ecData = getECData(eventProxy); if (eveName === "mousemove") { if (dataIndex != null) { if (dataIndex !== this._lastDataIndex) { if (parseInt(this._lastDataIndex, 10) >= 0) { ecData.dataIndex = this._lastDataIndex; ecData.seriesIndex = this._lastSeriesIndex; this.zr.handler.dispatchToElement(targetInfo, "mouseout", originalEvent); } elChangedInMouseMove = true; } } else if (eventData != null) { if (eventData !== this._lastEventData) { if (this._lastEventData != null) { ecData.eventData = this._lastEventData; this.zr.handler.dispatchToElement(targetInfo, "mouseout", originalEvent); } elChangedInMouseMove = true; } } this._lastEventData = eventData; this._lastDataIndex = dataIndex; this._lastSeriesIndex = seriesIndex; } ecData.eventData = eventData; ecData.dataIndex = dataIndex; ecData.seriesIndex = seriesIndex; if (eventData != null || parseInt(dataIndex, 10) >= 0 && parseInt(seriesIndex, 10) >= 0) { this.zr.handler.dispatchToElement(targetInfo, eveName, originalEvent); if (elChangedInMouseMove) { this.zr.handler.dispatchToElement(targetInfo, "mouseover", originalEvent); } } }; LayerGL.prototype._dispatchToView = function(eventName, e2) { for (var i = 0; i < this.views.length; i++) { if (this.views[i].containPoint(e2.offsetX, e2.offsetY)) { this.views[i].trigger(eventName, e2); } } }; Object.assign(LayerGL.prototype, notifier); var GL_SERIES = ["bar3D", "line3D", "map3D", "scatter3D", "surface", "lines3D", "scatterGL", "scatter3D"]; function convertNormalEmphasis(option, optType) { if (option && option[optType] && (option[optType].normal || option[optType].emphasis)) { var normalOpt = option[optType].normal; var emphasisOpt = option[optType].emphasis; if (normalOpt) { option[optType] = normalOpt; } if (emphasisOpt) { option.emphasis = option.emphasis || {}; option.emphasis[optType] = emphasisOpt; } } } function convertNormalEmphasisForEach(option) { convertNormalEmphasis(option, "itemStyle"); convertNormalEmphasis(option, "lineStyle"); convertNormalEmphasis(option, "areaStyle"); convertNormalEmphasis(option, "label"); } function removeTextStyleInAxis(axesOpt) { if (!axesOpt) { return; } if (!(axesOpt instanceof Array)) { axesOpt = [axesOpt]; } each(axesOpt, function(axisOpt) { if (axisOpt.axisLabel) { var labelOpt = axisOpt.axisLabel; Object.assign(labelOpt, labelOpt.textStyle); labelOpt.textStyle = null; } }); } function backwardCompat(option) { each(option.series, function(series) { if (indexOf(GL_SERIES, series.type) >= 0) { convertNormalEmphasisForEach(series); if (series.coordinateSystem === "mapbox") { series.coordinateSystem = "mapbox3D"; option.mapbox3D = option.mapbox; } } }); removeTextStyleInAxis(option.xAxis3D); removeTextStyleInAxis(option.yAxis3D); removeTextStyleInAxis(option.zAxis3D); removeTextStyleInAxis(option.grid3D); convertNormalEmphasis(option.geo3D); } function EChartsGL(zr) { this._layers = {}; this._zr = zr; } EChartsGL.prototype.update = function(ecModel, api) { var self2 = this; var zr = api.getZr(); if (!zr.getWidth() || !zr.getHeight()) { console.warn("Dom has no width or height"); return; } function getLayerGL(model) { zr.setSleepAfterStill(0); var zlevel2; if (model.coordinateSystem && model.coordinateSystem.model) { zlevel2 = model.get("zlevel"); } else { zlevel2 = model.get("zlevel"); } var layers = self2._layers; var layerGL = layers[zlevel2]; if (!layerGL) { layerGL = layers[zlevel2] = new LayerGL("gl-" + zlevel2, zr); if (zr.painter.isSingleCanvas()) { layerGL.virtual = true; var img = new ZRImage({ z: 1e4, style: { image: layerGL.renderer.canvas }, silent: true }); layerGL.__hostImage = img; zr.add(img); } zr.painter.insertLayer(zlevel2, layerGL); } if (layerGL.__hostImage) { layerGL.__hostImage.setStyle({ width: layerGL.renderer.getWidth(), height: layerGL.renderer.getHeight() }); } return layerGL; } function setSilent(groupGL, silent) { if (groupGL) { groupGL.traverse(function(mesh2) { if (mesh2.isRenderable && mesh2.isRenderable()) { mesh2.ignorePicking = mesh2.$ignorePicking != null ? mesh2.$ignorePicking : silent; } }); } } for (var zlevel in this._layers) { this._layers[zlevel].removeViewsAll(); } ecModel.eachComponent(function(componentType, componentModel) { if (componentType !== "series") { var view = api.getViewOfComponentModel(componentModel); var coordSys = componentModel.coordinateSystem; if (view.__ecgl__) { var viewGL; if (coordSys) { if (!coordSys.viewGL) { console.error("Can't find viewGL in coordinateSystem of component " + componentModel.id); return; } viewGL = coordSys.viewGL; } else { if (!componentModel.viewGL) { console.error("Can't find viewGL of component " + componentModel.id); return; } viewGL = coordSys.viewGL; } var viewGL = coordSys.viewGL; var layerGL = getLayerGL(componentModel); layerGL.addView(viewGL); view.afterRender && view.afterRender(componentModel, ecModel, api, layerGL); setSilent(view.groupGL, componentModel.get("silent")); } } }); ecModel.eachSeries(function(seriesModel) { var chartView = api.getViewOfSeriesModel(seriesModel); var coordSys = seriesModel.coordinateSystem; if (chartView.__ecgl__) { if (coordSys && !coordSys.viewGL && !chartView.viewGL) { console.error("Can't find viewGL of series " + chartView.id); return; } var viewGL = coordSys && coordSys.viewGL || chartView.viewGL; var layerGL = getLayerGL(seriesModel); layerGL.addView(viewGL); chartView.afterRender && chartView.afterRender(seriesModel, ecModel, api, layerGL); setSilent(chartView.groupGL, seriesModel.get("silent")); } }); }; registerPostInit(function(chart) { var zr = chart.getZr(); var oldDispose = zr.painter.dispose; zr.painter.dispose = function() { if (typeof this.eachOtherLayer === "function") { this.eachOtherLayer(function(layer) { if (layer instanceof LayerGL) { layer.dispose(); } }); } oldDispose.call(this); }; zr.painter.getRenderedCanvas = function(opts) { opts = opts || {}; if (this._singleCanvas) { return this._layers[0].dom; } var canvas = document.createElement("canvas"); var dpr = opts.pixelRatio || this.dpr; canvas.width = this.getWidth() * dpr; canvas.height = this.getHeight() * dpr; var ctx = canvas.getContext("2d"); ctx.dpr = dpr; ctx.clearRect(0, 0, canvas.width, canvas.height); if (opts.backgroundColor) { ctx.fillStyle = opts.backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); } var displayList = this.storage.getDisplayList(true); var scope = {}; var zlevel; var self2 = this; function findAndDrawOtherLayer(smaller, larger) { var zlevelList = self2._zlevelList; if (smaller == null) { smaller = -Infinity; } var intermediateLayer; for (var i2 = 0; i2 < zlevelList.length; i2++) { var z = zlevelList[i2]; var layer2 = self2._layers[z]; if (!layer2.__builtin__ && z > smaller && z < larger) { intermediateLayer = layer2; break; } } if (intermediateLayer && intermediateLayer.renderToCanvas) { ctx.save(); intermediateLayer.renderToCanvas(ctx); ctx.restore(); } } var layer = { ctx }; for (var i = 0; i < displayList.length; i++) { var el = displayList[i]; if (el.zlevel !== zlevel) { findAndDrawOtherLayer(zlevel, el.zlevel); zlevel = el.zlevel; } this._doPaintEl(el, layer, true, null, scope); } findAndDrawOtherLayer(zlevel, Infinity); return canvas; }; }); registerPostUpdate(function(ecModel, api) { var zr = api.getZr(); var egl = zr.__egl = zr.__egl || new EChartsGL(zr); egl.update(ecModel, api); }); registerPreprocessor(backwardCompat); const componentViewControlMixin = { defaultOption: { viewControl: { // perspective, orthographic. // TODO Isometric projection: "perspective", // If rotate on on init autoRotate: false, // cw or ccw autoRotateDirection: "cw", // Degree per second autoRotateSpeed: 10, // Start rotating after still for a given time // default is 3 seconds autoRotateAfterStill: 3, // Rotate, zoom damping. damping: 0.8, // Sensitivities for operations. // Can be array to set x,y respectively rotateSensitivity: 1, zoomSensitivity: 1, // Can be array to set x,y respectively panSensitivity: 1, // Which mouse button do rotate or pan panMouseButton: "middle", rotateMouseButton: "left", // Distance to the target // Only available when camera is perspective. distance: 150, // Min distance mouse can zoom in minDistance: 40, // Max distance mouse can zoom out maxDistance: 400, // Size of viewing volume. // Only available when camera is orthographic orthographicSize: 150, maxOrthographicSize: 400, minOrthographicSize: 20, // Center view point center: [0, 0, 0], // Alpha angle for top-down rotation // Positive to rotate to top. alpha: 0, // beta angle for left-right rotation // Positive to rotate to right. beta: 0, minAlpha: -90, maxAlpha: 90 // minBeta: -Infinity // maxBeta: -Infinity } }, setView: function(opts) { opts = opts || {}; this.option.viewControl = this.option.viewControl || {}; if (opts.alpha != null) { this.option.viewControl.alpha = opts.alpha; } if (opts.beta != null) { this.option.viewControl.beta = opts.beta; } if (opts.distance != null) { this.option.viewControl.distance = opts.distance; } if (opts.center != null) { this.option.viewControl.center = opts.center; } } }; const componentPostEffectMixin = { defaultOption: { // Post effect postEffect: { enable: false, bloom: { enable: true, intensity: 0.1 }, depthOfField: { enable: false, focalRange: 20, focalDistance: 50, blurRadius: 10, fstop: 2.8, quality: "medium" }, screenSpaceAmbientOcclusion: { enable: false, radius: 2, // low, medium, high, ultra quality: "medium", intensity: 1 }, screenSpaceReflection: { enable: false, quality: "medium", maxRoughness: 0.8 }, colorCorrection: { enable: true, exposure: 0, brightness: 0, contrast: 1, saturation: 1, lookupTexture: "" }, edge: { enable: false }, FXAA: { enable: false } }, // Temporal super sampling when the picture is still. temporalSuperSampling: { // Only enabled when postEffect is enabled enable: "auto" } } }; const componentLightMixin = { defaultOption: { // Light is available when material.shading is not color light: { // Main light main: { shadow: false, // low, medium, high, ultra shadowQuality: "high", color: "#fff", intensity: 1, alpha: 0, beta: 0 }, ambient: { color: "#fff", intensity: 0.2 }, ambientCubemap: { // Panorama environment texture, // Support .hdr and commmon web formats. texture: null, // Available when texture is hdr. exposure: 1, // Intensity for diffuse term diffuseIntensity: 0.5, // Intensity for specular term, only available when shading is realastic specularIntensity: 0.5 } } } }; var Grid3DModel = ComponentModel.extend({ type: "grid3D", dependencies: ["xAxis3D", "yAxis3D", "zAxis3D"], defaultOption: { show: true, zlevel: -10, // Layout used for viewport left: 0, top: 0, width: "100%", height: "100%", environment: "auto", // Dimension of grid3D boxWidth: 100, boxHeight: 100, boxDepth: 100, // Common axis options. axisPointer: { show: true, lineStyle: { color: "rgba(0, 0, 0, 0.8)", width: 1 }, label: { show: true, // (dimValue: number, value: Array) => string formatter: null, // TODO, Consider boxWidth margin: 8, // backgroundColor: '#ffbd67', // borderColor: '#000', // borderWidth: 0, textStyle: { fontSize: 14, color: "#fff", backgroundColor: "rgba(0,0,0,0.5)", padding: 3, borderRadius: 3 } } }, axisLine: { show: true, lineStyle: { color: "#333", width: 2, type: "solid" } }, axisTick: { show: true, inside: false, length: 3, lineStyle: { width: 1 } }, axisLabel: { show: true, inside: false, rotate: 0, margin: 8, textStyle: { fontSize: 12 } }, splitLine: { show: true, lineStyle: { color: ["#ccc"], width: 1, type: "solid" } }, splitArea: { show: false, areaStyle: { color: ["rgba(250,250,250,0.3)", "rgba(200,200,200,0.3)"] } }, // Light options light: { main: { // Alpha angle for top-down rotation // Positive to rotate to top. alpha: 30, // beta angle for left-right rotation // Positive to rotate to right. beta: 40 }, ambient: { intensity: 0.4 } }, viewControl: { // Small damping for precise control. // damping: 0.1, // Alpha angle for top-down rotation // Positive to rotate to top. alpha: 20, // beta angle for left-right rotation // Positive to rotate to right. beta: 40, autoRotate: false, // Distance to the surface of grid3D. distance: 200, // Min distance to the surface of grid3D minDistance: 40, // Max distance to the surface of grid3D maxDistance: 400 } } }); merge(Grid3DModel.prototype, componentViewControlMixin); merge(Grid3DModel.prototype, componentPostEffectMixin); merge(Grid3DModel.prototype, componentLightMixin); var firstNotNull$3 = retrieve.firstNotNull; var MOUSE_BUTTON_KEY_MAP = { left: 0, middle: 1, right: 2 }; function convertToArray(val) { if (!(val instanceof Array)) { val = [val, val]; } return val; } var OrbitControl = Base.extend(function() { return { /** * @type {module:zrender~ZRender} */ zr: null, /** * @type {module:echarts-gl/core/ViewGL} */ viewGL: null, /** * @type {clay.math.Vector3} */ _center: new Vector3(), /** * Minimum distance to the center * Only available when camera is perspective. * @type {number} * @default 0.5 */ minDistance: 0.5, /** * Maximum distance to the center * Only available when camera is perspective. * @type {number} * @default 2 */ maxDistance: 1.5, /** * Only available when camera is orthographic */ maxOrthographicSize: 300, /** * Only available when camera is orthographic */ minOrthographicSize: 30, /** * Minimum alpha rotation */ minAlpha: -90, /** * Maximum alpha rotation */ maxAlpha: 90, /** * Minimum beta rotation */ minBeta: -Infinity, /** * Maximum beta rotation */ maxBeta: Infinity, /** * Start auto rotating after still for the given time */ autoRotateAfterStill: 0, /** * Direction of autoRotate. cw or ccw when looking top down. */ autoRotateDirection: "cw", /** * Degree per second */ autoRotateSpeed: 60, /** * @param {number} */ damping: 0.8, /** * @param {number} */ rotateSensitivity: 1, /** * @param {number} */ zoomSensitivity: 1, /** * @param {number} */ panSensitivity: 1, panMouseButton: "middle", rotateMouseButton: "left", /** * Pan or rotate * @private * @type {String} */ _mode: "rotate", /** * @private * @type {clay.Camera} */ _camera: null, _needsUpdate: false, _rotating: false, // Rotation around yAxis in radian _phi: 0, // Rotation around xAxis in radian _theta: 0, _mouseX: 0, _mouseY: 0, _rotateVelocity: new Vector2(), _panVelocity: new Vector2(), _distance: 500, _zoomSpeed: 0, _stillTimeout: 0, _animators: [] }; }, function() { ["_mouseDownHandler", "_mouseWheelHandler", "_mouseMoveHandler", "_mouseUpHandler", "_pinchHandler", "_contextMenuHandler", "_update"].forEach(function(hdlName) { this[hdlName] = this[hdlName].bind(this); }, this); }, { /** * Initialize. * Mouse event binding */ init: function() { var zr = this.zr; if (zr) { zr.on("mousedown", this._mouseDownHandler); zr.on("globalout", this._mouseUpHandler); zr.on("mousewheel", this._mouseWheelHandler); zr.on("pinch", this._pinchHandler); zr.animation.on("frame", this._update); zr.dom.addEventListener("contextmenu", this._contextMenuHandler); } }, /** * Dispose. * Mouse event unbinding */ dispose: function() { var zr = this.zr; if (zr) { zr.off("mousedown", this._mouseDownHandler); zr.off("mousemove", this._mouseMoveHandler); zr.off("mouseup", this._mouseUpHandler); zr.off("mousewheel", this._mouseWheelHandler); zr.off("pinch", this._pinchHandler); zr.off("globalout", this._mouseUpHandler); zr.dom.removeEventListener("contextmenu", this._contextMenuHandler); zr.animation.off("frame", this._update); } this.stopAllAnimation(); }, /** * Get distance * @return {number} */ getDistance: function() { return this._distance; }, /** * Set distance * @param {number} distance */ setDistance: function(distance) { this._distance = distance; this._needsUpdate = true; }, /** * Get size of orthographic viewing volume * @return {number} */ getOrthographicSize: function() { return this._orthoSize; }, /** * Set size of orthographic viewing volume * @param {number} size */ setOrthographicSize: function(size) { this._orthoSize = size; this._needsUpdate = true; }, /** * Get alpha rotation * Alpha angle for top-down rotation. Positive to rotate to top. * * Which means camera rotation around x axis. */ getAlpha: function() { return this._theta / Math.PI * 180; }, /** * Get beta rotation * Beta angle for left-right rotation. Positive to rotate to right. * * Which means camera rotation around y axis. */ getBeta: function() { return -this._phi / Math.PI * 180; }, /** * Get control center * @return {Array.} */ getCenter: function() { return this._center.toArray(); }, /** * Set alpha rotation angle * @param {number} alpha */ setAlpha: function(alpha) { alpha = Math.max(Math.min(this.maxAlpha, alpha), this.minAlpha); this._theta = alpha / 180 * Math.PI; this._needsUpdate = true; }, /** * Set beta rotation angle * @param {number} beta */ setBeta: function(beta) { beta = Math.max(Math.min(this.maxBeta, beta), this.minBeta); this._phi = -beta / 180 * Math.PI; this._needsUpdate = true; }, /** * Set control center * @param {Array.} center */ setCenter: function(centerArr) { this._center.setArray(centerArr); }, /** * @param {module:echarts-gl/core/ViewGL} viewGL */ setViewGL: function(viewGL) { this.viewGL = viewGL; }, /** * @return {clay.Camera} */ getCamera: function() { return this.viewGL.camera; }, setFromViewControlModel: function(viewControlModel, extraOpts) { extraOpts = extraOpts || {}; var baseDistance = extraOpts.baseDistance || 0; var baseOrthoSize = extraOpts.baseOrthoSize || 1; var projection = viewControlModel.get("projection"); if (projection !== "perspective" && projection !== "orthographic" && projection !== "isometric") { projection = "perspective"; } this._projection = projection; this.viewGL.setProjection(projection); var targetDistance = viewControlModel.get("distance") + baseDistance; var targetOrthographicSize = viewControlModel.get("orthographicSize") + baseOrthoSize; [["damping", 0.8], ["autoRotate", false], ["autoRotateAfterStill", 3], ["autoRotateDirection", "cw"], ["autoRotateSpeed", 10], ["minDistance", 30], ["maxDistance", 400], ["minOrthographicSize", 30], ["maxOrthographicSize", 300], ["minAlpha", -90], ["maxAlpha", 90], ["minBeta", -Infinity], ["maxBeta", Infinity], ["rotateSensitivity", 1], ["zoomSensitivity", 1], ["panSensitivity", 1], ["panMouseButton", "left"], ["rotateMouseButton", "middle"]].forEach(function(prop) { this[prop[0]] = firstNotNull$3(viewControlModel.get(prop[0]), prop[1]); }, this); this.minDistance += baseDistance; this.maxDistance += baseDistance; this.minOrthographicSize += baseOrthoSize, this.maxOrthographicSize += baseOrthoSize; var ecModel = viewControlModel.ecModel; var animationOpts = {}; ["animation", "animationDurationUpdate", "animationEasingUpdate"].forEach(function(key) { animationOpts[key] = firstNotNull$3(viewControlModel.get(key), ecModel && ecModel.get(key)); }); var alpha = firstNotNull$3(extraOpts.alpha, viewControlModel.get("alpha")) || 0; var beta = firstNotNull$3(extraOpts.beta, viewControlModel.get("beta")) || 0; var center = firstNotNull$3(extraOpts.center, viewControlModel.get("center")) || [0, 0, 0]; if (animationOpts.animation && animationOpts.animationDurationUpdate > 0 && this._notFirst) { this.animateTo({ alpha, beta, center, distance: targetDistance, orthographicSize: targetOrthographicSize, easing: animationOpts.animationEasingUpdate, duration: animationOpts.animationDurationUpdate }); } else { this.setDistance(targetDistance); this.setAlpha(alpha); this.setBeta(beta); this.setCenter(center); this.setOrthographicSize(targetOrthographicSize); } this._notFirst = true; this._validateProperties(); }, _validateProperties: function() { }, /** * @param {Object} opts * @param {number} opts.distance * @param {number} opts.alpha * @param {number} opts.beta * @param {number} opts.orthographicSize * @param {number} [opts.duration=1000] * @param {number} [opts.easing='linear'] */ animateTo: function(opts) { var zr = this.zr; var self2 = this; var obj = {}; var target = {}; if (opts.distance != null) { obj.distance = this.getDistance(); target.distance = opts.distance; } if (opts.orthographicSize != null) { obj.orthographicSize = this.getOrthographicSize(); target.orthographicSize = opts.orthographicSize; } if (opts.alpha != null) { obj.alpha = this.getAlpha(); target.alpha = opts.alpha; } if (opts.beta != null) { obj.beta = this.getBeta(); target.beta = opts.beta; } if (opts.center != null) { obj.center = this.getCenter(); target.center = opts.center; } return this._addAnimator(zr.animation.animate(obj).when(opts.duration || 1e3, target).during(function() { if (obj.alpha != null) { self2.setAlpha(obj.alpha); } if (obj.beta != null) { self2.setBeta(obj.beta); } if (obj.distance != null) { self2.setDistance(obj.distance); } if (obj.center != null) { self2.setCenter(obj.center); } if (obj.orthographicSize != null) { self2.setOrthographicSize(obj.orthographicSize); } self2._needsUpdate = true; })).start(opts.easing || "linear"); }, /** * Stop all animation */ stopAllAnimation: function() { for (var i = 0; i < this._animators.length; i++) { this._animators[i].stop(); } this._animators.length = 0; }, update: function() { this._needsUpdate = true; this._update(20); }, _isAnimating: function() { return this._animators.length > 0; }, /** * Call update each frame * @param {number} deltaTime Frame time */ _update: function(deltaTime) { if (this._rotating) { var radian = (this.autoRotateDirection === "cw" ? 1 : -1) * this.autoRotateSpeed / 180 * Math.PI; this._phi -= radian * deltaTime / 1e3; this._needsUpdate = true; } else if (this._rotateVelocity.len() > 0) { this._needsUpdate = true; } if (Math.abs(this._zoomSpeed) > 0.1 || this._panVelocity.len() > 0) { this._needsUpdate = true; } if (!this._needsUpdate) { return; } deltaTime = Math.min(deltaTime, 50); this._updateDistanceOrSize(deltaTime); this._updatePan(deltaTime); this._updateRotate(deltaTime); this._updateTransform(); this.getCamera().update(); this.zr && this.zr.refresh(); this.trigger("update"); this._needsUpdate = false; }, _updateRotate: function(deltaTime) { var velocity = this._rotateVelocity; this._phi = velocity.y * deltaTime / 20 + this._phi; this._theta = velocity.x * deltaTime / 20 + this._theta; this.setAlpha(this.getAlpha()); this.setBeta(this.getBeta()); this._vectorDamping(velocity, Math.pow(this.damping, deltaTime / 16)); }, _updateDistanceOrSize: function(deltaTime) { if (this._projection === "perspective") { this._setDistance(this._distance + this._zoomSpeed * deltaTime / 20); } else { this._setOrthoSize(this._orthoSize + this._zoomSpeed * deltaTime / 20); } this._zoomSpeed *= Math.pow(this.damping, deltaTime / 16); }, _setDistance: function(distance) { this._distance = Math.max(Math.min(distance, this.maxDistance), this.minDistance); }, _setOrthoSize: function(size) { this._orthoSize = Math.max(Math.min(size, this.maxOrthographicSize), this.minOrthographicSize); var camera2 = this.getCamera(); var cameraHeight = this._orthoSize; var cameraWidth = cameraHeight / this.viewGL.viewport.height * this.viewGL.viewport.width; camera2.left = -cameraWidth / 2; camera2.right = cameraWidth / 2; camera2.top = cameraHeight / 2; camera2.bottom = -cameraHeight / 2; }, _updatePan: function(deltaTime) { var velocity = this._panVelocity; var len = this._distance; var target = this.getCamera(); var yAxis = target.worldTransform.y; var xAxis = target.worldTransform.x; this._center.scaleAndAdd(xAxis, -velocity.x * len / 200).scaleAndAdd(yAxis, -velocity.y * len / 200); this._vectorDamping(velocity, 0); }, _updateTransform: function() { var camera2 = this.getCamera(); var dir = new Vector3(); var theta = this._theta + Math.PI / 2; var phi = this._phi + Math.PI / 2; var r = Math.sin(theta); dir.x = r * Math.cos(phi); dir.y = -Math.cos(theta); dir.z = r * Math.sin(phi); camera2.position.copy(this._center).scaleAndAdd(dir, this._distance); camera2.rotation.identity().rotateY(-this._phi).rotateX(-this._theta); }, _startCountingStill: function() { clearTimeout(this._stillTimeout); var time = this.autoRotateAfterStill; var self2 = this; if (!isNaN(time) && time > 0) { this._stillTimeout = setTimeout(function() { self2._rotating = true; }, time * 1e3); } }, _vectorDamping: function(v, damping) { var speed = v.len(); speed = speed * damping; if (speed < 1e-4) { speed = 0; } v.normalize().scale(speed); }, _decomposeTransform: function() { if (!this.getCamera()) { return; } this.getCamera().updateWorldTransform(); var forward = this.getCamera().worldTransform.z; var alpha = Math.asin(forward.y); var beta = Math.atan2(forward.x, forward.z); this._theta = alpha; this._phi = -beta; this.setBeta(this.getBeta()); this.setAlpha(this.getAlpha()); if (this.getCamera().aspect) { this._setDistance(this.getCamera().position.dist(this._center)); } else { this._setOrthoSize(this.getCamera().top - this.getCamera().bottom); } }, _mouseDownHandler: function(e2) { if (e2.target) { return; } if (this._isAnimating()) { return; } var x = e2.offsetX; var y = e2.offsetY; if (this.viewGL && !this.viewGL.containPoint(x, y)) { return; } this.zr.on("mousemove", this._mouseMoveHandler); this.zr.on("mouseup", this._mouseUpHandler); if (e2.event.targetTouches) { if (e2.event.targetTouches.length === 1) { this._mode = "rotate"; } } else { if (e2.event.button === MOUSE_BUTTON_KEY_MAP[this.rotateMouseButton]) { this._mode = "rotate"; } else if (e2.event.button === MOUSE_BUTTON_KEY_MAP[this.panMouseButton]) { this._mode = "pan"; } else { this._mode = ""; } } this._rotateVelocity.set(0, 0); this._rotating = false; if (this.autoRotate) { this._startCountingStill(); } this._mouseX = e2.offsetX; this._mouseY = e2.offsetY; }, _mouseMoveHandler: function(e2) { if (e2.target && e2.target.__isGLToZRProxy) { return; } if (this._isAnimating()) { return; } var panSensitivity = convertToArray(this.panSensitivity); var rotateSensitivity = convertToArray(this.rotateSensitivity); if (this._mode === "rotate") { this._rotateVelocity.y = (e2.offsetX - this._mouseX) / this.zr.getHeight() * 2 * rotateSensitivity[0]; this._rotateVelocity.x = (e2.offsetY - this._mouseY) / this.zr.getWidth() * 2 * rotateSensitivity[1]; } else if (this._mode === "pan") { this._panVelocity.x = (e2.offsetX - this._mouseX) / this.zr.getWidth() * panSensitivity[0] * 400; this._panVelocity.y = (-e2.offsetY + this._mouseY) / this.zr.getHeight() * panSensitivity[1] * 400; } this._mouseX = e2.offsetX; this._mouseY = e2.offsetY; e2.event.preventDefault(); }, _mouseWheelHandler: function(e2) { if (this._isAnimating()) { return; } var delta = e2.event.wheelDelta || -e2.event.detail; this._zoomHandler(e2, delta); }, _pinchHandler: function(e2) { if (this._isAnimating()) { return; } this._zoomHandler(e2, e2.pinchScale > 1 ? 1 : -1); this._mode = ""; }, _zoomHandler: function(e2, delta) { if (delta === 0) { return; } var x = e2.offsetX; var y = e2.offsetY; if (this.viewGL && !this.viewGL.containPoint(x, y)) { return; } var speed; if (this._projection === "perspective") { speed = Math.max(Math.max(Math.min(this._distance - this.minDistance, this.maxDistance - this._distance)) / 20, 0.5); } else { speed = Math.max(Math.max(Math.min(this._orthoSize - this.minOrthographicSize, this.maxOrthographicSize - this._orthoSize)) / 20, 0.5); } this._zoomSpeed = (delta > 0 ? -1 : 1) * speed * this.zoomSensitivity; this._rotating = false; if (this.autoRotate && this._mode === "rotate") { this._startCountingStill(); } e2.event.preventDefault(); }, _mouseUpHandler: function() { this.zr.off("mousemove", this._mouseMoveHandler); this.zr.off("mouseup", this._mouseUpHandler); }, _isRightMouseButtonUsed: function() { return this.rotateMouseButton === "right" || this.panMouseButton === "right"; }, _contextMenuHandler: function(e2) { if (this._isRightMouseButtonUsed()) { e2.preventDefault(); } }, _addAnimator: function(animator) { var animators = this._animators; animators.push(animator); animator.done(function() { var idx = animators.indexOf(animator); if (idx >= 0) { animators.splice(idx, 1); } }); return animator; } }); Object.defineProperty(OrbitControl.prototype, "autoRotate", { get: function(val) { return this._autoRotate; }, set: function(val) { this._autoRotate = val; this._rotating = val; } }); const dynamicConvertMixin = { convertToDynamicArray: function(clear) { if (clear) { this.resetOffset(); } var attributes = this.attributes; for (var name in attributes) { if (clear || !attributes[name].value) { attributes[name].value = []; } else { attributes[name].value = Array.prototype.slice.call(attributes[name].value); } } if (clear || !this.indices) { this.indices = []; } else { this.indices = Array.prototype.slice.call(this.indices); } }, convertToTypedArray: function() { var attributes = this.attributes; for (var name in attributes) { if (attributes[name].value && attributes[name].value.length > 0) { attributes[name].value = new Float32Array(attributes[name].value); } else { attributes[name].value = null; } } if (this.indices && this.indices.length > 0) { this.indices = this.vertexCount > 65535 ? new Uint32Array(this.indices) : new Uint16Array(this.indices); } this.dirty(); } }; const glmatrix = { vec2: vec2$3, vec3: vec3$f, vec4: vec4$1, mat3: mat3$1, mat4: mat4$2 }; var vec3$e = glmatrix.vec3; var sampleLinePoints$1 = [[0, 0], [1, 1]]; var LinesGeometry$2 = Geometry.extend( function() { return { segmentScale: 1, dynamic: true, /** * Need to use mesh to expand lines if lineWidth > MAX_LINE_WIDTH */ useNativeLine: true, attributes: { position: new Geometry.Attribute("position", "float", 3, "POSITION"), positionPrev: new Geometry.Attribute("positionPrev", "float", 3), positionNext: new Geometry.Attribute("positionNext", "float", 3), prevPositionPrev: new Geometry.Attribute("prevPositionPrev", "float", 3), prevPosition: new Geometry.Attribute("prevPosition", "float", 3), prevPositionNext: new Geometry.Attribute("prevPositionNext", "float", 3), offset: new Geometry.Attribute("offset", "float", 1), color: new Geometry.Attribute("color", "float", 4, "COLOR") } }; }, /** @lends module: echarts-gl/util/geometry/LinesGeometry.prototype */ { /** * Reset offset */ resetOffset: function() { this._vertexOffset = 0; this._triangleOffset = 0; this._itemVertexOffsets = []; }, /** * @param {number} nVertex */ setVertexCount: function(nVertex) { var attributes = this.attributes; if (this.vertexCount !== nVertex) { attributes.position.init(nVertex); attributes.color.init(nVertex); if (!this.useNativeLine) { attributes.positionPrev.init(nVertex); attributes.positionNext.init(nVertex); attributes.offset.init(nVertex); } if (nVertex > 65535) { if (this.indices instanceof Uint16Array) { this.indices = new Uint32Array(this.indices); } } else { if (this.indices instanceof Uint32Array) { this.indices = new Uint16Array(this.indices); } } } }, /** * @param {number} nTriangle */ setTriangleCount: function(nTriangle) { if (this.triangleCount !== nTriangle) { if (nTriangle === 0) { this.indices = null; } else { this.indices = this.vertexCount > 65535 ? new Uint32Array(nTriangle * 3) : new Uint16Array(nTriangle * 3); } } }, _getCubicCurveApproxStep: function(p02, p12, p22, p3) { var len = vec3$e.dist(p02, p12) + vec3$e.dist(p22, p12) + vec3$e.dist(p3, p22); var step = 1 / (len + 1) * this.segmentScale; return step; }, /** * Get vertex count of cubic curve * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @param {Array.} p3 * @return number */ getCubicCurveVertexCount: function(p02, p12, p22, p3) { var step = this._getCubicCurveApproxStep(p02, p12, p22, p3); var segCount = Math.ceil(1 / step); if (!this.useNativeLine) { return segCount * 2 + 2; } else { return segCount * 2; } }, /** * Get face count of cubic curve * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @param {Array.} p3 * @return number */ getCubicCurveTriangleCount: function(p02, p12, p22, p3) { var step = this._getCubicCurveApproxStep(p02, p12, p22, p3); var segCount = Math.ceil(1 / step); if (!this.useNativeLine) { return segCount * 2; } else { return 0; } }, /** * Get vertex count of line * @return {number} */ getLineVertexCount: function() { return this.getPolylineVertexCount(sampleLinePoints$1); }, /** * Get face count of line * @return {number} */ getLineTriangleCount: function() { return this.getPolylineTriangleCount(sampleLinePoints$1); }, /** * Get how many vertices will polyline take. * @type {number|Array} points Can be a 1d/2d list of points, or a number of points amount. * @return {number} */ getPolylineVertexCount: function(points) { var pointsLen; if (typeof points === "number") { pointsLen = points; } else { var is2DArray = typeof points[0] !== "number"; pointsLen = is2DArray ? points.length : points.length / 3; } return !this.useNativeLine ? (pointsLen - 1) * 2 + 2 : (pointsLen - 1) * 2; }, /** * Get how many triangles will polyline take. * @type {number|Array} points Can be a 1d/2d list of points, or a number of points amount. * @return {number} */ getPolylineTriangleCount: function(points) { var pointsLen; if (typeof points === "number") { pointsLen = points; } else { var is2DArray = typeof points[0] !== "number"; pointsLen = is2DArray ? points.length : points.length / 3; } return !this.useNativeLine ? Math.max(pointsLen - 1, 0) * 2 : 0; }, /** * Add a cubic curve * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @param {Array.} p3 * @param {Array.} color * @param {number} [lineWidth=1] */ addCubicCurve: function(p02, p12, p22, p3, color, lineWidth) { if (lineWidth == null) { lineWidth = 1; } var x0 = p02[0], y0 = p02[1], z0 = p02[2]; var x1 = p12[0], y1 = p12[1], z1 = p12[2]; var x2 = p22[0], y2 = p22[1], z2 = p22[2]; var x3 = p3[0], y3 = p3[1], z3 = p3[2]; var step = this._getCubicCurveApproxStep(p02, p12, p22, p3); var step2 = step * step; var step3 = step2 * step; var pre1 = 3 * step; var pre2 = 3 * step2; var pre4 = 6 * step2; var pre5 = 6 * step3; var tmp1x = x0 - x1 * 2 + x2; var tmp1y = y0 - y1 * 2 + y2; var tmp1z = z0 - z1 * 2 + z2; var tmp2x = (x1 - x2) * 3 - x0 + x3; var tmp2y = (y1 - y2) * 3 - y0 + y3; var tmp2z = (z1 - z2) * 3 - z0 + z3; var fx = x0; var fy = y0; var fz = z0; var dfx = (x1 - x0) * pre1 + tmp1x * pre2 + tmp2x * step3; var dfy = (y1 - y0) * pre1 + tmp1y * pre2 + tmp2y * step3; var dfz = (z1 - z0) * pre1 + tmp1z * pre2 + tmp2z * step3; var ddfx = tmp1x * pre4 + tmp2x * pre5; var ddfy = tmp1y * pre4 + tmp2y * pre5; var ddfz = tmp1z * pre4 + tmp2z * pre5; var dddfx = tmp2x * pre5; var dddfy = tmp2y * pre5; var dddfz = tmp2z * pre5; var t = 0; var k = 0; var segCount = Math.ceil(1 / step); var points = new Float32Array((segCount + 1) * 3); var points = []; var offset = 0; for (var k = 0; k < segCount + 1; k++) { points[offset++] = fx; points[offset++] = fy; points[offset++] = fz; fx += dfx; fy += dfy; fz += dfz; dfx += ddfx; dfy += ddfy; dfz += ddfz; ddfx += dddfx; ddfy += dddfy; ddfz += dddfz; t += step; if (t > 1) { fx = dfx > 0 ? Math.min(fx, x3) : Math.max(fx, x3); fy = dfy > 0 ? Math.min(fy, y3) : Math.max(fy, y3); fz = dfz > 0 ? Math.min(fz, z3) : Math.max(fz, z3); } } return this.addPolyline(points, color, lineWidth); }, /** * Add a straight line * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} color * @param {number} [lineWidth=1] */ addLine: function(p02, p12, color, lineWidth) { return this.addPolyline([p02, p12], color, lineWidth); }, /** * Add a straight line * @param {Array. | Array.} points * @param {Array. | Array.} color * @param {number} [lineWidth=1] * @param {number} [startOffset=0] * @param {number} [pointsCount] Default to be amount of points in the first argument */ addPolyline: function(points, color, lineWidth, startOffset, pointsCount) { if (!points.length) { return; } var is2DArray = typeof points[0] !== "number"; if (pointsCount == null) { pointsCount = is2DArray ? points.length : points.length / 3; } if (pointsCount < 2) { return; } if (startOffset == null) { startOffset = 0; } if (lineWidth == null) { lineWidth = 1; } this._itemVertexOffsets.push(this._vertexOffset); var is2DArray = typeof points[0] !== "number"; var notSharingColor = is2DArray ? typeof color[0] !== "number" : color.length / 4 === pointsCount; var positionAttr = this.attributes.position; var positionPrevAttr = this.attributes.positionPrev; var positionNextAttr = this.attributes.positionNext; var colorAttr = this.attributes.color; var offsetAttr = this.attributes.offset; var indices = this.indices; var vertexOffset = this._vertexOffset; var point; var pointColor; lineWidth = Math.max(lineWidth, 0.01); for (var k = startOffset; k < pointsCount; k++) { if (is2DArray) { point = points[k]; if (notSharingColor) { pointColor = color[k]; } else { pointColor = color; } } else { var k3 = k * 3; point = point || []; point[0] = points[k3]; point[1] = points[k3 + 1]; point[2] = points[k3 + 2]; if (notSharingColor) { var k4 = k * 4; pointColor = pointColor || []; pointColor[0] = color[k4]; pointColor[1] = color[k4 + 1]; pointColor[2] = color[k4 + 2]; pointColor[3] = color[k4 + 3]; } else { pointColor = color; } } if (!this.useNativeLine) { if (k < pointsCount - 1) { positionPrevAttr.set(vertexOffset + 2, point); positionPrevAttr.set(vertexOffset + 3, point); } if (k > 0) { positionNextAttr.set(vertexOffset - 2, point); positionNextAttr.set(vertexOffset - 1, point); } positionAttr.set(vertexOffset, point); positionAttr.set(vertexOffset + 1, point); colorAttr.set(vertexOffset, pointColor); colorAttr.set(vertexOffset + 1, pointColor); offsetAttr.set(vertexOffset, lineWidth / 2); offsetAttr.set(vertexOffset + 1, -lineWidth / 2); vertexOffset += 2; } else { if (k > 1) { positionAttr.copy(vertexOffset, vertexOffset - 1); colorAttr.copy(vertexOffset, vertexOffset - 1); vertexOffset++; } } if (!this.useNativeLine) { if (k > 0) { var idx3 = this._triangleOffset * 3; var indices = this.indices; indices[idx3] = vertexOffset - 4; indices[idx3 + 1] = vertexOffset - 3; indices[idx3 + 2] = vertexOffset - 2; indices[idx3 + 3] = vertexOffset - 3; indices[idx3 + 4] = vertexOffset - 1; indices[idx3 + 5] = vertexOffset - 2; this._triangleOffset += 2; } } else { colorAttr.set(vertexOffset, pointColor); positionAttr.set(vertexOffset, point); vertexOffset++; } } if (!this.useNativeLine) { var start = this._vertexOffset; var end = this._vertexOffset + pointsCount * 2; positionPrevAttr.copy(start, start + 2); positionPrevAttr.copy(start + 1, start + 3); positionNextAttr.copy(end - 1, end - 3); positionNextAttr.copy(end - 2, end - 4); } this._vertexOffset = vertexOffset; return this._vertexOffset; }, /** * Set color of single line. */ setItemColor: function(idx, color) { var startOffset = this._itemVertexOffsets[idx]; var endOffset = idx < this._itemVertexOffsets.length - 1 ? this._itemVertexOffsets[idx + 1] : this._vertexOffset; for (var i = startOffset; i < endOffset; i++) { this.attributes.color.set(i, color); } this.dirty("color"); }, /** * @return {number} */ currentTriangleOffset: function() { return this._triangleOffset; }, /** * @return {number} */ currentVertexOffset: function() { return this._vertexOffset; } } ); defaults(LinesGeometry$2.prototype, dynamicConvertMixin); function ZRTextureAtlasSurfaceNode(zr, offsetX, offsetY, width, height, gap, dpr) { this._zr = zr; this._x = 0; this._y = 0; this._rowHeight = 0; this.width = width; this.height = height; this.offsetX = offsetX; this.offsetY = offsetY; this.dpr = dpr; this.gap = gap; } ZRTextureAtlasSurfaceNode.prototype = { constructor: ZRTextureAtlasSurfaceNode, clear: function() { this._x = 0; this._y = 0; this._rowHeight = 0; }, /** * Add shape to atlas * @param {module:zrender/graphic/Displayable} shape * @param {number} width * @param {number} height * @return {Array} */ add: function(el, width, height) { var rect = el.getBoundingRect(); if (width == null) { width = rect.width; } if (height == null) { height = rect.height; } width *= this.dpr; height *= this.dpr; this._fitElement(el, width, height); var x = this._x; var y = this._y; var canvasWidth = this.width * this.dpr; var canvasHeight = this.height * this.dpr; var gap = this.gap; if (x + width + gap > canvasWidth) { x = this._x = 0; y += this._rowHeight + gap; this._y = y; this._rowHeight = 0; } this._x += width + gap; this._rowHeight = Math.max(this._rowHeight, height); if (y + height + gap > canvasHeight) { return null; } el.x += this.offsetX * this.dpr + x; el.y += this.offsetY * this.dpr + y; this._zr.add(el); var coordsOffset = [this.offsetX / this.width, this.offsetY / this.height]; var coords = [[x / canvasWidth + coordsOffset[0], y / canvasHeight + coordsOffset[1]], [(x + width) / canvasWidth + coordsOffset[0], (y + height) / canvasHeight + coordsOffset[1]]]; return coords; }, /** * Fit element size by correct its position and scaling * @param {module:zrender/graphic/Displayable} el * @param {number} spriteWidth * @param {number} spriteHeight */ _fitElement: function(el, spriteWidth, spriteHeight) { var rect = el.getBoundingRect(); var scaleX = spriteWidth / rect.width; var scaleY = spriteHeight / rect.height; el.x = -rect.x * scaleX; el.y = -rect.y * scaleY; el.scaleX = scaleX; el.scaleY = scaleY; el.update(); } }; function ZRTextureAtlasSurface(opt) { opt = opt || {}; opt.width = opt.width || 512; opt.height = opt.height || 512; opt.devicePixelRatio = opt.devicePixelRatio || 1; opt.gap = opt.gap == null ? 2 : opt.gap; var canvas = document.createElement("canvas"); canvas.width = opt.width * opt.devicePixelRatio; canvas.height = opt.height * opt.devicePixelRatio; this._canvas = canvas; this._texture = new Texture2D({ image: canvas, flipY: false }); var self2 = this; this._zr = init(canvas); var oldRefreshImmediately = this._zr.refreshImmediately; this._zr.refreshImmediately = function() { oldRefreshImmediately.call(this); self2._texture.dirty(); self2.onupdate && self2.onupdate(); }; this._dpr = opt.devicePixelRatio; this._coords = {}; this.onupdate = opt.onupdate; this._gap = opt.gap; this._textureAtlasNodes = [new ZRTextureAtlasSurfaceNode(this._zr, 0, 0, opt.width, opt.height, this._gap, this._dpr)]; this._nodeWidth = opt.width; this._nodeHeight = opt.height; this._currentNodeIdx = 0; } ZRTextureAtlasSurface.prototype = { /** * Clear the texture atlas */ clear: function() { for (var i = 0; i < this._textureAtlasNodes.length; i++) { this._textureAtlasNodes[i].clear(); } this._currentNodeIdx = 0; this._zr.clear(); this._coords = {}; }, /** * @return {number} */ getWidth: function() { return this._width; }, /** * @return {number} */ getHeight: function() { return this._height; }, /** * @return {number} */ getTexture: function() { return this._texture; }, /** * @return {number} */ getDevicePixelRatio: function() { return this._dpr; }, getZr: function() { return this._zr; }, _getCurrentNode: function() { return this._textureAtlasNodes[this._currentNodeIdx]; }, _expand: function() { this._currentNodeIdx++; if (this._textureAtlasNodes[this._currentNodeIdx]) { return this._textureAtlasNodes[this._currentNodeIdx]; } var maxSize = 4096 / this._dpr; var textureAtlasNodes = this._textureAtlasNodes; var nodeLen = textureAtlasNodes.length; var offsetX = nodeLen * this._nodeWidth % maxSize; var offsetY = Math.floor(nodeLen * this._nodeWidth / maxSize) * this._nodeHeight; if (offsetY >= maxSize) { return; } var width = (offsetX + this._nodeWidth) * this._dpr; var height = (offsetY + this._nodeHeight) * this._dpr; try { this._zr.resize({ width, height }); } catch (e2) { this._canvas.width = width; this._canvas.height = height; } var newNode = new ZRTextureAtlasSurfaceNode(this._zr, offsetX, offsetY, this._nodeWidth, this._nodeHeight, this._gap, this._dpr); this._textureAtlasNodes.push(newNode); return newNode; }, add: function(el, width, height) { if (this._coords[el.id]) { return this._coords[el.id]; } var coords = this._getCurrentNode().add(el, width, height); if (!coords) { var newNode = this._expand(); if (!newNode) { return; } coords = newNode.add(el, width, height); } this._coords[el.id] = coords; return coords; }, /** * Get coord scale after texture atlas is expanded. * @return {Array.} */ getCoordsScale: function() { var dpr = this._dpr; return [this._nodeWidth / this._canvas.width * dpr, this._nodeHeight / this._canvas.height * dpr]; }, /** * Get texture coords of sprite image * @param {string} id Image id * @return {Array} */ getCoords: function(id) { return this._coords[id]; }, dispose: function() { this._zr.dispose(); } }; function SceneHelper() { } SceneHelper.prototype = { constructor: SceneHelper, setScene: function(scene) { this._scene = scene; if (this._skybox) { this._skybox.attachScene(this._scene); } }, initLight: function(rootNode) { this._lightRoot = rootNode; this.mainLight = new graphicGL.DirectionalLight({ shadowBias: 5e-3 }); this.ambientLight = new graphicGL.AmbientLight(); rootNode.add(this.mainLight); rootNode.add(this.ambientLight); }, dispose: function() { if (this._lightRoot) { this._lightRoot.remove(this.mainLight); this._lightRoot.remove(this.ambientLight); } }, updateLight: function(componentModel) { var mainLight = this.mainLight; var ambientLight = this.ambientLight; var lightModel = componentModel.getModel("light"); var mainLightModel = lightModel.getModel("main"); var ambientLightModel = lightModel.getModel("ambient"); mainLight.intensity = mainLightModel.get("intensity"); ambientLight.intensity = ambientLightModel.get("intensity"); mainLight.color = graphicGL.parseColor(mainLightModel.get("color")).slice(0, 3); ambientLight.color = graphicGL.parseColor(ambientLightModel.get("color")).slice(0, 3); var alpha = mainLightModel.get("alpha") || 0; var beta = mainLightModel.get("beta") || 0; mainLight.position.setArray(graphicGL.directionFromAlphaBeta(alpha, beta)); mainLight.lookAt(graphicGL.Vector3.ZERO); mainLight.castShadow = mainLightModel.get("shadow"); mainLight.shadowResolution = graphicGL.getShadowResolution(mainLightModel.get("shadowQuality")); }, updateAmbientCubemap: function(renderer, componentModel, api) { var ambientCubemapModel = componentModel.getModel("light.ambientCubemap"); var textureUrl = ambientCubemapModel.get("texture"); if (textureUrl) { this._cubemapLightsCache = this._cubemapLightsCache || {}; var lights = this._cubemapLightsCache[textureUrl]; if (!lights) { var self2 = this; lights = this._cubemapLightsCache[textureUrl] = graphicGL.createAmbientCubemap(ambientCubemapModel.option, renderer, api, function() { if (self2._isSkyboxFromAmbientCubemap) { self2._skybox.setEnvironmentMap(lights.specular.cubemap); } api.getZr().refresh(); }); } this._lightRoot.add(lights.diffuse); this._lightRoot.add(lights.specular); this._currentCubemapLights = lights; } else if (this._currentCubemapLights) { this._lightRoot.remove(this._currentCubemapLights.diffuse); this._lightRoot.remove(this._currentCubemapLights.specular); this._currentCubemapLights = null; } }, updateSkybox: function(renderer, componentModel, api) { var environmentUrl = componentModel.get("environment"); var self2 = this; function getSkybox() { self2._skybox = self2._skybox || new Skybox(); return self2._skybox; } var skybox = getSkybox(); if (environmentUrl && environmentUrl !== "none") { if (environmentUrl === "auto") { this._isSkyboxFromAmbientCubemap = true; if (this._currentCubemapLights) { var cubemap = this._currentCubemapLights.specular.cubemap; skybox.setEnvironmentMap(cubemap); if (this._scene) { skybox.attachScene(this._scene); } skybox.material.set("lod", 3); } else if (this._skybox) { this._skybox.detachScene(); } } else if (typeof environmentUrl === "object" && environmentUrl.colorStops || typeof environmentUrl === "string" && parse$1(environmentUrl)) { this._isSkyboxFromAmbientCubemap = false; var texture = new graphicGL.Texture2D({ anisotropic: 8, flipY: false }); skybox.setEnvironmentMap(texture); var canvas = texture.image = document.createElement("canvas"); canvas.width = canvas.height = 16; var ctx = canvas.getContext("2d"); var rect = new Rect({ shape: { x: 0, y: 0, width: 16, height: 16 }, style: { fill: environmentUrl } }); brushSingle(ctx, rect); skybox.attachScene(this._scene); } else { this._isSkyboxFromAmbientCubemap = false; var texture = graphicGL.loadTexture(environmentUrl, api, { anisotropic: 8, flipY: false }); skybox.setEnvironmentMap(texture); skybox.attachScene(this._scene); } } else { if (this._skybox) { this._skybox.detachScene(this._scene); } this._skybox = null; } var coordSys = componentModel.coordinateSystem; if (this._skybox) { if (coordSys && coordSys.viewGL && environmentUrl !== "auto" && !(environmentUrl.match && environmentUrl.match(/.hdr$/))) { var srgbDefineMethod = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; this._skybox.material[srgbDefineMethod]("fragment", "SRGB_DECODE"); } else { this._skybox.material.undefine("fragment", "SRGB_DECODE"); } } } }; var vec3$d = glmatrix.vec3; var QuadsGeometry = Geometry.extend( function() { return { segmentScale: 1, /** * Need to use mesh to expand lines if lineWidth > MAX_LINE_WIDTH */ useNativeLine: true, attributes: { position: new Geometry.Attribute("position", "float", 3, "POSITION"), normal: new Geometry.Attribute("normal", "float", 3, "NORMAL"), color: new Geometry.Attribute("color", "float", 4, "COLOR") } }; }, /** @lends module: echarts-gl/util/geometry/QuadsGeometry.prototype */ { /** * Reset offset */ resetOffset: function() { this._vertexOffset = 0; this._faceOffset = 0; }, /** * @param {number} nQuad */ setQuadCount: function(nQuad) { var attributes = this.attributes; var vertexCount = this.getQuadVertexCount() * nQuad; var triangleCount = this.getQuadTriangleCount() * nQuad; if (this.vertexCount !== vertexCount) { attributes.position.init(vertexCount); attributes.normal.init(vertexCount); attributes.color.init(vertexCount); } if (this.triangleCount !== triangleCount) { this.indices = vertexCount > 65535 ? new Uint32Array(triangleCount * 3) : new Uint16Array(triangleCount * 3); } }, getQuadVertexCount: function() { return 4; }, getQuadTriangleCount: function() { return 2; }, /** * Add a quad, which in following order: * 0-----1 * 3-----2 */ addQuad: function() { var a = vec3$d.create(); var b = vec3$d.create(); var normal2 = vec3$d.create(); var indices = [0, 3, 1, 3, 2, 1]; return function(coords, color) { var positionAttr = this.attributes.position; var normalAttr = this.attributes.normal; var colorAttr = this.attributes.color; vec3$d.sub(a, coords[1], coords[0]); vec3$d.sub(b, coords[2], coords[1]); vec3$d.cross(normal2, a, b); vec3$d.normalize(normal2, normal2); for (var i = 0; i < 4; i++) { positionAttr.set(this._vertexOffset + i, coords[i]); colorAttr.set(this._vertexOffset + i, color); normalAttr.set(this._vertexOffset + i, normal2); } var idx = this._faceOffset * 3; for (var i = 0; i < 6; i++) { this.indices[idx + i] = indices[i] + this._vertexOffset; } this._vertexOffset += 4; this._faceOffset += 2; }; }() } ); defaults(QuadsGeometry.prototype, dynamicConvertMixin); var firstNotNull$2 = retrieve.firstNotNull; var dimIndicesMap$2 = { // Left to right x: 0, // Far to near y: 2, // Bottom to up z: 1 }; function updateFacePlane(node, plane, otherAxis, dir) { var coord = [0, 0, 0]; var distance = dir < 0 ? otherAxis.getExtentMin() : otherAxis.getExtentMax(); coord[dimIndicesMap$2[otherAxis.dim]] = distance; node.position.setArray(coord); node.rotation.identity(); plane.distance = -Math.abs(distance); plane.normal.set(0, 0, 0); if (otherAxis.dim === "x") { node.rotation.rotateY(dir * Math.PI / 2); plane.normal.x = -dir; } else if (otherAxis.dim === "z") { node.rotation.rotateX(-dir * Math.PI / 2); plane.normal.y = -dir; } else { if (dir > 0) { node.rotation.rotateY(Math.PI); } plane.normal.z = -dir; } } function Grid3DFace(faceInfo, linesMaterial, quadsMaterial) { this.rootNode = new graphicGL.Node(); var linesMesh = new graphicGL.Mesh({ geometry: new LinesGeometry$2({ useNativeLine: false }), material: linesMaterial, castShadow: false, ignorePicking: true, $ignorePicking: true, renderOrder: 1 }); var quadsMesh = new graphicGL.Mesh({ geometry: new QuadsGeometry(), material: quadsMaterial, castShadow: false, culling: false, ignorePicking: true, $ignorePicking: true, renderOrder: 0 }); this.rootNode.add(quadsMesh); this.rootNode.add(linesMesh); this.faceInfo = faceInfo; this.plane = new graphicGL.Plane(); this.linesMesh = linesMesh; this.quadsMesh = quadsMesh; } Grid3DFace.prototype.update = function(grid3DModel, ecModel, api) { var cartesian = grid3DModel.coordinateSystem; var axes = [cartesian.getAxis(this.faceInfo[0]), cartesian.getAxis(this.faceInfo[1])]; var lineGeometry = this.linesMesh.geometry; var quadsGeometry = this.quadsMesh.geometry; lineGeometry.convertToDynamicArray(true); quadsGeometry.convertToDynamicArray(true); this._updateSplitLines(lineGeometry, axes, grid3DModel, api); this._udpateSplitAreas(quadsGeometry, axes, grid3DModel, api); lineGeometry.convertToTypedArray(); quadsGeometry.convertToTypedArray(); var otherAxis = cartesian.getAxis(this.faceInfo[2]); updateFacePlane(this.rootNode, this.plane, otherAxis, this.faceInfo[3]); }; Grid3DFace.prototype._updateSplitLines = function(geometry, axes, grid3DModel, api) { var dpr = api.getDevicePixelRatio(); axes.forEach(function(axis, idx) { var axisModel = axis.model; var otherExtent = axes[1 - idx].getExtent(); if (axis.scale.isBlank()) { return; } var splitLineModel = axisModel.getModel("splitLine", grid3DModel.getModel("splitLine")); if (splitLineModel.get("show")) { var lineStyleModel = splitLineModel.getModel("lineStyle"); var lineColors = lineStyleModel.get("color"); var opacity = firstNotNull$2(lineStyleModel.get("opacity"), 1); var lineWidth = firstNotNull$2(lineStyleModel.get("width"), 1); lineColors = isArray(lineColors) ? lineColors : [lineColors]; var ticksCoords = axis.getTicksCoords({ tickModel: splitLineModel }); var count = 0; for (var i = 0; i < ticksCoords.length; i++) { var tickCoord = ticksCoords[i].coord; var lineColor = graphicGL.parseColor(lineColors[count % lineColors.length]); lineColor[3] *= opacity; var p02 = [0, 0, 0]; var p12 = [0, 0, 0]; p02[idx] = p12[idx] = tickCoord; p02[1 - idx] = otherExtent[0]; p12[1 - idx] = otherExtent[1]; geometry.addLine(p02, p12, lineColor, lineWidth * dpr); count++; } } }); }; Grid3DFace.prototype._udpateSplitAreas = function(geometry, axes, grid3DModel, api) { axes.forEach(function(axis, idx) { var axisModel = axis.model; var otherExtent = axes[1 - idx].getExtent(); if (axis.scale.isBlank()) { return; } var splitAreaModel = axisModel.getModel("splitArea", grid3DModel.getModel("splitArea")); if (splitAreaModel.get("show")) { var areaStyleModel = splitAreaModel.getModel("areaStyle"); var colors = areaStyleModel.get("color"); var opacity = firstNotNull$2(areaStyleModel.get("opacity"), 1); colors = isArray(colors) ? colors : [colors]; var ticksCoords = axis.getTicksCoords({ tickModel: splitAreaModel, clamp: true }); var count = 0; var prevP0 = [0, 0, 0]; var prevP1 = [0, 0, 0]; for (var i = 0; i < ticksCoords.length; i++) { var tickCoord = ticksCoords[i].coord; var p02 = [0, 0, 0]; var p12 = [0, 0, 0]; p02[idx] = p12[idx] = tickCoord; p02[1 - idx] = otherExtent[0]; p12[1 - idx] = otherExtent[1]; if (i === 0) { prevP0 = p02; prevP1 = p12; continue; } var color = graphicGL.parseColor(colors[count % colors.length]); color[3] *= opacity; geometry.addQuad([prevP0, p02, p12, prevP1], color); prevP0 = p02; prevP1 = p12; count++; } } }); }; var squareTriangles = [0, 1, 2, 0, 2, 3]; var SpritesGeometry = Geometry.extend(function() { return { attributes: { position: new Geometry.Attribute("position", "float", 3, "POSITION"), texcoord: new Geometry.Attribute("texcoord", "float", 2, "TEXCOORD_0"), offset: new Geometry.Attribute("offset", "float", 2), color: new Geometry.Attribute("color", "float", 4, "COLOR") } }; }, { resetOffset: function() { this._vertexOffset = 0; this._faceOffset = 0; }, setSpriteCount: function(spriteCount) { this._spriteCount = spriteCount; var vertexCount = spriteCount * 4; var triangleCount = spriteCount * 2; if (this.vertexCount !== vertexCount) { this.attributes.position.init(vertexCount); this.attributes.offset.init(vertexCount); this.attributes.color.init(vertexCount); } if (this.triangleCount !== triangleCount) { this.indices = vertexCount > 65535 ? new Uint32Array(triangleCount * 3) : new Uint16Array(triangleCount * 3); } }, setSpriteAlign: function(spriteOffset, size, align, verticalAlign, margin) { if (align == null) { align = "left"; } if (verticalAlign == null) { verticalAlign = "top"; } var leftOffset, topOffset, rightOffset, bottomOffset; margin = margin || 0; switch (align) { case "left": leftOffset = margin; rightOffset = size[0] + margin; break; case "center": case "middle": leftOffset = -size[0] / 2; rightOffset = size[0] / 2; break; case "right": leftOffset = -size[0] - margin; rightOffset = -margin; break; } switch (verticalAlign) { case "bottom": topOffset = margin; bottomOffset = size[1] + margin; break; case "middle": topOffset = -size[1] / 2; bottomOffset = size[1] / 2; break; case "top": topOffset = -size[1] - margin; bottomOffset = -margin; break; } var vertexOffset = spriteOffset * 4; var offsetAttr = this.attributes.offset; offsetAttr.set(vertexOffset, [leftOffset, bottomOffset]); offsetAttr.set(vertexOffset + 1, [rightOffset, bottomOffset]); offsetAttr.set(vertexOffset + 2, [rightOffset, topOffset]); offsetAttr.set(vertexOffset + 3, [leftOffset, topOffset]); }, /** * Add sprite * @param {Array.} position * @param {Array.} size [width, height] * @param {Array.} coords [leftBottom, rightTop] * @param {string} [align='left'] 'left' 'center' 'right' * @param {string} [verticalAlign='top'] 'top' 'middle' 'bottom' * @param {number} [screenMargin=0] */ addSprite: function(position, size, coords, align, verticalAlign, screenMargin) { var vertexOffset = this._vertexOffset; this.setSprite(this._vertexOffset / 4, position, size, coords, align, verticalAlign, screenMargin); for (var i = 0; i < squareTriangles.length; i++) { this.indices[this._faceOffset * 3 + i] = squareTriangles[i] + vertexOffset; } this._faceOffset += 2; this._vertexOffset += 4; return vertexOffset / 4; }, setSprite: function(spriteOffset, position, size, coords, align, verticalAlign, screenMargin) { var vertexOffset = spriteOffset * 4; var attributes = this.attributes; for (var i = 0; i < 4; i++) { attributes.position.set(vertexOffset + i, position); } var texcoordAttr = attributes.texcoord; texcoordAttr.set(vertexOffset, [coords[0][0], coords[0][1]]); texcoordAttr.set(vertexOffset + 1, [coords[1][0], coords[0][1]]); texcoordAttr.set(vertexOffset + 2, [coords[1][0], coords[1][1]]); texcoordAttr.set(vertexOffset + 3, [coords[0][0], coords[1][1]]); this.setSpriteAlign(spriteOffset, size, align, verticalAlign, screenMargin); } }); defaults(SpritesGeometry.prototype, dynamicConvertMixin); const labelsGLSL = "@export ecgl.labels.vertex\n\nattribute vec3 position: POSITION;\nattribute vec2 texcoord: TEXCOORD_0;\nattribute vec2 offset;\n#ifdef VERTEX_COLOR\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n#endif\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform vec4 viewport : VIEWPORT;\n\nvarying vec2 v_Texcoord;\n\nvoid main()\n{\n vec4 proj = worldViewProjection * vec4(position, 1.0);\n\n vec2 screen = (proj.xy / abs(proj.w) + 1.0) * 0.5 * viewport.zw;\n\n screen += offset;\n\n proj.xy = (screen / viewport.zw - 0.5) * 2.0 * abs(proj.w);\n gl_Position = proj;\n#ifdef VERTEX_COLOR\n v_Color = a_Color;\n#endif\n v_Texcoord = texcoord;\n}\n@end\n\n\n@export ecgl.labels.fragment\n\nuniform vec3 color : [1.0, 1.0, 1.0];\nuniform float alpha : 1.0;\nuniform sampler2D textureAtlas;\nuniform vec2 uvScale: [1.0, 1.0];\n\n#ifdef VERTEX_COLOR\nvarying vec4 v_Color;\n#endif\nvarying float v_Miter;\n\nvarying vec2 v_Texcoord;\n\nvoid main()\n{\n gl_FragColor = vec4(color, alpha) * texture2D(textureAtlas, v_Texcoord * uvScale);\n#ifdef VERTEX_COLOR\n gl_FragColor *= v_Color;\n#endif\n}\n\n@end"; graphicGL.Shader.import(labelsGLSL); const LabelsMesh = graphicGL.Mesh.extend(function() { var geometry = new SpritesGeometry({ dynamic: true }); var material = new graphicGL.Material({ shader: graphicGL.createShader("ecgl.labels"), transparent: true, depthMask: false }); return { geometry, material, culling: false, castShadow: false, ignorePicking: true }; }); var firstNotNull$1 = retrieve.firstNotNull; var dimIndicesMap$1 = { // Left to right x: 0, // Far to near y: 2, // Bottom to up z: 1 }; function Grid3DAxis(dim, linesMaterial) { var linesMesh = new graphicGL.Mesh({ geometry: new LinesGeometry$2({ useNativeLine: false }), material: linesMaterial, castShadow: false, ignorePicking: true, renderOrder: 2 }); var axisLabelsMesh = new LabelsMesh(); axisLabelsMesh.material.depthMask = false; var rootNode = new graphicGL.Node(); rootNode.add(linesMesh); rootNode.add(axisLabelsMesh); this.rootNode = rootNode; this.dim = dim; this.linesMesh = linesMesh; this.labelsMesh = axisLabelsMesh; this.axisLineCoords = null; this.labelElements = []; } var otherDim = { x: "y", y: "x", z: "y" }; Grid3DAxis.prototype.update = function(grid3DModel, axisLabelSurface, api) { var cartesian = grid3DModel.coordinateSystem; var axis = cartesian.getAxis(this.dim); var linesGeo = this.linesMesh.geometry; var labelsGeo = this.labelsMesh.geometry; linesGeo.convertToDynamicArray(true); labelsGeo.convertToDynamicArray(true); var axisModel = axis.model; var extent = axis.getExtent(); var dpr = api.getDevicePixelRatio(); var axisLineModel = axisModel.getModel("axisLine", grid3DModel.getModel("axisLine")); var axisTickModel = axisModel.getModel("axisTick", grid3DModel.getModel("axisTick")); var axisLabelModel = axisModel.getModel("axisLabel", grid3DModel.getModel("axisLabel")); var axisLineColor = axisLineModel.get("lineStyle.color"); if (axisLineModel.get("show")) { var axisLineStyleModel = axisLineModel.getModel("lineStyle"); var p02 = [0, 0, 0]; var p12 = [0, 0, 0]; var idx = dimIndicesMap$1[axis.dim]; p02[idx] = extent[0]; p12[idx] = extent[1]; this.axisLineCoords = [p02, p12]; var color = graphicGL.parseColor(axisLineColor); var lineWidth = firstNotNull$1(axisLineStyleModel.get("width"), 1); var opacity = firstNotNull$1(axisLineStyleModel.get("opacity"), 1); color[3] *= opacity; linesGeo.addLine(p02, p12, color, lineWidth * dpr); } if (axisTickModel.get("show")) { var lineStyleModel = axisTickModel.getModel("lineStyle"); var lineColor = graphicGL.parseColor(firstNotNull$1(lineStyleModel.get("color"), axisLineColor)); var lineWidth = firstNotNull$1(lineStyleModel.get("width"), 1); lineColor[3] *= firstNotNull$1(lineStyleModel.get("opacity"), 1); var ticksCoords = axis.getTicksCoords(); var tickLength = axisTickModel.get("length"); for (var i = 0; i < ticksCoords.length; i++) { var tickCoord = ticksCoords[i].coord; var p02 = [0, 0, 0]; var p12 = [0, 0, 0]; var idx = dimIndicesMap$1[axis.dim]; var otherIdx = dimIndicesMap$1[otherDim[axis.dim]]; p02[idx] = p12[idx] = tickCoord; p12[otherIdx] = tickLength; linesGeo.addLine(p02, p12, lineColor, lineWidth * dpr); } } this.labelElements = []; var dpr = api.getDevicePixelRatio(); if (axisLabelModel.get("show")) { var ticksCoords = axis.getTicksCoords(); var categoryData = axisModel.get("data"); var labelMargin = axisLabelModel.get("margin"); var labels = axis.getViewLabels(); for (var i = 0; i < labels.length; i++) { var tickValue = labels[i].tickValue; var formattedLabel = labels[i].formattedLabel; var rawLabel = labels[i].rawLabel; var tickCoord = axis.dataToCoord(tickValue); var p = [0, 0, 0]; var idx = dimIndicesMap$1[axis.dim]; var otherIdx = dimIndicesMap$1[otherDim[axis.dim]]; p[idx] = p[idx] = tickCoord; p[otherIdx] = labelMargin; var itemTextStyleModel = axisLabelModel; if (categoryData && categoryData[tickValue] && categoryData[tickValue].textStyle) { itemTextStyleModel = new Model(categoryData[tickValue].textStyle, axisLabelModel, axisModel.ecModel); } var textColor = firstNotNull$1(itemTextStyleModel.get("color"), axisLineColor); var textEl = new ZRText({ style: createTextStyle(itemTextStyleModel, { text: formattedLabel, fill: typeof textColor === "function" ? textColor( // (1) In category axis with data zoom, tick is not the original // index of axis.data. So tick should not be exposed to user // in category axis. // (2) Compatible with previous version, which always returns labelStr. // But in interval scale labelStr is like '223,445', which maked // user repalce ','. So we modify it to return original val but remain // it as 'string' to avoid error in replacing. axis.type === "category" ? rawLabel : axis.type === "value" ? tickValue + "" : tickValue, i ) : textColor, verticalAlign: "top", align: "left" }) }); var coords = axisLabelSurface.add(textEl); var rect = textEl.getBoundingRect(); labelsGeo.addSprite(p, [rect.width * dpr, rect.height * dpr], coords); this.labelElements.push(textEl); } } if (axisModel.get("name")) { var nameTextStyleModel = axisModel.getModel("nameTextStyle"); var p = [0, 0, 0]; var idx = dimIndicesMap$1[axis.dim]; var otherIdx = dimIndicesMap$1[otherDim[axis.dim]]; var labelColor = firstNotNull$1(nameTextStyleModel.get("color"), axisLineColor); var strokeColor = nameTextStyleModel.get("borderColor"); var lineWidth = nameTextStyleModel.get("borderWidth"); p[idx] = p[idx] = (extent[0] + extent[1]) / 2; p[otherIdx] = axisModel.get("nameGap"); var textEl = new ZRText({ style: createTextStyle(nameTextStyleModel, { text: axisModel.get("name"), fill: labelColor, stroke: strokeColor, lineWidth }) }); var coords = axisLabelSurface.add(textEl); var rect = textEl.getBoundingRect(); labelsGeo.addSprite(p, [rect.width * dpr, rect.height * dpr], coords); textEl.__idx = this.labelElements.length; this.nameLabelElement = textEl; } this.labelsMesh.material.set("textureAtlas", axisLabelSurface.getTexture()); this.labelsMesh.material.set("uvScale", axisLabelSurface.getCoordsScale()); linesGeo.convertToTypedArray(); labelsGeo.convertToTypedArray(); }; Grid3DAxis.prototype.setSpriteAlign = function(textAlign, textVerticalAlign, api) { var dpr = api.getDevicePixelRatio(); var labelGeo = this.labelsMesh.geometry; for (var i = 0; i < this.labelElements.length; i++) { var labelEl = this.labelElements[i]; var rect = labelEl.getBoundingRect(); labelGeo.setSpriteAlign(i, [rect.width * dpr, rect.height * dpr], textAlign, textVerticalAlign); } var nameLabelEl = this.nameLabelElement; if (nameLabelEl) { var rect = nameLabelEl.getBoundingRect(); labelGeo.setSpriteAlign(nameLabelEl.__idx, [rect.width * dpr, rect.height * dpr], textAlign, textVerticalAlign); labelGeo.dirty(); } this.textAlign = textAlign; this.textVerticalAlign = textVerticalAlign; }; const lines3DGLSL = "@export ecgl.lines3D.vertex\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\nattribute vec3 position: POSITION;\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n\nvoid main()\n{\n gl_Position = worldViewProjection * vec4(position, 1.0);\n v_Color = a_Color;\n}\n\n@end\n\n@export ecgl.lines3D.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nvarying vec4 v_Color;\n\n@import clay.util.srgb\n\nvoid main()\n{\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(color * v_Color);\n#else\n gl_FragColor = color * v_Color;\n#endif\n}\n@end\n\n\n\n@export ecgl.lines3D.clipNear\n\nvec4 clipNear(vec4 p1, vec4 p2) {\n float n = (p1.w - near) / (p1.w - p2.w);\n return vec4(mix(p1.xy, p2.xy, n), -near, near);\n}\n\n@end\n\n@export ecgl.lines3D.expandLine\n#ifdef VERTEX_ANIMATION\n vec4 prevProj = worldViewProjection * vec4(mix(prevPositionPrev, positionPrev, percent), 1.0);\n vec4 currProj = worldViewProjection * vec4(mix(prevPosition, position, percent), 1.0);\n vec4 nextProj = worldViewProjection * vec4(mix(prevPositionNext, positionNext, percent), 1.0);\n#else\n vec4 prevProj = worldViewProjection * vec4(positionPrev, 1.0);\n vec4 currProj = worldViewProjection * vec4(position, 1.0);\n vec4 nextProj = worldViewProjection * vec4(positionNext, 1.0);\n#endif\n\n if (currProj.w < 0.0) {\n if (nextProj.w > 0.0) {\n currProj = clipNear(currProj, nextProj);\n }\n else if (prevProj.w > 0.0) {\n currProj = clipNear(currProj, prevProj);\n }\n }\n\n vec2 prevScreen = (prevProj.xy / abs(prevProj.w) + 1.0) * 0.5 * viewport.zw;\n vec2 currScreen = (currProj.xy / abs(currProj.w) + 1.0) * 0.5 * viewport.zw;\n vec2 nextScreen = (nextProj.xy / abs(nextProj.w) + 1.0) * 0.5 * viewport.zw;\n\n vec2 dir;\n float len = offset;\n if (position == positionPrev) {\n dir = normalize(nextScreen - currScreen);\n }\n else if (position == positionNext) {\n dir = normalize(currScreen - prevScreen);\n }\n else {\n vec2 dirA = normalize(currScreen - prevScreen);\n vec2 dirB = normalize(nextScreen - currScreen);\n\n vec2 tanget = normalize(dirA + dirB);\n\n float miter = 1.0 / max(dot(tanget, dirA), 0.5);\n len *= miter;\n dir = tanget;\n }\n\n dir = vec2(-dir.y, dir.x) * len;\n currScreen += dir;\n\n currProj.xy = (currScreen / viewport.zw - 0.5) * 2.0 * abs(currProj.w);\n@end\n\n\n@export ecgl.meshLines3D.vertex\n\nattribute vec3 position: POSITION;\nattribute vec3 positionPrev;\nattribute vec3 positionNext;\nattribute float offset;\nattribute vec4 a_Color : COLOR;\n\n#ifdef VERTEX_ANIMATION\nattribute vec3 prevPosition;\nattribute vec3 prevPositionPrev;\nattribute vec3 prevPositionNext;\nuniform float percent : 1.0;\n#endif\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform vec4 viewport : VIEWPORT;\nuniform float near : NEAR;\n\nvarying vec4 v_Color;\n\n@import ecgl.common.wireframe.vertexHeader\n\n@import ecgl.lines3D.clipNear\n\nvoid main()\n{\n @import ecgl.lines3D.expandLine\n\n gl_Position = currProj;\n\n v_Color = a_Color;\n\n @import ecgl.common.wireframe.vertexMain\n}\n@end\n\n\n@export ecgl.meshLines3D.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nvarying vec4 v_Color;\n\n@import ecgl.common.wireframe.fragmentHeader\n\n@import clay.util.srgb\n\nvoid main()\n{\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(color * v_Color);\n#else\n gl_FragColor = color * v_Color;\n#endif\n\n @import ecgl.common.wireframe.fragmentMain\n}\n\n@end"; var firstNotNull = retrieve.firstNotNull; graphicGL.Shader.import(lines3DGLSL); var dimIndicesMap = { // Left to right x: 0, // Far to near y: 2, // Bottom to up z: 1 }; const Grid3DView = ComponentView.extend({ type: "grid3D", __ecgl__: true, init: function(ecModel, api) { var FACES = [ // planeDim0, planeDim1, offsetDim, dir on dim3 axis(gl), plane. ["y", "z", "x", -1, "left"], ["y", "z", "x", 1, "right"], ["x", "y", "z", -1, "bottom"], ["x", "y", "z", 1, "top"], ["x", "z", "y", -1, "far"], ["x", "z", "y", 1, "near"] ]; var DIMS = ["x", "y", "z"]; var quadsMaterial = new graphicGL.Material({ // transparent: true, shader: graphicGL.createShader("ecgl.color"), depthMask: false, transparent: true }); var linesMaterial = new graphicGL.Material({ // transparent: true, shader: graphicGL.createShader("ecgl.meshLines3D"), depthMask: false, transparent: true }); quadsMaterial.define("fragment", "DOUBLE_SIDED"); quadsMaterial.define("both", "VERTEX_COLOR"); this.groupGL = new graphicGL.Node(); this._control = new OrbitControl({ zr: api.getZr() }); this._control.init(); this._faces = FACES.map(function(faceInfo) { var face = new Grid3DFace(faceInfo, linesMaterial, quadsMaterial); this.groupGL.add(face.rootNode); return face; }, this); this._axes = DIMS.map(function(dim) { var axis = new Grid3DAxis(dim, linesMaterial); this.groupGL.add(axis.rootNode); return axis; }, this); var dpr = api.getDevicePixelRatio(); this._axisLabelSurface = new ZRTextureAtlasSurface({ width: 256, height: 256, devicePixelRatio: dpr }); this._axisLabelSurface.onupdate = function() { api.getZr().refresh(); }; this._axisPointerLineMesh = new graphicGL.Mesh({ geometry: new LinesGeometry$2({ useNativeLine: false }), material: linesMaterial, castShadow: false, // PENDING ignorePicking: true, renderOrder: 3 }); this.groupGL.add(this._axisPointerLineMesh); this._axisPointerLabelsSurface = new ZRTextureAtlasSurface({ width: 128, height: 128, devicePixelRatio: dpr }); this._axisPointerLabelsMesh = new LabelsMesh({ ignorePicking: true, renderOrder: 4, castShadow: false }); this._axisPointerLabelsMesh.material.set("textureAtlas", this._axisPointerLabelsSurface.getTexture()); this.groupGL.add(this._axisPointerLabelsMesh); this._lightRoot = new graphicGL.Node(); this._sceneHelper = new SceneHelper(); this._sceneHelper.initLight(this._lightRoot); }, render: function(grid3DModel, ecModel, api) { this._model = grid3DModel; this._api = api; var cartesian = grid3DModel.coordinateSystem; cartesian.viewGL.add(this._lightRoot); if (grid3DModel.get("show")) { cartesian.viewGL.add(this.groupGL); } else { cartesian.viewGL.remove(this.groupGL); } var control = this._control; control.setViewGL(cartesian.viewGL); var viewControlModel = grid3DModel.getModel("viewControl"); control.setFromViewControlModel(viewControlModel, 0); this._axisLabelSurface.clear(); control.off("update"); if (grid3DModel.get("show")) { this._faces.forEach(function(face) { face.update(grid3DModel, ecModel, api); }, this); this._axes.forEach(function(axis) { axis.update(grid3DModel, this._axisLabelSurface, api); }, this); } control.on("update", this._onCameraChange.bind(this, grid3DModel, api), this); this._sceneHelper.setScene(cartesian.viewGL.scene); this._sceneHelper.updateLight(grid3DModel); cartesian.viewGL.setPostEffect(grid3DModel.getModel("postEffect"), api); cartesian.viewGL.setTemporalSuperSampling(grid3DModel.getModel("temporalSuperSampling")); this._initMouseHandler(grid3DModel); }, afterRender: function(grid3DModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; this._sceneHelper.updateAmbientCubemap(renderer, grid3DModel, api); this._sceneHelper.updateSkybox(renderer, grid3DModel, api); }, /** * showAxisPointer will be triggered by action. */ showAxisPointer: function(grid3dModel, ecModel, api, payload) { this._doShowAxisPointer(); this._updateAxisPointer(payload.value); }, /** * hideAxisPointer will be triggered by action. */ hideAxisPointer: function(grid3dModel, ecModel, api, payload) { this._doHideAxisPointer(); }, _initMouseHandler: function(grid3DModel) { var cartesian = grid3DModel.coordinateSystem; var viewGL = cartesian.viewGL; if (grid3DModel.get("show") && grid3DModel.get("axisPointer.show")) { viewGL.on("mousemove", this._updateAxisPointerOnMousePosition, this); } else { viewGL.off("mousemove", this._updateAxisPointerOnMousePosition); } }, /** * Try find and show axisPointer on the intersect point * of mouse ray with grid plane. */ _updateAxisPointerOnMousePosition: function(e2) { if (e2.target) { return; } var grid3DModel = this._model; var cartesian = grid3DModel.coordinateSystem; var viewGL = cartesian.viewGL; var ray = viewGL.castRay(e2.offsetX, e2.offsetY, new graphicGL.Ray()); var nearestIntersectPoint; for (var i = 0; i < this._faces.length; i++) { var face = this._faces[i]; if (face.rootNode.invisible) { continue; } if (face.plane.normal.dot(viewGL.camera.worldTransform.z) < 0) { face.plane.normal.negate(); } var point = ray.intersectPlane(face.plane); if (!point) { continue; } var axis0 = cartesian.getAxis(face.faceInfo[0]); var axis1 = cartesian.getAxis(face.faceInfo[1]); var idx0 = dimIndicesMap[face.faceInfo[0]]; var idx1 = dimIndicesMap[face.faceInfo[1]]; if (axis0.contain(point.array[idx0]) && axis1.contain(point.array[idx1])) { nearestIntersectPoint = point; } } if (nearestIntersectPoint) { var data = cartesian.pointToData(nearestIntersectPoint.array, [], true); this._updateAxisPointer(data); this._doShowAxisPointer(); } else { this._doHideAxisPointer(); } }, _onCameraChange: function(grid3DModel, api) { if (grid3DModel.get("show")) { this._updateFaceVisibility(); this._updateAxisLinePosition(); } var control = this._control; api.dispatchAction({ type: "grid3DChangeCamera", alpha: control.getAlpha(), beta: control.getBeta(), distance: control.getDistance(), center: control.getCenter(), from: this.uid, grid3DId: grid3DModel.id }); }, /** * Update visibility of each face when camera view changed, front face will be invisible. * @private */ _updateFaceVisibility: function() { var camera2 = this._control.getCamera(); var viewSpacePos = new graphicGL.Vector3(); camera2.update(); for (var idx = 0; idx < this._faces.length / 2; idx++) { var depths = []; for (var k = 0; k < 2; k++) { var face = this._faces[idx * 2 + k]; face.rootNode.getWorldPosition(viewSpacePos); viewSpacePos.transformMat4(camera2.viewMatrix); depths[k] = viewSpacePos.z; } var frontIndex = depths[0] > depths[1] ? 0 : 1; var frontFace = this._faces[idx * 2 + frontIndex]; var backFace = this._faces[idx * 2 + 1 - frontIndex]; frontFace.rootNode.invisible = true; backFace.rootNode.invisible = false; } }, /** * Update axis line position when camera view changed. * @private */ _updateAxisLinePosition: function() { var cartesian = this._model.coordinateSystem; var xAxis = cartesian.getAxis("x"); var yAxis = cartesian.getAxis("y"); var zAxis = cartesian.getAxis("z"); var top = zAxis.getExtentMax(); var bottom = zAxis.getExtentMin(); var left = xAxis.getExtentMin(); var right = xAxis.getExtentMax(); var near = yAxis.getExtentMax(); var far = yAxis.getExtentMin(); var xAxisNode = this._axes[0].rootNode; var yAxisNode = this._axes[1].rootNode; var zAxisNode = this._axes[2].rootNode; var faces = this._faces; var xAxisZOffset = faces[4].rootNode.invisible ? far : near; var xAxisYOffset = faces[2].rootNode.invisible ? top : bottom; var yAxisXOffset = faces[0].rootNode.invisible ? left : right; var yAxisYOffset = faces[2].rootNode.invisible ? top : bottom; var zAxisXOffset = faces[0].rootNode.invisible ? right : left; var zAxisZOffset = faces[4].rootNode.invisible ? far : near; xAxisNode.rotation.identity(); yAxisNode.rotation.identity(); zAxisNode.rotation.identity(); if (faces[4].rootNode.invisible) { this._axes[0].flipped = true; xAxisNode.rotation.rotateX(Math.PI); } if (faces[0].rootNode.invisible) { this._axes[1].flipped = true; yAxisNode.rotation.rotateZ(Math.PI); } if (faces[4].rootNode.invisible) { this._axes[2].flipped = true; zAxisNode.rotation.rotateY(Math.PI); } xAxisNode.position.set(0, xAxisYOffset, xAxisZOffset); yAxisNode.position.set(yAxisXOffset, yAxisYOffset, 0); zAxisNode.position.set(zAxisXOffset, 0, zAxisZOffset); xAxisNode.update(); yAxisNode.update(); zAxisNode.update(); this._updateAxisLabelAlign(); }, /** * Update label align on axis when axisLine position changed. * @private */ _updateAxisLabelAlign: function() { var camera2 = this._control.getCamera(); var coords = [new graphicGL.Vector4(), new graphicGL.Vector4()]; var center = new graphicGL.Vector4(); this.groupGL.getWorldPosition(center); center.w = 1; center.transformMat4(camera2.viewMatrix).transformMat4(camera2.projectionMatrix); center.x /= center.w; center.y /= center.w; this._axes.forEach(function(axisInfo) { var lineCoords = axisInfo.axisLineCoords; axisInfo.labelsMesh.geometry; for (var i = 0; i < coords.length; i++) { coords[i].setArray(lineCoords[i]); coords[i].w = 1; coords[i].transformMat4(axisInfo.rootNode.worldTransform).transformMat4(camera2.viewMatrix).transformMat4(camera2.projectionMatrix); coords[i].x /= coords[i].w; coords[i].y /= coords[i].w; } var dx = coords[1].x - coords[0].x; var dy = coords[1].y - coords[0].y; var cx = (coords[1].x + coords[0].x) / 2; var cy = (coords[1].y + coords[0].y) / 2; var textAlign; var verticalAlign; if (Math.abs(dy / dx) < 0.5) { textAlign = "center"; verticalAlign = cy > center.y ? "bottom" : "top"; } else { verticalAlign = "middle"; textAlign = cx > center.x ? "left" : "right"; } axisInfo.setSpriteAlign(textAlign, verticalAlign, this._api); }, this); }, _doShowAxisPointer: function() { if (!this._axisPointerLineMesh.invisible) { return; } this._axisPointerLineMesh.invisible = false; this._axisPointerLabelsMesh.invisible = false; this._api.getZr().refresh(); }, _doHideAxisPointer: function() { if (this._axisPointerLineMesh.invisible) { return; } this._axisPointerLineMesh.invisible = true; this._axisPointerLabelsMesh.invisible = true; this._api.getZr().refresh(); }, /** * @private updateAxisPointer. */ _updateAxisPointer: function(data) { var cartesian = this._model.coordinateSystem; var point = cartesian.dataToPoint(data); var axisPointerLineMesh = this._axisPointerLineMesh; var linesGeo = axisPointerLineMesh.geometry; var axisPointerParentModel = this._model.getModel("axisPointer"); var dpr = this._api.getDevicePixelRatio(); linesGeo.convertToDynamicArray(true); function ifShowAxisPointer(axis2) { return retrieve.firstNotNull(axis2.model.get("axisPointer.show"), axisPointerParentModel.get("show")); } function getAxisColorAndLineWidth(axis2) { var axisPointerModel = axis2.model.getModel("axisPointer", axisPointerParentModel); var lineStyleModel = axisPointerModel.getModel("lineStyle"); var color = graphicGL.parseColor(lineStyleModel.get("color")); var lineWidth = firstNotNull(lineStyleModel.get("width"), 1); var opacity = firstNotNull(lineStyleModel.get("opacity"), 1); color[3] *= opacity; return { color, lineWidth }; } for (var k = 0; k < this._faces.length; k++) { var face = this._faces[k]; if (face.rootNode.invisible) { continue; } var faceInfo = face.faceInfo; var otherCoord = faceInfo[3] < 0 ? cartesian.getAxis(faceInfo[2]).getExtentMin() : cartesian.getAxis(faceInfo[2]).getExtentMax(); var otherDimIdx = dimIndicesMap[faceInfo[2]]; for (var i = 0; i < 2; i++) { var dim = faceInfo[i]; var faceOtherDim = faceInfo[1 - i]; var axis = cartesian.getAxis(dim); var faceOtherAxis = cartesian.getAxis(faceOtherDim); if (!ifShowAxisPointer(axis)) { continue; } var p02 = [0, 0, 0]; var p12 = [0, 0, 0]; var dimIdx = dimIndicesMap[dim]; var faceOtherDimIdx = dimIndicesMap[faceOtherDim]; p02[dimIdx] = p12[dimIdx] = point[dimIdx]; p02[otherDimIdx] = p12[otherDimIdx] = otherCoord; p02[faceOtherDimIdx] = faceOtherAxis.getExtentMin(); p12[faceOtherDimIdx] = faceOtherAxis.getExtentMax(); var colorAndLineWidth = getAxisColorAndLineWidth(axis); linesGeo.addLine(p02, p12, colorAndLineWidth.color, colorAndLineWidth.lineWidth * dpr); } if (ifShowAxisPointer(cartesian.getAxis(faceInfo[2]))) { var p02 = point.slice(); var p12 = point.slice(); p12[otherDimIdx] = otherCoord; var colorAndLineWidth = getAxisColorAndLineWidth(cartesian.getAxis(faceInfo[2])); linesGeo.addLine(p02, p12, colorAndLineWidth.color, colorAndLineWidth.lineWidth * dpr); } } linesGeo.convertToTypedArray(); this._updateAxisPointerLabelsMesh(data); this._api.getZr().refresh(); }, _updateAxisPointerLabelsMesh: function(data) { var grid3dModel = this._model; var axisPointerLabelsMesh = this._axisPointerLabelsMesh; var axisPointerLabelsSurface = this._axisPointerLabelsSurface; var cartesian = grid3dModel.coordinateSystem; var axisPointerParentModel = grid3dModel.getModel("axisPointer"); axisPointerLabelsMesh.geometry.convertToDynamicArray(true); axisPointerLabelsSurface.clear(); var otherDim2 = { x: "y", y: "x", z: "y" }; this._axes.forEach(function(axisInfo, idx) { var axis = cartesian.getAxis(axisInfo.dim); var axisModel = axis.model; var axisPointerModel = axisModel.getModel("axisPointer", axisPointerParentModel); var labelModel = axisPointerModel.getModel("label"); var lineColor = axisPointerModel.get("lineStyle.color"); if (!labelModel.get("show") || !axisPointerModel.get("show")) { return; } var val = data[idx]; var formatter = labelModel.get("formatter"); var text = axis.scale.getLabel({ value: val }); if (formatter != null) { text = formatter(text, data); } else { if (axis.scale.type === "interval" || axis.scale.type === "log") { var precision = getPrecisionSafe(axis.scale.getTicks()[0]); text = val.toFixed(precision + 2); } } var labelColor = labelModel.get("color"); var textEl = new ZRText({ style: createTextStyle(labelModel, { text, fill: labelColor || lineColor, align: "left", verticalAlign: "top" }) }); var coords = axisPointerLabelsSurface.add(textEl); var rect = textEl.getBoundingRect(); var dpr = this._api.getDevicePixelRatio(); var pos = axisInfo.rootNode.position.toArray(); var otherIdx = dimIndicesMap[otherDim2[axisInfo.dim]]; pos[otherIdx] += (axisInfo.flipped ? -1 : 1) * labelModel.get("margin"); pos[dimIndicesMap[axisInfo.dim]] = axis.dataToCoord(data[idx]); axisPointerLabelsMesh.geometry.addSprite(pos, [rect.width * dpr, rect.height * dpr], coords, axisInfo.textAlign, axisInfo.textVerticalAlign); }, this); axisPointerLabelsSurface.getZr().refreshImmediately(); axisPointerLabelsMesh.material.set("uvScale", axisPointerLabelsSurface.getCoordsScale()); axisPointerLabelsMesh.geometry.convertToTypedArray(); }, dispose: function() { this.groupGL.removeAll(); this._control.dispose(); this._axisLabelSurface.dispose(); this._axisPointerLabelsSurface.dispose(); } }); function Cartesian3D(name) { Cartesian.call(this, name); this.type = "cartesian3D"; this.dimensions = ["x", "y", "z"]; this.size = [0, 0, 0]; } Cartesian3D.prototype = { constructor: Cartesian3D, model: null, containPoint: function(point) { return this.getAxis("x").contain(point[0]) && this.getAxis("y").contain(point[2]) && this.getAxis("z").contain(point[1]); }, containData: function(data) { return this.getAxis("x").containData(data[0]) && this.getAxis("y").containData(data[1]) && this.getAxis("z").containData(data[2]); }, dataToPoint: function(data, out, clamp2) { out = out || []; out[0] = this.getAxis("x").dataToCoord(data[0], clamp2); out[2] = this.getAxis("y").dataToCoord(data[1], clamp2); out[1] = this.getAxis("z").dataToCoord(data[2], clamp2); return out; }, pointToData: function(point, out, clamp2) { out = out || []; out[0] = this.getAxis("x").coordToData(point[0], clamp2); out[1] = this.getAxis("y").coordToData(point[2], clamp2); out[2] = this.getAxis("z").coordToData(point[1], clamp2); return out; } }; inherits(Cartesian3D, Cartesian); function Axis3D(dim, scale, extent) { Axis.call(this, dim, scale, extent); } Axis3D.prototype = { constructor: Axis3D, getExtentMin: function() { var extent = this._extent; return Math.min(extent[0], extent[1]); }, getExtentMax: function() { var extent = this._extent; return Math.max(extent[0], extent[1]); }, calculateCategoryInterval: function() { return Math.floor(this.scale.count() / 8); } }; inherits(Axis3D, Axis); var TexturePool = function() { this._pool = {}; this._allocatedTextures = []; }; TexturePool.prototype = { constructor: TexturePool, get: function(parameters) { var key = generateKey(parameters); if (!this._pool.hasOwnProperty(key)) { this._pool[key] = []; } var list = this._pool[key]; if (!list.length) { var texture = new Texture2D(parameters); this._allocatedTextures.push(texture); return texture; } return list.pop(); }, put: function(texture) { var key = generateKey(texture); if (!this._pool.hasOwnProperty(key)) { this._pool[key] = []; } var list = this._pool[key]; list.push(texture); }, clear: function(renderer) { for (var i = 0; i < this._allocatedTextures.length; i++) { this._allocatedTextures[i].dispose(renderer); } this._pool = {}; this._allocatedTextures = []; } }; var defaultParams = { width: 512, height: 512, type: glenum.UNSIGNED_BYTE, format: glenum.RGBA, wrapS: glenum.CLAMP_TO_EDGE, wrapT: glenum.CLAMP_TO_EDGE, minFilter: glenum.LINEAR_MIPMAP_LINEAR, magFilter: glenum.LINEAR, useMipmap: true, anisotropic: 1, flipY: true, unpackAlignment: 4, premultiplyAlpha: false }; var defaultParamPropList = Object.keys(defaultParams); function generateKey(parameters) { util.defaultsWithPropList(parameters, defaultParams, defaultParamPropList); fallBack(parameters); var key = ""; for (var i = 0; i < defaultParamPropList.length; i++) { var name = defaultParamPropList[i]; var chunk = parameters[name].toString(); key += chunk; } return key; } function fallBack(target) { var IPOT = isPowerOfTwo(target.width, target.height); if (target.format === glenum.DEPTH_COMPONENT) { target.useMipmap = false; } if (!IPOT || !target.useMipmap) { if (target.minFilter == glenum.NEAREST_MIPMAP_NEAREST || target.minFilter == glenum.NEAREST_MIPMAP_LINEAR) { target.minFilter = glenum.NEAREST; } else if (target.minFilter == glenum.LINEAR_MIPMAP_LINEAR || target.minFilter == glenum.LINEAR_MIPMAP_NEAREST) { target.minFilter = glenum.LINEAR; } } if (!IPOT) { target.wrapS = glenum.CLAMP_TO_EDGE; target.wrapT = glenum.CLAMP_TO_EDGE; } } function isPowerOfTwo(width, height) { return (width & width - 1) === 0 && (height & height - 1) === 0; } const shadowmapEssl = "@export clay.sm.depth.vertex\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nattribute vec3 position : POSITION;\nattribute vec2 texcoord : TEXCOORD_0;\nuniform vec2 uvRepeat = vec2(1.0, 1.0);\nuniform vec2 uvOffset = vec2(0.0, 0.0);\n@import clay.chunk.skinning_header\n@import clay.chunk.instancing_header\nvarying vec4 v_ViewPosition;\nvarying vec2 v_Texcoord;\nvoid main(){\n vec4 P = vec4(position, 1.0);\n#ifdef SKINNING\n @import clay.chunk.skin_matrix\n P = skinMatrixWS * P;\n#endif\n#ifdef INSTANCING\n @import clay.chunk.instancing_matrix\n P = instanceMat * P;\n#endif\n v_ViewPosition = worldViewProjection * P;\n gl_Position = v_ViewPosition;\n v_Texcoord = texcoord * uvRepeat + uvOffset;\n}\n@end\n@export clay.sm.depth.fragment\nvarying vec4 v_ViewPosition;\nvarying vec2 v_Texcoord;\nuniform float bias : 0.001;\nuniform float slopeScale : 1.0;\nuniform sampler2D alphaMap;\nuniform float alphaCutoff: 0.0;\n@import clay.util.encode_float\nvoid main(){\n float depth = v_ViewPosition.z / v_ViewPosition.w;\n if (alphaCutoff > 0.0) {\n if (texture2D(alphaMap, v_Texcoord).a <= alphaCutoff) {\n discard;\n }\n }\n#ifdef USE_VSM\n depth = depth * 0.5 + 0.5;\n float moment1 = depth;\n float moment2 = depth * depth;\n #ifdef SUPPORT_STANDARD_DERIVATIVES\n float dx = dFdx(depth);\n float dy = dFdy(depth);\n moment2 += 0.25*(dx*dx+dy*dy);\n #endif\n gl_FragColor = vec4(moment1, moment2, 0.0, 1.0);\n#else\n #ifdef SUPPORT_STANDARD_DERIVATIVES\n float dx = dFdx(depth);\n float dy = dFdy(depth);\n depth += sqrt(dx*dx + dy*dy) * slopeScale + bias;\n #else\n depth += bias;\n #endif\n gl_FragColor = encodeFloat(depth * 0.5 + 0.5);\n#endif\n}\n@end\n@export clay.sm.debug_depth\nuniform sampler2D depthMap;\nvarying vec2 v_Texcoord;\n@import clay.util.decode_float\nvoid main() {\n vec4 tex = texture2D(depthMap, v_Texcoord);\n#ifdef USE_VSM\n gl_FragColor = vec4(tex.rgb, 1.0);\n#else\n float depth = decodeFloat(tex);\n gl_FragColor = vec4(depth, depth, depth, 1.0);\n#endif\n}\n@end\n@export clay.sm.distance.vertex\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform mat4 world : WORLD;\nattribute vec3 position : POSITION;\n@import clay.chunk.skinning_header\nvarying vec3 v_WorldPosition;\nvoid main (){\n vec4 P = vec4(position, 1.0);\n#ifdef SKINNING\n @import clay.chunk.skin_matrix\n P = skinMatrixWS * P;\n#endif\n#ifdef INSTANCING\n @import clay.chunk.instancing_matrix\n P = instanceMat * P;\n#endif\n gl_Position = worldViewProjection * P;\n v_WorldPosition = (world * P).xyz;\n}\n@end\n@export clay.sm.distance.fragment\nuniform vec3 lightPosition;\nuniform float range : 100;\nvarying vec3 v_WorldPosition;\n@import clay.util.encode_float\nvoid main(){\n float dist = distance(lightPosition, v_WorldPosition);\n#ifdef USE_VSM\n gl_FragColor = vec4(dist, dist * dist, 0.0, 0.0);\n#else\n dist = dist / range;\n gl_FragColor = encodeFloat(dist);\n#endif\n}\n@end\n@export clay.plugin.shadow_map_common\n@import clay.util.decode_float\nfloat tapShadowMap(sampler2D map, vec2 uv, float z){\n vec4 tex = texture2D(map, uv);\n return step(z, decodeFloat(tex) * 2.0 - 1.0);\n}\nfloat pcf(sampler2D map, vec2 uv, float z, float textureSize, vec2 scale) {\n float shadowContrib = tapShadowMap(map, uv, z);\n vec2 offset = vec2(1.0 / textureSize) * scale;\n#ifdef PCF_KERNEL_SIZE\n for (int _idx_ = 0; _idx_ < PCF_KERNEL_SIZE; _idx_++) {{\n shadowContrib += tapShadowMap(map, uv + offset * pcfKernel[_idx_], z);\n }}\n return shadowContrib / float(PCF_KERNEL_SIZE + 1);\n#else\n shadowContrib += tapShadowMap(map, uv+vec2(offset.x, 0.0), z);\n shadowContrib += tapShadowMap(map, uv+vec2(offset.x, offset.y), z);\n shadowContrib += tapShadowMap(map, uv+vec2(-offset.x, offset.y), z);\n shadowContrib += tapShadowMap(map, uv+vec2(0.0, offset.y), z);\n shadowContrib += tapShadowMap(map, uv+vec2(-offset.x, 0.0), z);\n shadowContrib += tapShadowMap(map, uv+vec2(-offset.x, -offset.y), z);\n shadowContrib += tapShadowMap(map, uv+vec2(offset.x, -offset.y), z);\n shadowContrib += tapShadowMap(map, uv+vec2(0.0, -offset.y), z);\n return shadowContrib / 9.0;\n#endif\n}\nfloat pcf(sampler2D map, vec2 uv, float z, float textureSize) {\n return pcf(map, uv, z, textureSize, vec2(1.0));\n}\nfloat chebyshevUpperBound(vec2 moments, float z){\n float p = 0.0;\n z = z * 0.5 + 0.5;\n if (z <= moments.x) {\n p = 1.0;\n }\n float variance = moments.y - moments.x * moments.x;\n variance = max(variance, 0.0000001);\n float mD = moments.x - z;\n float pMax = variance / (variance + mD * mD);\n pMax = clamp((pMax-0.4)/(1.0-0.4), 0.0, 1.0);\n return max(p, pMax);\n}\nfloat computeShadowContrib(\n sampler2D map, mat4 lightVPM, vec3 position, float textureSize, vec2 scale, vec2 offset\n) {\n vec4 posInLightSpace = lightVPM * vec4(position, 1.0);\n posInLightSpace.xyz /= posInLightSpace.w;\n float z = posInLightSpace.z;\n if(all(greaterThan(posInLightSpace.xyz, vec3(-0.99, -0.99, -1.0))) &&\n all(lessThan(posInLightSpace.xyz, vec3(0.99, 0.99, 1.0)))){\n vec2 uv = (posInLightSpace.xy+1.0) / 2.0;\n #ifdef USE_VSM\n vec2 moments = texture2D(map, uv * scale + offset).xy;\n return chebyshevUpperBound(moments, z);\n #else\n return pcf(map, uv * scale + offset, z, textureSize, scale);\n #endif\n }\n return 1.0;\n}\nfloat computeShadowContrib(sampler2D map, mat4 lightVPM, vec3 position, float textureSize) {\n return computeShadowContrib(map, lightVPM, position, textureSize, vec2(1.0), vec2(0.0));\n}\nfloat computeShadowContribOmni(samplerCube map, vec3 direction, float range)\n{\n float dist = length(direction);\n vec4 shadowTex = textureCube(map, direction);\n#ifdef USE_VSM\n vec2 moments = shadowTex.xy;\n float variance = moments.y - moments.x * moments.x;\n float mD = moments.x - dist;\n float p = variance / (variance + mD * mD);\n if(moments.x + 0.001 < dist){\n return clamp(p, 0.0, 1.0);\n }else{\n return 1.0;\n }\n#else\n return step(dist, (decodeFloat(shadowTex) + 0.0002) * range);\n#endif\n}\n@end\n@export clay.plugin.compute_shadow_map\n#if defined(SPOT_LIGHT_SHADOWMAP_COUNT) || defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT) || defined(POINT_LIGHT_SHADOWMAP_COUNT)\n#ifdef SPOT_LIGHT_SHADOWMAP_COUNT\nuniform sampler2D spotLightShadowMaps[SPOT_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\nuniform mat4 spotLightMatrices[SPOT_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\nuniform float spotLightShadowMapSizes[SPOT_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\n#endif\n#ifdef DIRECTIONAL_LIGHT_SHADOWMAP_COUNT\n#if defined(SHADOW_CASCADE)\nuniform sampler2D directionalLightShadowMaps[1]:unconfigurable;\nuniform mat4 directionalLightMatrices[SHADOW_CASCADE]:unconfigurable;\nuniform float directionalLightShadowMapSizes[1]:unconfigurable;\nuniform float shadowCascadeClipsNear[SHADOW_CASCADE]:unconfigurable;\nuniform float shadowCascadeClipsFar[SHADOW_CASCADE]:unconfigurable;\n#else\nuniform sampler2D directionalLightShadowMaps[DIRECTIONAL_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\nuniform mat4 directionalLightMatrices[DIRECTIONAL_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\nuniform float directionalLightShadowMapSizes[DIRECTIONAL_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\n#endif\n#endif\n#ifdef POINT_LIGHT_SHADOWMAP_COUNT\nuniform samplerCube pointLightShadowMaps[POINT_LIGHT_SHADOWMAP_COUNT]:unconfigurable;\n#endif\nuniform bool shadowEnabled : true;\n#ifdef PCF_KERNEL_SIZE\nuniform vec2 pcfKernel[PCF_KERNEL_SIZE];\n#endif\n@import clay.plugin.shadow_map_common\n#if defined(SPOT_LIGHT_SHADOWMAP_COUNT)\nvoid computeShadowOfSpotLights(vec3 position, inout float shadowContribs[SPOT_LIGHT_COUNT] ) {\n float shadowContrib;\n for(int _idx_ = 0; _idx_ < SPOT_LIGHT_SHADOWMAP_COUNT; _idx_++) {{\n shadowContrib = computeShadowContrib(\n spotLightShadowMaps[_idx_], spotLightMatrices[_idx_], position,\n spotLightShadowMapSizes[_idx_]\n );\n shadowContribs[_idx_] = shadowContrib;\n }}\n for(int _idx_ = SPOT_LIGHT_SHADOWMAP_COUNT; _idx_ < SPOT_LIGHT_COUNT; _idx_++){{\n shadowContribs[_idx_] = 1.0;\n }}\n}\n#endif\n#if defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n#ifdef SHADOW_CASCADE\nvoid computeShadowOfDirectionalLights(vec3 position, inout float shadowContribs[DIRECTIONAL_LIGHT_COUNT]){\n float depth = (2.0 * gl_FragCoord.z - gl_DepthRange.near - gl_DepthRange.far)\n / (gl_DepthRange.far - gl_DepthRange.near);\n float shadowContrib;\n shadowContribs[0] = 1.0;\n for (int _idx_ = 0; _idx_ < SHADOW_CASCADE; _idx_++) {{\n if (\n depth >= shadowCascadeClipsNear[_idx_] &&\n depth <= shadowCascadeClipsFar[_idx_]\n ) {\n shadowContrib = computeShadowContrib(\n directionalLightShadowMaps[0], directionalLightMatrices[_idx_], position,\n directionalLightShadowMapSizes[0],\n vec2(1.0 / float(SHADOW_CASCADE), 1.0),\n vec2(float(_idx_) / float(SHADOW_CASCADE), 0.0)\n );\n shadowContribs[0] = shadowContrib;\n }\n }}\n for(int _idx_ = DIRECTIONAL_LIGHT_SHADOWMAP_COUNT; _idx_ < DIRECTIONAL_LIGHT_COUNT; _idx_++) {{\n shadowContribs[_idx_] = 1.0;\n }}\n}\n#else\nvoid computeShadowOfDirectionalLights(vec3 position, inout float shadowContribs[DIRECTIONAL_LIGHT_COUNT]){\n float shadowContrib;\n for(int _idx_ = 0; _idx_ < DIRECTIONAL_LIGHT_SHADOWMAP_COUNT; _idx_++) {{\n shadowContrib = computeShadowContrib(\n directionalLightShadowMaps[_idx_], directionalLightMatrices[_idx_], position,\n directionalLightShadowMapSizes[_idx_]\n );\n shadowContribs[_idx_] = shadowContrib;\n }}\n for(int _idx_ = DIRECTIONAL_LIGHT_SHADOWMAP_COUNT; _idx_ < DIRECTIONAL_LIGHT_COUNT; _idx_++) {{\n shadowContribs[_idx_] = 1.0;\n }}\n}\n#endif\n#endif\n#if defined(POINT_LIGHT_SHADOWMAP_COUNT)\nvoid computeShadowOfPointLights(vec3 position, inout float shadowContribs[POINT_LIGHT_COUNT] ){\n vec3 lightPosition;\n vec3 direction;\n for(int _idx_ = 0; _idx_ < POINT_LIGHT_SHADOWMAP_COUNT; _idx_++) {{\n lightPosition = pointLightPosition[_idx_];\n direction = position - lightPosition;\n shadowContribs[_idx_] = computeShadowContribOmni(pointLightShadowMaps[_idx_], direction, pointLightRange[_idx_]);\n }}\n for(int _idx_ = POINT_LIGHT_SHADOWMAP_COUNT; _idx_ < POINT_LIGHT_COUNT; _idx_++) {{\n shadowContribs[_idx_] = 1.0;\n }}\n}\n#endif\n#endif\n@end"; var targets = ["px", "nx", "py", "ny", "pz", "nz"]; Shader["import"](shadowmapEssl); function getDepthMaterialUniform(renderable, depthMaterial, symbol) { if (symbol === "alphaMap") { return renderable.material.get("diffuseMap"); } else if (symbol === "alphaCutoff") { if (renderable.material.isDefined("fragment", "ALPHA_TEST") && renderable.material.get("diffuseMap")) { var alphaCutoff = renderable.material.get("alphaCutoff"); return alphaCutoff || 0; } return 0; } else if (symbol === "uvRepeat") { return renderable.material.get("uvRepeat"); } else if (symbol === "uvOffset") { return renderable.material.get("uvOffset"); } else { return depthMaterial.get(symbol); } } function isDepthMaterialChanged(renderable, prevRenderable) { var matA = renderable.material; var matB = prevRenderable.material; return matA.get("diffuseMap") !== matB.get("diffuseMap") || (matA.get("alphaCutoff") || 0) !== (matB.get("alphaCutoff") || 0); } var ShadowMapPass = Base.extend(function() { return ( /** @lends clay.prePass.ShadowMap# */ { /** * Soft shadow technique. * Can be {@link clay.prePass.ShadowMap.PCF} or {@link clay.prePass.ShadowMap.VSM} * @type {number} */ softShadow: ShadowMapPass.PCF, /** * Soft shadow blur size * @type {number} */ shadowBlur: 1, lightFrustumBias: "auto", kernelPCF: new Float32Array([ 1, 0, 1, 1, -1, 1, 0, 1, -1, 0, -1, -1, 1, -1, 0, -1 ]), precision: "highp", _lastRenderNotCastShadow: false, _frameBuffer: new FrameBuffer(), _textures: {}, _shadowMapNumber: { "POINT_LIGHT": 0, "DIRECTIONAL_LIGHT": 0, "SPOT_LIGHT": 0 }, _depthMaterials: {}, _distanceMaterials: {}, _receivers: [], _lightsCastShadow: [], _lightCameras: {}, _lightMaterials: {}, _texturePool: new TexturePool() } ); }, function() { this._gaussianPassH = new Pass({ fragment: Shader.source("clay.compositor.gaussian_blur") }); this._gaussianPassV = new Pass({ fragment: Shader.source("clay.compositor.gaussian_blur") }); this._gaussianPassH.setUniform("blurSize", this.shadowBlur); this._gaussianPassH.setUniform("blurDir", 0); this._gaussianPassV.setUniform("blurSize", this.shadowBlur); this._gaussianPassV.setUniform("blurDir", 1); this._outputDepthPass = new Pass({ fragment: Shader.source("clay.sm.debug_depth") }); }, { /** * Render scene to shadow textures * @param {clay.Renderer} renderer * @param {clay.Scene} scene * @param {clay.Camera} sceneCamera * @param {boolean} [notUpdateScene=false] * @memberOf clay.prePass.ShadowMap.prototype */ render: function(renderer, scene, sceneCamera, notUpdateScene) { if (!sceneCamera) { sceneCamera = scene.getMainCamera(); } this.trigger("beforerender", this, renderer, scene, sceneCamera); this._renderShadowPass(renderer, scene, sceneCamera, notUpdateScene); this.trigger("afterrender", this, renderer, scene, sceneCamera); }, /** * Debug rendering of shadow textures * @param {clay.Renderer} renderer * @param {number} size * @memberOf clay.prePass.ShadowMap.prototype */ renderDebug: function(renderer, size) { renderer.saveClear(); var viewport = renderer.viewport; var x = 0, y = 0; var width = size || viewport.width / 4; var height = width; if (this.softShadow === ShadowMapPass.VSM) { this._outputDepthPass.material.define("fragment", "USE_VSM"); } else { this._outputDepthPass.material.undefine("fragment", "USE_VSM"); } for (var name in this._textures) { var texture = this._textures[name]; renderer.setViewport(x, y, width * texture.width / texture.height, height); this._outputDepthPass.setUniform("depthMap", texture); this._outputDepthPass.render(renderer); x += width * texture.width / texture.height; } renderer.setViewport(viewport); renderer.restoreClear(); }, _updateReceivers: function(renderer, mesh2) { if (mesh2.receiveShadow) { this._receivers.push(mesh2); mesh2.material.set("shadowEnabled", 1); mesh2.material.set("pcfKernel", this.kernelPCF); } else { mesh2.material.set("shadowEnabled", 0); } if (this.softShadow === ShadowMapPass.VSM) { mesh2.material.define("fragment", "USE_VSM"); mesh2.material.undefine("fragment", "PCF_KERNEL_SIZE"); } else { mesh2.material.undefine("fragment", "USE_VSM"); var kernelPCF = this.kernelPCF; if (kernelPCF && kernelPCF.length) { mesh2.material.define("fragment", "PCF_KERNEL_SIZE", kernelPCF.length / 2); } else { mesh2.material.undefine("fragment", "PCF_KERNEL_SIZE"); } } }, _update: function(renderer, scene) { var self2 = this; scene.traverse(function(renderable) { if (renderable.isRenderable()) { self2._updateReceivers(renderer, renderable); } }); for (var i = 0; i < scene.lights.length; i++) { var light = scene.lights[i]; if (light.castShadow && !light.invisible) { this._lightsCastShadow.push(light); } } }, _renderShadowPass: function(renderer, scene, sceneCamera, notUpdateScene) { for (var name in this._shadowMapNumber) { this._shadowMapNumber[name] = 0; } this._lightsCastShadow.length = 0; this._receivers.length = 0; var _gl = renderer.gl; if (!notUpdateScene) { scene.update(); } if (sceneCamera) { sceneCamera.update(); } scene.updateLights(); this._update(renderer, scene); if (!this._lightsCastShadow.length && this._lastRenderNotCastShadow) { return; } this._lastRenderNotCastShadow = this._lightsCastShadow === 0; _gl.enable(_gl.DEPTH_TEST); _gl.depthMask(true); _gl.disable(_gl.BLEND); _gl.clearColor(1, 1, 1, 1); var spotLightShadowMaps = []; var spotLightMatrices = []; var directionalLightShadowMaps = []; var directionalLightMatrices = []; var shadowCascadeClips = []; var pointLightShadowMaps = []; var dirLightHasCascade; for (var i = 0; i < this._lightsCastShadow.length; i++) { var light = this._lightsCastShadow[i]; if (light.type === "DIRECTIONAL_LIGHT") { if (dirLightHasCascade) { console.warn("Only one direectional light supported with shadow cascade"); continue; } if (light.shadowCascade > 4) { console.warn("Support at most 4 cascade"); continue; } if (light.shadowCascade > 1) { dirLightHasCascade = light; } this.renderDirectionalLightShadow( renderer, scene, sceneCamera, light, shadowCascadeClips, directionalLightMatrices, directionalLightShadowMaps ); } else if (light.type === "SPOT_LIGHT") { this.renderSpotLightShadow( renderer, scene, light, spotLightMatrices, spotLightShadowMaps ); } else if (light.type === "POINT_LIGHT") { this.renderPointLightShadow( renderer, scene, light, pointLightShadowMaps ); } this._shadowMapNumber[light.type]++; } for (var lightType in this._shadowMapNumber) { var number = this._shadowMapNumber[lightType]; var key = lightType + "_SHADOWMAP_COUNT"; for (var i = 0; i < this._receivers.length; i++) { var mesh2 = this._receivers[i]; var material = mesh2.material; if (material.fragmentDefines[key] !== number) { if (number > 0) { material.define("fragment", key, number); } else if (material.isDefined("fragment", key)) { material.undefine("fragment", key); } } } } for (var i = 0; i < this._receivers.length; i++) { var mesh2 = this._receivers[i]; var material = mesh2.material; if (dirLightHasCascade) { material.define("fragment", "SHADOW_CASCADE", dirLightHasCascade.shadowCascade); } else { material.undefine("fragment", "SHADOW_CASCADE"); } } var shadowUniforms = scene.shadowUniforms; function getSize(texture) { return texture.height; } if (directionalLightShadowMaps.length > 0) { var directionalLightShadowMapSizes = directionalLightShadowMaps.map(getSize); shadowUniforms.directionalLightShadowMaps = { value: directionalLightShadowMaps, type: "tv" }; shadowUniforms.directionalLightMatrices = { value: directionalLightMatrices, type: "m4v" }; shadowUniforms.directionalLightShadowMapSizes = { value: directionalLightShadowMapSizes, type: "1fv" }; if (dirLightHasCascade) { var shadowCascadeClipsNear = shadowCascadeClips.slice(); var shadowCascadeClipsFar = shadowCascadeClips.slice(); shadowCascadeClipsNear.pop(); shadowCascadeClipsFar.shift(); shadowCascadeClipsNear.reverse(); shadowCascadeClipsFar.reverse(); directionalLightMatrices.reverse(); shadowUniforms.shadowCascadeClipsNear = { value: shadowCascadeClipsNear, type: "1fv" }; shadowUniforms.shadowCascadeClipsFar = { value: shadowCascadeClipsFar, type: "1fv" }; } } if (spotLightShadowMaps.length > 0) { var spotLightShadowMapSizes = spotLightShadowMaps.map(getSize); var shadowUniforms = scene.shadowUniforms; shadowUniforms.spotLightShadowMaps = { value: spotLightShadowMaps, type: "tv" }; shadowUniforms.spotLightMatrices = { value: spotLightMatrices, type: "m4v" }; shadowUniforms.spotLightShadowMapSizes = { value: spotLightShadowMapSizes, type: "1fv" }; } if (pointLightShadowMaps.length > 0) { shadowUniforms.pointLightShadowMaps = { value: pointLightShadowMaps, type: "tv" }; } }, renderDirectionalLightShadow: function() { var splitFrustum = new Frustum(); var splitProjMatrix = new Matrix4(); var cropBBox = new BoundingBox(); var cropMatrix = new Matrix4(); var lightViewMatrix = new Matrix4(); var lightViewProjMatrix = new Matrix4(); var lightProjMatrix = new Matrix4(); return function(renderer, scene, sceneCamera, light, shadowCascadeClips, directionalLightMatrices, directionalLightShadowMaps) { var defaultShadowMaterial = this._getDepthMaterial(light); var passConfig = { getMaterial: function(renderable) { return renderable.shadowDepthMaterial || defaultShadowMaterial; }, isMaterialChanged: isDepthMaterialChanged, getUniform: getDepthMaterialUniform, ifRender: function(renderable) { return renderable.castShadow; }, sortCompare: Renderer.opaqueSortCompare }; if (!scene.viewBoundingBoxLastFrame.isFinite()) { var boundingBox = scene.getBoundingBox(); scene.viewBoundingBoxLastFrame.copy(boundingBox).applyTransform(sceneCamera.viewMatrix); } var clippedFar = Math.min(-scene.viewBoundingBoxLastFrame.min.z, sceneCamera.far); var clippedNear = Math.max(-scene.viewBoundingBoxLastFrame.max.z, sceneCamera.near); var lightCamera = this._getDirectionalLightCamera(light, scene, sceneCamera); var lvpMat4Arr = lightViewProjMatrix.array; lightProjMatrix.copy(lightCamera.projectionMatrix); mat4$2.invert(lightViewMatrix.array, lightCamera.worldTransform.array); mat4$2.multiply(lightViewMatrix.array, lightViewMatrix.array, sceneCamera.worldTransform.array); mat4$2.multiply(lvpMat4Arr, lightProjMatrix.array, lightViewMatrix.array); var clipPlanes = []; var isPerspective = sceneCamera instanceof Perspective; var scaleZ = (sceneCamera.near + sceneCamera.far) / (sceneCamera.near - sceneCamera.far); var offsetZ = 2 * sceneCamera.near * sceneCamera.far / (sceneCamera.near - sceneCamera.far); for (var i = 0; i <= light.shadowCascade; i++) { var clog = clippedNear * Math.pow(clippedFar / clippedNear, i / light.shadowCascade); var cuni = clippedNear + (clippedFar - clippedNear) * i / light.shadowCascade; var c = clog * light.cascadeSplitLogFactor + cuni * (1 - light.cascadeSplitLogFactor); clipPlanes.push(c); shadowCascadeClips.push(-(-c * scaleZ + offsetZ) / -c); } var texture = this._getTexture(light, light.shadowCascade); directionalLightShadowMaps.push(texture); var viewport = renderer.viewport; var _gl = renderer.gl; this._frameBuffer.attach(texture); this._frameBuffer.bind(renderer); _gl.clear(_gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT); for (var i = 0; i < light.shadowCascade; i++) { var nearPlane = clipPlanes[i]; var farPlane = clipPlanes[i + 1]; if (isPerspective) { mat4$2.perspective(splitProjMatrix.array, sceneCamera.fov / 180 * Math.PI, sceneCamera.aspect, nearPlane, farPlane); } else { mat4$2.ortho( splitProjMatrix.array, sceneCamera.left, sceneCamera.right, sceneCamera.bottom, sceneCamera.top, nearPlane, farPlane ); } splitFrustum.setFromProjection(splitProjMatrix); splitFrustum.getTransformedBoundingBox(cropBBox, lightViewMatrix); cropBBox.applyProjection(lightProjMatrix); var _min = cropBBox.min.array; var _max = cropBBox.max.array; _min[0] = Math.max(_min[0], -1); _min[1] = Math.max(_min[1], -1); _max[0] = Math.min(_max[0], 1); _max[1] = Math.min(_max[1], 1); cropMatrix.ortho(_min[0], _max[0], _min[1], _max[1], 1, -1); lightCamera.projectionMatrix.multiplyLeft(cropMatrix); var shadowSize = light.shadowResolution || 512; renderer.setViewport((light.shadowCascade - i - 1) * shadowSize, 0, shadowSize, shadowSize, 1); var renderList = scene.updateRenderList(lightCamera); renderer.renderPass(renderList.opaque, lightCamera, passConfig); if (this.softShadow === ShadowMapPass.VSM) { this._gaussianFilter(renderer, texture, texture.width); } var matrix = new Matrix4(); matrix.copy(lightCamera.viewMatrix).multiplyLeft(lightCamera.projectionMatrix); directionalLightMatrices.push(matrix.array); lightCamera.projectionMatrix.copy(lightProjMatrix); } this._frameBuffer.unbind(renderer); renderer.setViewport(viewport); }; }(), renderSpotLightShadow: function(renderer, scene, light, spotLightMatrices, spotLightShadowMaps) { var texture = this._getTexture(light); var lightCamera = this._getSpotLightCamera(light); var _gl = renderer.gl; this._frameBuffer.attach(texture); this._frameBuffer.bind(renderer); _gl.clear(_gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT); var defaultShadowMaterial = this._getDepthMaterial(light); var passConfig = { getMaterial: function(renderable) { return renderable.shadowDepthMaterial || defaultShadowMaterial; }, isMaterialChanged: isDepthMaterialChanged, getUniform: getDepthMaterialUniform, ifRender: function(renderable) { return renderable.castShadow; }, sortCompare: Renderer.opaqueSortCompare }; var renderList = scene.updateRenderList(lightCamera); renderer.renderPass(renderList.opaque, lightCamera, passConfig); this._frameBuffer.unbind(renderer); if (this.softShadow === ShadowMapPass.VSM) { this._gaussianFilter(renderer, texture, texture.width); } var matrix = new Matrix4(); matrix.copy(lightCamera.worldTransform).invert().multiplyLeft(lightCamera.projectionMatrix); spotLightShadowMaps.push(texture); spotLightMatrices.push(matrix.array); }, renderPointLightShadow: function(renderer, scene, light, pointLightShadowMaps) { var texture = this._getTexture(light); var _gl = renderer.gl; pointLightShadowMaps.push(texture); var defaultShadowMaterial = this._getDepthMaterial(light); var passConfig = { getMaterial: function(renderable) { return renderable.shadowDepthMaterial || defaultShadowMaterial; }, getUniform: getDepthMaterialUniform, sortCompare: Renderer.opaqueSortCompare }; var renderListEachSide = { px: [], py: [], pz: [], nx: [], ny: [], nz: [] }; var bbox = new BoundingBox(); var lightWorldPosition = light.getWorldPosition().array; var lightBBox = new BoundingBox(); var range = light.range; lightBBox.min.setArray(lightWorldPosition); lightBBox.max.setArray(lightWorldPosition); var extent = new Vector3(range, range, range); lightBBox.max.add(extent); lightBBox.min.sub(extent); var targetsNeedRender = { px: false, py: false, pz: false, nx: false, ny: false, nz: false }; scene.traverse(function(renderable) { if (renderable.isRenderable() && renderable.castShadow) { var geometry = renderable.geometry; if (!geometry.boundingBox) { for (var i2 = 0; i2 < targets.length; i2++) { renderListEachSide[targets[i2]].push(renderable); } return; } bbox.transformFrom(geometry.boundingBox, renderable.worldTransform); if (!bbox.intersectBoundingBox(lightBBox)) { return; } bbox.updateVertices(); for (var i2 = 0; i2 < targets.length; i2++) { targetsNeedRender[targets[i2]] = false; } for (var i2 = 0; i2 < 8; i2++) { var vtx = bbox.vertices[i2]; var x = vtx[0] - lightWorldPosition[0]; var y = vtx[1] - lightWorldPosition[1]; var z = vtx[2] - lightWorldPosition[2]; var absx = Math.abs(x); var absy = Math.abs(y); var absz = Math.abs(z); if (absx > absy) { if (absx > absz) { targetsNeedRender[x > 0 ? "px" : "nx"] = true; } else { targetsNeedRender[z > 0 ? "pz" : "nz"] = true; } } else { if (absy > absz) { targetsNeedRender[y > 0 ? "py" : "ny"] = true; } else { targetsNeedRender[z > 0 ? "pz" : "nz"] = true; } } } for (var i2 = 0; i2 < targets.length; i2++) { if (targetsNeedRender[targets[i2]]) { renderListEachSide[targets[i2]].push(renderable); } } } }); for (var i = 0; i < 6; i++) { var target = targets[i]; var camera2 = this._getPointLightCamera(light, target); this._frameBuffer.attach(texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i); this._frameBuffer.bind(renderer); _gl.clear(_gl.COLOR_BUFFER_BIT | _gl.DEPTH_BUFFER_BIT); renderer.renderPass(renderListEachSide[target], camera2, passConfig); } this._frameBuffer.unbind(renderer); }, _getDepthMaterial: function(light) { var shadowMaterial = this._lightMaterials[light.__uid__]; var isPointLight = light.type === "POINT_LIGHT"; if (!shadowMaterial) { var shaderPrefix = isPointLight ? "clay.sm.distance." : "clay.sm.depth."; shadowMaterial = new Material({ precision: this.precision, shader: new Shader(Shader.source(shaderPrefix + "vertex"), Shader.source(shaderPrefix + "fragment")) }); this._lightMaterials[light.__uid__] = shadowMaterial; } if (light.shadowSlopeScale != null) { shadowMaterial.setUniform("slopeScale", light.shadowSlopeScale); } if (light.shadowBias != null) { shadowMaterial.setUniform("bias", light.shadowBias); } if (this.softShadow === ShadowMapPass.VSM) { shadowMaterial.define("fragment", "USE_VSM"); } else { shadowMaterial.undefine("fragment", "USE_VSM"); } if (isPointLight) { shadowMaterial.set("lightPosition", light.getWorldPosition().array); shadowMaterial.set("range", light.range); } return shadowMaterial; }, _gaussianFilter: function(renderer, texture, size) { var parameter = { width: size, height: size, type: Texture.FLOAT }; var tmpTexture = this._texturePool.get(parameter); this._frameBuffer.attach(tmpTexture); this._frameBuffer.bind(renderer); this._gaussianPassH.setUniform("texture", texture); this._gaussianPassH.setUniform("textureWidth", size); this._gaussianPassH.render(renderer); this._frameBuffer.attach(texture); this._gaussianPassV.setUniform("texture", tmpTexture); this._gaussianPassV.setUniform("textureHeight", size); this._gaussianPassV.render(renderer); this._frameBuffer.unbind(renderer); this._texturePool.put(tmpTexture); }, _getTexture: function(light, cascade) { var key = light.__uid__; var texture = this._textures[key]; var resolution = light.shadowResolution || 512; cascade = cascade || 1; if (!texture) { if (light.type === "POINT_LIGHT") { texture = new TextureCube(); } else { texture = new Texture2D(); } texture.width = resolution * cascade; texture.height = resolution; if (this.softShadow === ShadowMapPass.VSM) { texture.type = Texture.FLOAT; texture.anisotropic = 4; } else { texture.minFilter = glenum.NEAREST; texture.magFilter = glenum.NEAREST; texture.useMipmap = false; } this._textures[key] = texture; } return texture; }, _getPointLightCamera: function(light, target) { if (!this._lightCameras.point) { this._lightCameras.point = { px: new Perspective(), nx: new Perspective(), py: new Perspective(), ny: new Perspective(), pz: new Perspective(), nz: new Perspective() }; } var camera2 = this._lightCameras.point[target]; camera2.far = light.range; camera2.fov = 90; camera2.position.set(0, 0, 0); switch (target) { case "px": camera2.lookAt(Vector3.POSITIVE_X, Vector3.NEGATIVE_Y); break; case "nx": camera2.lookAt(Vector3.NEGATIVE_X, Vector3.NEGATIVE_Y); break; case "py": camera2.lookAt(Vector3.POSITIVE_Y, Vector3.POSITIVE_Z); break; case "ny": camera2.lookAt(Vector3.NEGATIVE_Y, Vector3.NEGATIVE_Z); break; case "pz": camera2.lookAt(Vector3.POSITIVE_Z, Vector3.NEGATIVE_Y); break; case "nz": camera2.lookAt(Vector3.NEGATIVE_Z, Vector3.NEGATIVE_Y); break; } light.getWorldPosition(camera2.position); camera2.update(); return camera2; }, _getDirectionalLightCamera: function() { var lightViewMatrix = new Matrix4(); var sceneViewBoundingBox = new BoundingBox(); var lightViewBBox = new BoundingBox(); return function(light, scene, sceneCamera) { if (!this._lightCameras.directional) { this._lightCameras.directional = new Orthographic(); } var camera2 = this._lightCameras.directional; sceneViewBoundingBox.copy(scene.viewBoundingBoxLastFrame); sceneViewBoundingBox.intersection(sceneCamera.frustum.boundingBox); camera2.position.copy(sceneViewBoundingBox.min).add(sceneViewBoundingBox.max).scale(0.5).transformMat4(sceneCamera.worldTransform); camera2.rotation.copy(light.rotation); camera2.scale.copy(light.scale); camera2.updateWorldTransform(); Matrix4.invert(lightViewMatrix, camera2.worldTransform); Matrix4.multiply(lightViewMatrix, lightViewMatrix, sceneCamera.worldTransform); lightViewBBox.copy(sceneViewBoundingBox).applyTransform(lightViewMatrix); var min = lightViewBBox.min.array; var max = lightViewBBox.max.array; camera2.position.set((min[0] + max[0]) / 2, (min[1] + max[1]) / 2, max[2]).transformMat4(camera2.worldTransform); camera2.near = 0; camera2.far = -min[2] + max[2]; if (isNaN(this.lightFrustumBias)) { camera2.far *= 4; } else { camera2.far += this.lightFrustumBias; } camera2.left = min[0]; camera2.right = max[0]; camera2.top = max[1]; camera2.bottom = min[1]; camera2.update(true); return camera2; }; }(), _getSpotLightCamera: function(light) { if (!this._lightCameras.spot) { this._lightCameras.spot = new Perspective(); } var camera2 = this._lightCameras.spot; camera2.fov = light.penumbraAngle * 2; camera2.far = light.range; camera2.worldTransform.copy(light.worldTransform); camera2.updateProjectionMatrix(); mat4$2.invert(camera2.viewMatrix.array, camera2.worldTransform.array); return camera2; }, /** * @param {clay.Renderer|WebGLRenderingContext} [renderer] * @memberOf clay.prePass.ShadowMap.prototype */ // PENDING Renderer or WebGLRenderingContext dispose: function(renderer) { var _gl = renderer.gl || renderer; if (this._frameBuffer) { this._frameBuffer.dispose(_gl); } for (var name in this._textures) { this._textures[name].dispose(_gl); } this._texturePool.clear(renderer.gl); this._depthMaterials = {}; this._distanceMaterials = {}; this._textures = {}; this._lightCameras = {}; this._shadowMapNumber = { "POINT_LIGHT": 0, "DIRECTIONAL_LIGHT": 0, "SPOT_LIGHT": 0 }; this._meshMaterials = {}; for (var i = 0; i < this._receivers.length; i++) { var mesh2 = this._receivers[i]; if (mesh2.material) { var material = mesh2.material; material.undefine("fragment", "POINT_LIGHT_SHADOW_COUNT"); material.undefine("fragment", "DIRECTIONAL_LIGHT_SHADOW_COUNT"); material.undefine("fragment", "AMBIENT_LIGHT_SHADOW_COUNT"); material.set("shadowEnabled", 0); } } this._receivers = []; this._lightsCastShadow = []; } }); ShadowMapPass.VSM = 1; ShadowMapPass.PCF = 2; var CompositorNode = Base.extend( function() { return ( /** @lends clay.compositor.CompositorNode# */ { /** * @type {string} */ name: "", /** * Input links, will be updated by the graph * @example: * inputName: { * node: someNode, * pin: 'xxxx' * } * @type {Object} */ inputLinks: {}, /** * Output links, will be updated by the graph * @example: * outputName: { * node: someNode, * pin: 'xxxx' * } * @type {Object} */ outputLinks: {}, // Save the output texture of previous frame // Will be used when there exist a circular reference _prevOutputTextures: {}, _outputTextures: {}, // Example: { name: 2 } _outputReferences: {}, _rendering: false, // If rendered in this frame _rendered: false, _compositor: null } ); }, /** @lends clay.compositor.CompositorNode.prototype */ { // TODO Remove parameter function callback updateParameter: function(outputName, renderer) { var outputInfo = this.outputs[outputName]; var parameters = outputInfo.parameters; var parametersCopy = outputInfo._parametersCopy; if (!parametersCopy) { parametersCopy = outputInfo._parametersCopy = {}; } if (parameters) { for (var key in parameters) { if (key !== "width" && key !== "height") { parametersCopy[key] = parameters[key]; } } } var width, height; if (parameters.width instanceof Function) { width = parameters.width.call(this, renderer); } else { width = parameters.width; } if (parameters.height instanceof Function) { height = parameters.height.call(this, renderer); } else { height = parameters.height; } if (parametersCopy.width !== width || parametersCopy.height !== height) { if (this._outputTextures[outputName]) { this._outputTextures[outputName].dispose(renderer.gl); } } parametersCopy.width = width; parametersCopy.height = height; return parametersCopy; }, /** * Set parameter * @param {string} name * @param {} value */ setParameter: function(name, value) { }, /** * Get parameter value * @param {string} name * @return {} */ getParameter: function(name) { }, /** * Set parameters * @param {Object} obj */ setParameters: function(obj) { for (var name in obj) { this.setParameter(name, obj[name]); } }, render: function() { }, getOutput: function(renderer, name) { if (name == null) { name = renderer; return this._outputTextures[name]; } var outputInfo = this.outputs[name]; if (!outputInfo) { return; } if (this._rendered) { if (outputInfo.outputLastFrame) { return this._prevOutputTextures[name]; } else { return this._outputTextures[name]; } } else if ( // TODO this._rendering ) { if (!this._prevOutputTextures[name]) { this._prevOutputTextures[name] = this._compositor.allocateTexture(outputInfo.parameters || {}); } return this._prevOutputTextures[name]; } this.render(renderer); return this._outputTextures[name]; }, removeReference: function(outputName) { this._outputReferences[outputName]--; if (this._outputReferences[outputName] === 0) { var outputInfo = this.outputs[outputName]; if (outputInfo.keepLastFrame) { if (this._prevOutputTextures[outputName]) { this._compositor.releaseTexture(this._prevOutputTextures[outputName]); } this._prevOutputTextures[outputName] = this._outputTextures[outputName]; } else { this._compositor.releaseTexture(this._outputTextures[outputName]); } } }, link: function(inputPinName, fromNode, fromPinName) { this.inputLinks[inputPinName] = { node: fromNode, pin: fromPinName }; if (!fromNode.outputLinks[fromPinName]) { fromNode.outputLinks[fromPinName] = []; } fromNode.outputLinks[fromPinName].push({ node: this, pin: inputPinName }); this.pass.material.enableTexture(inputPinName); }, clear: function() { this.inputLinks = {}; this.outputLinks = {}; }, updateReference: function(outputName) { if (!this._rendering) { this._rendering = true; for (var inputName in this.inputLinks) { var link = this.inputLinks[inputName]; link.node.updateReference(link.pin); } this._rendering = false; } if (outputName) { this._outputReferences[outputName]++; } }, beforeFrame: function() { this._rendered = false; for (var name in this.outputLinks) { this._outputReferences[name] = 0; } }, afterFrame: function() { for (var name in this.outputLinks) { if (this._outputReferences[name] > 0) { var outputInfo = this.outputs[name]; if (outputInfo.keepLastFrame) { if (this._prevOutputTextures[name]) { this._compositor.releaseTexture(this._prevOutputTextures[name]); } this._prevOutputTextures[name] = this._outputTextures[name]; } else { this._compositor.releaseTexture(this._outputTextures[name]); } } } } } ); var Graph = Base.extend( function() { return ( /** @lends clay.compositor.Graph# */ { /** * @type {Array.} */ nodes: [] } ); }, /** @lends clay.compositor.Graph.prototype */ { /** * Mark to update */ dirty: function() { this._dirty = true; }, /** * @param {clay.compositor.CompositorNode} node */ addNode: function(node) { if (this.nodes.indexOf(node) >= 0) { return; } this.nodes.push(node); this._dirty = true; }, /** * @param {clay.compositor.CompositorNode|string} node */ removeNode: function(node) { if (typeof node === "string") { node = this.getNodeByName(node); } var idx = this.nodes.indexOf(node); if (idx >= 0) { this.nodes.splice(idx, 1); this._dirty = true; } }, /** * @param {string} name * @return {clay.compositor.CompositorNode} */ getNodeByName: function(name) { for (var i = 0; i < this.nodes.length; i++) { if (this.nodes[i].name === name) { return this.nodes[i]; } } }, /** * Update links of graph */ update: function() { for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].clear(); } for (var i = 0; i < this.nodes.length; i++) { var node = this.nodes[i]; if (!node.inputs) { continue; } for (var inputName in node.inputs) { if (!node.inputs[inputName]) { continue; } if (node.pass && !node.pass.material.isUniformEnabled(inputName)) { console.warn("Pin " + node.name + "." + inputName + " not used."); continue; } var fromPinInfo = node.inputs[inputName]; var fromPin = this.findPin(fromPinInfo); if (fromPin) { node.link(inputName, fromPin.node, fromPin.pin); } else { if (typeof fromPinInfo === "string") { console.warn("Node " + fromPinInfo + " not exist"); } else { console.warn("Pin of " + fromPinInfo.node + "." + fromPinInfo.pin + " not exist"); } } } } }, findPin: function(input) { var node; if (typeof input === "string" || input instanceof CompositorNode) { input = { node: input }; } if (typeof input.node === "string") { for (var i = 0; i < this.nodes.length; i++) { var tmp = this.nodes[i]; if (tmp.name === input.node) { node = tmp; } } } else { node = input.node; } if (node) { var inputPin = input.pin; if (!inputPin) { if (node.outputs) { inputPin = Object.keys(node.outputs)[0]; } } if (node.outputs[inputPin]) { return { node, pin: inputPin }; } } } } ); var Compositor = Graph.extend( function() { return { // Output node _outputs: [], _texturePool: new TexturePool(), _frameBuffer: new FrameBuffer({ depthBuffer: false }) }; }, /** @lends clay.compositor.Compositor.prototype */ { addNode: function(node) { Graph.prototype.addNode.call(this, node); node._compositor = this; }, /** * @param {clay.Renderer} renderer */ render: function(renderer, frameBuffer) { if (this._dirty) { this.update(); this._dirty = false; this._outputs.length = 0; for (var i = 0; i < this.nodes.length; i++) { if (!this.nodes[i].outputs) { this._outputs.push(this.nodes[i]); } } } for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].beforeFrame(); } for (var i = 0; i < this._outputs.length; i++) { this._outputs[i].updateReference(); } for (var i = 0; i < this._outputs.length; i++) { this._outputs[i].render(renderer, frameBuffer); } for (var i = 0; i < this.nodes.length; i++) { this.nodes[i].afterFrame(); } }, allocateTexture: function(parameters) { return this._texturePool.get(parameters); }, releaseTexture: function(parameters) { this._texturePool.put(parameters); }, getFrameBuffer: function() { return this._frameBuffer; }, /** * Dispose compositor * @param {clay.Renderer} renderer */ dispose: function(renderer) { this._texturePool.clear(renderer); } } ); var SceneNode = CompositorNode.extend( /** @lends clay.compositor.SceneNode# */ { name: "scene", /** * @type {clay.Scene} */ scene: null, /** * @type {clay.Camera} */ camera: null, /** * @type {boolean} */ autoUpdateScene: true, /** * @type {boolean} */ preZ: false }, function() { this.frameBuffer = new FrameBuffer(); }, { render: function(renderer) { this._rendering = true; var _gl = renderer.gl; this.trigger("beforerender"); var renderInfo; if (!this.outputs) { renderInfo = renderer.render(this.scene, this.camera, !this.autoUpdateScene, this.preZ); } else { var frameBuffer = this.frameBuffer; for (var name in this.outputs) { var parameters = this.updateParameter(name, renderer); var outputInfo = this.outputs[name]; var texture = this._compositor.allocateTexture(parameters); this._outputTextures[name] = texture; var attachment = outputInfo.attachment || _gl.COLOR_ATTACHMENT0; if (typeof attachment == "string") { attachment = _gl[attachment]; } frameBuffer.attach(texture, attachment); } frameBuffer.bind(renderer); var ext = renderer.getGLExtension("EXT_draw_buffers"); if (ext) { var bufs = []; for (var attachment in this.outputs) { attachment = parseInt(attachment); if (attachment >= _gl.COLOR_ATTACHMENT0 && attachment <= _gl.COLOR_ATTACHMENT0 + 8) { bufs.push(attachment); } } ext.drawBuffersEXT(bufs); } renderer.saveClear(); renderer.clearBit = glenum.DEPTH_BUFFER_BIT | glenum.COLOR_BUFFER_BIT; renderInfo = renderer.render(this.scene, this.camera, !this.autoUpdateScene, this.preZ); renderer.restoreClear(); frameBuffer.unbind(renderer); } this.trigger("afterrender", renderInfo); this._rendering = false; this._rendered = true; } } ); var TextureNode = CompositorNode.extend(function() { return ( /** @lends clay.compositor.TextureNode# */ { /** * @type {clay.Texture2D} */ texture: null, // Texture node must have output without parameters outputs: { color: {} } } ); }, function() { }, { getOutput: function(renderer, name) { return this.texture; }, // Do nothing beforeFrame: function() { }, afterFrame: function() { } }); var FilterNode = CompositorNode.extend( function() { return ( /** @lends clay.compositor.FilterNode# */ { /** * @type {string} */ name: "", /** * @type {Object} */ inputs: {}, /** * @type {Object} */ outputs: null, /** * @type {string} */ shader: "", /** * Input links, will be updated by the graph * @example: * inputName: { * node: someNode, * pin: 'xxxx' * } * @type {Object} */ inputLinks: {}, /** * Output links, will be updated by the graph * @example: * outputName: { * node: someNode, * pin: 'xxxx' * } * @type {Object} */ outputLinks: {}, /** * @type {clay.compositor.Pass} */ pass: null, // Save the output texture of previous frame // Will be used when there exist a circular reference _prevOutputTextures: {}, _outputTextures: {}, // Example: { name: 2 } _outputReferences: {}, _rendering: false, // If rendered in this frame _rendered: false, _compositor: null } ); }, function() { var pass = new Pass({ fragment: this.shader }); this.pass = pass; }, /** @lends clay.compositor.FilterNode.prototype */ { /** * @param {clay.Renderer} renderer */ render: function(renderer, frameBuffer) { this.trigger("beforerender", renderer); this._rendering = true; var _gl = renderer.gl; for (var inputName in this.inputLinks) { var link = this.inputLinks[inputName]; var inputTexture = link.node.getOutput(renderer, link.pin); this.pass.setUniform(inputName, inputTexture); } if (!this.outputs) { this.pass.outputs = null; this._compositor.getFrameBuffer().unbind(renderer); this.pass.render(renderer, frameBuffer); } else { this.pass.outputs = {}; var attachedTextures = {}; for (var name in this.outputs) { var parameters = this.updateParameter(name, renderer); if (isNaN(parameters.width)) { this.updateParameter(name, renderer); } var outputInfo = this.outputs[name]; var texture = this._compositor.allocateTexture(parameters); this._outputTextures[name] = texture; var attachment = outputInfo.attachment || _gl.COLOR_ATTACHMENT0; if (typeof attachment === "string") { attachment = _gl[attachment]; } attachedTextures[attachment] = texture; } this._compositor.getFrameBuffer().bind(renderer); for (var attachment in attachedTextures) { this._compositor.getFrameBuffer().attach( attachedTextures[attachment], attachment ); } this.pass.render(renderer); this._compositor.getFrameBuffer().updateMipmap(renderer); } for (var inputName in this.inputLinks) { var link = this.inputLinks[inputName]; link.node.removeReference(link.pin); } this._rendering = false; this._rendered = true; this.trigger("afterrender", renderer); }, // TODO Remove parameter function callback updateParameter: function(outputName, renderer) { var outputInfo = this.outputs[outputName]; var parameters = outputInfo.parameters; var parametersCopy = outputInfo._parametersCopy; if (!parametersCopy) { parametersCopy = outputInfo._parametersCopy = {}; } if (parameters) { for (var key in parameters) { if (key !== "width" && key !== "height") { parametersCopy[key] = parameters[key]; } } } var width, height; if (typeof parameters.width === "function") { width = parameters.width.call(this, renderer); } else { width = parameters.width; } if (typeof parameters.height === "function") { height = parameters.height.call(this, renderer); } else { height = parameters.height; } width = Math.ceil(width); height = Math.ceil(height); if (parametersCopy.width !== width || parametersCopy.height !== height) { if (this._outputTextures[outputName]) { this._outputTextures[outputName].dispose(renderer); } } parametersCopy.width = width; parametersCopy.height = height; return parametersCopy; }, /** * Set parameter * @param {string} name * @param {} value */ setParameter: function(name, value) { this.pass.setUniform(name, value); }, /** * Get parameter value * @param {string} name * @return {} */ getParameter: function(name) { return this.pass.getUniform(name); }, /** * Set parameters * @param {Object} obj */ setParameters: function(obj) { for (var name in obj) { this.setParameter(name, obj[name]); } }, // /** // * Set shader code // * @param {string} shaderStr // */ // setShader: function (shaderStr) { // var material = this.pass.material; // material.shader.setFragment(shaderStr); // material.attachShader(material.shader, true); // }, /** * Proxy of pass.material.define('fragment', xxx); * @param {string} symbol * @param {number} [val] */ define: function(symbol, val) { this.pass.material.define("fragment", symbol, val); }, /** * Proxy of pass.material.undefine('fragment', xxx) * @param {string} symbol */ undefine: function(symbol) { this.pass.material.undefine("fragment", symbol); }, removeReference: function(outputName) { this._outputReferences[outputName]--; if (this._outputReferences[outputName] === 0) { var outputInfo = this.outputs[outputName]; if (outputInfo.keepLastFrame) { if (this._prevOutputTextures[outputName]) { this._compositor.releaseTexture(this._prevOutputTextures[outputName]); } this._prevOutputTextures[outputName] = this._outputTextures[outputName]; } else { this._compositor.releaseTexture(this._outputTextures[outputName]); } } }, clear: function() { CompositorNode.prototype.clear.call(this); this.pass.material.disableTexturesAll(); } } ); const coloradjustEssl = "@export clay.compositor.coloradjust\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float brightness : 0.0;\nuniform float contrast : 1.0;\nuniform float exposure : 0.0;\nuniform float gamma : 1.0;\nuniform float saturation : 1.0;\nconst vec3 w = vec3(0.2125, 0.7154, 0.0721);\nvoid main()\n{\n vec4 tex = texture2D( texture, v_Texcoord);\n vec3 color = clamp(tex.rgb + vec3(brightness), 0.0, 1.0);\n color = clamp( (color-vec3(0.5))*contrast+vec3(0.5), 0.0, 1.0);\n color = clamp( color * pow(2.0, exposure), 0.0, 1.0);\n color = clamp( pow(color, vec3(gamma)), 0.0, 1.0);\n float luminance = dot( color, w );\n color = mix(vec3(luminance), color, saturation);\n gl_FragColor = vec4(color, tex.a);\n}\n@end\n@export clay.compositor.brightness\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float brightness : 0.0;\nvoid main()\n{\n vec4 tex = texture2D( texture, v_Texcoord);\n vec3 color = tex.rgb + vec3(brightness);\n gl_FragColor = vec4(color, tex.a);\n}\n@end\n@export clay.compositor.contrast\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float contrast : 1.0;\nvoid main()\n{\n vec4 tex = texture2D( texture, v_Texcoord);\n vec3 color = (tex.rgb-vec3(0.5))*contrast+vec3(0.5);\n gl_FragColor = vec4(color, tex.a);\n}\n@end\n@export clay.compositor.exposure\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float exposure : 0.0;\nvoid main()\n{\n vec4 tex = texture2D(texture, v_Texcoord);\n vec3 color = tex.rgb * pow(2.0, exposure);\n gl_FragColor = vec4(color, tex.a);\n}\n@end\n@export clay.compositor.gamma\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float gamma : 1.0;\nvoid main()\n{\n vec4 tex = texture2D(texture, v_Texcoord);\n vec3 color = pow(tex.rgb, vec3(gamma));\n gl_FragColor = vec4(color, tex.a);\n}\n@end\n@export clay.compositor.saturation\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float saturation : 1.0;\nconst vec3 w = vec3(0.2125, 0.7154, 0.0721);\nvoid main()\n{\n vec4 tex = texture2D(texture, v_Texcoord);\n vec3 color = tex.rgb;\n float luminance = dot(color, w);\n color = mix(vec3(luminance), color, saturation);\n gl_FragColor = vec4(color, tex.a);\n}\n@end"; const blurCode = "@export clay.compositor.kernel.gaussian_9\nfloat gaussianKernel[9];\ngaussianKernel[0] = 0.07;\ngaussianKernel[1] = 0.09;\ngaussianKernel[2] = 0.12;\ngaussianKernel[3] = 0.14;\ngaussianKernel[4] = 0.16;\ngaussianKernel[5] = 0.14;\ngaussianKernel[6] = 0.12;\ngaussianKernel[7] = 0.09;\ngaussianKernel[8] = 0.07;\n@end\n@export clay.compositor.kernel.gaussian_13\nfloat gaussianKernel[13];\ngaussianKernel[0] = 0.02;\ngaussianKernel[1] = 0.03;\ngaussianKernel[2] = 0.06;\ngaussianKernel[3] = 0.08;\ngaussianKernel[4] = 0.11;\ngaussianKernel[5] = 0.13;\ngaussianKernel[6] = 0.14;\ngaussianKernel[7] = 0.13;\ngaussianKernel[8] = 0.11;\ngaussianKernel[9] = 0.08;\ngaussianKernel[10] = 0.06;\ngaussianKernel[11] = 0.03;\ngaussianKernel[12] = 0.02;\n@end\n@export clay.compositor.gaussian_blur\n#define SHADER_NAME gaussian_blur\nuniform sampler2D texture;varying vec2 v_Texcoord;\nuniform float blurSize : 2.0;\nuniform vec2 textureSize : [512.0, 512.0];\nuniform float blurDir : 0.0;\n@import clay.util.rgbm\n@import clay.util.clamp_sample\nvoid main (void)\n{\n @import clay.compositor.kernel.gaussian_9\n vec2 off = blurSize / textureSize;\n off *= vec2(1.0 - blurDir, blurDir);\n vec4 sum = vec4(0.0);\n float weightAll = 0.0;\n for (int i = 0; i < 9; i++) {\n float w = gaussianKernel[i];\n vec4 texel = decodeHDR(clampSample(texture, v_Texcoord + float(i - 4) * off));\n sum += texel * w;\n weightAll += w;\n }\n gl_FragColor = encodeHDR(sum / max(weightAll, 0.01));\n}\n@end\n"; const lumEssl = "@export clay.compositor.hdr.log_lum\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nconst vec3 w = vec3(0.2125, 0.7154, 0.0721);\n@import clay.util.rgbm\nvoid main()\n{\n vec4 tex = decodeHDR(texture2D(texture, v_Texcoord));\n float luminance = dot(tex.rgb, w);\n luminance = log(luminance + 0.001);\n gl_FragColor = encodeHDR(vec4(vec3(luminance), 1.0));\n}\n@end\n@export clay.compositor.hdr.lum_adaption\nvarying vec2 v_Texcoord;\nuniform sampler2D adaptedLum;\nuniform sampler2D currentLum;\nuniform float frameTime : 0.02;\n@import clay.util.rgbm\nvoid main()\n{\n float fAdaptedLum = decodeHDR(texture2D(adaptedLum, vec2(0.5, 0.5))).r;\n float fCurrentLum = exp(encodeHDR(texture2D(currentLum, vec2(0.5, 0.5))).r);\n fAdaptedLum += (fCurrentLum - fAdaptedLum) * (1.0 - pow(0.98, 30.0 * frameTime));\n gl_FragColor = encodeHDR(vec4(vec3(fAdaptedLum), 1.0));\n}\n@end\n@export clay.compositor.lum\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nconst vec3 w = vec3(0.2125, 0.7154, 0.0721);\nvoid main()\n{\n vec4 tex = texture2D( texture, v_Texcoord );\n float luminance = dot(tex.rgb, w);\n gl_FragColor = vec4(vec3(luminance), 1.0);\n}\n@end"; const lutCode = "\n@export clay.compositor.lut\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform sampler2D lookup;\nvoid main()\n{\n vec4 tex = texture2D(texture, v_Texcoord);\n float blueColor = tex.b * 63.0;\n vec2 quad1;\n quad1.y = floor(floor(blueColor) / 8.0);\n quad1.x = floor(blueColor) - (quad1.y * 8.0);\n vec2 quad2;\n quad2.y = floor(ceil(blueColor) / 8.0);\n quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n vec2 texPos1;\n texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * tex.r);\n texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * tex.g);\n vec2 texPos2;\n texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * tex.r);\n texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * tex.g);\n vec4 newColor1 = texture2D(lookup, texPos1);\n vec4 newColor2 = texture2D(lookup, texPos2);\n vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n gl_FragColor = vec4(newColor.rgb, tex.w);\n}\n@end"; const vigentteEssl = "@export clay.compositor.vignette\n#define OUTPUT_ALPHA\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\nuniform float darkness: 1;\nuniform float offset: 1;\n@import clay.util.rgbm\nvoid main()\n{\n vec4 texel = decodeHDR(texture2D(texture, v_Texcoord));\n gl_FragColor.rgb = texel.rgb;\n vec2 uv = (v_Texcoord - vec2(0.5)) * vec2(offset);\n gl_FragColor = encodeHDR(vec4(mix(texel.rgb, vec3(1.0 - darkness), dot(uv, uv)), texel.a));\n}\n@end"; const outputCode = "@export clay.compositor.output\n#define OUTPUT_ALPHA\nvarying vec2 v_Texcoord;\nuniform sampler2D texture;\n@import clay.util.rgbm\nvoid main()\n{\n vec4 tex = decodeHDR(texture2D(texture, v_Texcoord));\n gl_FragColor.rgb = tex.rgb;\n#ifdef OUTPUT_ALPHA\n gl_FragColor.a = tex.a;\n#else\n gl_FragColor.a = 1.0;\n#endif\n gl_FragColor = encodeHDR(gl_FragColor);\n#ifdef PREMULTIPLY_ALPHA\n gl_FragColor.rgb *= gl_FragColor.a;\n#endif\n}\n@end"; const brightCode = "@export clay.compositor.bright\nuniform sampler2D texture;\nuniform float threshold : 1;\nuniform float scale : 1.0;\nuniform vec2 textureSize: [512, 512];\nvarying vec2 v_Texcoord;\nconst vec3 lumWeight = vec3(0.2125, 0.7154, 0.0721);\n@import clay.util.rgbm\nvec4 median(vec4 a, vec4 b, vec4 c)\n{\n return a + b + c - min(min(a, b), c) - max(max(a, b), c);\n}\nvoid main()\n{\n vec4 texel = decodeHDR(texture2D(texture, v_Texcoord));\n#ifdef ANTI_FLICKER\n vec3 d = 1.0 / textureSize.xyx * vec3(1.0, 1.0, 0.0);\n vec4 s1 = decodeHDR(texture2D(texture, v_Texcoord - d.xz));\n vec4 s2 = decodeHDR(texture2D(texture, v_Texcoord + d.xz));\n vec4 s3 = decodeHDR(texture2D(texture, v_Texcoord - d.zy));\n vec4 s4 = decodeHDR(texture2D(texture, v_Texcoord + d.zy));\n texel = median(median(texel, s1, s2), s3, s4);\n#endif\n float lum = dot(texel.rgb , lumWeight);\n vec4 color;\n if (lum > threshold && texel.a > 0.0)\n {\n color = vec4(texel.rgb * scale, texel.a * scale);\n }\n else\n {\n color = vec4(0.0);\n }\n gl_FragColor = encodeHDR(color);\n}\n@end\n"; const downsampleCode = "@export clay.compositor.downsample\nuniform sampler2D texture;\nuniform vec2 textureSize : [512, 512];\nvarying vec2 v_Texcoord;\n@import clay.util.rgbm\nfloat brightness(vec3 c)\n{\n return max(max(c.r, c.g), c.b);\n}\n@import clay.util.clamp_sample\nvoid main()\n{\n vec4 d = vec4(-1.0, -1.0, 1.0, 1.0) / textureSize.xyxy;\n#ifdef ANTI_FLICKER\n vec3 s1 = decodeHDR(clampSample(texture, v_Texcoord + d.xy)).rgb;\n vec3 s2 = decodeHDR(clampSample(texture, v_Texcoord + d.zy)).rgb;\n vec3 s3 = decodeHDR(clampSample(texture, v_Texcoord + d.xw)).rgb;\n vec3 s4 = decodeHDR(clampSample(texture, v_Texcoord + d.zw)).rgb;\n float s1w = 1.0 / (brightness(s1) + 1.0);\n float s2w = 1.0 / (brightness(s2) + 1.0);\n float s3w = 1.0 / (brightness(s3) + 1.0);\n float s4w = 1.0 / (brightness(s4) + 1.0);\n float oneDivideSum = 1.0 / (s1w + s2w + s3w + s4w);\n vec4 color = vec4(\n (s1 * s1w + s2 * s2w + s3 * s3w + s4 * s4w) * oneDivideSum,\n 1.0\n );\n#else\n vec4 color = decodeHDR(clampSample(texture, v_Texcoord + d.xy));\n color += decodeHDR(clampSample(texture, v_Texcoord + d.zy));\n color += decodeHDR(clampSample(texture, v_Texcoord + d.xw));\n color += decodeHDR(clampSample(texture, v_Texcoord + d.zw));\n color *= 0.25;\n#endif\n gl_FragColor = encodeHDR(color);\n}\n@end"; const upsampleCode = "\n@export clay.compositor.upsample\n#define HIGH_QUALITY\nuniform sampler2D texture;\nuniform vec2 textureSize : [512, 512];\nuniform float sampleScale: 0.5;\nvarying vec2 v_Texcoord;\n@import clay.util.rgbm\n@import clay.util.clamp_sample\nvoid main()\n{\n#ifdef HIGH_QUALITY\n vec4 d = vec4(1.0, 1.0, -1.0, 0.0) / textureSize.xyxy * sampleScale;\n vec4 s;\n s = decodeHDR(clampSample(texture, v_Texcoord - d.xy));\n s += decodeHDR(clampSample(texture, v_Texcoord - d.wy)) * 2.0;\n s += decodeHDR(clampSample(texture, v_Texcoord - d.zy));\n s += decodeHDR(clampSample(texture, v_Texcoord + d.zw)) * 2.0;\n s += decodeHDR(clampSample(texture, v_Texcoord )) * 4.0;\n s += decodeHDR(clampSample(texture, v_Texcoord + d.xw)) * 2.0;\n s += decodeHDR(clampSample(texture, v_Texcoord + d.zy));\n s += decodeHDR(clampSample(texture, v_Texcoord + d.wy)) * 2.0;\n s += decodeHDR(clampSample(texture, v_Texcoord + d.xy));\n gl_FragColor = encodeHDR(s / 16.0);\n#else\n vec4 d = vec4(-1.0, -1.0, +1.0, +1.0) / textureSize.xyxy;\n vec4 s;\n s = decodeHDR(clampSample(texture, v_Texcoord + d.xy));\n s += decodeHDR(clampSample(texture, v_Texcoord + d.zy));\n s += decodeHDR(clampSample(texture, v_Texcoord + d.xw));\n s += decodeHDR(clampSample(texture, v_Texcoord + d.zw));\n gl_FragColor = encodeHDR(s / 4.0);\n#endif\n}\n@end"; const hdrCode = "@export clay.compositor.hdr.composite\n#define TONEMAPPING\nuniform sampler2D texture;\n#ifdef BLOOM_ENABLED\nuniform sampler2D bloom;\n#endif\n#ifdef LENSFLARE_ENABLED\nuniform sampler2D lensflare;\nuniform sampler2D lensdirt;\n#endif\n#ifdef LUM_ENABLED\nuniform sampler2D lum;\n#endif\n#ifdef LUT_ENABLED\nuniform sampler2D lut;\n#endif\n#ifdef COLOR_CORRECTION\nuniform float brightness : 0.0;\nuniform float contrast : 1.0;\nuniform float saturation : 1.0;\n#endif\n#ifdef VIGNETTE\nuniform float vignetteDarkness: 1.0;\nuniform float vignetteOffset: 1.0;\n#endif\nuniform float exposure : 1.0;\nuniform float bloomIntensity : 0.25;\nuniform float lensflareIntensity : 1;\nvarying vec2 v_Texcoord;\n@import clay.util.srgb\nvec3 ACESToneMapping(vec3 color)\n{\n const float A = 2.51;\n const float B = 0.03;\n const float C = 2.43;\n const float D = 0.59;\n const float E = 0.14;\n return (color * (A * color + B)) / (color * (C * color + D) + E);\n}\nfloat eyeAdaption(float fLum)\n{\n return mix(0.2, fLum, 0.5);\n}\n#ifdef LUT_ENABLED\nvec3 lutTransform(vec3 color) {\n float blueColor = color.b * 63.0;\n vec2 quad1;\n quad1.y = floor(floor(blueColor) / 8.0);\n quad1.x = floor(blueColor) - (quad1.y * 8.0);\n vec2 quad2;\n quad2.y = floor(ceil(blueColor) / 8.0);\n quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n vec2 texPos1;\n texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);\n texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);\n vec2 texPos2;\n texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.r);\n texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * color.g);\n vec4 newColor1 = texture2D(lut, texPos1);\n vec4 newColor2 = texture2D(lut, texPos2);\n vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n return newColor.rgb;\n}\n#endif\n@import clay.util.rgbm\nvoid main()\n{\n vec4 texel = vec4(0.0);\n vec4 originalTexel = vec4(0.0);\n#ifdef TEXTURE_ENABLED\n texel = decodeHDR(texture2D(texture, v_Texcoord));\n originalTexel = texel;\n#endif\n#ifdef BLOOM_ENABLED\n vec4 bloomTexel = decodeHDR(texture2D(bloom, v_Texcoord));\n texel.rgb += bloomTexel.rgb * bloomIntensity;\n texel.a += bloomTexel.a * bloomIntensity;\n#endif\n#ifdef LENSFLARE_ENABLED\n texel += decodeHDR(texture2D(lensflare, v_Texcoord)) * texture2D(lensdirt, v_Texcoord) * lensflareIntensity;\n#endif\n texel.a = min(texel.a, 1.0);\n#ifdef LUM_ENABLED\n float fLum = texture2D(lum, vec2(0.5, 0.5)).r;\n float adaptedLumDest = 3.0 / (max(0.1, 1.0 + 10.0*eyeAdaption(fLum)));\n float exposureBias = adaptedLumDest * exposure;\n#else\n float exposureBias = exposure;\n#endif\n#ifdef TONEMAPPING\n texel.rgb *= exposureBias;\n texel.rgb = ACESToneMapping(texel.rgb);\n#endif\n texel = linearTosRGB(texel);\n#ifdef LUT_ENABLED\n texel.rgb = lutTransform(clamp(texel.rgb,vec3(0.0),vec3(1.0)));\n#endif\n#ifdef COLOR_CORRECTION\n texel.rgb = clamp(texel.rgb + vec3(brightness), 0.0, 1.0);\n texel.rgb = clamp((texel.rgb - vec3(0.5))*contrast+vec3(0.5), 0.0, 1.0);\n float lum = dot(texel.rgb, vec3(0.2125, 0.7154, 0.0721));\n texel.rgb = mix(vec3(lum), texel.rgb, saturation);\n#endif\n#ifdef VIGNETTE\n vec2 uv = (v_Texcoord - vec2(0.5)) * vec2(vignetteOffset);\n texel.rgb = mix(texel.rgb, vec3(1.0 - vignetteDarkness), dot(uv, uv));\n#endif\n gl_FragColor = encodeHDR(texel);\n#ifdef DEBUG\n #if DEBUG == 1\n gl_FragColor = encodeHDR(decodeHDR(texture2D(texture, v_Texcoord)));\n #elif DEBUG == 2\n gl_FragColor = encodeHDR(decodeHDR(texture2D(bloom, v_Texcoord)) * bloomIntensity);\n #elif DEBUG == 3\n gl_FragColor = encodeHDR(decodeHDR(texture2D(lensflare, v_Texcoord) * lensflareIntensity));\n #endif\n#endif\n if (originalTexel.a <= 0.01 && gl_FragColor.a > 1e-5) {\n gl_FragColor.a = dot(gl_FragColor.rgb, vec3(0.2125, 0.7154, 0.0721));\n }\n#ifdef PREMULTIPLY_ALPHA\n gl_FragColor.rgb *= gl_FragColor.a;\n#endif\n}\n@end"; const lensflareEssl = "@export clay.compositor.lensflare\n#define SAMPLE_NUMBER 8\nuniform sampler2D texture;\nuniform sampler2D lenscolor;\nuniform vec2 textureSize : [512, 512];\nuniform float dispersal : 0.3;\nuniform float haloWidth : 0.4;\nuniform float distortion : 1.0;\nvarying vec2 v_Texcoord;\n@import clay.util.rgbm\nvec4 textureDistorted(\n in vec2 texcoord,\n in vec2 direction,\n in vec3 distortion\n) {\n return vec4(\n decodeHDR(texture2D(texture, texcoord + direction * distortion.r)).r,\n decodeHDR(texture2D(texture, texcoord + direction * distortion.g)).g,\n decodeHDR(texture2D(texture, texcoord + direction * distortion.b)).b,\n 1.0\n );\n}\nvoid main()\n{\n vec2 texcoord = -v_Texcoord + vec2(1.0); vec2 textureOffset = 1.0 / textureSize;\n vec2 ghostVec = (vec2(0.5) - texcoord) * dispersal;\n vec2 haloVec = normalize(ghostVec) * haloWidth;\n vec3 distortion = vec3(-textureOffset.x * distortion, 0.0, textureOffset.x * distortion);\n vec4 result = vec4(0.0);\n for (int i = 0; i < SAMPLE_NUMBER; i++)\n {\n vec2 offset = fract(texcoord + ghostVec * float(i));\n float weight = length(vec2(0.5) - offset) / length(vec2(0.5));\n weight = pow(1.0 - weight, 10.0);\n result += textureDistorted(offset, normalize(ghostVec), distortion) * weight;\n }\n result *= texture2D(lenscolor, vec2(length(vec2(0.5) - texcoord)) / length(vec2(0.5)));\n float weight = length(vec2(0.5) - fract(texcoord + haloVec)) / length(vec2(0.5));\n weight = pow(1.0 - weight, 10.0);\n vec2 offset = fract(texcoord + haloVec);\n result += textureDistorted(offset, normalize(ghostVec), distortion) * weight;\n gl_FragColor = result;\n}\n@end"; const blendCode = "@export clay.compositor.blend\n#define SHADER_NAME blend\n#ifdef TEXTURE1_ENABLED\nuniform sampler2D texture1;\nuniform float weight1 : 1.0;\n#endif\n#ifdef TEXTURE2_ENABLED\nuniform sampler2D texture2;\nuniform float weight2 : 1.0;\n#endif\n#ifdef TEXTURE3_ENABLED\nuniform sampler2D texture3;\nuniform float weight3 : 1.0;\n#endif\n#ifdef TEXTURE4_ENABLED\nuniform sampler2D texture4;\nuniform float weight4 : 1.0;\n#endif\n#ifdef TEXTURE5_ENABLED\nuniform sampler2D texture5;\nuniform float weight5 : 1.0;\n#endif\n#ifdef TEXTURE6_ENABLED\nuniform sampler2D texture6;\nuniform float weight6 : 1.0;\n#endif\nvarying vec2 v_Texcoord;\n@import clay.util.rgbm\nvoid main()\n{\n vec4 tex = vec4(0.0);\n#ifdef TEXTURE1_ENABLED\n tex += decodeHDR(texture2D(texture1, v_Texcoord)) * weight1;\n#endif\n#ifdef TEXTURE2_ENABLED\n tex += decodeHDR(texture2D(texture2, v_Texcoord)) * weight2;\n#endif\n#ifdef TEXTURE3_ENABLED\n tex += decodeHDR(texture2D(texture3, v_Texcoord)) * weight3;\n#endif\n#ifdef TEXTURE4_ENABLED\n tex += decodeHDR(texture2D(texture4, v_Texcoord)) * weight4;\n#endif\n#ifdef TEXTURE5_ENABLED\n tex += decodeHDR(texture2D(texture5, v_Texcoord)) * weight5;\n#endif\n#ifdef TEXTURE6_ENABLED\n tex += decodeHDR(texture2D(texture6, v_Texcoord)) * weight6;\n#endif\n gl_FragColor = encodeHDR(tex);\n}\n@end"; const fxaaCode = "@export clay.compositor.fxaa\nuniform sampler2D texture;\nuniform vec4 viewport : VIEWPORT;\nvarying vec2 v_Texcoord;\n#define FXAA_REDUCE_MIN (1.0/128.0)\n#define FXAA_REDUCE_MUL (1.0/8.0)\n#define FXAA_SPAN_MAX 8.0\n@import clay.util.rgbm\nvoid main()\n{\n vec2 resolution = 1.0 / viewport.zw;\n vec3 rgbNW = decodeHDR( texture2D( texture, ( gl_FragCoord.xy + vec2( -1.0, -1.0 ) ) * resolution ) ).xyz;\n vec3 rgbNE = decodeHDR( texture2D( texture, ( gl_FragCoord.xy + vec2( 1.0, -1.0 ) ) * resolution ) ).xyz;\n vec3 rgbSW = decodeHDR( texture2D( texture, ( gl_FragCoord.xy + vec2( -1.0, 1.0 ) ) * resolution ) ).xyz;\n vec3 rgbSE = decodeHDR( texture2D( texture, ( gl_FragCoord.xy + vec2( 1.0, 1.0 ) ) * resolution ) ).xyz;\n vec4 rgbaM = decodeHDR( texture2D( texture, gl_FragCoord.xy * resolution ) );\n vec3 rgbM = rgbaM.xyz;\n float opacity = rgbaM.w;\n vec3 luma = vec3( 0.299, 0.587, 0.114 );\n float lumaNW = dot( rgbNW, luma );\n float lumaNE = dot( rgbNE, luma );\n float lumaSW = dot( rgbSW, luma );\n float lumaSE = dot( rgbSE, luma );\n float lumaM = dot( rgbM, luma );\n float lumaMin = min( lumaM, min( min( lumaNW, lumaNE ), min( lumaSW, lumaSE ) ) );\n float lumaMax = max( lumaM, max( max( lumaNW, lumaNE) , max( lumaSW, lumaSE ) ) );\n vec2 dir;\n dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n float dirReduce = max( ( lumaNW + lumaNE + lumaSW + lumaSE ) * ( 0.25 * FXAA_REDUCE_MUL ), FXAA_REDUCE_MIN );\n float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce );\n dir = min( vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),\n max( vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),\n dir * rcpDirMin)) * resolution;\n vec3 rgbA = decodeHDR( texture2D( texture, gl_FragCoord.xy * resolution + dir * ( 1.0 / 3.0 - 0.5 ) ) ).xyz;\n rgbA += decodeHDR( texture2D( texture, gl_FragCoord.xy * resolution + dir * ( 2.0 / 3.0 - 0.5 ) ) ).xyz;\n rgbA *= 0.5;\n vec3 rgbB = decodeHDR( texture2D( texture, gl_FragCoord.xy * resolution + dir * -0.5 ) ).xyz;\n rgbB += decodeHDR( texture2D( texture, gl_FragCoord.xy * resolution + dir * 0.5 ) ).xyz;\n rgbB *= 0.25;\n rgbB += rgbA * 0.5;\n float lumaB = dot( rgbB, luma );\n if ( ( lumaB < lumaMin ) || ( lumaB > lumaMax ) )\n {\n gl_FragColor = vec4( rgbA, opacity );\n }\n else {\n gl_FragColor = vec4( rgbB, opacity );\n }\n}\n@end"; function register(Shader2) { Shader2["import"](coloradjustEssl); Shader2["import"](blurCode); Shader2["import"](lumEssl); Shader2["import"](lutCode); Shader2["import"](vigentteEssl); Shader2["import"](outputCode); Shader2["import"](brightCode); Shader2["import"](downsampleCode); Shader2["import"](upsampleCode); Shader2["import"](hdrCode); Shader2["import"](lensflareEssl); Shader2["import"](blendCode); Shader2["import"](fxaaCode); } register(Shader); var shaderSourceReg = /^#source\((.*?)\)/; function createCompositor(json, opts) { var compositor = new Compositor(); opts = opts || {}; var lib = { textures: {}, parameters: {} }; var afterLoad = function(shaderLib, textureLib) { for (var i = 0; i < json.nodes.length; i++) { var nodeInfo = json.nodes[i]; var node = createNode(nodeInfo, lib, opts); if (node) { compositor.addNode(node); } } }; for (var name in json.parameters) { var paramInfo = json.parameters[name]; lib.parameters[name] = convertParameter(paramInfo); } loadTextures(json, lib, opts, function(textureLib) { lib.textures = textureLib; afterLoad(); }); return compositor; } function createNode(nodeInfo, lib, opts) { var type = nodeInfo.type || "filter"; var shaderSource; var inputs; var outputs; if (type === "filter") { var shaderExp = nodeInfo.shader.trim(); var res = shaderSourceReg.exec(shaderExp); if (res) { shaderSource = Shader.source(res[1].trim()); } else if (shaderExp.charAt(0) === "#") { shaderSource = lib.shaders[shaderExp.substr(1)]; } if (!shaderSource) { shaderSource = shaderExp; } if (!shaderSource) { return; } } if (nodeInfo.inputs) { inputs = {}; for (var name in nodeInfo.inputs) { if (typeof nodeInfo.inputs[name] === "string") { inputs[name] = nodeInfo.inputs[name]; } else { inputs[name] = { node: nodeInfo.inputs[name].node, pin: nodeInfo.inputs[name].pin }; } } } if (nodeInfo.outputs) { outputs = {}; for (var name in nodeInfo.outputs) { var outputInfo = nodeInfo.outputs[name]; outputs[name] = {}; if (outputInfo.attachment != null) { outputs[name].attachment = outputInfo.attachment; } if (outputInfo.keepLastFrame != null) { outputs[name].keepLastFrame = outputInfo.keepLastFrame; } if (outputInfo.outputLastFrame != null) { outputs[name].outputLastFrame = outputInfo.outputLastFrame; } if (outputInfo.parameters) { outputs[name].parameters = convertParameter(outputInfo.parameters); } } } var node; if (type === "scene") { node = new SceneNode({ name: nodeInfo.name, scene: opts.scene, camera: opts.camera, outputs }); } else if (type === "texture") { node = new TextureNode({ name: nodeInfo.name, outputs }); } else { node = new FilterNode({ name: nodeInfo.name, shader: shaderSource, inputs, outputs }); } if (node) { if (nodeInfo.parameters) { for (var name in nodeInfo.parameters) { var val = nodeInfo.parameters[name]; if (typeof val === "string") { val = val.trim(); if (val.charAt(0) === "#") { val = lib.textures[val.substr(1)]; } else { node.on( "beforerender", createSizeSetHandler( name, tryConvertExpr(val) ) ); } } else if (typeof val === "function") { node.on("beforerender", val); } node.setParameter(name, val); } } if (nodeInfo.defines && node.pass) { for (var name in nodeInfo.defines) { var val = nodeInfo.defines[name]; node.pass.material.define("fragment", name, val); } } } return node; } function defaultWidthFunc(width, height) { return width; } function defaultHeightFunc(width, height) { return height; } function convertParameter(paramInfo) { var param = {}; if (!paramInfo) { return param; } ["type", "minFilter", "magFilter", "wrapS", "wrapT", "flipY", "useMipmap"].forEach(function(name) { var val = paramInfo[name]; if (val != null) { if (typeof val === "string") { val = Texture[val]; } param[name] = val; } }); var sizeScale = paramInfo.scale || 1; ["width", "height"].forEach(function(name) { if (paramInfo[name] != null) { var val = paramInfo[name]; if (typeof val === "string") { val = val.trim(); param[name] = createSizeParser( name, tryConvertExpr(val), sizeScale ); } else { param[name] = val; } } }); if (!param.width) { param.width = defaultWidthFunc; } if (!param.height) { param.height = defaultHeightFunc; } if (paramInfo.useMipmap != null) { param.useMipmap = paramInfo.useMipmap; } return param; } function loadTextures(json, lib, opts, callback) { if (!json.textures) { callback({}); return; } var textures = {}; var loading = 0; var cbd = false; var textureRootPath = opts.textureRootPath; util.each(json.textures, function(textureInfo, name) { var texture; var path = textureInfo.path; var parameters = convertParameter(textureInfo.parameters); if (Array.isArray(path) && path.length === 6) { if (textureRootPath) { path = path.map(function(item) { return util.relative2absolute(item, textureRootPath); }); } texture = new TextureCube(parameters); } else if (typeof path === "string") { if (textureRootPath) { path = util.relative2absolute(path, textureRootPath); } texture = new Texture2D(parameters); } else { return; } texture.load(path); loading++; texture.once("success", function() { textures[name] = texture; loading--; if (loading === 0) { callback(textures); cbd = true; } }); }); if (loading === 0 && !cbd) { callback(textures); } } function createSizeSetHandler(name, exprFunc) { return function(renderer) { var dpr = renderer.getDevicePixelRatio(); var width = renderer.getWidth(); var height = renderer.getHeight(); var result = exprFunc(width, height, dpr); this.setParameter(name, result); }; } function createSizeParser(name, exprFunc, scale) { scale = scale || 1; return function(renderer) { var dpr = renderer.getDevicePixelRatio(); var width = renderer.getWidth() * scale; var height = renderer.getHeight() * scale; return exprFunc(width, height, dpr); }; } function tryConvertExpr(string) { var exprRes = /^expr\((.*)\)$/.exec(string); if (exprRes) { try { var func = new Function("width", "height", "dpr", "return " + exprRes[1]); func(1, 1); return func; } catch (e2) { throw new Error("Invalid expression."); } } } function halton(index, base) { var result = 0; var f = 1 / base; var i = index; while (i > 0) { result = result + f * (i % base); i = Math.floor(i / base); f = f / base; } return result; } const SSAOGLSL = "@export ecgl.ssao.estimate\n\nuniform sampler2D depthTex;\n\nuniform sampler2D normalTex;\n\nuniform sampler2D noiseTex;\n\nuniform vec2 depthTexSize;\n\nuniform vec2 noiseTexSize;\n\nuniform mat4 projection;\n\nuniform mat4 projectionInv;\n\nuniform mat4 viewInverseTranspose;\n\nuniform vec3 kernel[KERNEL_SIZE];\n\nuniform float radius : 1;\n\nuniform float power : 1;\n\nuniform float bias: 1e-2;\n\nuniform float intensity: 1.0;\n\nvarying vec2 v_Texcoord;\n\nfloat ssaoEstimator(in vec3 originPos, in mat3 kernelBasis) {\n float occlusion = 0.0;\n\n for (int i = 0; i < KERNEL_SIZE; i++) {\n vec3 samplePos = kernel[i];\n#ifdef NORMALTEX_ENABLED\n samplePos = kernelBasis * samplePos;\n#endif\n samplePos = samplePos * radius + originPos;\n\n vec4 texCoord = projection * vec4(samplePos, 1.0);\n texCoord.xy /= texCoord.w;\n\n vec4 depthTexel = texture2D(depthTex, texCoord.xy * 0.5 + 0.5);\n\n float sampleDepth = depthTexel.r * 2.0 - 1.0;\n if (projection[3][3] == 0.0) {\n sampleDepth = projection[3][2] / (sampleDepth * projection[2][3] - projection[2][2]);\n }\n else {\n sampleDepth = (sampleDepth - projection[3][2]) / projection[2][2];\n }\n \n float rangeCheck = smoothstep(0.0, 1.0, radius / abs(originPos.z - sampleDepth));\n occlusion += rangeCheck * step(samplePos.z, sampleDepth - bias);\n }\n#ifdef NORMALTEX_ENABLED\n occlusion = 1.0 - occlusion / float(KERNEL_SIZE);\n#else\n occlusion = 1.0 - clamp((occlusion / float(KERNEL_SIZE) - 0.6) * 2.5, 0.0, 1.0);\n#endif\n return pow(occlusion, power);\n}\n\nvoid main()\n{\n\n vec4 depthTexel = texture2D(depthTex, v_Texcoord);\n\n#ifdef NORMALTEX_ENABLED\n vec4 tex = texture2D(normalTex, v_Texcoord);\n if (dot(tex.rgb, tex.rgb) == 0.0) {\n gl_FragColor = vec4(1.0);\n return;\n }\n vec3 N = tex.rgb * 2.0 - 1.0;\n N = (viewInverseTranspose * vec4(N, 0.0)).xyz;\n\n vec2 noiseTexCoord = depthTexSize / vec2(noiseTexSize) * v_Texcoord;\n vec3 rvec = texture2D(noiseTex, noiseTexCoord).rgb * 2.0 - 1.0;\n vec3 T = normalize(rvec - N * dot(rvec, N));\n vec3 BT = normalize(cross(N, T));\n mat3 kernelBasis = mat3(T, BT, N);\n#else\n if (depthTexel.r > 0.99999) {\n gl_FragColor = vec4(1.0);\n return;\n }\n mat3 kernelBasis;\n#endif\n\n float z = depthTexel.r * 2.0 - 1.0;\n\n vec4 projectedPos = vec4(v_Texcoord * 2.0 - 1.0, z, 1.0);\n vec4 p4 = projectionInv * projectedPos;\n\n vec3 position = p4.xyz / p4.w;\n\n float ao = ssaoEstimator(position, kernelBasis);\n ao = clamp(1.0 - (1.0 - ao) * intensity, 0.0, 1.0);\n gl_FragColor = vec4(vec3(ao), 1.0);\n}\n\n@end\n\n\n@export ecgl.ssao.blur\n#define SHADER_NAME SSAO_BLUR\n\nuniform sampler2D ssaoTexture;\n\n#ifdef NORMALTEX_ENABLED\nuniform sampler2D normalTex;\n#endif\n\nvarying vec2 v_Texcoord;\n\nuniform vec2 textureSize;\nuniform float blurSize : 1.0;\n\nuniform int direction: 0.0;\n\n#ifdef DEPTHTEX_ENABLED\nuniform sampler2D depthTex;\nuniform mat4 projection;\nuniform float depthRange : 0.5;\n\nfloat getLinearDepth(vec2 coord)\n{\n float depth = texture2D(depthTex, coord).r * 2.0 - 1.0;\n return projection[3][2] / (depth * projection[2][3] - projection[2][2]);\n}\n#endif\n\nvoid main()\n{\n float kernel[5];\n kernel[0] = 0.122581;\n kernel[1] = 0.233062;\n kernel[2] = 0.288713;\n kernel[3] = 0.233062;\n kernel[4] = 0.122581;\n\n vec2 off = vec2(0.0);\n if (direction == 0) {\n off[0] = blurSize / textureSize.x;\n }\n else {\n off[1] = blurSize / textureSize.y;\n }\n\n vec2 coord = v_Texcoord;\n\n float sum = 0.0;\n float weightAll = 0.0;\n\n#ifdef NORMALTEX_ENABLED\n vec3 centerNormal = texture2D(normalTex, v_Texcoord).rgb * 2.0 - 1.0;\n#endif\n#if defined(DEPTHTEX_ENABLED)\n float centerDepth = getLinearDepth(v_Texcoord);\n#endif\n\n for (int i = 0; i < 5; i++) {\n vec2 coord = clamp(v_Texcoord + vec2(float(i) - 2.0) * off, vec2(0.0), vec2(1.0));\n\n float w = kernel[i];\n#ifdef NORMALTEX_ENABLED\n vec3 normal = texture2D(normalTex, coord).rgb * 2.0 - 1.0;\n w *= clamp(dot(normal, centerNormal), 0.0, 1.0);\n#endif\n#ifdef DEPTHTEX_ENABLED\n float d = getLinearDepth(coord);\n w *= (1.0 - smoothstep(abs(centerDepth - d) / depthRange, 0.0, 1.0));\n#endif\n\n weightAll += w;\n sum += texture2D(ssaoTexture, coord).r * w;\n }\n\n gl_FragColor = vec4(vec3(sum / weightAll), 1.0);\n}\n\n@end\n"; Shader.import(SSAOGLSL); function generateNoiseData(size) { var data = new Uint8Array(size * size * 4); var n = 0; var v3 = new Vector3(); for (var i = 0; i < size; i++) { for (var j = 0; j < size; j++) { v3.set(Math.random() * 2 - 1, Math.random() * 2 - 1, 0).normalize(); data[n++] = (v3.x * 0.5 + 0.5) * 255; data[n++] = (v3.y * 0.5 + 0.5) * 255; data[n++] = 0; data[n++] = 255; } } return data; } function generateNoiseTexture(size) { return new Texture2D({ pixels: generateNoiseData(size), wrapS: Texture.REPEAT, wrapT: Texture.REPEAT, width: size, height: size }); } function generateKernel(size, offset, hemisphere) { var kernel = new Float32Array(size * 3); offset = offset || 0; for (var i = 0; i < size; i++) { var phi = halton(i + offset, 2) * (hemisphere ? 1 : 2) * Math.PI; var theta = halton(i + offset, 3) * Math.PI; var r = Math.random(); var x = Math.cos(phi) * Math.sin(theta) * r; var y = Math.cos(theta) * r; var z = Math.sin(phi) * Math.sin(theta) * r; kernel[i * 3] = x; kernel[i * 3 + 1] = y; kernel[i * 3 + 2] = z; } return kernel; } function SSAOPass(opt) { opt = opt || {}; this._ssaoPass = new Pass({ fragment: Shader.source("ecgl.ssao.estimate") }); this._blurPass = new Pass({ fragment: Shader.source("ecgl.ssao.blur") }); this._framebuffer = new FrameBuffer({ depthBuffer: false }); this._ssaoTexture = new Texture2D(); this._blurTexture = new Texture2D(); this._blurTexture2 = new Texture2D(); this._depthTex = opt.depthTexture; this._normalTex = opt.normalTexture; this.setNoiseSize(4); this.setKernelSize(opt.kernelSize || 12); if (opt.radius != null) { this.setParameter("radius", opt.radius); } if (opt.power != null) { this.setParameter("power", opt.power); } if (!this._normalTex) { this._ssaoPass.material.disableTexture("normalTex"); this._blurPass.material.disableTexture("normalTex"); } if (!this._depthTex) { this._blurPass.material.disableTexture("depthTex"); } this._blurPass.material.setUniform("normalTex", this._normalTex); this._blurPass.material.setUniform("depthTex", this._depthTex); } SSAOPass.prototype.setDepthTexture = function(depthTex) { this._depthTex = depthTex; }; SSAOPass.prototype.setNormalTexture = function(normalTex) { this._normalTex = normalTex; this._ssaoPass.material[normalTex ? "enableTexture" : "disableTexture"]("normalTex"); this.setKernelSize(this._kernelSize); }; SSAOPass.prototype.update = function(renderer, camera2, frame) { var width = renderer.getWidth(); var height = renderer.getHeight(); var ssaoPass = this._ssaoPass; var blurPass = this._blurPass; ssaoPass.setUniform("kernel", this._kernels[frame % this._kernels.length]); ssaoPass.setUniform("depthTex", this._depthTex); if (this._normalTex != null) { ssaoPass.setUniform("normalTex", this._normalTex); } ssaoPass.setUniform("depthTexSize", [this._depthTex.width, this._depthTex.height]); var viewInverseTranspose = new Matrix4(); Matrix4.transpose(viewInverseTranspose, camera2.worldTransform); ssaoPass.setUniform("projection", camera2.projectionMatrix.array); ssaoPass.setUniform("projectionInv", camera2.invProjectionMatrix.array); ssaoPass.setUniform("viewInverseTranspose", viewInverseTranspose.array); var ssaoTexture = this._ssaoTexture; var blurTexture = this._blurTexture; var blurTexture2 = this._blurTexture2; ssaoTexture.width = width / 2; ssaoTexture.height = height / 2; blurTexture.width = width; blurTexture.height = height; blurTexture2.width = width; blurTexture2.height = height; this._framebuffer.attach(ssaoTexture); this._framebuffer.bind(renderer); renderer.gl.clearColor(1, 1, 1, 1); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); ssaoPass.render(renderer); blurPass.setUniform("textureSize", [width / 2, height / 2]); blurPass.setUniform("projection", camera2.projectionMatrix.array); this._framebuffer.attach(blurTexture); blurPass.setUniform("direction", 0); blurPass.setUniform("ssaoTexture", ssaoTexture); blurPass.render(renderer); this._framebuffer.attach(blurTexture2); blurPass.setUniform("textureSize", [width, height]); blurPass.setUniform("direction", 1); blurPass.setUniform("ssaoTexture", blurTexture); blurPass.render(renderer); this._framebuffer.unbind(renderer); var clearColor = renderer.clearColor; renderer.gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); }; SSAOPass.prototype.getTargetTexture = function() { return this._blurTexture2; }; SSAOPass.prototype.setParameter = function(name, val) { if (name === "noiseTexSize") { this.setNoiseSize(val); } else if (name === "kernelSize") { this.setKernelSize(val); } else if (name === "intensity") { this._ssaoPass.material.set("intensity", val); } else { this._ssaoPass.setUniform(name, val); } }; SSAOPass.prototype.setKernelSize = function(size) { this._kernelSize = size; this._ssaoPass.material.define("fragment", "KERNEL_SIZE", size); this._kernels = this._kernels || []; for (var i = 0; i < 30; i++) { this._kernels[i] = generateKernel(size, i * size, !!this._normalTex); } }; SSAOPass.prototype.setNoiseSize = function(size) { var texture = this._ssaoPass.getUniform("noiseTex"); if (!texture) { texture = generateNoiseTexture(size); this._ssaoPass.setUniform("noiseTex", generateNoiseTexture(size)); } else { texture.data = generateNoiseData(size); texture.width = texture.height = size; texture.dirty(); } this._ssaoPass.setUniform("noiseTexSize", [size, size]); }; SSAOPass.prototype.dispose = function(renderer) { this._blurTexture.dispose(renderer); this._ssaoTexture.dispose(renderer); this._blurTexture2.dispose(renderer); }; const SSRGLSLCode = "@export ecgl.ssr.main\n\n#define SHADER_NAME SSR\n#define MAX_ITERATION 20;\n#define SAMPLE_PER_FRAME 5;\n#define TOTAL_SAMPLES 128;\n\nuniform sampler2D sourceTexture;\nuniform sampler2D gBufferTexture1;\nuniform sampler2D gBufferTexture2;\nuniform sampler2D gBufferTexture3;\nuniform samplerCube specularCubemap;\nuniform float specularIntensity: 1;\n\nuniform mat4 projection;\nuniform mat4 projectionInv;\nuniform mat4 toViewSpace;\nuniform mat4 toWorldSpace;\n\nuniform float maxRayDistance: 200;\n\nuniform float pixelStride: 16;\nuniform float pixelStrideZCutoff: 50; \nuniform float screenEdgeFadeStart: 0.9; \nuniform float eyeFadeStart : 0.2; uniform float eyeFadeEnd: 0.8; \nuniform float minGlossiness: 0.2; uniform float zThicknessThreshold: 1;\n\nuniform float nearZ;\nuniform vec2 viewportSize : VIEWPORT_SIZE;\n\nuniform float jitterOffset: 0;\n\nvarying vec2 v_Texcoord;\n\n#ifdef DEPTH_DECODE\n@import clay.util.decode_float\n#endif\n\n#ifdef PHYSICALLY_CORRECT\nuniform sampler2D normalDistribution;\nuniform float sampleOffset: 0;\nuniform vec2 normalDistributionSize;\n\nvec3 transformNormal(vec3 H, vec3 N) {\n vec3 upVector = N.y > 0.999 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0);\n vec3 tangentX = normalize(cross(N, upVector));\n vec3 tangentZ = cross(N, tangentX);\n return normalize(tangentX * H.x + N * H.y + tangentZ * H.z);\n}\nvec3 importanceSampleNormalGGX(float i, float roughness, vec3 N) {\n float p = fract((i + sampleOffset) / float(TOTAL_SAMPLES));\n vec3 H = texture2D(normalDistribution,vec2(roughness, p)).rgb;\n return transformNormal(H, N);\n}\nfloat G_Smith(float g, float ndv, float ndl) {\n float roughness = 1.0 - g;\n float k = roughness * roughness / 2.0;\n float G1V = ndv / (ndv * (1.0 - k) + k);\n float G1L = ndl / (ndl * (1.0 - k) + k);\n return G1L * G1V;\n}\nvec3 F_Schlick(float ndv, vec3 spec) {\n return spec + (1.0 - spec) * pow(1.0 - ndv, 5.0);\n}\n#endif\n\nfloat fetchDepth(sampler2D depthTexture, vec2 uv)\n{\n vec4 depthTexel = texture2D(depthTexture, uv);\n return depthTexel.r * 2.0 - 1.0;\n}\n\nfloat linearDepth(float depth)\n{\n if (projection[3][3] == 0.0) {\n return projection[3][2] / (depth * projection[2][3] - projection[2][2]);\n }\n else {\n return (depth - projection[3][2]) / projection[2][2];\n }\n}\n\nbool rayIntersectDepth(float rayZNear, float rayZFar, vec2 hitPixel)\n{\n if (rayZFar > rayZNear)\n {\n float t = rayZFar; rayZFar = rayZNear; rayZNear = t;\n }\n float cameraZ = linearDepth(fetchDepth(gBufferTexture2, hitPixel));\n return rayZFar <= cameraZ && rayZNear >= cameraZ - zThicknessThreshold;\n}\n\n\nbool traceScreenSpaceRay(\n vec3 rayOrigin, vec3 rayDir, float jitter,\n out vec2 hitPixel, out vec3 hitPoint, out float iterationCount\n)\n{\n float rayLength = ((rayOrigin.z + rayDir.z * maxRayDistance) > -nearZ)\n ? (-nearZ - rayOrigin.z) / rayDir.z : maxRayDistance;\n\n vec3 rayEnd = rayOrigin + rayDir * rayLength;\n\n vec4 H0 = projection * vec4(rayOrigin, 1.0);\n vec4 H1 = projection * vec4(rayEnd, 1.0);\n\n float k0 = 1.0 / H0.w, k1 = 1.0 / H1.w;\n\n vec3 Q0 = rayOrigin * k0, Q1 = rayEnd * k1;\n\n vec2 P0 = (H0.xy * k0 * 0.5 + 0.5) * viewportSize;\n vec2 P1 = (H1.xy * k1 * 0.5 + 0.5) * viewportSize;\n\n P1 += dot(P1 - P0, P1 - P0) < 0.0001 ? 0.01 : 0.0;\n vec2 delta = P1 - P0;\n\n bool permute = false;\n if (abs(delta.x) < abs(delta.y)) {\n permute = true;\n delta = delta.yx;\n P0 = P0.yx;\n P1 = P1.yx;\n }\n float stepDir = sign(delta.x);\n float invdx = stepDir / delta.x;\n\n vec3 dQ = (Q1 - Q0) * invdx;\n float dk = (k1 - k0) * invdx;\n\n vec2 dP = vec2(stepDir, delta.y * invdx);\n\n float strideScaler = 1.0 - min(1.0, -rayOrigin.z / pixelStrideZCutoff);\n float pixStride = 1.0 + strideScaler * pixelStride;\n\n dP *= pixStride; dQ *= pixStride; dk *= pixStride;\n\n vec4 pqk = vec4(P0, Q0.z, k0);\n vec4 dPQK = vec4(dP, dQ.z, dk);\n\n pqk += dPQK * jitter;\n float rayZFar = (dPQK.z * 0.5 + pqk.z) / (dPQK.w * 0.5 + pqk.w);\n float rayZNear;\n\n bool intersect = false;\n\n vec2 texelSize = 1.0 / viewportSize;\n\n iterationCount = 0.0;\n\n for (int i = 0; i < MAX_ITERATION; i++)\n {\n pqk += dPQK;\n\n rayZNear = rayZFar;\n rayZFar = (dPQK.z * 0.5 + pqk.z) / (dPQK.w * 0.5 + pqk.w);\n\n hitPixel = permute ? pqk.yx : pqk.xy;\n hitPixel *= texelSize;\n\n intersect = rayIntersectDepth(rayZNear, rayZFar, hitPixel);\n\n iterationCount += 1.0;\n\n dPQK *= 1.2;\n\n if (intersect) {\n break;\n }\n }\n\n Q0.xy += dQ.xy * iterationCount;\n Q0.z = pqk.z;\n hitPoint = Q0 / pqk.w;\n\n return intersect;\n}\n\nfloat calculateAlpha(\n float iterationCount, float reflectivity,\n vec2 hitPixel, vec3 hitPoint, float dist, vec3 rayDir\n)\n{\n float alpha = clamp(reflectivity, 0.0, 1.0);\n alpha *= 1.0 - (iterationCount / float(MAX_ITERATION));\n vec2 hitPixelNDC = hitPixel * 2.0 - 1.0;\n float maxDimension = min(1.0, max(abs(hitPixelNDC.x), abs(hitPixelNDC.y)));\n alpha *= 1.0 - max(0.0, maxDimension - screenEdgeFadeStart) / (1.0 - screenEdgeFadeStart);\n\n float _eyeFadeStart = eyeFadeStart;\n float _eyeFadeEnd = eyeFadeEnd;\n if (_eyeFadeStart > _eyeFadeEnd) {\n float tmp = _eyeFadeEnd;\n _eyeFadeEnd = _eyeFadeStart;\n _eyeFadeStart = tmp;\n }\n\n float eyeDir = clamp(rayDir.z, _eyeFadeStart, _eyeFadeEnd);\n alpha *= 1.0 - (eyeDir - _eyeFadeStart) / (_eyeFadeEnd - _eyeFadeStart);\n\n alpha *= 1.0 - clamp(dist / maxRayDistance, 0.0, 1.0);\n\n return alpha;\n}\n\n@import clay.util.rand\n\n@import clay.util.rgbm\n\nvoid main()\n{\n vec4 normalAndGloss = texture2D(gBufferTexture1, v_Texcoord);\n\n if (dot(normalAndGloss.rgb, vec3(1.0)) == 0.0) {\n discard;\n }\n\n float g = normalAndGloss.a;\n#if !defined(PHYSICALLY_CORRECT)\n if (g <= minGlossiness) {\n discard;\n }\n#endif\n\n float reflectivity = (g - minGlossiness) / (1.0 - minGlossiness);\n\n vec3 N = normalize(normalAndGloss.rgb * 2.0 - 1.0);\n N = normalize((toViewSpace * vec4(N, 0.0)).xyz);\n\n vec4 projectedPos = vec4(v_Texcoord * 2.0 - 1.0, fetchDepth(gBufferTexture2, v_Texcoord), 1.0);\n vec4 pos = projectionInv * projectedPos;\n vec3 rayOrigin = pos.xyz / pos.w;\n vec3 V = -normalize(rayOrigin);\n\n float ndv = clamp(dot(N, V), 0.0, 1.0);\n float iterationCount;\n float jitter = rand(fract(v_Texcoord + jitterOffset));\n\n#ifdef PHYSICALLY_CORRECT\n vec4 color = vec4(vec3(0.0), 1.0);\n vec4 albedoMetalness = texture2D(gBufferTexture3, v_Texcoord);\n vec3 albedo = albedoMetalness.rgb;\n float m = albedoMetalness.a;\n vec3 diffuseColor = albedo * (1.0 - m);\n vec3 spec = mix(vec3(0.04), albedo, m);\n\n float jitter2 = rand(fract(v_Texcoord)) * float(TOTAL_SAMPLES);\n\n for (int i = 0; i < SAMPLE_PER_FRAME; i++) {\n vec3 H = importanceSampleNormalGGX(float(i) + jitter2, 1.0 - g, N);\n vec3 rayDir = normalize(reflect(-V, H));\n#else\n vec3 rayDir = normalize(reflect(-V, N));\n#endif\n vec2 hitPixel;\n vec3 hitPoint;\n\n bool intersect = traceScreenSpaceRay(rayOrigin, rayDir, jitter, hitPixel, hitPoint, iterationCount);\n\n float dist = distance(rayOrigin, hitPoint);\n\n vec3 hitNormal = texture2D(gBufferTexture1, hitPixel).rgb * 2.0 - 1.0;\n hitNormal = normalize((toViewSpace * vec4(hitNormal, 0.0)).xyz);\n#ifdef PHYSICALLY_CORRECT\n float ndl = clamp(dot(N, rayDir), 0.0, 1.0);\n float vdh = clamp(dot(V, H), 0.0, 1.0);\n float ndh = clamp(dot(N, H), 0.0, 1.0);\n vec3 litTexel = vec3(0.0);\n if (dot(hitNormal, rayDir) < 0.0 && intersect) {\n litTexel = texture2D(sourceTexture, hitPixel).rgb;\n litTexel *= pow(clamp(1.0 - dist / 200.0, 0.0, 1.0), 3.0);\n\n }\n else {\n #ifdef SPECULARCUBEMAP_ENABLED\n vec3 rayDirW = normalize(toWorldSpace * vec4(rayDir, 0.0)).rgb;\n litTexel = RGBMDecode(textureCubeLodEXT(specularCubemap, rayDirW, 0.0), 8.12).rgb * specularIntensity;\n#endif\n }\n color.rgb += ndl * litTexel * (\n F_Schlick(ndl, spec) * G_Smith(g, ndv, ndl) * vdh / (ndh * ndv + 0.001)\n );\n }\n color.rgb /= float(SAMPLE_PER_FRAME);\n#else\n #if !defined(SPECULARCUBEMAP_ENABLED)\n if (dot(hitNormal, rayDir) >= 0.0) {\n discard;\n }\n if (!intersect) {\n discard;\n }\n#endif\n float alpha = clamp(calculateAlpha(iterationCount, reflectivity, hitPixel, hitPoint, dist, rayDir), 0.0, 1.0);\n vec4 color = texture2D(sourceTexture, hitPixel);\n color.rgb *= alpha;\n\n#ifdef SPECULARCUBEMAP_ENABLED\n vec3 rayDirW = normalize(toWorldSpace * vec4(rayDir, 0.0)).rgb;\n alpha = alpha * (intersect ? 1.0 : 0.0);\n float bias = (1.0 -g) * 5.0;\n color.rgb += (1.0 - alpha)\n * RGBMDecode(textureCubeLodEXT(specularCubemap, rayDirW, bias), 8.12).rgb\n * specularIntensity;\n#endif\n\n#endif\n\n gl_FragColor = encodeHDR(color);\n}\n@end\n\n@export ecgl.ssr.blur\n\nuniform sampler2D texture;\nuniform sampler2D gBufferTexture1;\nuniform sampler2D gBufferTexture2;\nuniform mat4 projection;\nuniform float depthRange : 0.05;\n\nvarying vec2 v_Texcoord;\n\nuniform vec2 textureSize;\nuniform float blurSize : 1.0;\n\n#ifdef BLEND\n #ifdef SSAOTEX_ENABLED\nuniform sampler2D ssaoTex;\n #endif\nuniform sampler2D sourceTexture;\n#endif\n\nfloat getLinearDepth(vec2 coord)\n{\n float depth = texture2D(gBufferTexture2, coord).r * 2.0 - 1.0;\n return projection[3][2] / (depth * projection[2][3] - projection[2][2]);\n}\n\n@import clay.util.rgbm\n\n\nvoid main()\n{\n @import clay.compositor.kernel.gaussian_9\n\n vec4 centerNTexel = texture2D(gBufferTexture1, v_Texcoord);\n float g = centerNTexel.a;\n float maxBlurSize = clamp(1.0 - g, 0.0, 1.0) * blurSize;\n#ifdef VERTICAL\n vec2 off = vec2(0.0, maxBlurSize / textureSize.y);\n#else\n vec2 off = vec2(maxBlurSize / textureSize.x, 0.0);\n#endif\n\n vec2 coord = v_Texcoord;\n\n vec4 sum = vec4(0.0);\n float weightAll = 0.0;\n\n vec3 cN = centerNTexel.rgb * 2.0 - 1.0;\n float cD = getLinearDepth(v_Texcoord);\n for (int i = 0; i < 9; i++) {\n vec2 coord = clamp((float(i) - 4.0) * off + v_Texcoord, vec2(0.0), vec2(1.0));\n float w = gaussianKernel[i]\n * clamp(dot(cN, texture2D(gBufferTexture1, coord).rgb * 2.0 - 1.0), 0.0, 1.0);\n float d = getLinearDepth(coord);\n w *= (1.0 - smoothstep(abs(cD - d) / depthRange, 0.0, 1.0));\n\n weightAll += w;\n sum += decodeHDR(texture2D(texture, coord)) * w;\n }\n\n#ifdef BLEND\n float aoFactor = 1.0;\n #ifdef SSAOTEX_ENABLED\n aoFactor = texture2D(ssaoTex, v_Texcoord).r;\n #endif\n gl_FragColor = encodeHDR(\n sum / weightAll * aoFactor + decodeHDR(texture2D(sourceTexture, v_Texcoord))\n );\n#else\n gl_FragColor = encodeHDR(sum / weightAll);\n#endif\n}\n\n@end"; Shader.import(SSRGLSLCode); function SSRPass(opt) { opt = opt || {}; this._ssrPass = new Pass({ fragment: Shader.source("ecgl.ssr.main"), clearColor: [0, 0, 0, 0] }); this._blurPass1 = new Pass({ fragment: Shader.source("ecgl.ssr.blur"), clearColor: [0, 0, 0, 0] }); this._blurPass2 = new Pass({ fragment: Shader.source("ecgl.ssr.blur"), clearColor: [0, 0, 0, 0] }); this._blendPass = new Pass({ fragment: Shader.source("clay.compositor.blend") }); this._blendPass.material.disableTexturesAll(); this._blendPass.material.enableTexture(["texture1", "texture2"]); this._ssrPass.setUniform("gBufferTexture1", opt.normalTexture); this._ssrPass.setUniform("gBufferTexture2", opt.depthTexture); this._blurPass1.setUniform("gBufferTexture1", opt.normalTexture); this._blurPass1.setUniform("gBufferTexture2", opt.depthTexture); this._blurPass2.setUniform("gBufferTexture1", opt.normalTexture); this._blurPass2.setUniform("gBufferTexture2", opt.depthTexture); this._blurPass2.material.define("fragment", "VERTICAL"); this._blurPass2.material.define("fragment", "BLEND"); this._ssrTexture = new Texture2D({ type: Texture.HALF_FLOAT }); this._texture2 = new Texture2D({ type: Texture.HALF_FLOAT }); this._texture3 = new Texture2D({ type: Texture.HALF_FLOAT }); this._prevTexture = new Texture2D({ type: Texture.HALF_FLOAT }); this._currentTexture = new Texture2D({ type: Texture.HALF_FLOAT }); this._frameBuffer = new FrameBuffer({ depthBuffer: false }); this._normalDistribution = null; this._totalSamples = 256; this._samplePerFrame = 4; this._ssrPass.material.define("fragment", "SAMPLE_PER_FRAME", this._samplePerFrame); this._ssrPass.material.define("fragment", "TOTAL_SAMPLES", this._totalSamples); this._downScale = 1; } SSRPass.prototype.setAmbientCubemap = function(specularCubemap, specularIntensity) { this._ssrPass.material.set("specularCubemap", specularCubemap); this._ssrPass.material.set("specularIntensity", specularIntensity); var enableSpecularMap = specularCubemap && specularIntensity; this._ssrPass.material[enableSpecularMap ? "enableTexture" : "disableTexture"]("specularCubemap"); }; SSRPass.prototype.update = function(renderer, camera2, sourceTexture, frame) { var width = renderer.getWidth(); var height = renderer.getHeight(); var ssrTexture = this._ssrTexture; var texture2 = this._texture2; var texture3 = this._texture3; ssrTexture.width = this._prevTexture.width = this._currentTexture.width = width / this._downScale; ssrTexture.height = this._prevTexture.height = this._currentTexture.height = height / this._downScale; texture2.width = texture3.width = width; texture2.height = texture3.height = height; var frameBuffer = this._frameBuffer; var ssrPass = this._ssrPass; var blurPass1 = this._blurPass1; var blurPass2 = this._blurPass2; var blendPass = this._blendPass; var toViewSpace = new Matrix4(); var toWorldSpace = new Matrix4(); Matrix4.transpose(toViewSpace, camera2.worldTransform); Matrix4.transpose(toWorldSpace, camera2.viewMatrix); ssrPass.setUniform("sourceTexture", sourceTexture); ssrPass.setUniform("projection", camera2.projectionMatrix.array); ssrPass.setUniform("projectionInv", camera2.invProjectionMatrix.array); ssrPass.setUniform("toViewSpace", toViewSpace.array); ssrPass.setUniform("toWorldSpace", toWorldSpace.array); ssrPass.setUniform("nearZ", camera2.near); var percent = frame / this._totalSamples * this._samplePerFrame; ssrPass.setUniform("jitterOffset", percent); ssrPass.setUniform("sampleOffset", frame * this._samplePerFrame); blurPass1.setUniform("textureSize", [ssrTexture.width, ssrTexture.height]); blurPass2.setUniform("textureSize", [width, height]); blurPass2.setUniform("sourceTexture", sourceTexture); blurPass1.setUniform("projection", camera2.projectionMatrix.array); blurPass2.setUniform("projection", camera2.projectionMatrix.array); frameBuffer.attach(ssrTexture); frameBuffer.bind(renderer); ssrPass.render(renderer); if (this._physicallyCorrect) { frameBuffer.attach(this._currentTexture); blendPass.setUniform("texture1", this._prevTexture); blendPass.setUniform("texture2", ssrTexture); blendPass.material.set({ "weight1": frame >= 1 ? 0.95 : 0, "weight2": frame >= 1 ? 0.05 : 1 // weight1: frame >= 1 ? 1 : 0, // weight2: 1 }); blendPass.render(renderer); } frameBuffer.attach(texture2); blurPass1.setUniform("texture", this._physicallyCorrect ? this._currentTexture : ssrTexture); blurPass1.render(renderer); frameBuffer.attach(texture3); blurPass2.setUniform("texture", texture2); blurPass2.render(renderer); frameBuffer.unbind(renderer); if (this._physicallyCorrect) { var tmp = this._prevTexture; this._prevTexture = this._currentTexture; this._currentTexture = tmp; } }; SSRPass.prototype.getTargetTexture = function() { return this._texture3; }; SSRPass.prototype.setParameter = function(name, val) { if (name === "maxIteration") { this._ssrPass.material.define("fragment", "MAX_ITERATION", val); } else { this._ssrPass.setUniform(name, val); } }; SSRPass.prototype.setPhysicallyCorrect = function(isPhysicallyCorrect) { if (isPhysicallyCorrect) { if (!this._normalDistribution) { this._normalDistribution = cubemapUtil.generateNormalDistribution(64, this._totalSamples); } this._ssrPass.material.define("fragment", "PHYSICALLY_CORRECT"); this._ssrPass.material.set("normalDistribution", this._normalDistribution); this._ssrPass.material.set("normalDistributionSize", [64, this._totalSamples]); } else { this._ssrPass.material.undefine("fragment", "PHYSICALLY_CORRECT"); } this._physicallyCorrect = isPhysicallyCorrect; }; SSRPass.prototype.setSSAOTexture = function(texture) { var blendPass = this._blurPass2; if (texture) { blendPass.material.enableTexture("ssaoTex"); blendPass.material.set("ssaoTex", texture); } else { blendPass.material.disableTexture("ssaoTex"); } }; SSRPass.prototype.isFinished = function(frame) { if (this._physicallyCorrect) { return frame > this._totalSamples / this._samplePerFrame; } else { return true; } }; SSRPass.prototype.dispose = function(renderer) { this._ssrTexture.dispose(renderer); this._texture2.dispose(renderer); this._texture3.dispose(renderer); this._prevTexture.dispose(renderer); this._currentTexture.dispose(renderer); this._frameBuffer.dispose(renderer); }; const poissonKernel = [0, 0, -0.321585265978, -0.154972575841, 0.458126042375, 0.188473391593, 0.842080129861, 0.527766490688, 0.147304551086, -0.659453822776, -0.331943915203, -0.940619700594, 0.0479226680259, 0.54812163202, 0.701581552186, -0.709825561388, -0.295436780218, 0.940589268233, -0.901489676764, 0.237713156085, 0.973570876096, -0.109899459384, -0.866792314779, -0.451805525005, 0.330975007087, 0.800048655954, -0.344275183665, 0.381779221166, -0.386139432542, -0.437418421534, -0.576478634965, -0.0148463392551, 0.385798197415, -0.262426961053, -0.666302061145, 0.682427250835, -0.628010632582, -0.732836215494, 0.10163141741, -0.987658134403, 0.711995289051, -0.320024291314, 0.0296005138058, 0.950296523438, 0.0130612307608, -0.351024443122, -0.879596633704, -0.10478487883, 0.435712737232, 0.504254490347, 0.779203817497, 0.206477676721, 0.388264289969, -0.896736162545, -0.153106280781, -0.629203242522, -0.245517550697, 0.657969239148, 0.126830499058, 0.26862328493, -0.634888119007, -0.302301223431, 0.617074219636, 0.779817204925]; const normalGLSL = "@export ecgl.normal.vertex\n\n@import ecgl.common.transformUniforms\n\n@import ecgl.common.uv.header\n\n@import ecgl.common.attributes\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\n@import ecgl.common.normalMap.vertexHeader\n\n@import ecgl.common.vertexAnimation.header\n\nvoid main()\n{\n\n @import ecgl.common.vertexAnimation.main\n\n @import ecgl.common.uv.main\n\n v_Normal = normalize((worldInverseTranspose * vec4(normal, 0.0)).xyz);\n v_WorldPosition = (world * vec4(pos, 1.0)).xyz;\n\n @import ecgl.common.normalMap.vertexMain\n\n gl_Position = worldViewProjection * vec4(pos, 1.0);\n\n}\n\n\n@end\n\n\n@export ecgl.normal.fragment\n\n#define ROUGHNESS_CHANEL 0\n\nuniform bool useBumpMap;\nuniform bool useRoughnessMap;\nuniform bool doubleSide;\nuniform float roughness;\n\n@import ecgl.common.uv.fragmentHeader\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nuniform mat4 viewInverse : VIEWINVERSE;\n\n@import ecgl.common.normalMap.fragmentHeader\n@import ecgl.common.bumpMap.header\n\nuniform sampler2D roughnessMap;\n\nvoid main()\n{\n vec3 N = v_Normal;\n \n bool flipNormal = false;\n if (doubleSide) {\n vec3 eyePos = viewInverse[3].xyz;\n vec3 V = normalize(eyePos - v_WorldPosition);\n\n if (dot(N, V) < 0.0) {\n flipNormal = true;\n }\n }\n\n @import ecgl.common.normalMap.fragmentMain\n\n if (useBumpMap) {\n N = bumpNormal(v_WorldPosition, v_Normal, N);\n }\n\n float g = 1.0 - roughness;\n\n if (useRoughnessMap) {\n float g2 = 1.0 - texture2D(roughnessMap, v_DetailTexcoord)[ROUGHNESS_CHANEL];\n g = clamp(g2 + (g - 0.5) * 2.0, 0.0, 1.0);\n }\n\n if (flipNormal) {\n N = -N;\n }\n\n gl_FragColor.rgb = (N.xyz + 1.0) * 0.5;\n gl_FragColor.a = g;\n}\n@end"; Shader.import(normalGLSL); function attachTextureToSlot(renderer, program, symbol, texture, slot) { var gl = renderer.gl; program.setUniform(gl, "1i", symbol, slot); gl.activeTexture(gl.TEXTURE0 + slot); if (texture.isRenderable()) { texture.bind(renderer); } else { texture.unbind(renderer); } } function getBeforeRenderHook(renderer, defaultNormalMap, defaultBumpMap, defaultRoughnessMap, normalMaterial) { var previousNormalMap; var previousBumpMap; var previousRoughnessMap; var previousRenderable; var gl = renderer.gl; return function(renderable, normalMaterial2, prevNormalMaterial) { if (previousRenderable && previousRenderable.material === renderable.material) { return; } var material = renderable.material; var program = renderable.__program; var roughness = material.get("roughness"); if (roughness == null) { roughness = 1; } var normalMap = material.get("normalMap") || defaultNormalMap; var roughnessMap = material.get("roughnessMap"); var bumpMap = material.get("bumpMap"); var uvRepeat = material.get("uvRepeat"); var uvOffset = material.get("uvOffset"); var detailUvRepeat = material.get("detailUvRepeat"); var detailUvOffset = material.get("detailUvOffset"); var useBumpMap = !!bumpMap && material.isTextureEnabled("bumpMap"); var useRoughnessMap = !!roughnessMap && material.isTextureEnabled("roughnessMap"); var doubleSide = material.isDefined("fragment", "DOUBLE_SIDED"); bumpMap = bumpMap || defaultBumpMap; roughnessMap = roughnessMap || defaultRoughnessMap; if (prevNormalMaterial !== normalMaterial2) { normalMaterial2.set("normalMap", normalMap); normalMaterial2.set("bumpMap", bumpMap); normalMaterial2.set("roughnessMap", roughnessMap); normalMaterial2.set("useBumpMap", useBumpMap); normalMaterial2.set("useRoughnessMap", useRoughnessMap); normalMaterial2.set("doubleSide", doubleSide); uvRepeat != null && normalMaterial2.set("uvRepeat", uvRepeat); uvOffset != null && normalMaterial2.set("uvOffset", uvOffset); detailUvRepeat != null && normalMaterial2.set("detailUvRepeat", detailUvRepeat); detailUvOffset != null && normalMaterial2.set("detailUvOffset", detailUvOffset); normalMaterial2.set("roughness", roughness); } else { program.setUniform(gl, "1f", "roughness", roughness); if (previousNormalMap !== normalMap) { attachTextureToSlot(renderer, program, "normalMap", normalMap, 0); } if (previousBumpMap !== bumpMap && bumpMap) { attachTextureToSlot(renderer, program, "bumpMap", bumpMap, 1); } if (previousRoughnessMap !== roughnessMap && roughnessMap) { attachTextureToSlot(renderer, program, "roughnessMap", roughnessMap, 2); } if (uvRepeat != null) { program.setUniform(gl, "2f", "uvRepeat", uvRepeat); } if (uvOffset != null) { program.setUniform(gl, "2f", "uvOffset", uvOffset); } if (detailUvRepeat != null) { program.setUniform(gl, "2f", "detailUvRepeat", detailUvRepeat); } if (detailUvOffset != null) { program.setUniform(gl, "2f", "detailUvOffset", detailUvOffset); } program.setUniform(gl, "1i", "useBumpMap", +useBumpMap); program.setUniform(gl, "1i", "useRoughnessMap", +useRoughnessMap); program.setUniform(gl, "1i", "doubleSide", +doubleSide); } previousNormalMap = normalMap; previousBumpMap = bumpMap; previousRoughnessMap = roughnessMap; previousRenderable = renderable; }; } function NormalPass(opt) { this._depthTex = new Texture2D({ format: Texture.DEPTH_COMPONENT, type: Texture.UNSIGNED_INT }); this._normalTex = new Texture2D({ type: Texture.HALF_FLOAT }); this._framebuffer = new FrameBuffer(); this._framebuffer.attach(this._normalTex); this._framebuffer.attach(this._depthTex, FrameBuffer.DEPTH_ATTACHMENT); this._normalMaterial = new Material({ shader: new Shader(Shader.source("ecgl.normal.vertex"), Shader.source("ecgl.normal.fragment")) }); this._normalMaterial.enableTexture(["normalMap", "bumpMap", "roughnessMap"]); this._defaultNormalMap = textureUtil.createBlank("#000"); this._defaultBumpMap = textureUtil.createBlank("#000"); this._defaultRoughessMap = textureUtil.createBlank("#000"); this._debugPass = new Pass({ fragment: Shader.source("clay.compositor.output") }); this._debugPass.setUniform("texture", this._normalTex); this._debugPass.material.undefine("fragment", "OUTPUT_ALPHA"); } NormalPass.prototype.getDepthTexture = function() { return this._depthTex; }; NormalPass.prototype.getNormalTexture = function() { return this._normalTex; }; NormalPass.prototype.update = function(renderer, scene, camera2) { var width = renderer.getWidth(); var height = renderer.getHeight(); var depthTexture = this._depthTex; var normalTexture = this._normalTex; var normalMaterial = this._normalMaterial; depthTexture.width = width; depthTexture.height = height; normalTexture.width = width; normalTexture.height = height; var opaqueList = scene.getRenderList(camera2).opaque; this._framebuffer.bind(renderer); renderer.gl.clearColor(0, 0, 0, 0); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT | renderer.gl.DEPTH_BUFFER_BIT); renderer.gl.disable(renderer.gl.BLEND); renderer.renderPass(opaqueList, camera2, { getMaterial: function() { return normalMaterial; }, ifRender: function(object) { return object.renderNormal; }, beforeRender: getBeforeRenderHook(renderer, this._defaultNormalMap, this._defaultBumpMap, this._defaultRoughessMap, this._normalMaterial), sort: renderer.opaqueSortCompare }); this._framebuffer.unbind(renderer); }; NormalPass.prototype.renderDebug = function(renderer) { this._debugPass.render(renderer); }; NormalPass.prototype.dispose = function(renderer) { this._depthTex.dispose(renderer); this._normalTex.dispose(renderer); }; function EdgePass(opt) { opt = opt || {}; this._edgePass = new Pass({ fragment: Shader.source("ecgl.edge") }); this._edgePass.setUniform("normalTexture", opt.normalTexture); this._edgePass.setUniform("depthTexture", opt.depthTexture); this._targetTexture = new Texture2D({ type: Texture.HALF_FLOAT }); this._frameBuffer = new FrameBuffer(); this._frameBuffer.attach(this._targetTexture); } EdgePass.prototype.update = function(renderer, camera2, sourceTexture, frame) { var width = renderer.getWidth(); var height = renderer.getHeight(); var texture = this._targetTexture; texture.width = width; texture.height = height; var frameBuffer = this._frameBuffer; frameBuffer.bind(renderer); this._edgePass.setUniform("projectionInv", camera2.invProjectionMatrix.array); this._edgePass.setUniform("textureSize", [width, height]); this._edgePass.setUniform("texture", sourceTexture); this._edgePass.render(renderer); frameBuffer.unbind(renderer); }; EdgePass.prototype.getTargetTexture = function() { return this._targetTexture; }; EdgePass.prototype.setParameter = function(name, val) { this._edgePass.setUniform(name, val); }; EdgePass.prototype.dispose = function(renderer) { this._targetTexture.dispose(renderer); this._frameBuffer.dispose(renderer); }; const effectJson = { "nodes": [{ "name": "source", "type": "texture", "outputs": { "color": {} } }, { "name": "source_half", "shader": "#source(clay.compositor.downsample)", "inputs": { "texture": "source" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 2)", "height": "expr(height * 1.0 / 2)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0, height * 1.0] )" } }, { "name": "bright", "shader": "#source(clay.compositor.bright)", "inputs": { "texture": "source_half" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 2)", "height": "expr(height * 1.0 / 2)", "type": "HALF_FLOAT" } } }, "parameters": { "threshold": 2, "scale": 4, "textureSize": "expr([width * 1.0 / 2, height / 2])" } }, { "name": "bright_downsample_4", "shader": "#source(clay.compositor.downsample)", "inputs": { "texture": "bright" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 4)", "height": "expr(height * 1.0 / 4)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0 / 2, height / 2] )" } }, { "name": "bright_downsample_8", "shader": "#source(clay.compositor.downsample)", "inputs": { "texture": "bright_downsample_4" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 8)", "height": "expr(height * 1.0 / 8)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0 / 4, height / 4] )" } }, { "name": "bright_downsample_16", "shader": "#source(clay.compositor.downsample)", "inputs": { "texture": "bright_downsample_8" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 16)", "height": "expr(height * 1.0 / 16)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0 / 8, height / 8] )" } }, { "name": "bright_downsample_32", "shader": "#source(clay.compositor.downsample)", "inputs": { "texture": "bright_downsample_16" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 32)", "height": "expr(height * 1.0 / 32)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0 / 16, height / 16] )" } }, { "name": "bright_upsample_16_blur_h", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_downsample_32" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 16)", "height": "expr(height * 1.0 / 16)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 0, "textureSize": "expr( [width * 1.0 / 32, height / 32] )" } }, { "name": "bright_upsample_16_blur_v", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_upsample_16_blur_h" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 16)", "height": "expr(height * 1.0 / 16)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 1, "textureSize": "expr( [width * 1.0 / 16, height * 1.0 / 16] )" } }, { "name": "bright_upsample_8_blur_h", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_downsample_16" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 8)", "height": "expr(height * 1.0 / 8)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 0, "textureSize": "expr( [width * 1.0 / 16, height * 1.0 / 16] )" } }, { "name": "bright_upsample_8_blur_v", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_upsample_8_blur_h" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 8)", "height": "expr(height * 1.0 / 8)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 1, "textureSize": "expr( [width * 1.0 / 8, height * 1.0 / 8] )" } }, { "name": "bright_upsample_8_blend", "shader": "#source(clay.compositor.blend)", "inputs": { "texture1": "bright_upsample_8_blur_v", "texture2": "bright_upsample_16_blur_v" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 8)", "height": "expr(height * 1.0 / 8)", "type": "HALF_FLOAT" } } }, "parameters": { "weight1": 0.3, "weight2": 0.7 } }, { "name": "bright_upsample_4_blur_h", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_downsample_8" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 4)", "height": "expr(height * 1.0 / 4)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 0, "textureSize": "expr( [width * 1.0 / 8, height * 1.0 / 8] )" } }, { "name": "bright_upsample_4_blur_v", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_upsample_4_blur_h" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 4)", "height": "expr(height * 1.0 / 4)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 1, "textureSize": "expr( [width * 1.0 / 4, height * 1.0 / 4] )" } }, { "name": "bright_upsample_4_blend", "shader": "#source(clay.compositor.blend)", "inputs": { "texture1": "bright_upsample_4_blur_v", "texture2": "bright_upsample_8_blend" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 4)", "height": "expr(height * 1.0 / 4)", "type": "HALF_FLOAT" } } }, "parameters": { "weight1": 0.3, "weight2": 0.7 } }, { "name": "bright_upsample_2_blur_h", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_downsample_4" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 2)", "height": "expr(height * 1.0 / 2)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 0, "textureSize": "expr( [width * 1.0 / 4, height * 1.0 / 4] )" } }, { "name": "bright_upsample_2_blur_v", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_upsample_2_blur_h" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 2)", "height": "expr(height * 1.0 / 2)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 1, "textureSize": "expr( [width * 1.0 / 2, height * 1.0 / 2] )" } }, { "name": "bright_upsample_2_blend", "shader": "#source(clay.compositor.blend)", "inputs": { "texture1": "bright_upsample_2_blur_v", "texture2": "bright_upsample_4_blend" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0 / 2)", "height": "expr(height * 1.0 / 2)", "type": "HALF_FLOAT" } } }, "parameters": { "weight1": 0.3, "weight2": 0.7 } }, { "name": "bright_upsample_full_blur_h", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 0, "textureSize": "expr( [width * 1.0 / 2, height * 1.0 / 2] )" } }, { "name": "bright_upsample_full_blur_v", "shader": "#source(clay.compositor.gaussian_blur)", "inputs": { "texture": "bright_upsample_full_blur_h" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)", "type": "HALF_FLOAT" } } }, "parameters": { "blurSize": 1, "blurDir": 1, "textureSize": "expr( [width * 1.0, height * 1.0] )" } }, { "name": "bloom_composite", "shader": "#source(clay.compositor.blend)", "inputs": { "texture1": "bright_upsample_full_blur_v", "texture2": "bright_upsample_2_blend" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)", "type": "HALF_FLOAT" } } }, "parameters": { "weight1": 0.3, "weight2": 0.7 } }, { "name": "coc", "shader": "#source(ecgl.dof.coc)", "outputs": { "color": { "parameters": { "minFilter": "NEAREST", "magFilter": "NEAREST", "width": "expr(width * 1.0)", "height": "expr(height * 1.0)" } } }, "parameters": { "focalDist": 50, "focalRange": 30 } }, { "name": "dof_far_blur", "shader": "#source(ecgl.dof.diskBlur)", "inputs": { "texture": "source", "coc": "coc" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0, height * 1.0] )" } }, { "name": "dof_near_blur", "shader": "#source(ecgl.dof.diskBlur)", "inputs": { "texture": "source", "coc": "coc" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)", "type": "HALF_FLOAT" } } }, "parameters": { "textureSize": "expr( [width * 1.0, height * 1.0] )" }, "defines": { "BLUR_NEARFIELD": null } }, { "name": "dof_coc_blur", "shader": "#source(ecgl.dof.diskBlur)", "inputs": { "texture": "coc" }, "outputs": { "color": { "parameters": { "minFilter": "NEAREST", "magFilter": "NEAREST", "width": "expr(width * 1.0)", "height": "expr(height * 1.0)" } } }, "parameters": { "textureSize": "expr( [width * 1.0, height * 1.0] )" }, "defines": { "BLUR_COC": null } }, { "name": "dof_composite", "shader": "#source(ecgl.dof.composite)", "inputs": { "original": "source", "blurred": "dof_far_blur", "nearfield": "dof_near_blur", "coc": "coc", "nearcoc": "dof_coc_blur" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)", "type": "HALF_FLOAT" } } } }, { "name": "composite", "shader": "#source(clay.compositor.hdr.composite)", "inputs": { "texture": "source", "bloom": "bloom_composite" }, "outputs": { "color": { "parameters": { "width": "expr(width * 1.0)", "height": "expr(height * 1.0)" } } }, "defines": { // Images are all premultiplied alpha before composite because of blending. // 'PREMULTIPLY_ALPHA': null, // 'DEBUG': 2 } }, { "name": "FXAA", "shader": "#source(clay.compositor.fxaa)", "inputs": { "texture": "composite" } }] }; const DOFCode = "@export ecgl.dof.coc\n\nuniform sampler2D depth;\n\nuniform float zNear: 0.1;\nuniform float zFar: 2000;\n\nuniform float focalDistance: 3;\nuniform float focalRange: 1;\nuniform float focalLength: 30;\nuniform float fstop: 2.8;\n\nvarying vec2 v_Texcoord;\n\n@import clay.util.encode_float\n\nvoid main()\n{\n float z = texture2D(depth, v_Texcoord).r * 2.0 - 1.0;\n\n float dist = 2.0 * zNear * zFar / (zFar + zNear - z * (zFar - zNear));\n\n float aperture = focalLength / fstop;\n\n float coc;\n\n float uppper = focalDistance + focalRange;\n float lower = focalDistance - focalRange;\n if (dist <= uppper && dist >= lower) {\n coc = 0.5;\n }\n else {\n float focalAdjusted = dist > uppper ? uppper : lower;\n\n coc = abs(aperture * (focalLength * (dist - focalAdjusted)) / (dist * (focalAdjusted - focalLength)));\n coc = clamp(coc, 0.0, 2.0) / 2.00001;\n\n if (dist < lower) {\n coc = -coc;\n }\n coc = coc * 0.5 + 0.5;\n }\n\n gl_FragColor = encodeFloat(coc);\n}\n@end\n\n\n@export ecgl.dof.composite\n\n#define DEBUG 0\n\nuniform sampler2D original;\nuniform sampler2D blurred;\nuniform sampler2D nearfield;\nuniform sampler2D coc;\nuniform sampler2D nearcoc;\nvarying vec2 v_Texcoord;\n\n@import clay.util.rgbm\n@import clay.util.float\n\nvoid main()\n{\n vec4 blurredColor = texture2D(blurred, v_Texcoord);\n vec4 originalColor = texture2D(original, v_Texcoord);\n\n float fCoc = decodeFloat(texture2D(coc, v_Texcoord));\n\n fCoc = abs(fCoc * 2.0 - 1.0);\n\n float weight = smoothstep(0.0, 1.0, fCoc);\n \n#ifdef NEARFIELD_ENABLED\n vec4 nearfieldColor = texture2D(nearfield, v_Texcoord);\n float fNearCoc = decodeFloat(texture2D(nearcoc, v_Texcoord));\n fNearCoc = abs(fNearCoc * 2.0 - 1.0);\n\n gl_FragColor = encodeHDR(\n mix(\n nearfieldColor, mix(originalColor, blurredColor, weight),\n pow(1.0 - fNearCoc, 4.0)\n )\n );\n#else\n gl_FragColor = encodeHDR(mix(originalColor, blurredColor, weight));\n#endif\n\n}\n\n@end\n\n\n\n@export ecgl.dof.diskBlur\n\n#define POISSON_KERNEL_SIZE 16;\n\nuniform sampler2D texture;\nuniform sampler2D coc;\nvarying vec2 v_Texcoord;\n\nuniform float blurRadius : 10.0;\nuniform vec2 textureSize : [512.0, 512.0];\n\nuniform vec2 poissonKernel[POISSON_KERNEL_SIZE];\n\nuniform float percent;\n\nfloat nrand(const in vec2 n) {\n return fract(sin(dot(n.xy ,vec2(12.9898,78.233))) * 43758.5453);\n}\n\n@import clay.util.rgbm\n@import clay.util.float\n\n\nvoid main()\n{\n vec2 offset = blurRadius / textureSize;\n\n float rnd = 6.28318 * nrand(v_Texcoord + 0.07 * percent );\n float cosa = cos(rnd);\n float sina = sin(rnd);\n vec4 basis = vec4(cosa, -sina, sina, cosa);\n\n#if !defined(BLUR_NEARFIELD) && !defined(BLUR_COC)\n offset *= abs(decodeFloat(texture2D(coc, v_Texcoord)) * 2.0 - 1.0);\n#endif\n\n#ifdef BLUR_COC\n float cocSum = 0.0;\n#else\n vec4 color = vec4(0.0);\n#endif\n\n\n float weightSum = 0.0;\n\n for (int i = 0; i < POISSON_KERNEL_SIZE; i++) {\n vec2 ofs = poissonKernel[i];\n\n ofs = vec2(dot(ofs, basis.xy), dot(ofs, basis.zw));\n\n vec2 uv = v_Texcoord + ofs * offset;\n vec4 texel = texture2D(texture, uv);\n\n float w = 1.0;\n#ifdef BLUR_COC\n float fCoc = decodeFloat(texel) * 2.0 - 1.0;\n cocSum += clamp(fCoc, -1.0, 0.0) * w;\n#else\n texel = texel;\n #if !defined(BLUR_NEARFIELD)\n float fCoc = decodeFloat(texture2D(coc, uv)) * 2.0 - 1.0;\n w *= abs(fCoc);\n #endif\n texel.rgb *= texel.a;\n color += texel * w;\n#endif\n\n weightSum += w;\n }\n\n#ifdef BLUR_COC\n gl_FragColor = encodeFloat(clamp(cocSum / weightSum, -1.0, 0.0) * 0.5 + 0.5);\n#else\n color /= weightSum;\n color.rgb /= (color.a + 0.0001);\n gl_FragColor = color;\n#endif\n}\n\n@end"; const edgeCode = "@export ecgl.edge\n\nuniform sampler2D texture;\n\nuniform sampler2D normalTexture;\nuniform sampler2D depthTexture;\n\nuniform mat4 projectionInv;\n\nuniform vec2 textureSize;\n\nuniform vec4 edgeColor: [0,0,0,0.8];\n\nvarying vec2 v_Texcoord;\n\nvec3 packColor(vec2 coord) {\n float z = texture2D(depthTexture, coord).r * 2.0 - 1.0;\n vec4 p = vec4(v_Texcoord * 2.0 - 1.0, z, 1.0);\n vec4 p4 = projectionInv * p;\n\n return vec3(\n texture2D(normalTexture, coord).rg,\n -p4.z / p4.w / 5.0\n );\n}\n\nvoid main() {\n vec2 cc = v_Texcoord;\n vec3 center = packColor(cc);\n\n float size = clamp(1.0 - (center.z - 10.0) / 100.0, 0.0, 1.0) * 0.5;\n float dx = size / textureSize.x;\n float dy = size / textureSize.y;\n\n vec2 coord;\n vec3 topLeft = packColor(cc+vec2(-dx, -dy));\n vec3 top = packColor(cc+vec2(0.0, -dy));\n vec3 topRight = packColor(cc+vec2(dx, -dy));\n vec3 left = packColor(cc+vec2(-dx, 0.0));\n vec3 right = packColor(cc+vec2(dx, 0.0));\n vec3 bottomLeft = packColor(cc+vec2(-dx, dy));\n vec3 bottom = packColor(cc+vec2(0.0, dy));\n vec3 bottomRight = packColor(cc+vec2(dx, dy));\n\n vec3 v = -topLeft-2.0*top-topRight+bottomLeft+2.0*bottom+bottomRight;\n vec3 h = -bottomLeft-2.0*left-topLeft+bottomRight+2.0*right+topRight;\n\n float edge = sqrt(dot(h, h) + dot(v, v));\n\n edge = smoothstep(0.8, 1.0, edge);\n\n gl_FragColor = mix(texture2D(texture, v_Texcoord), vec4(edgeColor.rgb, 1.0), edgeColor.a * edge);\n}\n@end"; Shader["import"](blurCode); Shader["import"](lutCode); Shader["import"](outputCode); Shader["import"](brightCode); Shader["import"](downsampleCode); Shader["import"](upsampleCode); Shader["import"](hdrCode); Shader["import"](blendCode); Shader["import"](fxaaCode); Shader["import"](DOFCode); Shader["import"](edgeCode); function makeCommonOutputs(getWidth, getHeight) { return { color: { parameters: { width: getWidth, height: getHeight } } }; } var FINAL_NODES_CHAIN = ["composite", "FXAA"]; function EffectCompositor() { this._width; this._height; this._dpr; this._sourceTexture = new Texture2D({ type: Texture.HALF_FLOAT }); this._depthTexture = new Texture2D({ format: Texture.DEPTH_COMPONENT, type: Texture.UNSIGNED_INT }); this._framebuffer = new FrameBuffer(); this._framebuffer.attach(this._sourceTexture); this._framebuffer.attach(this._depthTexture, FrameBuffer.DEPTH_ATTACHMENT); this._normalPass = new NormalPass(); this._compositor = createCompositor(effectJson); var sourceNode = this._compositor.getNodeByName("source"); sourceNode.texture = this._sourceTexture; var cocNode = this._compositor.getNodeByName("coc"); this._sourceNode = sourceNode; this._cocNode = cocNode; this._compositeNode = this._compositor.getNodeByName("composite"); this._fxaaNode = this._compositor.getNodeByName("FXAA"); this._dofBlurNodes = ["dof_far_blur", "dof_near_blur", "dof_coc_blur"].map(function(name) { return this._compositor.getNodeByName(name); }, this); this._dofBlurKernel = 0; this._dofBlurKernelSize = new Float32Array(0); this._finalNodesChain = FINAL_NODES_CHAIN.map(function(name) { return this._compositor.getNodeByName(name); }, this); var gBufferObj = { normalTexture: this._normalPass.getNormalTexture(), depthTexture: this._normalPass.getDepthTexture() }; this._ssaoPass = new SSAOPass(gBufferObj); this._ssrPass = new SSRPass(gBufferObj); this._edgePass = new EdgePass(gBufferObj); } EffectCompositor.prototype.resize = function(width, height, dpr) { dpr = dpr || 1; var width = width * dpr; var height = height * dpr; var sourceTexture = this._sourceTexture; var depthTexture = this._depthTexture; sourceTexture.width = width; sourceTexture.height = height; depthTexture.width = width; depthTexture.height = height; var rendererMock = { getWidth: function() { return width; }, getHeight: function() { return height; }, getDevicePixelRatio: function() { return dpr; } }; function wrapCallback(obj, key) { if (typeof obj[key] === "function") { var oldFunc = obj[key].__original || obj[key]; obj[key] = function(renderer) { return oldFunc.call(this, rendererMock); }; obj[key].__original = oldFunc; } } this._compositor.nodes.forEach(function(node) { for (var outKey in node.outputs) { var parameters = node.outputs[outKey].parameters; if (parameters) { wrapCallback(parameters, "width"); wrapCallback(parameters, "height"); } } for (var paramKey in node.parameters) { wrapCallback(node.parameters, paramKey); } }); this._width = width; this._height = height; this._dpr = dpr; }; EffectCompositor.prototype.getWidth = function() { return this._width; }; EffectCompositor.prototype.getHeight = function() { return this._height; }; EffectCompositor.prototype._ifRenderNormalPass = function() { return this._enableSSAO || this._enableEdge || this._enableSSR; }; EffectCompositor.prototype._getPrevNode = function(node) { var idx = FINAL_NODES_CHAIN.indexOf(node.name) - 1; var prevNode = this._finalNodesChain[idx]; while (prevNode && !this._compositor.getNodeByName(prevNode.name)) { idx -= 1; prevNode = this._finalNodesChain[idx]; } return prevNode; }; EffectCompositor.prototype._getNextNode = function(node) { var idx = FINAL_NODES_CHAIN.indexOf(node.name) + 1; var nextNode = this._finalNodesChain[idx]; while (nextNode && !this._compositor.getNodeByName(nextNode.name)) { idx += 1; nextNode = this._finalNodesChain[idx]; } return nextNode; }; EffectCompositor.prototype._addChainNode = function(node) { var prevNode = this._getPrevNode(node); var nextNode = this._getNextNode(node); if (!prevNode) { return; } node.inputs.texture = prevNode.name; if (nextNode) { node.outputs = makeCommonOutputs(this.getWidth.bind(this), this.getHeight.bind(this)); nextNode.inputs.texture = node.name; } else { node.outputs = null; } this._compositor.addNode(node); }; EffectCompositor.prototype._removeChainNode = function(node) { var prevNode = this._getPrevNode(node); var nextNode = this._getNextNode(node); if (!prevNode) { return; } if (nextNode) { prevNode.outputs = makeCommonOutputs(this.getWidth.bind(this), this.getHeight.bind(this)); nextNode.inputs.texture = prevNode.name; } else { prevNode.outputs = null; } this._compositor.removeNode(node); }; EffectCompositor.prototype.updateNormal = function(renderer, scene, camera2, frame) { if (this._ifRenderNormalPass()) { this._normalPass.update(renderer, scene, camera2); } }; EffectCompositor.prototype.updateSSAO = function(renderer, scene, camera2, frame) { this._ssaoPass.update(renderer, camera2, frame); }; EffectCompositor.prototype.enableSSAO = function() { this._enableSSAO = true; }; EffectCompositor.prototype.disableSSAO = function() { this._enableSSAO = false; }; EffectCompositor.prototype.enableSSR = function() { this._enableSSR = true; }; EffectCompositor.prototype.disableSSR = function() { this._enableSSR = false; }; EffectCompositor.prototype.getSSAOTexture = function() { return this._ssaoPass.getTargetTexture(); }; EffectCompositor.prototype.getSourceFrameBuffer = function() { return this._framebuffer; }; EffectCompositor.prototype.getSourceTexture = function() { return this._sourceTexture; }; EffectCompositor.prototype.disableFXAA = function() { this._removeChainNode(this._fxaaNode); }; EffectCompositor.prototype.enableFXAA = function() { this._addChainNode(this._fxaaNode); }; EffectCompositor.prototype.enableBloom = function() { this._compositeNode.inputs.bloom = "bloom_composite"; this._compositor.dirty(); }; EffectCompositor.prototype.disableBloom = function() { this._compositeNode.inputs.bloom = null; this._compositor.dirty(); }; EffectCompositor.prototype.enableDOF = function() { this._compositeNode.inputs.texture = "dof_composite"; this._compositor.dirty(); }; EffectCompositor.prototype.disableDOF = function() { this._compositeNode.inputs.texture = "source"; this._compositor.dirty(); }; EffectCompositor.prototype.enableColorCorrection = function() { this._compositeNode.define("COLOR_CORRECTION"); this._enableColorCorrection = true; }; EffectCompositor.prototype.disableColorCorrection = function() { this._compositeNode.undefine("COLOR_CORRECTION"); this._enableColorCorrection = false; }; EffectCompositor.prototype.enableEdge = function() { this._enableEdge = true; }; EffectCompositor.prototype.disableEdge = function() { this._enableEdge = false; }; EffectCompositor.prototype.setBloomIntensity = function(value) { this._compositeNode.setParameter("bloomIntensity", value); }; EffectCompositor.prototype.setSSAOParameter = function(name, value) { switch (name) { case "quality": var kernelSize = { low: 6, medium: 12, high: 32, ultra: 62 }[value] || 12; this._ssaoPass.setParameter("kernelSize", kernelSize); break; case "radius": this._ssaoPass.setParameter(name, value); this._ssaoPass.setParameter("bias", value / 200); break; case "intensity": this._ssaoPass.setParameter(name, value); break; } }; EffectCompositor.prototype.setDOFParameter = function(name, value) { switch (name) { case "focalDistance": case "focalRange": case "fstop": this._cocNode.setParameter(name, value); break; case "blurRadius": for (var i = 0; i < this._dofBlurNodes.length; i++) { this._dofBlurNodes[i].setParameter("blurRadius", value); } break; case "quality": var kernelSize = { low: 4, medium: 8, high: 16, ultra: 32 }[value] || 8; this._dofBlurKernelSize = kernelSize; for (var i = 0; i < this._dofBlurNodes.length; i++) { this._dofBlurNodes[i].pass.material.define("POISSON_KERNEL_SIZE", kernelSize); } this._dofBlurKernel = new Float32Array(kernelSize * 2); break; } }; EffectCompositor.prototype.setSSRParameter = function(name, value) { if (value == null) { return; } switch (name) { case "quality": var maxIteration = { low: 10, medium: 15, high: 30, ultra: 80 }[value] || 20; var pixelStride = { low: 32, medium: 16, high: 8, ultra: 4 }[value] || 16; this._ssrPass.setParameter("maxIteration", maxIteration); this._ssrPass.setParameter("pixelStride", pixelStride); break; case "maxRoughness": this._ssrPass.setParameter("minGlossiness", Math.max(Math.min(1 - value, 1), 0)); break; case "physical": this.setPhysicallyCorrectSSR(value); break; default: console.warn("Unkown SSR parameter " + name); } }; EffectCompositor.prototype.setPhysicallyCorrectSSR = function(physical) { this._ssrPass.setPhysicallyCorrect(physical); }; EffectCompositor.prototype.setEdgeColor = function(value) { var color = graphicGL.parseColor(value); this._edgePass.setParameter("edgeColor", color); }; EffectCompositor.prototype.setExposure = function(value) { this._compositeNode.setParameter("exposure", Math.pow(2, value)); }; EffectCompositor.prototype.setColorLookupTexture = function(image, api) { this._compositeNode.pass.material.setTextureImage("lut", this._enableColorCorrection ? image : "none", api, { minFilter: graphicGL.Texture.NEAREST, magFilter: graphicGL.Texture.NEAREST, flipY: false }); }; EffectCompositor.prototype.setColorCorrection = function(type, value) { this._compositeNode.setParameter(type, value); }; EffectCompositor.prototype.isSSREnabled = function() { return this._enableSSR; }; EffectCompositor.prototype.composite = function(renderer, scene, camera2, framebuffer, frame) { var sourceTexture = this._sourceTexture; var targetTexture = sourceTexture; if (this._enableEdge) { this._edgePass.update(renderer, camera2, sourceTexture, frame); sourceTexture = targetTexture = this._edgePass.getTargetTexture(); } if (this._enableSSR) { this._ssrPass.update(renderer, camera2, sourceTexture, frame); targetTexture = this._ssrPass.getTargetTexture(); this._ssrPass.setSSAOTexture(this._enableSSAO ? this._ssaoPass.getTargetTexture() : null); } this._sourceNode.texture = targetTexture; this._cocNode.setParameter("depth", this._depthTexture); var blurKernel = this._dofBlurKernel; var blurKernelSize = this._dofBlurKernelSize; var frameAll = Math.floor(poissonKernel.length / 2 / blurKernelSize); var kernelOffset = frame % frameAll; for (var i = 0; i < blurKernelSize * 2; i++) { blurKernel[i] = poissonKernel[i + kernelOffset * blurKernelSize * 2]; } for (var i = 0; i < this._dofBlurNodes.length; i++) { this._dofBlurNodes[i].setParameter("percent", frame / 30); this._dofBlurNodes[i].setParameter("poissonKernel", blurKernel); } this._cocNode.setParameter("zNear", camera2.near); this._cocNode.setParameter("zFar", camera2.far); this._compositor.render(renderer, framebuffer); }; EffectCompositor.prototype.dispose = function(renderer) { this._sourceTexture.dispose(renderer); this._depthTexture.dispose(renderer); this._framebuffer.dispose(renderer); this._compositor.dispose(renderer); this._normalPass.dispose(renderer); this._ssaoPass.dispose(renderer); }; function TemporalSuperSampling(frames) { var haltonSequence = []; for (var i = 0; i < 30; i++) { haltonSequence.push([halton(i, 2), halton(i, 3)]); } this._haltonSequence = haltonSequence; this._frame = 0; this._sourceTex = new Texture2D(); this._sourceFb = new FrameBuffer(); this._sourceFb.attach(this._sourceTex); this._prevFrameTex = new Texture2D(); this._outputTex = new Texture2D(); var blendPass = this._blendPass = new Pass({ fragment: Shader.source("clay.compositor.blend") }); blendPass.material.disableTexturesAll(); blendPass.material.enableTexture(["texture1", "texture2"]); this._blendFb = new FrameBuffer({ depthBuffer: false }); this._outputPass = new Pass({ fragment: Shader.source("clay.compositor.output"), // TODO, alpha is premultiplied? blendWithPrevious: true }); this._outputPass.material.define("fragment", "OUTPUT_ALPHA"); this._outputPass.material.blend = function(_gl) { _gl.blendEquationSeparate(_gl.FUNC_ADD, _gl.FUNC_ADD); _gl.blendFuncSeparate(_gl.ONE, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA); }; } TemporalSuperSampling.prototype = { constructor: TemporalSuperSampling, /** * Jitter camera projectionMatrix * @parma {clay.Renderer} renderer * @param {clay.Camera} camera */ jitterProjection: function(renderer, camera2) { var viewport = renderer.viewport; var dpr = viewport.devicePixelRatio || renderer.getDevicePixelRatio(); var width = viewport.width * dpr; var height = viewport.height * dpr; var offset = this._haltonSequence[this._frame % this._haltonSequence.length]; var translationMat = new Matrix4(); translationMat.array[12] = (offset[0] * 2 - 1) / width; translationMat.array[13] = (offset[1] * 2 - 1) / height; Matrix4.mul(camera2.projectionMatrix, translationMat, camera2.projectionMatrix); Matrix4.invert(camera2.invProjectionMatrix, camera2.projectionMatrix); }, /** * Reset accumulating frame */ resetFrame: function() { this._frame = 0; }, /** * Return current frame */ getFrame: function() { return this._frame; }, /** * Get source framebuffer for usage */ getSourceFrameBuffer: function() { return this._sourceFb; }, getOutputTexture: function() { return this._outputTex; }, resize: function(width, height) { this._prevFrameTex.width = width; this._prevFrameTex.height = height; this._outputTex.width = width; this._outputTex.height = height; this._sourceTex.width = width; this._sourceTex.height = height; this._prevFrameTex.dirty(); this._outputTex.dirty(); this._sourceTex.dirty(); }, isFinished: function() { return this._frame >= this._haltonSequence.length; }, render: function(renderer, sourceTex, notOutput) { var blendPass = this._blendPass; if (this._frame === 0) { blendPass.setUniform("weight1", 0); blendPass.setUniform("weight2", 1); } else { blendPass.setUniform("weight1", 0.9); blendPass.setUniform("weight2", 0.1); } blendPass.setUniform("texture1", this._prevFrameTex); blendPass.setUniform("texture2", sourceTex || this._sourceTex); this._blendFb.attach(this._outputTex); this._blendFb.bind(renderer); blendPass.render(renderer); this._blendFb.unbind(renderer); if (!notOutput) { this._outputPass.setUniform("texture", this._outputTex); this._outputPass.render(renderer); } var tmp = this._prevFrameTex; this._prevFrameTex = this._outputTex; this._outputTex = tmp; this._frame++; }, dispose: function(renderer) { this._sourceFb.dispose(renderer); this._blendFb.dispose(renderer); this._prevFrameTex.dispose(renderer); this._outputTex.dispose(renderer); this._sourceTex.dispose(renderer); this._outputPass.dispose(renderer); this._blendPass.dispose(renderer); } }; function ViewGL(projection) { projection = projection || "perspective"; this.layer = null; this.scene = new Scene(); this.rootNode = this.scene; this.viewport = { x: 0, y: 0, width: 0, height: 0 }; this.setProjection(projection); this._compositor = new EffectCompositor(); this._temporalSS = new TemporalSuperSampling(); this._shadowMapPass = new ShadowMapPass(); var pcfKernels = []; var off = 0; for (var i = 0; i < 30; i++) { var pcfKernel = []; for (var k = 0; k < 6; k++) { pcfKernel.push(halton(off, 2) * 4 - 2); pcfKernel.push(halton(off, 3) * 4 - 2); off++; } pcfKernels.push(pcfKernel); } this._pcfKernels = pcfKernels; this.scene.on("beforerender", function(renderer, scene, camera2) { if (this.needsTemporalSS()) { this._temporalSS.jitterProjection(renderer, camera2); } }, this); } ViewGL.prototype.setProjection = function(projection) { var oldCamera = this.camera; oldCamera && oldCamera.update(); if (projection === "perspective") { if (!(this.camera instanceof Perspective)) { this.camera = new Perspective(); if (oldCamera) { this.camera.setLocalTransform(oldCamera.localTransform); } } } else { if (!(this.camera instanceof Orthographic)) { this.camera = new Orthographic(); if (oldCamera) { this.camera.setLocalTransform(oldCamera.localTransform); } } } this.camera.near = 0.1; this.camera.far = 2e3; }; ViewGL.prototype.setViewport = function(x, y, width, height, dpr) { if (this.camera instanceof Perspective) { this.camera.aspect = width / height; } dpr = dpr || 1; this.viewport.x = x; this.viewport.y = y; this.viewport.width = width; this.viewport.height = height; this.viewport.devicePixelRatio = dpr; this._compositor.resize(width * dpr, height * dpr); this._temporalSS.resize(width * dpr, height * dpr); }; ViewGL.prototype.containPoint = function(x, y) { var viewport = this.viewport; var height = this.layer.renderer.getHeight(); y = height - y; return x >= viewport.x && y >= viewport.y && x <= viewport.x + viewport.width && y <= viewport.y + viewport.height; }; var ndc = new Vector2(); ViewGL.prototype.castRay = function(x, y, out) { var renderer = this.layer.renderer; var oldViewport = renderer.viewport; renderer.viewport = this.viewport; renderer.screenToNDC(x, y, ndc); this.camera.castRay(ndc, out); renderer.viewport = oldViewport; return out; }; ViewGL.prototype.prepareRender = function() { this.scene.update(); this.camera.update(); this.scene.updateLights(); var renderList = this.scene.updateRenderList(this.camera); this._needsSortProgressively = false; for (var i = 0; i < renderList.transparent.length; i++) { var renderable = renderList.transparent[i]; var geometry = renderable.geometry; if (geometry.needsSortVerticesProgressively && geometry.needsSortVerticesProgressively()) { this._needsSortProgressively = true; } if (geometry.needsSortTrianglesProgressively && geometry.needsSortTrianglesProgressively()) { this._needsSortProgressively = true; } } this._frame = 0; this._temporalSS.resetFrame(); }; ViewGL.prototype.render = function(renderer, accumulating) { this._doRender(renderer, accumulating, this._frame); this._frame++; }; ViewGL.prototype.needsAccumulate = function() { return this.needsTemporalSS() || this._needsSortProgressively; }; ViewGL.prototype.needsTemporalSS = function() { var enableTemporalSS = this._enableTemporalSS; if (enableTemporalSS === "auto") { enableTemporalSS = this._enablePostEffect; } return enableTemporalSS; }; ViewGL.prototype.hasDOF = function() { return this._enableDOF; }; ViewGL.prototype.isAccumulateFinished = function() { return this.needsTemporalSS() ? this._temporalSS.isFinished() : this._frame > 30; }; ViewGL.prototype._doRender = function(renderer, accumulating, accumFrame) { var scene = this.scene; var camera2 = this.camera; accumFrame = accumFrame || 0; this._updateTransparent(renderer, scene, camera2, accumFrame); if (!accumulating) { this._shadowMapPass.kernelPCF = this._pcfKernels[0]; this._shadowMapPass.render(renderer, scene, camera2, true); } this._updateShadowPCFKernel(accumFrame); var bgColor = renderer.clearColor; renderer.gl.clearColor(bgColor[0], bgColor[1], bgColor[2], bgColor[3]); if (this._enablePostEffect) { if (this.needsTemporalSS()) { this._temporalSS.jitterProjection(renderer, camera2); } this._compositor.updateNormal(renderer, scene, camera2, this._temporalSS.getFrame()); } this._updateSSAO(renderer, scene, camera2, this._temporalSS.getFrame()); if (this._enablePostEffect) { var frameBuffer = this._compositor.getSourceFrameBuffer(); frameBuffer.bind(renderer); renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT); renderer.render(scene, camera2, true, true); frameBuffer.unbind(renderer); if (this.needsTemporalSS() && accumulating) { this._compositor.composite(renderer, scene, camera2, this._temporalSS.getSourceFrameBuffer(), this._temporalSS.getFrame()); renderer.setViewport(this.viewport); this._temporalSS.render(renderer); } else { renderer.setViewport(this.viewport); this._compositor.composite(renderer, scene, camera2, null, 0); } } else { if (this.needsTemporalSS() && accumulating) { var frameBuffer = this._temporalSS.getSourceFrameBuffer(); frameBuffer.bind(renderer); renderer.saveClear(); renderer.clearBit = renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT; renderer.render(scene, camera2, true, true); renderer.restoreClear(); frameBuffer.unbind(renderer); renderer.setViewport(this.viewport); this._temporalSS.render(renderer); } else { renderer.setViewport(this.viewport); renderer.render(scene, camera2, true, true); } } }; ViewGL.prototype._updateTransparent = function(renderer, scene, camera2, frame) { var v3 = new Vector3(); var invWorldTransform = new Matrix4(); var cameraWorldPosition = camera2.getWorldPosition(); var transparentList = scene.getRenderList(camera2).transparent; for (var i = 0; i < transparentList.length; i++) { var renderable = transparentList[i]; var geometry = renderable.geometry; Matrix4.invert(invWorldTransform, renderable.worldTransform); Vector3.transformMat4(v3, cameraWorldPosition, invWorldTransform); if (geometry.needsSortTriangles && geometry.needsSortTriangles()) { geometry.doSortTriangles(v3, frame); } if (geometry.needsSortVertices && geometry.needsSortVertices()) { geometry.doSortVertices(v3, frame); } } }; ViewGL.prototype._updateSSAO = function(renderer, scene, camera2) { var ifEnableSSAO = this._enableSSAO && this._enablePostEffect; if (ifEnableSSAO) { this._compositor.updateSSAO(renderer, scene, camera2, this._temporalSS.getFrame()); } var renderList = scene.getRenderList(camera2); for (var i = 0; i < renderList.opaque.length; i++) { var renderable = renderList.opaque[i]; if (renderable.renderNormal) { renderable.material[ifEnableSSAO ? "enableTexture" : "disableTexture"]("ssaoMap"); } if (ifEnableSSAO) { renderable.material.set("ssaoMap", this._compositor.getSSAOTexture()); } } }; ViewGL.prototype._updateShadowPCFKernel = function(frame) { var pcfKernel = this._pcfKernels[frame % this._pcfKernels.length]; var renderList = this.scene.getRenderList(this.camera); var opaqueList = renderList.opaque; for (var i = 0; i < opaqueList.length; i++) { if (opaqueList[i].receiveShadow) { opaqueList[i].material.set("pcfKernel", pcfKernel); opaqueList[i].material.define("fragment", "PCF_KERNEL_SIZE", pcfKernel.length / 2); } } }; ViewGL.prototype.dispose = function(renderer) { this._compositor.dispose(renderer.gl); this._temporalSS.dispose(renderer.gl); this._shadowMapPass.dispose(renderer); }; ViewGL.prototype.setPostEffect = function(postEffectModel, api) { var compositor = this._compositor; this._enablePostEffect = postEffectModel.get("enable"); var bloomModel = postEffectModel.getModel("bloom"); var edgeModel = postEffectModel.getModel("edge"); var dofModel = postEffectModel.getModel("DOF", postEffectModel.getModel("depthOfField")); var ssaoModel = postEffectModel.getModel("SSAO", postEffectModel.getModel("screenSpaceAmbientOcclusion")); var ssrModel = postEffectModel.getModel("SSR", postEffectModel.getModel("screenSpaceReflection")); var fxaaModel = postEffectModel.getModel("FXAA"); var colorCorrModel = postEffectModel.getModel("colorCorrection"); bloomModel.get("enable") ? compositor.enableBloom() : compositor.disableBloom(); dofModel.get("enable") ? compositor.enableDOF() : compositor.disableDOF(); ssrModel.get("enable") ? compositor.enableSSR() : compositor.disableSSR(); colorCorrModel.get("enable") ? compositor.enableColorCorrection() : compositor.disableColorCorrection(); edgeModel.get("enable") ? compositor.enableEdge() : compositor.disableEdge(); fxaaModel.get("enable") ? compositor.enableFXAA() : compositor.disableFXAA(); this._enableDOF = dofModel.get("enable"); this._enableSSAO = ssaoModel.get("enable"); this._enableSSAO ? compositor.enableSSAO() : compositor.disableSSAO(); compositor.setBloomIntensity(bloomModel.get("intensity")); compositor.setEdgeColor(edgeModel.get("color")); compositor.setColorLookupTexture(colorCorrModel.get("lookupTexture"), api); compositor.setExposure(colorCorrModel.get("exposure")); ["radius", "quality", "intensity"].forEach(function(name) { compositor.setSSAOParameter(name, ssaoModel.get(name)); }); ["quality", "maxRoughness", "physical"].forEach(function(name) { compositor.setSSRParameter(name, ssrModel.get(name)); }); ["quality", "focalDistance", "focalRange", "blurRadius", "fstop"].forEach(function(name) { compositor.setDOFParameter(name, dofModel.get(name)); }); ["brightness", "contrast", "saturation"].forEach(function(name) { compositor.setColorCorrection(name, colorCorrModel.get(name)); }); }; ViewGL.prototype.setDOFFocusOnPoint = function(depth) { if (this._enablePostEffect) { if (depth > this.camera.far || depth < this.camera.near) { return; } this._compositor.setDOFParameter("focalDistance", depth); return true; } }; ViewGL.prototype.setTemporalSuperSampling = function(temporalSuperSamplingModel) { this._enableTemporalSS = temporalSuperSamplingModel.get("enable"); }; ViewGL.prototype.isLinearSpace = function() { return this._enablePostEffect; }; ViewGL.prototype.setRootNode = function(rootNode) { if (this.rootNode === rootNode) { return; } var children = this.rootNode.children(); for (var i = 0; i < children.length; i++) { rootNode.add(children[i]); } if (rootNode !== this.scene) { this.scene.add(rootNode); } this.rootNode = rootNode; }; ViewGL.prototype.add = function(node3D) { this.rootNode.add(node3D); }; ViewGL.prototype.remove = function(node3D) { this.rootNode.remove(node3D); }; ViewGL.prototype.removeAll = function(node3D) { this.rootNode.removeAll(node3D); }; Object.assign(ViewGL.prototype, notifier); function resizeCartesian3D(grid3DModel, api) { var boxLayoutOption = grid3DModel.getBoxLayoutParams(); var viewport = getLayoutRect(boxLayoutOption, { width: api.getWidth(), height: api.getHeight() }); viewport.y = api.getHeight() - viewport.y - viewport.height; this.viewGL.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, api.getDevicePixelRatio()); var boxWidth = grid3DModel.get("boxWidth"); var boxHeight = grid3DModel.get("boxHeight"); var boxDepth = grid3DModel.get("boxDepth"); this.getAxis("x").setExtent(-boxWidth / 2, boxWidth / 2); this.getAxis("y").setExtent(boxDepth / 2, -boxDepth / 2); this.getAxis("z").setExtent(-boxHeight / 2, boxHeight / 2); this.size = [boxWidth, boxHeight, boxDepth]; } function updateCartesian3D(ecModel, api) { var dataExtents = {}; function unionDataExtents(dim, extent) { dataExtents[dim] = dataExtents[dim] || [Infinity, -Infinity]; dataExtents[dim][0] = Math.min(extent[0], dataExtents[dim][0]); dataExtents[dim][1] = Math.max(extent[1], dataExtents[dim][1]); } ecModel.eachSeries(function(seriesModel) { if (seriesModel.coordinateSystem !== this) { return; } var data = seriesModel.getData(); ["x", "y", "z"].forEach(function(coordDim) { data.mapDimensionsAll(coordDim, true).forEach(function(dataDim) { unionDataExtents(coordDim, data.getDataExtent(dataDim, true)); }); }); }, this); ["xAxis3D", "yAxis3D", "zAxis3D"].forEach(function(axisType) { ecModel.eachComponent(axisType, function(axisModel) { var dim = axisType.charAt(0); var grid3DModel = axisModel.getReferringComponents("grid3D").models[0]; var cartesian3D = grid3DModel.coordinateSystem; if (cartesian3D !== this) { return; } var axis = cartesian3D.getAxis(dim); if (axis) { return; } var scale = createScale(dataExtents[dim] || [Infinity, -Infinity], axisModel); axis = new Axis3D(dim, scale); axis.type = axisModel.get("type"); var isCategory = axis.type === "category"; axis.onBand = isCategory && axisModel.get("boundaryGap"); axis.inverse = axisModel.get("inverse"); axisModel.axis = axis; axis.model = axisModel; axis.getLabelModel = function() { return axisModel.getModel("axisLabel", grid3DModel.getModel("axisLabel")); }; axis.getTickModel = function() { return axisModel.getModel("axisTick", grid3DModel.getModel("axisTick")); }; cartesian3D.addAxis(axis); }, this); }, this); this.resize(this.model, api); } var grid3DCreator = { dimensions: Cartesian3D.prototype.dimensions, create: function(ecModel, api) { var cartesian3DList = []; ecModel.eachComponent("grid3D", function(grid3DModel) { grid3DModel.__viewGL = grid3DModel.__viewGL || new ViewGL(); var cartesian3D = new Cartesian3D(); cartesian3D.model = grid3DModel; cartesian3D.viewGL = grid3DModel.__viewGL; grid3DModel.coordinateSystem = cartesian3D; cartesian3DList.push(cartesian3D); cartesian3D.resize = resizeCartesian3D; cartesian3D.update = updateCartesian3D; }); var axesTypes = ["xAxis3D", "yAxis3D", "zAxis3D"]; function findAxesModels(seriesModel, ecModel2) { return axesTypes.map(function(axisType) { var axisModel = seriesModel.getReferringComponents(axisType).models[0]; if (axisModel == null) { axisModel = ecModel2.getComponent(axisType); } return axisModel; }); } ecModel.eachSeries(function(seriesModel) { if (seriesModel.get("coordinateSystem") !== "cartesian3D") { return; } var firstGridModel = seriesModel.getReferringComponents("grid3D").models[0]; if (firstGridModel == null) { var axesModels = findAxesModels(seriesModel, ecModel); var firstGridModel = axesModels[0].getCoordSysModel(); axesModels.forEach(function(axisModel) { axisModel.getCoordSysModel(); }); } var coordSys = firstGridModel.coordinateSystem; seriesModel.coordinateSystem = coordSys; }); return cartesian3DList; } }; var Axis3DModel = ComponentModel.extend({ type: "cartesian3DAxis", axis: null, /** * @override */ getCoordSysModel: function() { return this.ecModel.queryComponents({ mainType: "grid3D", index: this.option.gridIndex, id: this.option.gridId })[0]; } }); mixinAxisModelCommonMethods(Axis3DModel); var defaultOption = { show: true, grid3DIndex: 0, // 反向坐标轴 inverse: false, // 坐标轴名字 name: "", // 坐标轴名字位置 nameLocation: "middle", nameTextStyle: { fontSize: 16 }, // 文字与轴线距离 nameGap: 20, axisPointer: {}, axisLine: {}, // 坐标轴小标记 axisTick: {}, axisLabel: {}, // 分隔区域 splitArea: {} }; var categoryAxis = merge({ // 类目起始和结束两端空白策略 boundaryGap: true, // splitArea: { // show: false // }, // 坐标轴小标记 axisTick: { // If tick is align with label when boundaryGap is true // Default with axisTick alignWithLabel: false, interval: "auto" }, // 坐标轴文本标签,详见axis.axisLabel axisLabel: { interval: "auto" }, axisPointer: { label: { show: false } } }, defaultOption); var valueAxis = merge({ // 数值起始和结束两端空白策略 boundaryGap: [0, 0], // 最小值, 设置成 'dataMin' 则从数据中计算最小值 // min: null, // 最大值,设置成 'dataMax' 则从数据中计算最大值 // max: null, // 脱离0值比例,放大聚焦到最终_min,_max区间 // scale: false, // 分割段数,默认为5 splitNumber: 5, // Minimum interval // minInterval: null axisPointer: { label: {} } }, defaultOption); var timeAxis = defaults({ scale: true, min: "dataMin", max: "dataMax" }, valueAxis); var logAxis = defaults({ logBase: 10 }, valueAxis); logAxis.scale = true; const axisDefault = { categoryAxis3D: categoryAxis, valueAxis3D: valueAxis, timeAxis3D: timeAxis, logAxis3D: logAxis }; var AXIS_TYPES = ["value", "category", "time", "log"]; function createAxis3DModel(registers, dim, BaseAxisModelClass, axisTypeDefaulter, extraDefaultOption) { AXIS_TYPES.forEach(function(axisType) { var AxisModel = BaseAxisModelClass.extend({ type: dim + "Axis3D." + axisType, /** * @type readOnly */ __ordinalMeta: null, mergeDefaultAndTheme: function(option, ecModel) { var themeModel = ecModel.getTheme(); merge(option, themeModel.get(axisType + "Axis3D")); merge(option, this.getDefaultOption()); option.type = axisTypeDefaulter(dim, option); }, /** * @override */ optionUpdated: function() { var thisOption = this.option; if (thisOption.type === "category") { this.__ordinalMeta = OrdinalMeta.createByAxisModel(this); } }, getCategories: function() { if (this.option.type === "category") { return this.__ordinalMeta.categories; } }, getOrdinalMeta: function() { return this.__ordinalMeta; }, defaultOption: merge(clone(axisDefault[axisType + "Axis3D"]), extraDefaultOption || {}, true) }); registers.registerComponentModel(AxisModel); }); registers.registerSubTypeDefaulter(dim + "Axis3D", curry(axisTypeDefaulter, dim)); } function getAxisType(axisDim, option) { return option.type || (option.data ? "category" : "value"); } function install$f(registers) { registers.registerComponentModel(Grid3DModel); registers.registerComponentView(Grid3DView); registers.registerCoordinateSystem("grid3D", grid3DCreator); ["x", "y", "z"].forEach(function(dim) { createAxis3DModel(registers, dim, Axis3DModel, getAxisType, { name: dim.toUpperCase() }); const AxisView = registers.ComponentView.extend({ type: dim + "Axis3D" }); registers.registerComponentView(AxisView); }); registers.registerAction({ type: "grid3DChangeCamera", event: "grid3dcamerachanged", update: "series:updateCamera" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "grid3D", query: payload }, function(componentModel) { componentModel.setView(payload); }); }); registers.registerAction({ type: "grid3DShowAxisPointer", event: "grid3dshowaxispointer", update: "grid3D:showAxisPointer" }, function(payload, ecModel) { }); registers.registerAction({ type: "grid3DHideAxisPointer", event: "grid3dhideaxispointer", update: "grid3D:hideAxisPointer" }, function(payload, ecModel) { }); } use(install$f); const componentShadingMixin = { defaultOption: { shading: null, realisticMaterial: { textureTiling: 1, textureOffset: 0, detailTexture: null }, lambertMaterial: { textureTiling: 1, textureOffset: 0, detailTexture: null }, colorMaterial: { textureTiling: 1, textureOffset: 0, detailTexture: null }, hatchingMaterial: { textureTiling: 1, textureOffset: 0, paperColor: "#fff" } } }; const geo3DModelMixin = { getFilledRegions: function(regions, mapData) { var regionsArr = (regions || []).slice(); var geoJson; if (typeof mapData === "string") { mapData = getMap(mapData); geoJson = mapData && mapData.geoJson; } else { if (mapData && mapData.features) { geoJson = mapData; } } if (!geoJson) { return []; } var dataNameMap = {}; var features = geoJson.features; for (var i = 0; i < regionsArr.length; i++) { dataNameMap[regionsArr[i].name] = regionsArr[i]; } for (var i = 0; i < features.length; i++) { var name = features[i].properties.name; if (!dataNameMap[name]) { regionsArr.push({ name }); } } return regionsArr; }, defaultOption: { show: true, zlevel: -10, // geoJson used by geo3D map: "", // Layout used for viewport left: 0, top: 0, width: "100%", height: "100%", boxWidth: 100, boxHeight: 10, boxDepth: "auto", regionHeight: 3, environment: "auto", groundPlane: { show: false, color: "#aaa" }, shading: "lambert", light: { main: { alpha: 40, beta: 30 } }, viewControl: { alpha: 40, beta: 0, distance: 100, orthographicSize: 60, minAlpha: 5, minBeta: -80, maxBeta: 80 }, label: { show: false, // Distance in 3d space. distance: 2, textStyle: { fontSize: 20, color: "#000", backgroundColor: "rgba(255,255,255,0.7)", padding: 3, borderRadius: 4 } }, // TODO // altitude: { // min: 'auto', // max: 'auto', // height: [] // }, // labelLine // light // postEffect // temporalSuperSampling itemStyle: { color: "#fff", borderWidth: 0, borderColor: "#333" }, emphasis: { itemStyle: { // color: '#f94b59' color: "#639fc0" }, label: { show: true } } } }; var Geo3DModel = ComponentModel.extend({ type: "geo3D", layoutMode: "box", coordinateSystem: null, optionUpdated: function() { var option = this.option; option.regions = this.getFilledRegions(option.regions, option.map); var dimensions = createDimensions(option.data || [], { coordDimensions: ["value"], encodeDefine: this.get("encode"), dimensionsDefine: this.get("dimensions") }); var list = new SeriesData(dimensions, this); list.initData(option.regions); var regionModelMap = {}; list.each(function(idx) { var name = list.getName(idx); var itemModel = list.getItemModel(idx); regionModelMap[name] = itemModel; }); this._regionModelMap = regionModelMap; this._data = list; }, getData: function() { return this._data; }, getRegionModel: function(idx) { var name = this.getData().getName(idx); return this._regionModelMap[name] || new Model(null, this); }, getRegionPolygonCoords: function(idx) { var name = this.getData().getName(idx); var region = this.coordinateSystem.getRegion(name); return region ? region.geometries : []; }, /** * Format label * @param {string} name Region name * @param {string} [status='normal'] 'normal' or 'emphasis' * @return {string} */ getFormattedLabel: function(dataIndex, status) { var name = this._data.getName(dataIndex); var regionModel = this.getRegionModel(dataIndex); var formatter = regionModel.get(status === "normal" ? ["label", "formatter"] : ["emphasis", "label", "formatter"]); if (formatter == null) { formatter = regionModel.get(["label", "formatter"]); } var params = { name }; if (typeof formatter === "function") { params.status = status; return formatter(params); } else if (typeof formatter === "string") { var serName = params.seriesName; return formatter.replace("{a}", serName != null ? serName : ""); } else { return name; } }, defaultOption: { // itemStyle: {}, // height, // label: {} // realisticMaterial regions: [] } }); merge(Geo3DModel.prototype, geo3DModelMixin); merge(Geo3DModel.prototype, componentViewControlMixin); merge(Geo3DModel.prototype, componentPostEffectMixin); merge(Geo3DModel.prototype, componentLightMixin); merge(Geo3DModel.prototype, componentShadingMixin); function earcut(data, holeIndices, dim) { dim = dim || 2; var hasHoles = holeIndices && holeIndices.length, outerLen = hasHoles ? holeIndices[0] * dim : data.length, outerNode = linkedList(data, 0, outerLen, dim, true), triangles = []; if (!outerNode) return triangles; var minX, minY, maxX, maxY, x, y, size; if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); if (data.length > 80 * dim) { minX = maxX = data[0]; minY = maxY = data[1]; for (var i = dim; i < outerLen; i += dim) { x = data[i]; y = data[i + 1]; if (x < minX) minX = x; if (y < minY) minY = y; if (x > maxX) maxX = x; if (y > maxY) maxY = y; } size = Math.max(maxX - minX, maxY - minY); } earcutLinked(outerNode, triangles, dim, minX, minY, size); return triangles; } function linkedList(data, start, end, dim, clockwise) { var i, last; if (clockwise === signedArea(data, start, end, dim) > 0) { for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last); } else { for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); } if (last && equals(last, last.next)) { removeNode(last); last = last.next; } return last; } function filterPoints(start, end) { if (!start) return start; if (!end) end = start; var p = start, again; do { again = false; if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { removeNode(p); p = end = p.prev; if (p === p.next) return null; again = true; } else { p = p.next; } } while (again || p !== end); return end; } function earcutLinked(ear, triangles, dim, minX, minY, size, pass) { if (!ear) return; if (!pass && size) indexCurve(ear, minX, minY, size); var stop = ear, prev, next; while (ear.prev !== ear.next) { prev = ear.prev; next = ear.next; if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) { triangles.push(prev.i / dim); triangles.push(ear.i / dim); triangles.push(next.i / dim); removeNode(ear); ear = next.next; stop = next.next; continue; } ear = next; if (ear === stop) { if (!pass) { earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1); } else if (pass === 1) { ear = cureLocalIntersections(ear, triangles, dim); earcutLinked(ear, triangles, dim, minX, minY, size, 2); } else if (pass === 2) { splitEarcut(ear, triangles, dim, minX, minY, size); } break; } } } function isEar(ear) { var a = ear.prev, b = ear, c = ear.next; if (area(a, b, c) >= 0) return false; var p = ear.next.next; while (p !== ear.prev) { if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.next; } return true; } function isEarHashed(ear, minX, minY, size) { var a = ear.prev, b = ear, c = ear.next; if (area(a, b, c) >= 0) return false; var minTX = a.x < b.x ? a.x < c.x ? a.x : c.x : b.x < c.x ? b.x : c.x, minTY = a.y < b.y ? a.y < c.y ? a.y : c.y : b.y < c.y ? b.y : c.y, maxTX = a.x > b.x ? a.x > c.x ? a.x : c.x : b.x > c.x ? b.x : c.x, maxTY = a.y > b.y ? a.y > c.y ? a.y : c.y : b.y > c.y ? b.y : c.y; var minZ = zOrder(minTX, minTY, minX, minY, size), maxZ = zOrder(maxTX, maxTY, minX, minY, size); var p = ear.nextZ; while (p && p.z <= maxZ) { if (p !== ear.prev && p !== ear.next && pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.nextZ; } p = ear.prevZ; while (p && p.z >= minZ) { if (p !== ear.prev && p !== ear.next && pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.prevZ; } return true; } function cureLocalIntersections(start, triangles, dim) { var p = start; do { var a = p.prev, b = p.next.next; if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { triangles.push(a.i / dim); triangles.push(p.i / dim); triangles.push(b.i / dim); removeNode(p); removeNode(p.next); p = start = b; } p = p.next; } while (p !== start); return p; } function splitEarcut(start, triangles, dim, minX, minY, size) { var a = start; do { var b = a.next.next; while (b !== a.prev) { if (a.i !== b.i && isValidDiagonal(a, b)) { var c = splitPolygon(a, b); a = filterPoints(a, a.next); c = filterPoints(c, c.next); earcutLinked(a, triangles, dim, minX, minY, size); earcutLinked(c, triangles, dim, minX, minY, size); return; } b = b.next; } a = a.next; } while (a !== start); } function eliminateHoles(data, holeIndices, outerNode, dim) { var queue = [], i, len, start, end, list; for (i = 0, len = holeIndices.length; i < len; i++) { start = holeIndices[i] * dim; end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; list = linkedList(data, start, end, dim, false); if (list === list.next) list.steiner = true; queue.push(getLeftmost(list)); } queue.sort(compareX); for (i = 0; i < queue.length; i++) { eliminateHole(queue[i], outerNode); outerNode = filterPoints(outerNode, outerNode.next); } return outerNode; } function compareX(a, b) { return a.x - b.x; } function eliminateHole(hole, outerNode) { outerNode = findHoleBridge(hole, outerNode); if (outerNode) { var b = splitPolygon(outerNode, hole); filterPoints(b, b.next); } } function findHoleBridge(hole, outerNode) { var p = outerNode, hx = hole.x, hy = hole.y, qx = -Infinity, m; do { if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); if (x <= hx && x > qx) { qx = x; if (x === hx) { if (hy === p.y) return p; if (hy === p.next.y) return p.next; } m = p.x < p.next.x ? p : p.next; } } p = p.next; } while (p !== outerNode); if (!m) return null; if (hx === qx) return m.prev; var stop = m, mx = m.x, my = m.y, tanMin = Infinity, tan2; p = m.next; while (p !== stop) { if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { tan2 = Math.abs(hy - p.y) / (hx - p.x); if ((tan2 < tanMin || tan2 === tanMin && p.x > m.x) && locallyInside(p, hole)) { m = p; tanMin = tan2; } } p = p.next; } return m; } function indexCurve(start, minX, minY, size) { var p = start; do { if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size); p.prevZ = p.prev; p.nextZ = p.next; p = p.next; } while (p !== start); p.prevZ.nextZ = null; p.prevZ = null; sortLinked(p); } function sortLinked(list) { var i, p, q, e2, tail, numMerges, pSize, qSize, inSize = 1; do { p = list; list = null; tail = null; numMerges = 0; while (p) { numMerges++; q = p; pSize = 0; for (i = 0; i < inSize; i++) { pSize++; q = q.nextZ; if (!q) break; } qSize = inSize; while (pSize > 0 || qSize > 0 && q) { if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { e2 = p; p = p.nextZ; pSize--; } else { e2 = q; q = q.nextZ; qSize--; } if (tail) tail.nextZ = e2; else list = e2; e2.prevZ = tail; tail = e2; } p = q; } tail.nextZ = null; inSize *= 2; } while (numMerges > 1); return list; } function zOrder(x, y, minX, minY, size) { x = 32767 * (x - minX) / size; y = 32767 * (y - minY) / size; x = (x | x << 8) & 16711935; x = (x | x << 4) & 252645135; x = (x | x << 2) & 858993459; x = (x | x << 1) & 1431655765; y = (y | y << 8) & 16711935; y = (y | y << 4) & 252645135; y = (y | y << 2) & 858993459; y = (y | y << 1) & 1431655765; return x | y << 1; } function getLeftmost(start) { var p = start, leftmost = start; do { if (p.x < leftmost.x) leftmost = p; p = p.next; } while (p !== start); return leftmost; } function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; } function isValidDiagonal(a, b) { return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b); } function area(p, q, r) { return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); } function equals(p12, p22) { return p12.x === p22.x && p12.y === p22.y; } function intersects(p12, q1, p22, q2) { if (equals(p12, q1) && equals(p22, q2) || equals(p12, q2) && equals(p22, q1)) return true; return area(p12, q1, p22) > 0 !== area(p12, q1, q2) > 0 && area(p22, q2, p12) > 0 !== area(p22, q2, q1) > 0; } function intersectsPolygon(a, b) { var p = a; do { if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) return true; p = p.next; } while (p !== a); return false; } function locallyInside(a, b) { return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; } function middleInside(a, b) { var p = a, inside = false, px = (a.x + b.x) / 2, py = (a.y + b.y) / 2; do { if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x) inside = !inside; p = p.next; } while (p !== a); return inside; } function splitPolygon(a, b) { var a2 = new Node(a.i, a.x, a.y), b2 = new Node(b.i, b.x, b.y), an = a.next, bp = b.prev; a.next = b; b.prev = a; a2.next = an; an.prev = a2; b2.next = a2; a2.prev = b2; bp.next = b2; b2.prev = bp; return b2; } function insertNode(i, x, y, last) { var p = new Node(i, x, y); if (!last) { p.prev = p; p.next = p; } else { p.next = last.next; p.prev = last; last.next.prev = p; last.next = p; } return p; } function removeNode(p) { p.next.prev = p.prev; p.prev.next = p.next; if (p.prevZ) p.prevZ.nextZ = p.nextZ; if (p.nextZ) p.nextZ.prevZ = p.prevZ; } function Node(i, x, y) { this.i = i; this.x = x; this.y = y; this.prev = null; this.next = null; this.z = null; this.prevZ = null; this.nextZ = null; this.steiner = false; } earcut.deviation = function(data, holeIndices, dim, triangles) { var hasHoles = holeIndices && holeIndices.length; var outerLen = hasHoles ? holeIndices[0] * dim : data.length; var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); if (hasHoles) { for (var i = 0, len = holeIndices.length; i < len; i++) { var start = holeIndices[i] * dim; var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; polygonArea -= Math.abs(signedArea(data, start, end, dim)); } } var trianglesArea = 0; for (i = 0; i < triangles.length; i += 3) { var a = triangles[i] * dim; var b = triangles[i + 1] * dim; var c = triangles[i + 2] * dim; trianglesArea += Math.abs((data[a] - data[c]) * (data[b + 1] - data[a + 1]) - (data[a] - data[b]) * (data[c + 1] - data[a + 1])); } return polygonArea === 0 && trianglesArea === 0 ? 0 : Math.abs((trianglesArea - polygonArea) / polygonArea); }; function signedArea(data, start, end, dim) { var sum = 0; for (var i = start, j = end - dim; i < end; i += dim) { sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); j = i; } return sum; } function swap(arr, a, b) { var tmp = arr[a]; arr[a] = arr[b]; arr[b] = tmp; } function partition(arr, pivot, left, right, compare) { var storeIndex = left; var pivotValue = arr[pivot]; swap(arr, pivot, right); for (var v = left; v < right; v++) { if (compare(arr[v], pivotValue) < 0) { swap(arr, v, storeIndex); storeIndex++; } } swap(arr, right, storeIndex); return storeIndex; } function quickSort(array, compare, left, right) { if (left < right) { var pivot = Math.floor((left + right) / 2); var newPivot = partition(array, pivot, left, right, compare); quickSort(array, compare, left, newPivot - 1); quickSort(array, compare, newPivot + 1, right); } } function ProgressiveQuickSort() { this._parts = []; } ProgressiveQuickSort.prototype.step = function(arr, compare, frame) { var len = arr.length; if (frame === 0) { this._parts = []; this._sorted = false; var pivot = Math.floor(len / 2); this._parts.push({ pivot, left: 0, right: len - 1 }); this._currentSortPartIdx = 0; } if (this._sorted) { return; } var parts = this._parts; if (parts.length === 0) { this._sorted = true; return true; } else if (parts.length < 512) { for (var i = 0; i < parts.length; i++) { parts[i].pivot = partition(arr, parts[i].pivot, parts[i].left, parts[i].right, compare); } var subdividedParts = []; for (var i = 0; i < parts.length; i++) { var left = parts[i].left; var right = parts[i].pivot - 1; if (right > left) { subdividedParts.push({ pivot: Math.floor((right + left) / 2), left, right }); } var left = parts[i].pivot + 1; var right = parts[i].right; if (right > left) { subdividedParts.push({ pivot: Math.floor((right + left) / 2), left, right }); } } parts = this._parts = subdividedParts; } else { for (var i = 0; i < Math.floor(parts.length / 10); i++) { var idx = parts.length - 1 - this._currentSortPartIdx; quickSort(arr, compare, parts[idx].left, parts[idx].right); this._currentSortPartIdx++; if (this._currentSortPartIdx === parts.length) { this._sorted = true; return true; } } } return false; }; ProgressiveQuickSort.sort = quickSort; var vec3$c = glmatrix.vec3; var p0 = vec3$c.create(); var p1 = vec3$c.create(); var p2 = vec3$c.create(); const trianglesSortMixin = { needsSortTriangles: function() { return this.indices && this.sortTriangles; }, needsSortTrianglesProgressively: function() { return this.needsSortTriangles() && this.triangleCount >= 2e4; }, doSortTriangles: function(cameraPos, frame) { var indices = this.indices; if (frame === 0) { var posAttr = this.attributes.position; var cameraPos = cameraPos.array; if (!this._triangleZList || this._triangleZList.length !== this.triangleCount) { this._triangleZList = new Float32Array(this.triangleCount); this._sortedTriangleIndices = new Uint32Array(this.triangleCount); this._indicesTmp = new indices.constructor(indices.length); this._triangleZListTmp = new Float32Array(this.triangleCount); } var cursor = 0; var firstZ; for (var i = 0; i < indices.length; ) { posAttr.get(indices[i++], p0); posAttr.get(indices[i++], p1); posAttr.get(indices[i++], p2); var z0 = vec3$c.sqrDist(p0, cameraPos); var z1 = vec3$c.sqrDist(p1, cameraPos); var z2 = vec3$c.sqrDist(p2, cameraPos); var zMax = Math.min(z0, z1); zMax = Math.min(zMax, z2); if (i === 3) { firstZ = zMax; zMax = 0; } else { zMax = zMax - firstZ; } this._triangleZList[cursor++] = zMax; } } var sortedTriangleIndices = this._sortedTriangleIndices; for (var i = 0; i < sortedTriangleIndices.length; i++) { sortedTriangleIndices[i] = i; } if (this.triangleCount < 2e4) { if (frame === 0) { this._simpleSort(true); } } else { for (var i = 0; i < 3; i++) { this._progressiveQuickSort(frame * 3 + i); } } var targetIndices = this._indicesTmp; var targetTriangleZList = this._triangleZListTmp; var faceZList = this._triangleZList; for (var i = 0; i < this.triangleCount; i++) { var fromIdx3 = sortedTriangleIndices[i] * 3; var toIdx3 = i * 3; targetIndices[toIdx3++] = indices[fromIdx3++]; targetIndices[toIdx3++] = indices[fromIdx3++]; targetIndices[toIdx3] = indices[fromIdx3]; targetTriangleZList[i] = faceZList[sortedTriangleIndices[i]]; } var tmp = this._indicesTmp; this._indicesTmp = this.indices; this.indices = tmp; var tmp = this._triangleZListTmp; this._triangleZListTmp = this._triangleZList; this._triangleZList = tmp; this.dirtyIndices(); }, _simpleSort: function(useNativeQuickSort) { var faceZList = this._triangleZList; var sortedTriangleIndices = this._sortedTriangleIndices; function compare(a, b) { return faceZList[b] - faceZList[a]; } if (useNativeQuickSort) { Array.prototype.sort.call(sortedTriangleIndices, compare); } else { ProgressiveQuickSort.sort(sortedTriangleIndices, compare, 0, sortedTriangleIndices.length - 1); } }, _progressiveQuickSort: function(frame) { var faceZList = this._triangleZList; var sortedTriangleIndices = this._sortedTriangleIndices; this._quickSort = this._quickSort || new ProgressiveQuickSort(); this._quickSort.step(sortedTriangleIndices, function(a, b) { return faceZList[b] - faceZList[a]; }, frame); } }; function getVisualColor(data) { const style = data.getVisual("style"); if (style) { const drawType = data.getVisual("drawType"); return style[drawType]; } } function getVisualOpacity(data) { const style = data.getVisual("style"); return style.opacity; } function getItemVisualColor(data, idx) { const style = data.getItemVisual(idx, "style"); if (style) { const drawType = data.getVisual("drawType"); return style[drawType]; } } function getItemVisualOpacity(data, idx) { const style = data.getItemVisual(idx, "style"); return style && style.opacity; } var LABEL_NORMAL_SHOW_BIT = 1; var LABEL_EMPHASIS_SHOW_BIT = 2; function LabelsBuilder(width, height, api) { this._labelsMesh = new LabelsMesh(); this._labelTextureSurface = new ZRTextureAtlasSurface({ width: 512, height: 512, devicePixelRatio: api.getDevicePixelRatio(), onupdate: function() { api.getZr().refresh(); } }); this._api = api; this._labelsMesh.material.set("textureAtlas", this._labelTextureSurface.getTexture()); } LabelsBuilder.prototype.getLabelPosition = function(dataIndex, positionDesc, distance) { return [0, 0, 0]; }; LabelsBuilder.prototype.getLabelDistance = function(dataIndex, positionDesc, distance) { return 0; }; LabelsBuilder.prototype.getMesh = function() { return this._labelsMesh; }; LabelsBuilder.prototype.updateData = function(data, start, end) { if (start == null) { start = 0; } if (end == null) { end = data.count(); } if (!this._labelsVisibilitiesBits || this._labelsVisibilitiesBits.length !== end - start) { this._labelsVisibilitiesBits = new Uint8Array(end - start); } var normalLabelVisibilityQuery = ["label", "show"]; var emphasisLabelVisibilityQuery = ["emphasis", "label", "show"]; for (var idx = start; idx < end; idx++) { var itemModel = data.getItemModel(idx); var normalVisibility = itemModel.get(normalLabelVisibilityQuery); var emphasisVisibility = itemModel.get(emphasisLabelVisibilityQuery); if (emphasisVisibility == null) { emphasisVisibility = normalVisibility; } var bit = (normalVisibility ? LABEL_NORMAL_SHOW_BIT : 0) | (emphasisVisibility ? LABEL_EMPHASIS_SHOW_BIT : 0); this._labelsVisibilitiesBits[idx - start] = bit; } this._start = start; this._end = end; this._data = data; }; LabelsBuilder.prototype.updateLabels = function(highlightDataIndices) { if (!this._data) { return; } highlightDataIndices = highlightDataIndices || []; var hasHighlightData = highlightDataIndices.length > 0; var highlightDataIndicesMap = {}; for (var i = 0; i < highlightDataIndices.length; i++) { highlightDataIndicesMap[highlightDataIndices[i]] = true; } this._labelsMesh.geometry.convertToDynamicArray(true); this._labelTextureSurface.clear(); var normalLabelQuery = ["label"]; var emphasisLabelQuery = ["emphasis", "label"]; var seriesModel = this._data.hostModel; var data = this._data; var seriesLabelModel = seriesModel.getModel(normalLabelQuery); var seriesLabelEmphasisModel = seriesModel.getModel(emphasisLabelQuery, seriesLabelModel); var textAlignMap = { left: "right", right: "left", top: "center", bottom: "center" }; var textVerticalAlignMap = { left: "middle", right: "middle", top: "bottom", bottom: "top" }; for (var dataIndex = this._start; dataIndex < this._end; dataIndex++) { var isEmphasis = false; if (hasHighlightData && highlightDataIndicesMap[dataIndex]) { isEmphasis = true; } var ifShow = this._labelsVisibilitiesBits[dataIndex - this._start] & (isEmphasis ? LABEL_EMPHASIS_SHOW_BIT : LABEL_NORMAL_SHOW_BIT); if (!ifShow) { continue; } var itemModel = data.getItemModel(dataIndex); var labelModel = itemModel.getModel(isEmphasis ? emphasisLabelQuery : normalLabelQuery, isEmphasis ? seriesLabelEmphasisModel : seriesLabelModel); var distance = labelModel.get("distance") || 0; var position = labelModel.get("position"); var dpr = this._api.getDevicePixelRatio(); var text = seriesModel.getFormattedLabel(dataIndex, isEmphasis ? "emphasis" : "normal"); if (text == null || text === "") { return; } var textEl = new ZRText({ style: createTextStyle(labelModel, { text, fill: labelModel.get("color") || getItemVisualColor(data, dataIndex) || "#000", align: "left", verticalAlign: "top", opacity: retrieve.firstNotNull(labelModel.get("opacity"), getItemVisualOpacity(data, dataIndex), 1) }) }); var rect = textEl.getBoundingRect(); var lineHeight = 1.2; rect.height *= lineHeight; var coords = this._labelTextureSurface.add(textEl); var textAlign = textAlignMap[position] || "center"; var textVerticalAlign = textVerticalAlignMap[position] || "bottom"; this._labelsMesh.geometry.addSprite(this.getLabelPosition(dataIndex, position, distance), [rect.width * dpr, rect.height * dpr], coords, textAlign, textVerticalAlign, this.getLabelDistance(dataIndex, position, distance) * dpr); } this._labelsMesh.material.set("uvScale", this._labelTextureSurface.getCoordsScale()); this._labelTextureSurface.getZr().refreshImmediately(); this._labelsMesh.geometry.convertToTypedArray(); this._labelsMesh.geometry.dirty(); }; LabelsBuilder.prototype.dispose = function() { this._labelTextureSurface.dispose(); }; var vec3$b = glmatrix.vec3; graphicGL.Shader.import(lines3DGLSL); function Geo3DBuilder(api) { this.rootNode = new graphicGL.Node(); this._triangulationResults = {}; this._shadersMap = graphicGL.COMMON_SHADERS.filter(function(shaderName) { return shaderName !== "shadow"; }).reduce(function(obj, shaderName) { obj[shaderName] = graphicGL.createShader("ecgl." + shaderName); return obj; }, {}); this._linesShader = graphicGL.createShader("ecgl.meshLines3D"); var groundMaterials = {}; graphicGL.COMMON_SHADERS.forEach(function(shading) { groundMaterials[shading] = new graphicGL.Material({ shader: graphicGL.createShader("ecgl." + shading) }); }); this._groundMaterials = groundMaterials; this._groundMesh = new graphicGL.Mesh({ geometry: new graphicGL.PlaneGeometry({ dynamic: true }), castShadow: false, renderNormal: true, $ignorePicking: true }); this._groundMesh.rotation.rotateX(-Math.PI / 2); this._labelsBuilder = new LabelsBuilder(512, 512, api); this._labelsBuilder.getMesh().renderOrder = 100; this._labelsBuilder.getMesh().material.depthTest = false; this.rootNode.add(this._labelsBuilder.getMesh()); this._initMeshes(); this._api = api; } Geo3DBuilder.prototype = { constructor: Geo3DBuilder, // Which dimension to extrude. Y or Z extrudeY: true, update: function(componentModel, ecModel, api, start, end) { var data = componentModel.getData(); if (start == null) { start = 0; } if (end == null) { end = data.count(); } this._startIndex = start; this._endIndex = end - 1; this._triangulation(componentModel, start, end); var shader = this._getShader(componentModel.get("shading")); this._prepareMesh(componentModel, shader, api, start, end); this.rootNode.updateWorldTransform(); this._updateRegionMesh(componentModel, api, start, end); var coordSys = componentModel.coordinateSystem; if (coordSys.type === "geo3D") { this._updateGroundPlane(componentModel, coordSys, api); } var self2 = this; this._labelsBuilder.updateData(data, start, end); this._labelsBuilder.getLabelPosition = function(dataIndex, positionDesc, distance) { var name = data.getName(dataIndex); var center; var height = distance; if (coordSys.type === "geo3D") { var region = coordSys.getRegion(name); if (!region) { return [NaN, NaN, NaN]; } center = region.getCenter(); var pos = coordSys.dataToPoint([center[0], center[1], height]); return pos; } else { var tmp = self2._triangulationResults[dataIndex - self2._startIndex]; var center = self2.extrudeY ? [(tmp.max[0] + tmp.min[0]) / 2, tmp.max[1] + height, (tmp.max[2] + tmp.min[2]) / 2] : [(tmp.max[0] + tmp.min[0]) / 2, (tmp.max[1] + tmp.min[1]) / 2, tmp.max[2] + height]; } }; this._data = data; this._labelsBuilder.updateLabels(); this._updateDebugWireframe(componentModel); this._lastHoverDataIndex = 0; }, _initMeshes: function() { var self2 = this; function createPolygonMesh() { var mesh2 = new graphicGL.Mesh({ name: "Polygon", material: new graphicGL.Material({ shader: self2._shadersMap.lambert }), geometry: new graphicGL.Geometry({ sortTriangles: true, dynamic: true }), // TODO Disable culling culling: false, ignorePicking: true, // Render normal in normal pass renderNormal: true }); Object.assign(mesh2.geometry, trianglesSortMixin); return mesh2; } var polygonMesh = createPolygonMesh(); var linesMesh = new graphicGL.Mesh({ material: new graphicGL.Material({ shader: this._linesShader }), castShadow: false, ignorePicking: true, $ignorePicking: true, geometry: new LinesGeometry$2({ useNativeLine: false }) }); this.rootNode.add(polygonMesh); this.rootNode.add(linesMesh); polygonMesh.material.define("both", "VERTEX_COLOR"); polygonMesh.material.define("fragment", "DOUBLE_SIDED"); this._polygonMesh = polygonMesh; this._linesMesh = linesMesh; this.rootNode.add(this._groundMesh); }, _getShader: function(shading) { var shader = this._shadersMap[shading]; if (!shader) { shader = this._shadersMap.lambert; } shader.__shading = shading; return shader; }, _prepareMesh: function(componentModel, shader, api, start, end) { var polygonVertexCount = 0; var polygonTriangleCount = 0; var linesVertexCount = 0; var linesTriangleCount = 0; for (var idx = start; idx < end; idx++) { var polyInfo = this._getRegionPolygonInfo(idx); var lineInfo = this._getRegionLinesInfo(idx, componentModel, this._linesMesh.geometry); polygonVertexCount += polyInfo.vertexCount; polygonTriangleCount += polyInfo.triangleCount; linesVertexCount += lineInfo.vertexCount; linesTriangleCount += lineInfo.triangleCount; } var polygonMesh = this._polygonMesh; var polygonGeo = polygonMesh.geometry; ["position", "normal", "texcoord0", "color"].forEach(function(attrName) { polygonGeo.attributes[attrName].init(polygonVertexCount); }); polygonGeo.indices = polygonVertexCount > 65535 ? new Uint32Array(polygonTriangleCount * 3) : new Uint16Array(polygonTriangleCount * 3); if (polygonMesh.material.shader !== shader) { polygonMesh.material.attachShader(shader, true); } graphicGL.setMaterialFromModel(shader.__shading, polygonMesh.material, componentModel, api); if (linesVertexCount > 0) { this._linesMesh.geometry.resetOffset(); this._linesMesh.geometry.setVertexCount(linesVertexCount); this._linesMesh.geometry.setTriangleCount(linesTriangleCount); } this._dataIndexOfVertex = new Uint32Array(polygonVertexCount); this._vertexRangeOfDataIndex = new Uint32Array((end - start) * 2); }, _updateRegionMesh: function(componentModel, api, start, end) { var data = componentModel.getData(); var vertexOffset = 0; var triangleOffset = 0; var hasTranparentRegion = false; var polygonMesh = this._polygonMesh; var linesMesh = this._linesMesh; for (var dataIndex = start; dataIndex < end; dataIndex++) { var regionModel = componentModel.getRegionModel(dataIndex); var itemStyleModel = regionModel.getModel("itemStyle"); var color = retrieve.firstNotNull(getItemVisualColor(data, dataIndex), itemStyleModel.get("color"), "#fff"); var opacity = retrieve.firstNotNull(getItemVisualOpacity(data, dataIndex), itemStyleModel.get("opacity"), 1); var colorArr = graphicGL.parseColor(color); var borderColorArr = graphicGL.parseColor(itemStyleModel.get("borderColor")); colorArr[3] *= opacity; borderColorArr[3] *= opacity; var isTransparent = colorArr[3] < 0.99; polygonMesh.material.set("color", [1, 1, 1, 1]); hasTranparentRegion = hasTranparentRegion || isTransparent; var regionHeight = retrieve.firstNotNull(regionModel.get("height", true), componentModel.get("regionHeight")); var newOffsets = this._updatePolygonGeometry(componentModel, polygonMesh.geometry, dataIndex, regionHeight, vertexOffset, triangleOffset, colorArr); for (var i = vertexOffset; i < newOffsets.vertexOffset; i++) { this._dataIndexOfVertex[i] = dataIndex; } this._vertexRangeOfDataIndex[(dataIndex - start) * 2] = vertexOffset; this._vertexRangeOfDataIndex[(dataIndex - start) * 2 + 1] = newOffsets.vertexOffset; vertexOffset = newOffsets.vertexOffset; triangleOffset = newOffsets.triangleOffset; var lineWidth = itemStyleModel.get("borderWidth"); var hasLine = lineWidth > 0; if (hasLine) { lineWidth *= api.getDevicePixelRatio(); this._updateLinesGeometry(linesMesh.geometry, componentModel, dataIndex, regionHeight, lineWidth, componentModel.coordinateSystem.transform); } linesMesh.invisible = !hasLine; linesMesh.material.set({ color: borderColorArr }); } var polygonMesh = this._polygonMesh; polygonMesh.material.transparent = hasTranparentRegion; polygonMesh.material.depthMask = !hasTranparentRegion; polygonMesh.geometry.updateBoundingBox(); polygonMesh.frontFace = this.extrudeY ? graphicGL.Mesh.CCW : graphicGL.Mesh.CW; if (polygonMesh.material.get("normalMap")) { polygonMesh.geometry.generateTangents(); } polygonMesh.seriesIndex = componentModel.seriesIndex; polygonMesh.on("mousemove", this._onmousemove, this); polygonMesh.on("mouseout", this._onmouseout, this); }, _updateDebugWireframe: function(componentModel) { var debugWireframeModel = componentModel.getModel("debug.wireframe"); if (debugWireframeModel.get("show")) { var color = graphicGL.parseColor(debugWireframeModel.get("lineStyle.color") || "rgba(0,0,0,0.5)"); var width = retrieve.firstNotNull(debugWireframeModel.get("lineStyle.width"), 1); var mesh2 = this._polygonMesh; mesh2.geometry.generateBarycentric(); mesh2.material.define("both", "WIREFRAME_TRIANGLE"); mesh2.material.set("wireframeLineColor", color); mesh2.material.set("wireframeLineWidth", width); } }, _onmousemove: function(e2) { var dataIndex = this._dataIndexOfVertex[e2.triangle[0]]; if (dataIndex == null) { dataIndex = -1; } if (dataIndex !== this._lastHoverDataIndex) { this.downplay(this._lastHoverDataIndex); this.highlight(dataIndex); this._labelsBuilder.updateLabels([dataIndex]); } this._lastHoverDataIndex = dataIndex; this._polygonMesh.dataIndex = dataIndex; }, _onmouseout: function(e2) { if (e2.target) { this.downplay(this._lastHoverDataIndex); this._lastHoverDataIndex = -1; this._polygonMesh.dataIndex = -1; } this._labelsBuilder.updateLabels([]); }, _updateGroundPlane: function(componentModel, geo3D, api) { var groundModel = componentModel.getModel("groundPlane", componentModel); this._groundMesh.invisible = !groundModel.get("show", true); if (this._groundMesh.invisible) { return; } var shading = componentModel.get("shading"); var material = this._groundMaterials[shading]; if (!material) { material = this._groundMaterials.lambert; } graphicGL.setMaterialFromModel(shading, material, groundModel, api); if (material.get("normalMap")) { this._groundMesh.geometry.generateTangents(); } this._groundMesh.material = material; this._groundMesh.material.set("color", graphicGL.parseColor(groundModel.get("color"))); this._groundMesh.scale.set(geo3D.size[0], geo3D.size[2], 1); }, _triangulation: function(componentModel, start, end) { this._triangulationResults = []; var minAll = [Infinity, Infinity, Infinity]; var maxAll = [-Infinity, -Infinity, -Infinity]; var coordSys = componentModel.coordinateSystem; for (var idx = start; idx < end; idx++) { var polygons = []; var polygonCoords = componentModel.getRegionPolygonCoords(idx); for (var i = 0; i < polygonCoords.length; i++) { var exterior = polygonCoords[i].exterior; var interiors = polygonCoords[i].interiors; var points = []; var holes = []; if (exterior.length < 3) { continue; } var offset = 0; for (var j = 0; j < exterior.length; j++) { var p = exterior[j]; points[offset++] = p[0]; points[offset++] = p[1]; } for (var j = 0; j < interiors.length; j++) { if (interiors[j].length < 3) { continue; } var startIdx = points.length / 2; for (var k = 0; k < interiors[j].length; k++) { var p = interiors[j][k]; points.push(p[0]); points.push(p[1]); } holes.push(startIdx); } var triangles = earcut(points, holes); var points3 = new Float64Array(points.length / 2 * 3); var pos = []; var min = [Infinity, Infinity, Infinity]; var max = [-Infinity, -Infinity, -Infinity]; var off3 = 0; for (var j = 0; j < points.length; ) { vec3$b.set(pos, points[j++], 0, points[j++]); if (coordSys && coordSys.transform) { vec3$b.transformMat4(pos, pos, coordSys.transform); } vec3$b.min(min, min, pos); vec3$b.max(max, max, pos); points3[off3++] = pos[0]; points3[off3++] = pos[1]; points3[off3++] = pos[2]; } vec3$b.min(minAll, minAll, min); vec3$b.max(maxAll, maxAll, max); polygons.push({ points: points3, indices: triangles, min, max }); } this._triangulationResults.push(polygons); } this._geoBoundingBox = [minAll, maxAll]; }, /** * Get region vertex and triangle count */ _getRegionPolygonInfo: function(idx) { var polygons = this._triangulationResults[idx - this._startIndex]; var sideVertexCount = 0; var sideTriangleCount = 0; for (var i = 0; i < polygons.length; i++) { sideVertexCount += polygons[i].points.length / 3; sideTriangleCount += polygons[i].indices.length / 3; } var vertexCount = sideVertexCount * 2 + sideVertexCount * 4; var triangleCount = sideTriangleCount * 2 + sideVertexCount * 2; return { vertexCount, triangleCount }; }, _updatePolygonGeometry: function(componentModel, geometry, dataIndex, regionHeight, vertexOffset, triangleOffset, color) { var projectUVOnGround = componentModel.get("projectUVOnGround"); var positionAttr = geometry.attributes.position; var normalAttr = geometry.attributes.normal; var texcoordAttr = geometry.attributes.texcoord0; var colorAttr = geometry.attributes.color; var polygons = this._triangulationResults[dataIndex - this._startIndex]; var hasColor = colorAttr.value && color; var indices = geometry.indices; var extrudeCoordIndex = this.extrudeY ? 1 : 2; var sideCoordIndex = this.extrudeY ? 2 : 1; var scale = [this.rootNode.worldTransform.x.len(), this.rootNode.worldTransform.y.len(), this.rootNode.worldTransform.z.len()]; var min = vec3$b.mul([], this._geoBoundingBox[0], scale); var max = vec3$b.mul([], this._geoBoundingBox[1], scale); var maxDimSize = Math.max(max[0] - min[0], max[2] - min[2]); function addVertices(polygon2, y, insideOffset) { var points = polygon2.points; var pointsLen = points.length; var currentPosition = []; var uv2 = []; for (var k2 = 0; k2 < pointsLen; k2 += 3) { currentPosition[0] = points[k2]; currentPosition[extrudeCoordIndex] = y; currentPosition[sideCoordIndex] = points[k2 + 2]; uv2[0] = (points[k2] * scale[0] - min[0]) / maxDimSize; uv2[1] = (points[k2 + 2] * scale[sideCoordIndex] - min[2]) / maxDimSize; positionAttr.set(vertexOffset, currentPosition); if (hasColor) { colorAttr.set(vertexOffset, color); } texcoordAttr.set(vertexOffset++, uv2); } } function buildTopBottom(polygon2, y, insideOffset) { var startVertexOffset2 = vertexOffset; addVertices(polygon2, y); var len2 = polygon2.indices.length; for (var k2 = 0; k2 < len2; k2++) { indices[triangleOffset * 3 + k2] = polygon2.indices[k2] + startVertexOffset2; } triangleOffset += polygon2.indices.length / 3; } var normalTop = this.extrudeY ? [0, 1, 0] : [0, 0, 1]; var normalBottom = vec3$b.negate([], normalTop); for (var p = 0; p < polygons.length; p++) { var startVertexOffset = vertexOffset; var polygon = polygons[p]; buildTopBottom(polygon, 0); buildTopBottom(polygon, regionHeight); var ringVertexCount = polygon.points.length / 3; for (var v = 0; v < ringVertexCount; v++) { normalAttr.set(startVertexOffset + v, normalBottom); normalAttr.set(startVertexOffset + v + ringVertexCount, normalTop); } var quadToTriangle = [0, 3, 1, 1, 3, 2]; var quadPos = [[], [], [], []]; var a = []; var b = []; var normal2 = []; var uv = []; var len = 0; for (var v = 0; v < ringVertexCount; v++) { var next = (v + 1) % ringVertexCount; var dx = (polygon.points[next * 3] - polygon.points[v * 3]) * scale[0]; var dy = (polygon.points[next * 3 + 2] - polygon.points[v * 3 + 2]) * scale[sideCoordIndex]; var sideLen = Math.sqrt(dx * dx + dy * dy); for (var k = 0; k < 4; k++) { var isCurrent = k === 0 || k === 3; var idx3 = (isCurrent ? v : next) * 3; quadPos[k][0] = polygon.points[idx3]; quadPos[k][extrudeCoordIndex] = k > 1 ? regionHeight : 0; quadPos[k][sideCoordIndex] = polygon.points[idx3 + 2]; positionAttr.set(vertexOffset + k, quadPos[k]); if (projectUVOnGround) { uv[0] = (polygon.points[idx3] * scale[0] - min[0]) / maxDimSize; uv[1] = (polygon.points[idx3 + 2] * scale[sideCoordIndex] - min[sideCoordIndex]) / maxDimSize; } else { uv[0] = (isCurrent ? len : len + sideLen) / maxDimSize; uv[1] = (quadPos[k][extrudeCoordIndex] * scale[extrudeCoordIndex] - min[extrudeCoordIndex]) / maxDimSize; } texcoordAttr.set(vertexOffset + k, uv); } vec3$b.sub(a, quadPos[1], quadPos[0]); vec3$b.sub(b, quadPos[3], quadPos[0]); vec3$b.cross(normal2, a, b); vec3$b.normalize(normal2, normal2); for (var k = 0; k < 4; k++) { normalAttr.set(vertexOffset + k, normal2); if (hasColor) { colorAttr.set(vertexOffset + k, color); } } for (var k = 0; k < 6; k++) { indices[triangleOffset * 3 + k] = quadToTriangle[k] + vertexOffset; } vertexOffset += 4; triangleOffset += 2; len += sideLen; } } geometry.dirty(); return { vertexOffset, triangleOffset }; }, _getRegionLinesInfo: function(idx, componentModel, geometry) { var vertexCount = 0; var triangleCount = 0; var regionModel = componentModel.getRegionModel(idx); var itemStyleModel = regionModel.getModel("itemStyle"); var lineWidth = itemStyleModel.get("borderWidth"); if (lineWidth > 0) { var polygonCoords = componentModel.getRegionPolygonCoords(idx); polygonCoords.forEach(function(coords) { var exterior = coords.exterior; var interiors = coords.interiors; vertexCount += geometry.getPolylineVertexCount(exterior); triangleCount += geometry.getPolylineTriangleCount(exterior); for (var i = 0; i < interiors.length; i++) { vertexCount += geometry.getPolylineVertexCount(interiors[i]); triangleCount += geometry.getPolylineTriangleCount(interiors[i]); } }, this); } return { vertexCount, triangleCount }; }, _updateLinesGeometry: function(geometry, componentModel, dataIndex, regionHeight, lineWidth, transform) { function convertToPoints3(polygon) { var points = new Float64Array(polygon.length * 3); var offset = 0; var pos = []; for (var i = 0; i < polygon.length; i++) { pos[0] = polygon[i][0]; pos[1] = regionHeight + 0.1; pos[2] = polygon[i][1]; if (transform) { vec3$b.transformMat4(pos, pos, transform); } points[offset++] = pos[0]; points[offset++] = pos[1]; points[offset++] = pos[2]; } return points; } var whiteColor = [1, 1, 1, 1]; var coords = componentModel.getRegionPolygonCoords(dataIndex); coords.forEach(function(geo) { var exterior = geo.exterior; var interiors = geo.interiors; geometry.addPolyline(convertToPoints3(exterior), whiteColor, lineWidth); for (var i = 0; i < interiors.length; i++) { geometry.addPolyline(convertToPoints3(interiors[i]), whiteColor, lineWidth); } }); }, highlight: function(dataIndex) { var data = this._data; if (!data) { return; } var itemModel = data.getItemModel(dataIndex); var emphasisItemStyleModel = itemModel.getModel(["emphasis", "itemStyle"]); var emphasisColor = emphasisItemStyleModel.get("color"); var emphasisOpacity = retrieve.firstNotNull(emphasisItemStyleModel.get("opacity"), getItemVisualOpacity(data, dataIndex), 1); if (emphasisColor == null) { var color = getItemVisualColor(data, dataIndex); emphasisColor = lift(color, -0.4); } if (emphasisOpacity == null) { emphasisOpacity = getItemVisualOpacity(data, dataIndex); } var colorArr = graphicGL.parseColor(emphasisColor); colorArr[3] *= emphasisOpacity; this._setColorOfDataIndex(data, dataIndex, colorArr); }, downplay: function(dataIndex) { var data = this._data; if (!data) { return; } var itemStyleModel = data.getItemModel(dataIndex); var color = retrieve.firstNotNull(getItemVisualColor(data, dataIndex), itemStyleModel.get(["itemStyle", "color"]), "#fff"); var opacity = retrieve.firstNotNull(getItemVisualOpacity(data, dataIndex), itemStyleModel.get(["itemStyle", "opacity"]), 1); var colorArr = graphicGL.parseColor(color); colorArr[3] *= opacity; this._setColorOfDataIndex(data, dataIndex, colorArr); }, dispose: function() { this._labelsBuilder.dispose(); }, _setColorOfDataIndex: function(data, dataIndex, colorArr) { if (dataIndex < this._startIndex && dataIndex > this._endIndex) { return; } dataIndex -= this._startIndex; for (var i = this._vertexRangeOfDataIndex[dataIndex * 2]; i < this._vertexRangeOfDataIndex[dataIndex * 2 + 1]; i++) { this._polygonMesh.geometry.attributes.color.set(i, colorArr); } this._polygonMesh.geometry.dirty(); this._api.getZr().refresh(); } }; const Geo3DView = ComponentView.extend({ type: "geo3D", __ecgl__: true, init: function(ecModel, api) { this._geo3DBuilder = new Geo3DBuilder(api); this.groupGL = new graphicGL.Node(); this._lightRoot = new graphicGL.Node(); this._sceneHelper = new SceneHelper(this._lightRoot); this._sceneHelper.initLight(this._lightRoot); this._control = new OrbitControl({ zr: api.getZr() }); this._control.init(); }, render: function(geo3DModel, ecModel, api) { this.groupGL.add(this._geo3DBuilder.rootNode); var geo3D = geo3DModel.coordinateSystem; if (!geo3D || !geo3D.viewGL) { return; } geo3D.viewGL.add(this._lightRoot); if (geo3DModel.get("show")) { geo3D.viewGL.add(this.groupGL); } else { geo3D.viewGL.remove(this.groupGL); } var control = this._control; control.setViewGL(geo3D.viewGL); var viewControlModel = geo3DModel.getModel("viewControl"); control.setFromViewControlModel(viewControlModel, 0); this._sceneHelper.setScene(geo3D.viewGL.scene); this._sceneHelper.updateLight(geo3DModel); geo3D.viewGL.setPostEffect(geo3DModel.getModel("postEffect"), api); geo3D.viewGL.setTemporalSuperSampling(geo3DModel.getModel("temporalSuperSampling")); this._geo3DBuilder.update(geo3DModel, ecModel, api, 0, geo3DModel.getData().count()); var srgbDefineMethod = geo3D.viewGL.isLinearSpace() ? "define" : "undefine"; this._geo3DBuilder.rootNode.traverse(function(mesh2) { if (mesh2.material) { mesh2.material[srgbDefineMethod]("fragment", "SRGB_DECODE"); } }); control.off("update"); control.on("update", function() { api.dispatchAction({ type: "geo3DChangeCamera", alpha: control.getAlpha(), beta: control.getBeta(), distance: control.getDistance(), center: control.getCenter(), from: this.uid, geo3DId: geo3DModel.id }); }); control.update(); }, afterRender: function(geo3DModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; this._sceneHelper.updateAmbientCubemap(renderer, geo3DModel, api); this._sceneHelper.updateSkybox(renderer, geo3DModel, api); }, dispose: function() { this._control.dispose(); this._geo3DBuilder.dispose(); } }); var geoCoordMap = { "Russia": [100, 60], "United States": [-99, 38], "United States of America": [-99, 38] }; function fixGeoCoords(mapType, region) { if (mapType === "world") { var geoCoord = geoCoordMap[region.name]; if (geoCoord) { var cp = [geoCoord[0], geoCoord[1]]; region.setCenter(cp); } } } var vec3$a = glmatrix.vec3; var mat4$1 = glmatrix.mat4; var geoFixFuncs = [fixTextCoords, fixGeoCoords]; function Geo3D(name, map2, geoJson, specialAreas, nameMap) { this.name = name; this.map = map2; this.regionHeight = 0; this.regions = []; this._nameCoordMap = {}; this.loadGeoJson(geoJson, specialAreas, nameMap); this.transform = mat4$1.identity(new Float64Array(16)); this.invTransform = mat4$1.identity(new Float64Array(16)); this.extrudeY = true; this.altitudeAxis; } Geo3D.prototype = { constructor: Geo3D, type: "geo3D", dimensions: ["lng", "lat", "alt"], containPoint: function() { }, loadGeoJson: function(geoJson, specialAreas, nameMap) { var parseGeoJSON$1 = parseGeoJSON || parseGeoJSON; try { this.regions = geoJson ? parseGeoJSON$1(geoJson) : []; } catch (e2) { throw "Invalid geoJson format\n" + e2; } specialAreas = specialAreas || {}; nameMap = nameMap || {}; var regions = this.regions; var regionsMap = {}; for (var i = 0; i < regions.length; i++) { var regionName = regions[i].name; regionName = nameMap[regionName] || regionName; regions[i].name = regionName; regionsMap[regionName] = regions[i]; this.addGeoCoord(regionName, regions[i].getCenter()); var specialArea = specialAreas[regionName]; if (specialArea) { regions[i].transformTo(specialArea.left, specialArea.top, specialArea.width, specialArea.height); } } this._regionsMap = regionsMap; this._geoRect = null; geoFixFuncs.forEach(function(fixFunc) { fixFunc(this); }, this); }, getGeoBoundingRect: function() { if (this._geoRect) { return this._geoRect; } var rect; var regions = this.regions; for (var i = 0; i < regions.length; i++) { var regionRect = regions[i].getBoundingRect(); rect = rect || regionRect.clone(); rect.union(regionRect); } return this._geoRect = rect || new BoundingRect(0, 0, 0, 0); }, /** * Add geoCoord for indexing by name * @param {string} name * @param {Array.} geoCoord */ addGeoCoord: function(name, geoCoord) { this._nameCoordMap[name] = geoCoord; }, /** * @param {string} name * @return {module:echarts/coord/geo/Region} */ getRegion: function(name) { return this._regionsMap[name]; }, getRegionByCoord: function(coord) { var regions = this.regions; for (var i = 0; i < regions.length; i++) { if (regions[i].contain(coord)) { return regions[i]; } } }, setSize: function(width, height, depth) { this.size = [width, height, depth]; var rect = this.getGeoBoundingRect(); var scaleX = width / rect.width; var scaleZ = -depth / rect.height; var translateX = -width / 2 - rect.x * scaleX; var translateZ = depth / 2 - rect.y * scaleZ; var position = this.extrudeY ? [translateX, 0, translateZ] : [translateX, translateZ, 0]; var scale = this.extrudeY ? [scaleX, 1, scaleZ] : [scaleX, scaleZ, 1]; var m = this.transform; mat4$1.identity(m); mat4$1.translate(m, m, position); mat4$1.scale(m, m, scale); mat4$1.invert(this.invTransform, m); }, dataToPoint: function(data, out) { out = out || []; var extrudeCoordIndex = this.extrudeY ? 1 : 2; var sideCoordIndex = this.extrudeY ? 2 : 1; var altitudeVal = data[2]; if (isNaN(altitudeVal)) { altitudeVal = 0; } out[0] = data[0]; out[sideCoordIndex] = data[1]; if (this.altitudeAxis) { out[extrudeCoordIndex] = this.altitudeAxis.dataToCoord(altitudeVal); } else { out[extrudeCoordIndex] = 0; } out[extrudeCoordIndex] += this.regionHeight; vec3$a.transformMat4(out, out, this.transform); return out; }, pointToData: function(point, out) { } }; function resizeGeo3D(geo3DModel, api) { var boxLayoutOption = geo3DModel.getBoxLayoutParams(); var viewport = getLayoutRect(boxLayoutOption, { width: api.getWidth(), height: api.getHeight() }); viewport.y = api.getHeight() - viewport.y - viewport.height; this.viewGL.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, api.getDevicePixelRatio()); var geoRect = this.getGeoBoundingRect(); var aspect = geoRect.width / geoRect.height * (geo3DModel.get("aspectScale") || 0.75); var width = geo3DModel.get("boxWidth"); var depth = geo3DModel.get("boxDepth"); var height = geo3DModel.get("boxHeight"); if (height == null) { height = 5; } if (isNaN(width) && isNaN(depth)) { width = 100; } if (isNaN(depth)) { depth = width / aspect; } else if (isNaN(width)) { width = depth / aspect; } this.setSize(width, height, depth); this.regionHeight = geo3DModel.get("regionHeight"); if (this.altitudeAxis) { this.altitudeAxis.setExtent(0, Math.max(height - this.regionHeight, 0)); } } function updateGeo3D(ecModel, api) { var altitudeDataExtent = [Infinity, -Infinity]; ecModel.eachSeries(function(seriesModel) { if (seriesModel.coordinateSystem !== this) { return; } if (seriesModel.type === "series.map3D") { return; } var data = seriesModel.getData(); var altDims = seriesModel.coordDimToDataDim("alt"); var altDim = altDims && altDims[0]; if (altDim) { var dataExtent = data.getDataExtent(altDim, true); altitudeDataExtent[0] = Math.min(altitudeDataExtent[0], dataExtent[0]); altitudeDataExtent[1] = Math.max(altitudeDataExtent[1], dataExtent[1]); } }, this); if (altitudeDataExtent && isFinite(altitudeDataExtent[1] - altitudeDataExtent[0])) { var scale = createScale(altitudeDataExtent, { type: "value", // PENDING min: "dataMin", max: "dataMax" }); this.altitudeAxis = new Axis("altitude", scale); this.resize(this.model, api); } } var idStart = 0; var geo3DCreator = { dimensions: Geo3D.prototype.dimensions, create: function(ecModel, api) { var geo3DList = []; if (!getMap) { throw new Error("geo3D component depends on geo component"); } function createGeo3D(componentModel, idx) { var geo3D = geo3DCreator.createGeo3D(componentModel); componentModel.__viewGL = componentModel.__viewGL || new ViewGL(); geo3D.viewGL = componentModel.__viewGL; componentModel.coordinateSystem = geo3D; geo3D.model = componentModel; geo3DList.push(geo3D); geo3D.resize = resizeGeo3D; geo3D.resize(componentModel, api); geo3D.update = updateGeo3D; } ecModel.eachComponent("geo3D", function(geo3DModel, idx) { createGeo3D(geo3DModel); }); ecModel.eachSeriesByType("map3D", function(map3DModel, idx) { var coordSys = map3DModel.get("coordinateSystem"); if (coordSys == null) { coordSys = "geo3D"; } if (coordSys === "geo3D") { createGeo3D(map3DModel); } }); ecModel.eachSeries(function(seriesModel) { if (seriesModel.get("coordinateSystem") === "geo3D") { if (seriesModel.type === "series.map3D") { return; } var geo3DModel = seriesModel.getReferringComponents("geo3D").models[0]; if (!geo3DModel) { geo3DModel = ecModel.getComponent("geo3D"); } if (!geo3DModel) { throw new Error('geo "' + retrieve.firstNotNull(seriesModel.get("geo3DIndex"), seriesModel.get("geo3DId"), 0) + '" not found'); } seriesModel.coordinateSystem = geo3DModel.coordinateSystem; } }); return geo3DList; }, createGeo3D: function(componentModel) { var mapData = componentModel.get("map"); var name; if (typeof mapData === "string") { name = mapData; mapData = getMap(mapData); } else { if (mapData && mapData.features) { mapData = { geoJson: mapData }; } } if (name == null) { name = "GEO_ANONYMOUS_" + idStart++; } return new Geo3D(name + idStart++, name, mapData && mapData.geoJson, mapData && mapData.specialAreas, componentModel.get("nameMap")); } }; function install$e(registers) { registers.registerComponentModel(Geo3DModel); registers.registerComponentView(Geo3DView); registers.registerAction({ type: "geo3DChangeCamera", event: "geo3dcamerachanged", update: "series:updateCamera" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "geo3D", query: payload }, function(componentModel) { componentModel.setView(payload); }); }); registers.registerCoordinateSystem("geo3D", geo3DCreator); } use(install$e); function defaultId(option, idx) { option.id = option.id || option.name || idx + ""; } var GlobeModel = ComponentModel.extend({ type: "globe", layoutMode: "box", coordinateSystem: null, init: function() { GlobeModel.superApply(this, "init", arguments); each(this.option.layers, function(layerOption, idx) { merge(layerOption, this.defaultLayerOption); defaultId(layerOption, idx); }, this); }, mergeOption: function(option) { var oldLayers = this.option.layers; this.option.layers = null; GlobeModel.superApply(this, "mergeOption", arguments); function createLayerMap(layers) { return reduce$1(layers, function(obj, layerOption, idx) { defaultId(layerOption, idx); obj[layerOption.id] = layerOption; return obj; }, {}); } if (oldLayers && oldLayers.length) { var newLayerMap = createLayerMap(option.layers); var oldLayerMap = createLayerMap(oldLayers); for (var id in newLayerMap) { if (oldLayerMap[id]) { merge(oldLayerMap[id], newLayerMap[id], true); } else { oldLayers.push(option.layers[id]); } } this.option.layers = oldLayers; } each(this.option.layers, function(layerOption) { merge(layerOption, this.defaultLayerOption); }, this); }, optionUpdated: function() { this.updateDisplacementHash(); }, defaultLayerOption: { show: true, type: "overlay" }, defaultOption: { show: true, zlevel: -10, // Layout used for viewport left: 0, top: 0, width: "100%", height: "100%", environment: "auto", baseColor: "#fff", // Base albedo texture baseTexture: "", // Height texture for bump mapping and vertex displacement heightTexture: "", // Texture for vertex displacement, default use heightTexture displacementTexture: "", // Scale of vertex displacement, available only if displacementTexture is set. displacementScale: 0, // Detail of displacement. 'low', 'medium', 'high', 'ultra' displacementQuality: "medium", // Globe radius globeRadius: 100, // Globe outer radius. Which is max of altitude. globeOuterRadius: 150, // Shading of globe shading: "lambert", // Extend light light: { // Main sun light main: { // Time, default it will use system time time: "" } }, // atmosphere atmosphere: { show: false, offset: 5, color: "#ffffff", glowPower: 6, innerGlowPower: 2 }, // light // postEffect // temporalSuperSampling viewControl: { autoRotate: true, panSensitivity: 0, targetCoord: null }, // { // show: true, // name: 'cloud', // type: 'overlay', // shading: 'lambert', // distance: 10, // texture: '' // } // { // type: 'blend', // blendTo: 'albedo' // blendType: 'source-over' // } layers: [] }, setDisplacementData: function(data, width, height) { this.displacementData = data; this.displacementWidth = width; this.displacementHeight = height; }, getDisplacementTexture: function() { return this.get("displacementTexture") || this.get("heightTexture"); }, getDisplacemenScale: function() { var displacementTexture = this.getDisplacementTexture(); var displacementScale = this.get("displacementScale"); if (!displacementTexture || displacementTexture === "none") { displacementScale = 0; } return displacementScale; }, hasDisplacement: function() { return this.getDisplacemenScale() > 0; }, _displacementChanged: true, _displacementScale: 0, updateDisplacementHash: function() { var displacementTexture = this.getDisplacementTexture(); var displacementScale = this.getDisplacemenScale(); this._displacementChanged = this._displacementTexture !== displacementTexture || this._displacementScale !== displacementScale; this._displacementTexture = displacementTexture; this._displacementScale = displacementScale; }, isDisplacementChanged: function() { return this._displacementChanged; } }); merge(GlobeModel.prototype, componentViewControlMixin); merge(GlobeModel.prototype, componentPostEffectMixin); merge(GlobeModel.prototype, componentLightMixin); merge(GlobeModel.prototype, componentShadingMixin); var PI$1 = Math.PI, sin = Math.sin, cos = Math.cos, tan = Math.tan, asin = Math.asin, atan = Math.atan2, rad = PI$1 / 180; var dayMs = 1e3 * 60 * 60 * 24, J1970 = 2440588, J2000 = 2451545; function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } function toDays(date) { return toJulian(date) - J2000; } var e = rad * 23.4397; function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } function eclipticLongitude(M) { var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 3e-4 * sin(3 * M)), P = rad * 102.9372; return M + C + P + PI$1; } function sunCoords(d) { var M = solarMeanAnomaly(d), L = eclipticLongitude(M); return { dec: declination(L, 0), ra: rightAscension(L, 0) }; } var SunCalc = {}; SunCalc.getPosition = function(date, lat, lng) { var lw = rad * -lng, phi = rad * lat, d = toDays(date), c = sunCoords(d), H = siderealTime(d, lw) - c.ra; return { azimuth: azimuth(H, phi, c.dec), altitude: altitude(H, phi, c.dec) }; }; const atmosphereShaderCode = "@export ecgl.atmosphere.vertex\nattribute vec3 position: POSITION;\nattribute vec3 normal : NORMAL;\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform mat4 normalMatrix : WORLDINVERSETRANSPOSE;\n\nvarying vec3 v_Normal;\n\nvoid main() {\n v_Normal = normalize((normalMatrix * vec4(normal, 0.0)).xyz);\n gl_Position = worldViewProjection * vec4(position, 1.0);\n}\n@end\n\n\n@export ecgl.atmosphere.fragment\nuniform mat4 viewTranspose: VIEWTRANSPOSE;\nuniform float glowPower;\nuniform vec3 glowColor;\n\nvarying vec3 v_Normal;\n\nvoid main() {\n float intensity = pow(1.0 - dot(v_Normal, (viewTranspose * vec4(0.0, 0.0, 1.0, 0.0)).xyz), glowPower);\n gl_FragColor = vec4(glowColor, intensity * intensity);\n}\n@end"; graphicGL.Shader["import"](utilShaderCode); graphicGL.Shader["import"](atmosphereShaderCode); const GlobeView = ComponentView.extend({ type: "globe", __ecgl__: true, _displacementScale: 0, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this._sphereGeometry = new graphicGL.SphereGeometry({ widthSegments: 200, heightSegments: 100, dynamic: true }); this._overlayGeometry = new graphicGL.SphereGeometry({ widthSegments: 80, heightSegments: 40 }); this._planeGeometry = new graphicGL.PlaneGeometry(); this._earthMesh = new graphicGL.Mesh({ renderNormal: true }); this._atmosphereMesh = new graphicGL.Mesh(); this._atmosphereGeometry = new graphicGL.SphereGeometry({ widthSegments: 80, heightSegments: 40 }); this._atmosphereMaterial = new graphicGL.Material({ shader: new graphicGL.Shader(graphicGL.Shader.source("ecgl.atmosphere.vertex"), graphicGL.Shader.source("ecgl.atmosphere.fragment")), transparent: true }); this._atmosphereMesh.geometry = this._atmosphereGeometry; this._atmosphereMesh.material = this._atmosphereMaterial; this._atmosphereMesh.frontFace = graphicGL.Mesh.CW; this._lightRoot = new graphicGL.Node(); this._sceneHelper = new SceneHelper(); this._sceneHelper.initLight(this._lightRoot); this.groupGL.add(this._atmosphereMesh); this.groupGL.add(this._earthMesh); this._control = new OrbitControl({ zr: api.getZr() }); this._control.init(); this._layerMeshes = {}; }, render: function(globeModel, ecModel, api) { var coordSys = globeModel.coordinateSystem; var shading = globeModel.get("shading"); coordSys.viewGL.add(this._lightRoot); if (globeModel.get("show")) { coordSys.viewGL.add(this.groupGL); } else { coordSys.viewGL.remove(this.groupGL); } this._sceneHelper.setScene(coordSys.viewGL.scene); coordSys.viewGL.setPostEffect(globeModel.getModel("postEffect"), api); coordSys.viewGL.setTemporalSuperSampling(globeModel.getModel("temporalSuperSampling")); var earthMesh = this._earthMesh; earthMesh.geometry = this._sphereGeometry; var shadingPrefix = "ecgl." + shading; if (!earthMesh.material || earthMesh.material.shader.name !== shadingPrefix) { earthMesh.material = graphicGL.createMaterial(shadingPrefix); } graphicGL.setMaterialFromModel(shading, earthMesh.material, globeModel, api); ["roughnessMap", "metalnessMap", "detailMap", "normalMap"].forEach(function(texName) { var texture = earthMesh.material.get(texName); if (texture) { texture.flipY = false; } }); earthMesh.material.set("color", graphicGL.parseColor(globeModel.get("baseColor"))); var scale = coordSys.radius * 0.99; earthMesh.scale.set(scale, scale, scale); if (globeModel.get("atmosphere.show")) { earthMesh.material.define("both", "ATMOSPHERE_ENABLED"); this._atmosphereMesh.invisible = false; this._atmosphereMaterial.setUniforms({ glowPower: globeModel.get("atmosphere.glowPower") || 6, glowColor: globeModel.get("atmosphere.color") || "#ffffff" }); earthMesh.material.setUniforms({ glowPower: globeModel.get("atmosphere.innerGlowPower") || 2, glowColor: globeModel.get("atmosphere.color") || "#ffffff" }); var offset = globeModel.get("atmosphere.offset") || 5; this._atmosphereMesh.scale.set(scale + offset, scale + offset, scale + offset); } else { earthMesh.material.undefine("both", "ATMOSPHERE_ENABLED"); this._atmosphereMesh.invisible = true; } var diffuseTexture = earthMesh.material.setTextureImage("diffuseMap", globeModel.get("baseTexture"), api, { flipY: false, anisotropic: 8 }); if (diffuseTexture && diffuseTexture.surface) { diffuseTexture.surface.attachToMesh(earthMesh); } var bumpTexture = earthMesh.material.setTextureImage("bumpMap", globeModel.get("heightTexture"), api, { flipY: false, anisotropic: 8 }); if (bumpTexture && bumpTexture.surface) { bumpTexture.surface.attachToMesh(earthMesh); } earthMesh.material[globeModel.get("postEffect.enable") ? "define" : "undefine"]("fragment", "SRGB_DECODE"); this._updateLight(globeModel, api); this._displaceVertices(globeModel, api); this._updateViewControl(globeModel, api); this._updateLayers(globeModel, api); }, afterRender: function(globeModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; this._sceneHelper.updateAmbientCubemap(renderer, globeModel, api); this._sceneHelper.updateSkybox(renderer, globeModel, api); }, _updateLayers: function(globeModel, api) { var coordSys = globeModel.coordinateSystem; var layers = globeModel.get("layers"); var lastDistance = coordSys.radius; var layerDiffuseTextures = []; var layerDiffuseIntensity = []; var layerEmissiveTextures = []; var layerEmissionIntensity = []; each(layers, function(layerOption) { var layerModel = new Model(layerOption); var layerType = layerModel.get("type"); var texture = graphicGL.loadTexture(layerModel.get("texture"), api, { flipY: false, anisotropic: 8 }); if (texture.surface) { texture.surface.attachToMesh(this._earthMesh); } if (layerType === "blend") { var blendTo = layerModel.get("blendTo"); var intensity = retrieve.firstNotNull(layerModel.get("intensity"), 1); if (blendTo === "emission") { layerEmissiveTextures.push(texture); layerEmissionIntensity.push(intensity); } else { layerDiffuseTextures.push(texture); layerDiffuseIntensity.push(intensity); } } else { var id = layerModel.get("id"); var overlayMesh = this._layerMeshes[id]; if (!overlayMesh) { overlayMesh = this._layerMeshes[id] = new graphicGL.Mesh({ geometry: this._overlayGeometry, castShadow: false, ignorePicking: true }); } var shading = layerModel.get("shading"); if (shading === "lambert") { overlayMesh.material = overlayMesh.__lambertMaterial || new graphicGL.Material({ autoUpdateTextureStatus: false, shader: graphicGL.createShader("ecgl.lambert"), transparent: true, depthMask: false }); overlayMesh.__lambertMaterial = overlayMesh.material; } else { overlayMesh.material = overlayMesh.__colorMaterial || new graphicGL.Material({ autoUpdateTextureStatus: false, shader: graphicGL.createShader("ecgl.color"), transparent: true, depthMask: false }); overlayMesh.__colorMaterial = overlayMesh.material; } overlayMesh.material.enableTexture("diffuseMap"); var distance = layerModel.get("distance"); var radius = lastDistance + (distance == null ? coordSys.radius / 100 : distance); overlayMesh.scale.set(radius, radius, radius); lastDistance = radius; var blankTexture = this._blankTexture || (this._blankTexture = graphicGL.createBlankTexture("rgba(255, 255, 255, 0)")); overlayMesh.material.set("diffuseMap", blankTexture); graphicGL.loadTexture(layerModel.get("texture"), api, { flipY: false, anisotropic: 8 }, function(texture2) { if (texture2.surface) { texture2.surface.attachToMesh(overlayMesh); } overlayMesh.material.set("diffuseMap", texture2); api.getZr().refresh(); }); layerModel.get("show") ? this.groupGL.add(overlayMesh) : this.groupGL.remove(overlayMesh); } }, this); var earthMaterial = this._earthMesh.material; earthMaterial.define("fragment", "LAYER_DIFFUSEMAP_COUNT", layerDiffuseTextures.length); earthMaterial.define("fragment", "LAYER_EMISSIVEMAP_COUNT", layerEmissiveTextures.length); earthMaterial.set("layerDiffuseMap", layerDiffuseTextures); earthMaterial.set("layerDiffuseIntensity", layerDiffuseIntensity); earthMaterial.set("layerEmissiveMap", layerEmissiveTextures); earthMaterial.set("layerEmissionIntensity", layerEmissionIntensity); var debugWireframeModel = globeModel.getModel("debug.wireframe"); if (debugWireframeModel.get("show")) { earthMaterial.define("both", "WIREFRAME_TRIANGLE"); var color = graphicGL.parseColor(debugWireframeModel.get("lineStyle.color") || "rgba(0,0,0,0.5)"); var width = retrieve.firstNotNull(debugWireframeModel.get("lineStyle.width"), 1); earthMaterial.set("wireframeLineWidth", width); earthMaterial.set("wireframeLineColor", color); } else { earthMaterial.undefine("both", "WIREFRAME_TRIANGLE"); } }, _updateViewControl: function(globeModel, api) { var coordSys = globeModel.coordinateSystem; var viewControlModel = globeModel.getModel("viewControl"); coordSys.viewGL.camera; var self2 = this; function makeAction() { return { type: "globeChangeCamera", alpha: control.getAlpha(), beta: control.getBeta(), distance: control.getDistance() - coordSys.radius, center: control.getCenter(), from: self2.uid, globeId: globeModel.id }; } var control = this._control; control.setViewGL(coordSys.viewGL); var coord = viewControlModel.get("targetCoord"); var alpha, beta; if (coord != null) { beta = coord[0] + 90; alpha = coord[1]; } control.setFromViewControlModel(viewControlModel, { baseDistance: coordSys.radius, alpha, beta }); control.off("update"); control.on("update", function() { api.dispatchAction(makeAction()); }); }, _displaceVertices: function(globeModel, api) { var displacementQuality = globeModel.get("displacementQuality"); var showDebugWireframe = globeModel.get("debug.wireframe.show"); var globe = globeModel.coordinateSystem; if (!globeModel.isDisplacementChanged() && displacementQuality === this._displacementQuality && showDebugWireframe === this._showDebugWireframe) { return; } this._displacementQuality = displacementQuality; this._showDebugWireframe = showDebugWireframe; var geometry = this._sphereGeometry; var widthSegments = { low: 100, medium: 200, high: 400, ultra: 800 }[displacementQuality] || 200; var heightSegments = widthSegments / 2; if (geometry.widthSegments !== widthSegments || showDebugWireframe) { geometry.widthSegments = widthSegments; geometry.heightSegments = heightSegments; geometry.build(); } this._doDisplaceVertices(geometry, globe); if (showDebugWireframe) { geometry.generateBarycentric(); } }, _doDisplaceVertices: function(geometry, globe) { var positionArr = geometry.attributes.position.value; var uvArr = geometry.attributes.texcoord0.value; var originalPositionArr = geometry.__originalPosition; if (!originalPositionArr || originalPositionArr.length !== positionArr.length) { originalPositionArr = new Float32Array(positionArr.length); originalPositionArr.set(positionArr); geometry.__originalPosition = originalPositionArr; } var width = globe.displacementWidth; var height = globe.displacementHeight; var data = globe.displacementData; for (var i = 0; i < geometry.vertexCount; i++) { var i3 = i * 3; var i2 = i * 2; var x = originalPositionArr[i3 + 1]; var y = originalPositionArr[i3 + 2]; var z = originalPositionArr[i3 + 3]; var u = uvArr[i2++]; var v = uvArr[i2++]; var j = Math.round(u * (width - 1)); var k = Math.round(v * (height - 1)); var idx = k * width + j; var scale = data ? data[idx] : 0; positionArr[i3 + 1] = x + x * scale; positionArr[i3 + 2] = y + y * scale; positionArr[i3 + 3] = z + z * scale; } geometry.generateVertexNormals(); geometry.dirty(); geometry.updateBoundingBox(); }, _updateLight: function(globeModel, api) { var earthMesh = this._earthMesh; this._sceneHelper.updateLight(globeModel); var mainLight = this._sceneHelper.mainLight; var time = globeModel.get("light.main.time") || /* @__PURE__ */ new Date(); var pos = SunCalc.getPosition(parseDate(time), 0, 0); var r0 = Math.cos(pos.altitude); mainLight.position.y = -r0 * Math.cos(pos.azimuth); mainLight.position.x = Math.sin(pos.altitude); mainLight.position.z = r0 * Math.sin(pos.azimuth); mainLight.lookAt(earthMesh.getWorldPosition()); }, dispose: function(ecModel, api) { this.groupGL.removeAll(); this._control.dispose(); } }); var vec3$9 = glmatrix.vec3; function Globe(radius) { this.radius = radius; this.viewGL = null; this.altitudeAxis; this.displacementData = null; this.displacementWidth; this.displacementHeight; } Globe.prototype = { constructor: Globe, dimensions: ["lng", "lat", "alt"], type: "globe", containPoint: function() { }, setDisplacementData: function(data, width, height) { this.displacementData = data; this.displacementWidth = width; this.displacementHeight = height; }, _getDisplacementScale: function(lng, lat) { var i = (lng + 180) / 360 * (this.displacementWidth - 1); var j = (90 - lat) / 180 * (this.displacementHeight - 1); var idx = Math.round(i) + Math.round(j) * this.displacementWidth; return this.displacementData[idx]; }, dataToPoint: function(data, out) { var lng = data[0]; var lat = data[1]; var altVal = data[2] || 0; var r = this.radius; if (this.displacementData) { r *= 1 + this._getDisplacementScale(lng, lat); } if (this.altitudeAxis) { r += this.altitudeAxis.dataToCoord(altVal); } lng = lng * Math.PI / 180; lat = lat * Math.PI / 180; var r0 = Math.cos(lat) * r; out = out || []; out[0] = -r0 * Math.cos(lng + Math.PI); out[1] = Math.sin(lat) * r; out[2] = r0 * Math.sin(lng + Math.PI); return out; }, pointToData: function(point, out) { var x = point[0]; var y = point[1]; var z = point[2]; var len = vec3$9.len(point); x /= len; y /= len; z /= len; var theta = Math.asin(y); var phi = Math.atan2(z, -x); if (phi < 0) { phi = Math.PI * 2 + phi; } var lat = theta * 180 / Math.PI; var lng = phi * 180 / Math.PI - 180; out = out || []; out[0] = lng; out[1] = lat; out[2] = len - this.radius; if (this.altitudeAxis) { out[2] = this.altitudeAxis.coordToData(out[2]); } return out; } }; function getDisplacementData(img, displacementScale) { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); var width = img.width; var height = img.height; canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); var rgbaArr = ctx.getImageData(0, 0, width, height).data; var displacementArr = new Float32Array(rgbaArr.length / 4); for (var i = 0; i < rgbaArr.length / 4; i++) { var x = rgbaArr[i * 4]; displacementArr[i] = x / 255 * displacementScale; } return { data: displacementArr, width, height }; } function resizeGlobe(globeModel, api) { var boxLayoutOption = globeModel.getBoxLayoutParams(); var viewport = getLayoutRect(boxLayoutOption, { width: api.getWidth(), height: api.getHeight() }); viewport.y = api.getHeight() - viewport.y - viewport.height; this.viewGL.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, api.getDevicePixelRatio()); this.radius = globeModel.get("globeRadius"); var outerRadius = globeModel.get("globeOuterRadius"); if (this.altitudeAxis) { this.altitudeAxis.setExtent(0, outerRadius - this.radius); } } function updateGlobe(ecModel, api) { var altitudeDataExtent = [Infinity, -Infinity]; ecModel.eachSeries(function(seriesModel) { if (seriesModel.coordinateSystem !== this) { return; } var data = seriesModel.getData(); var altDims = seriesModel.coordDimToDataDim("alt"); var altDim = altDims && altDims[0]; if (altDim) { var dataExtent = data.getDataExtent(altDim, true); altitudeDataExtent[0] = Math.min(altitudeDataExtent[0], dataExtent[0]); altitudeDataExtent[1] = Math.max(altitudeDataExtent[1], dataExtent[1]); } }, this); if (altitudeDataExtent && isFinite(altitudeDataExtent[1] - altitudeDataExtent[0])) { var scale = createScale(altitudeDataExtent, { type: "value", // PENDING min: "dataMin", max: "dataMax" }); this.altitudeAxis = new Axis("altitude", scale); this.resize(this.model, api); } } var globeCreator = { dimensions: Globe.prototype.dimensions, create: function(ecModel, api) { var globeList = []; ecModel.eachComponent("globe", function(globeModel) { globeModel.__viewGL = globeModel.__viewGL || new ViewGL(); var globe = new Globe(); globe.viewGL = globeModel.__viewGL; globeModel.coordinateSystem = globe; globe.model = globeModel; globeList.push(globe); globe.resize = resizeGlobe; globe.resize(globeModel, api); globe.update = updateGlobe; }); ecModel.eachSeries(function(seriesModel) { if (seriesModel.get("coordinateSystem") === "globe") { var globeModel = seriesModel.getReferringComponents("globe").models[0]; if (!globeModel) { globeModel = ecModel.getComponent("globe"); } if (!globeModel) { throw new Error('globe "' + retrieve.firstNotNull(seriesModel.get("globe3DIndex"), seriesModel.get("globe3DId"), 0) + '" not found'); } var coordSys = globeModel.coordinateSystem; seriesModel.coordinateSystem = coordSys; } }); ecModel.eachComponent("globe", function(globeModel, idx) { var globe = globeModel.coordinateSystem; var displacementTextureValue = globeModel.getDisplacementTexture(); var displacementScale = globeModel.getDisplacemenScale(); if (globeModel.isDisplacementChanged()) { if (globeModel.hasDisplacement()) { var immediateLoaded = true; graphicGL.loadTexture(displacementTextureValue, api, function(texture) { var img = texture.image; var displacementData = getDisplacementData(img, displacementScale); globeModel.setDisplacementData(displacementData.data, displacementData.width, displacementData.height); if (!immediateLoaded) { api.dispatchAction({ type: "globeUpdateDisplacment" }); } }); immediateLoaded = false; } else { globe.setDisplacementData(null, 0, 0); } globe.setDisplacementData(globeModel.displacementData, globeModel.displacementWidth, globeModel.displacementHeight); } }); return globeList; } }; function install$d(registers) { registers.registerComponentModel(GlobeModel); registers.registerComponentView(GlobeView); registers.registerCoordinateSystem("globe", globeCreator); registers.registerAction({ type: "globeChangeCamera", event: "globecamerachanged", update: "series:updateCamera" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "globe", query: payload }, function(componentModel) { componentModel.setView(payload); }); }); registers.registerAction({ type: "globeUpdateDisplacment", event: "globedisplacementupdated", update: "update" }, function(payload, ecModel) { }); } use(install$d); var MAPBOX_CAMERA_OPTION = ["zoom", "center", "pitch", "bearing"]; var Mapbox3DModel = ComponentModel.extend({ type: "mapbox3D", layoutMode: "box", coordinateSystem: null, defaultOption: { zlevel: -10, style: "mapbox://styles/mapbox/light-v9", center: [0, 0], zoom: 0, pitch: 0, bearing: 0, light: { main: { alpha: 20, beta: 30 } }, altitudeScale: 1, // Default depend on altitudeScale boxHeight: "auto" }, getMapboxCameraOption: function() { var self2 = this; return MAPBOX_CAMERA_OPTION.reduce(function(obj, key) { obj[key] = self2.get(key); return obj; }, {}); }, setMapboxCameraOption: function(option) { if (option != null) { MAPBOX_CAMERA_OPTION.forEach(function(key) { if (option[key] != null) { this.option[key] = option[key]; } }, this); } }, /** * Get mapbox instance */ getMapbox: function() { return this._mapbox; }, setMapbox: function(mapbox) { this._mapbox = mapbox; } }); merge(Mapbox3DModel.prototype, componentPostEffectMixin); merge(Mapbox3DModel.prototype, componentLightMixin); function Mapbox3DLayer(id, zr) { this.id = id; this.zr = zr; this.dom = document.createElement("div"); this.dom.style.cssText = "position:absolute;left:0;right:0;top:0;bottom:0;"; if (!mapboxgl) { throw new Error("Mapbox GL library must be included. See https://www.mapbox.com/mapbox-gl-js/api/"); } this._mapbox = new mapboxgl.Map({ container: this.dom }); this._initEvents(); } Mapbox3DLayer.prototype.setUnpainted = function() { }; Mapbox3DLayer.prototype.resize = function() { this._mapbox.resize(); }; Mapbox3DLayer.prototype.getMapbox = function() { return this._mapbox; }; Mapbox3DLayer.prototype.clear = function() { }; Mapbox3DLayer.prototype.refresh = function() { this._mapbox.resize(); }; var EVENTS$1 = ["mousedown", "mouseup", "click", "dblclick", "mousemove", "mousewheel", "wheel", "touchstart", "touchend", "touchmove", "touchcancel"]; Mapbox3DLayer.prototype._initEvents = function() { var mapboxRoot = this._mapbox.getCanvasContainer(); this._handlers = this._handlers || { contextmenu: function(e2) { e2.preventDefault(); return false; } }; EVENTS$1.forEach(function(eName) { this._handlers[eName] = function(e2) { var obj = {}; for (var name in e2) { obj[name] = e2[name]; } obj.bubbles = false; var newE = new e2.constructor(e2.type, obj); mapboxRoot.dispatchEvent(newE); }; this.zr.dom.addEventListener(eName, this._handlers[eName]); }, this); this.zr.dom.addEventListener("contextmenu", this._handlers.contextmenu); }; Mapbox3DLayer.prototype.dispose = function() { EVENTS$1.forEach(function(eName) { this.zr.dom.removeEventListener(eName, this._handlers[eName]); }, this); }; const displayShadowGLSL = "\n@export ecgl.displayShadow.vertex\n\n@import ecgl.common.transformUniforms\n\n@import ecgl.common.uv.header\n\n@import ecgl.common.attributes\n\nvarying vec3 v_WorldPosition;\n\nvarying vec3 v_Normal;\n\nvoid main()\n{\n @import ecgl.common.uv.main\n v_Normal = normalize((worldInverseTranspose * vec4(normal, 0.0)).xyz);\n\n v_WorldPosition = (world * vec4(position, 1.0)).xyz;\n gl_Position = worldViewProjection * vec4(position, 1.0);\n}\n\n@end\n\n\n@export ecgl.displayShadow.fragment\n\n@import ecgl.common.uv.fragmentHeader\n\nvarying vec3 v_Normal;\nvarying vec3 v_WorldPosition;\n\nuniform float roughness: 0.2;\n\n#ifdef DIRECTIONAL_LIGHT_COUNT\n@import clay.header.directional_light\n#endif\n\n@import ecgl.common.ssaoMap.header\n\n@import clay.plugin.compute_shadow_map\n\nvoid main()\n{\n float shadow = 1.0;\n\n @import ecgl.common.ssaoMap.main\n\n#if defined(DIRECTIONAL_LIGHT_COUNT) && defined(DIRECTIONAL_LIGHT_SHADOWMAP_COUNT)\n float shadowContribsDir[DIRECTIONAL_LIGHT_COUNT];\n if(shadowEnabled)\n {\n computeShadowOfDirectionalLights(v_WorldPosition, shadowContribsDir);\n }\n for (int i = 0; i < DIRECTIONAL_LIGHT_COUNT; i++) {\n shadow = min(shadow, shadowContribsDir[i] * 0.5 + 0.5);\n }\n#endif\n\n shadow *= 0.5 + ao * 0.5;\n shadow = clamp(shadow, 0.0, 1.0);\n\n gl_FragColor = vec4(vec3(0.0), 1.0 - shadow);\n}\n\n@end"; graphicGL.Shader.import(displayShadowGLSL); const Mapbox3DView = ComponentView.extend({ type: "mapbox3D", __ecgl__: true, init: function(ecModel, api) { var zr = api.getZr(); this._zrLayer = new Mapbox3DLayer("mapbox3D", zr); zr.painter.insertLayer(-1e3, this._zrLayer); this._lightRoot = new graphicGL.Node(); this._sceneHelper = new SceneHelper(this._lightRoot); this._sceneHelper.initLight(this._lightRoot); var mapbox = this._zrLayer.getMapbox(); var dispatchInteractAction = this._dispatchInteractAction.bind(this, api, mapbox); ["zoom", "rotate", "drag", "pitch", "rotate", "move"].forEach(function(eName) { mapbox.on(eName, dispatchInteractAction); }); this._groundMesh = new graphicGL.Mesh({ geometry: new graphicGL.PlaneGeometry(), material: new graphicGL.Material({ shader: new graphicGL.Shader({ vertex: graphicGL.Shader.source("ecgl.displayShadow.vertex"), fragment: graphicGL.Shader.source("ecgl.displayShadow.fragment") }), depthMask: false }), // Render first renderOrder: -100, culling: false, castShadow: false, $ignorePicking: true, renderNormal: true }); }, render: function(mapbox3DModel, ecModel, api) { var mapbox = this._zrLayer.getMapbox(); var styleDesc = mapbox3DModel.get("style"); var styleStr = JSON.stringify(styleDesc); if (styleStr !== this._oldStyleStr) { if (styleDesc) { mapbox.setStyle(styleDesc); } } this._oldStyleStr = styleStr; mapbox.setCenter(mapbox3DModel.get("center")); mapbox.setZoom(mapbox3DModel.get("zoom")); mapbox.setPitch(mapbox3DModel.get("pitch")); mapbox.setBearing(mapbox3DModel.get("bearing")); mapbox3DModel.setMapbox(mapbox); var coordSys = mapbox3DModel.coordinateSystem; coordSys.viewGL.scene.add(this._lightRoot); coordSys.viewGL.add(this._groundMesh); this._updateGroundMesh(); this._sceneHelper.setScene(coordSys.viewGL.scene); this._sceneHelper.updateLight(mapbox3DModel); coordSys.viewGL.setPostEffect(mapbox3DModel.getModel("postEffect"), api); coordSys.viewGL.setTemporalSuperSampling(mapbox3DModel.getModel("temporalSuperSampling")); this._mapbox3DModel = mapbox3DModel; }, afterRender: function(mapbox3DModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; this._sceneHelper.updateAmbientCubemap(renderer, mapbox3DModel, api); this._sceneHelper.updateSkybox(renderer, mapbox3DModel, api); mapbox3DModel.coordinateSystem.viewGL.scene.traverse(function(mesh2) { if (mesh2.material) { mesh2.material.define("fragment", "NORMAL_UP_AXIS", 2); mesh2.material.define("fragment", "NORMAL_FRONT_AXIS", 1); } }); }, updateCamera: function(mapbox3DModel, ecModel, api, payload) { mapbox3DModel.coordinateSystem.setCameraOption(payload); this._updateGroundMesh(); api.getZr().refresh(); }, _dispatchInteractAction: function(api, mapbox, mapbox3DModel) { api.dispatchAction({ type: "mapbox3DChangeCamera", pitch: mapbox.getPitch(), zoom: mapbox.getZoom(), center: mapbox.getCenter().toArray(), bearing: mapbox.getBearing(), mapbox3DId: this._mapbox3DModel && this._mapbox3DModel.id }); }, _updateGroundMesh: function() { if (this._mapbox3DModel) { var coordSys = this._mapbox3DModel.coordinateSystem; var pt = coordSys.dataToPoint(coordSys.center); this._groundMesh.position.set(pt[0], pt[1], -1e-3); var plane = new graphicGL.Plane(new graphicGL.Vector3(0, 0, 1), 0); var ray1 = coordSys.viewGL.camera.castRay(new graphicGL.Vector2(-1, -1)); var ray2 = coordSys.viewGL.camera.castRay(new graphicGL.Vector2(1, 1)); var pos0 = ray1.intersectPlane(plane); var pos1 = ray2.intersectPlane(plane); var scale = pos0.dist(pos1) / coordSys.viewGL.rootNode.scale.x; this._groundMesh.scale.set(scale, scale, 1); } }, dispose: function(ecModel, api) { if (this._zrLayer) { this._zrLayer.dispose(); } api.getZr().painter.delLayer(-1e3); } }); var mat4 = glmatrix.mat4; var TILE_SIZE = 512; var FOV = 0.6435011087932844; var PI = Math.PI; var WORLD_SCALE = 1 / 10; function MapServiceCoordSys3D() { this.width = 0; this.height = 0; this.altitudeScale = 1; this.boxHeight = "auto"; this.altitudeExtent; this.bearing = 0; this.pitch = 0; this.center = [0, 0]; this._origin; this.zoom = 0; this._initialZoom; this.maxPitch = 60; this.zoomOffset = 0; } MapServiceCoordSys3D.prototype = { constructor: MapServiceCoordSys3D, dimensions: ["lng", "lat", "alt"], containPoint: function() { }, setCameraOption: function(option) { this.bearing = option.bearing; this.pitch = option.pitch; this.center = option.center; this.zoom = option.zoom; if (!this._origin) { this._origin = this.projectOnTileWithScale(this.center, TILE_SIZE); } if (this._initialZoom == null) { this._initialZoom = this.zoom; } this.updateTransform(); }, // https://github.com/mapbox/mapbox-gl-js/blob/master/src/geo/transform.js#L479 updateTransform: function() { if (!this.height) { return; } var cameraToCenterDistance = 0.5 / Math.tan(FOV / 2) * this.height * WORLD_SCALE; var pitch = Math.max(Math.min(this.pitch, this.maxPitch), 0) / 180 * Math.PI; var halfFov = FOV / 2; var groundAngle = Math.PI / 2 + pitch; var topHalfSurfaceDistance = Math.sin(halfFov) * cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov); var furthestDistance = Math.cos(Math.PI / 2 - pitch) * topHalfSurfaceDistance + cameraToCenterDistance; var farZ = furthestDistance * 1.1; if (this.pitch > 50) { farZ = 1e3; } var m = []; mat4.perspective(m, FOV, this.width / this.height, 1, farZ); this.viewGL.camera.projectionMatrix.setArray(m); this.viewGL.camera.decomposeProjectionMatrix(); var m = mat4.identity([]); var pt = this.dataToPoint(this.center); mat4.scale(m, m, [1, -1, 1]); mat4.translate(m, m, [0, 0, -cameraToCenterDistance]); mat4.rotateX(m, m, pitch); mat4.rotateZ(m, m, -this.bearing / 180 * Math.PI); mat4.translate(m, m, [-pt[0] * this.getScale() * WORLD_SCALE, -pt[1] * this.getScale() * WORLD_SCALE, 0]); this.viewGL.camera.viewMatrix.array = m; var invertM = []; mat4.invert(invertM, m); this.viewGL.camera.worldTransform.array = invertM; this.viewGL.camera.decomposeWorldTransform(); var worldSize = TILE_SIZE * this.getScale(); var verticalScale; if (this.altitudeExtent && !isNaN(this.boxHeight)) { var range = this.altitudeExtent[1] - this.altitudeExtent[0]; verticalScale = this.boxHeight / range * this.getScale() / Math.pow(2, this._initialZoom - this.zoomOffset); } else { verticalScale = worldSize / (2 * Math.PI * 6378e3 * Math.abs(Math.cos(this.center[1] * (Math.PI / 180)))) * this.altitudeScale * WORLD_SCALE; } this.viewGL.rootNode.scale.set(this.getScale() * WORLD_SCALE, this.getScale() * WORLD_SCALE, verticalScale); }, getScale: function() { return Math.pow(2, this.zoom - this.zoomOffset); }, projectOnTile: function(data, out) { return this.projectOnTileWithScale(data, this.getScale() * TILE_SIZE, out); }, projectOnTileWithScale: function(data, scale, out) { var lng = data[0]; var lat = data[1]; var lambda2 = lng * PI / 180; var phi2 = lat * PI / 180; var x = scale * (lambda2 + PI) / (2 * PI); var y = scale * (PI - Math.log(Math.tan(PI / 4 + phi2 * 0.5))) / (2 * PI); out = out || []; out[0] = x; out[1] = y; return out; }, unprojectFromTile: function(point, out) { return this.unprojectOnTileWithScale(point, this.getScale() * TILE_SIZE, out); }, unprojectOnTileWithScale: function(point, scale, out) { var x = point[0]; var y = point[1]; var lambda2 = x / scale * (2 * PI) - PI; var phi2 = 2 * (Math.atan(Math.exp(PI - y / scale * (2 * PI))) - PI / 4); out = out || []; out[0] = lambda2 * 180 / PI; out[1] = phi2 * 180 / PI; return out; }, dataToPoint: function(data, out) { out = this.projectOnTileWithScale(data, TILE_SIZE, out); out[0] -= this._origin[0]; out[1] -= this._origin[1]; out[2] = !isNaN(data[2]) ? data[2] : 0; if (!isNaN(data[2])) { out[2] = data[2]; if (this.altitudeExtent) { out[2] -= this.altitudeExtent[0]; } } return out; } }; function Mapbox3D() { MapServiceCoordSys3D.apply(this, arguments); } Mapbox3D.prototype = new MapServiceCoordSys3D(); Mapbox3D.prototype.constructor = Mapbox3D; Mapbox3D.prototype.type = "mapbox3D"; function createMapService3DCreator(serviceComponentType, ServiceCtor, afterCreate) { function resizeMapService3D(mapService3DModel, api) { var width = api.getWidth(); var height = api.getHeight(); var dpr = api.getDevicePixelRatio(); this.viewGL.setViewport(0, 0, width, height, dpr); this.width = width; this.height = height; this.altitudeScale = mapService3DModel.get("altitudeScale"); this.boxHeight = mapService3DModel.get("boxHeight"); } function updateService3D(ecModel, api) { if (this.model.get("boxHeight") === "auto") { return; } var altitudeDataExtent = [Infinity, -Infinity]; ecModel.eachSeries(function(seriesModel) { if (seriesModel.coordinateSystem !== this) { return; } var data = seriesModel.getData(); var altDim = seriesModel.coordDimToDataDim("alt")[0]; if (altDim) { var dataExtent = data.getDataExtent(altDim, true); altitudeDataExtent[0] = Math.min(altitudeDataExtent[0], dataExtent[0]); altitudeDataExtent[1] = Math.max(altitudeDataExtent[1], dataExtent[1]); } }, this); if (altitudeDataExtent && isFinite(altitudeDataExtent[1] - altitudeDataExtent[0])) { this.altitudeExtent = altitudeDataExtent; } } return { dimensions: ServiceCtor.prototype.dimensions, create: function(ecModel, api) { var mapService3DList = []; ecModel.eachComponent(serviceComponentType, function(mapService3DModel) { var viewGL = mapService3DModel.__viewGL; if (!viewGL) { viewGL = mapService3DModel.__viewGL = new ViewGL(); viewGL.setRootNode(new graphicGL.Node()); } var mapService3DCoordSys = new ServiceCtor(); mapService3DCoordSys.viewGL = mapService3DModel.__viewGL; mapService3DCoordSys.resize = resizeMapService3D; mapService3DCoordSys.resize(mapService3DModel, api); mapService3DList.push(mapService3DCoordSys); mapService3DModel.coordinateSystem = mapService3DCoordSys; mapService3DCoordSys.model = mapService3DModel; mapService3DCoordSys.update = updateService3D; }); ecModel.eachSeries(function(seriesModel) { if (seriesModel.get("coordinateSystem") === serviceComponentType) { var mapService3DModel = seriesModel.getReferringComponents(serviceComponentType).models[0]; if (!mapService3DModel) { mapService3DModel = ecModel.getComponent(serviceComponentType); } if (!mapService3DModel) { throw new Error(serviceComponentType + ' "' + retrieve.firstNotNull(seriesModel.get(serviceComponentType + "Index"), seriesModel.get(serviceComponentType + "Id"), 0) + '" not found'); } seriesModel.coordinateSystem = mapService3DModel.coordinateSystem; } }); afterCreate && afterCreate(mapService3DList, ecModel, api); return mapService3DList; } }; } var mapbox3DCreator = createMapService3DCreator("mapbox3D", Mapbox3D, function(mapbox3DList) { mapbox3DList.forEach(function(mapbox3D) { mapbox3D.setCameraOption(mapbox3D.model.getMapboxCameraOption()); }); }); function install$c(registers) { registers.registerComponentModel(Mapbox3DModel); registers.registerComponentView(Mapbox3DView); registers.registerCoordinateSystem("mapbox3D", mapbox3DCreator); registers.registerAction({ type: "mapbox3DChangeCamera", event: "mapbox3dcamerachanged", update: "mapbox3D:updateCamera" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "mapbox3D", query: payload }, function(componentModel) { componentModel.setMapboxCameraOption(payload); }); }); } use(install$c); var MAPTALKS_CAMERA_OPTION = ["zoom", "center", "pitch", "bearing"]; var Maptalks3DModel = ComponentModel.extend({ type: "maptalks3D", layoutMode: "box", coordinateSystem: null, defaultOption: { zlevel: -10, urlTemplate: "http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png", attribution: '© OpenStreetMap contributors, © CARTO', center: [0, 0], zoom: 0, pitch: 0, bearing: 0, light: { main: { alpha: 20, beta: 30 } }, altitudeScale: 1, // Default depend on altitudeScale boxHeight: "auto" }, getMaptalksCameraOption: function() { var self2 = this; return MAPTALKS_CAMERA_OPTION.reduce(function(obj, key) { obj[key] = self2.get(key); return obj; }, {}); }, setMaptalksCameraOption: function(option) { if (option != null) { MAPTALKS_CAMERA_OPTION.forEach(function(key) { if (option[key] != null) { this.option[key] = option[key]; } }, this); } }, /** * Get maptalks instance */ getMaptalks: function() { return this._maptalks; }, setMaptalks: function(maptalks2) { this._maptalks = maptalks2; } }); merge(Maptalks3DModel.prototype, componentPostEffectMixin); merge(Maptalks3DModel.prototype, componentLightMixin); function Maptalks3DLayer(id, zr, defaultCenter, defaultZoom) { this.id = id; this.zr = zr; this.dom = document.createElement("div"); this.dom.style.cssText = "position:absolute;left:0;right:0;top:0;bottom:0;"; if (!maptalks) { throw new Error("Maptalks library must be included. See https://maptalks.org"); } this._maptalks = new maptalks.Map(this.dom, { center: defaultCenter, zoom: defaultZoom, doubleClickZoom: false, fog: false // fogColor: [0, 0, 0] }); this._initEvents(); } Maptalks3DLayer.prototype.setUnpainted = function() { }; Maptalks3DLayer.prototype.resize = function() { this._maptalks.checkSize(); }; Maptalks3DLayer.prototype.getMaptalks = function() { return this._maptalks; }; Maptalks3DLayer.prototype.clear = function() { }; Maptalks3DLayer.prototype.refresh = function() { this._maptalks.checkSize(); }; var EVENTS = ["mousedown", "mouseup", "click", "dblclick", "mousemove", "mousewheel", "DOMMouseScroll", "touchstart", "touchend", "touchmove", "touchcancel"]; Maptalks3DLayer.prototype._initEvents = function() { var maptalksRoot = this.dom; this._handlers = this._handlers || { contextmenu: function(e2) { e2.preventDefault(); return false; } }; EVENTS.forEach(function(eName) { this._handlers[eName] = function(e2) { var obj = {}; for (var name in e2) { obj[name] = e2[name]; } obj.bubbles = false; var newE = new e2.constructor(e2.type, obj); if (eName === "mousewheel" || eName === "DOMMouseScroll") { maptalksRoot.dispatchEvent(newE); } else { maptalksRoot.firstElementChild.dispatchEvent(newE); } }; this.zr.dom.addEventListener(eName, this._handlers[eName]); }, this); this.zr.dom.addEventListener("contextmenu", this._handlers.contextmenu); }; Maptalks3DLayer.prototype.dispose = function() { EVENTS.forEach(function(eName) { this.zr.dom.removeEventListener(eName, this._handlers[eName]); }, this); this._maptalks.remove(); }; graphicGL.Shader.import(displayShadowGLSL); const Maptalks3DView = ComponentView.extend({ type: "maptalks3D", __ecgl__: true, init: function(ecModel, api) { this._groundMesh = new graphicGL.Mesh({ geometry: new graphicGL.PlaneGeometry(), material: new graphicGL.Material({ shader: new graphicGL.Shader({ vertex: graphicGL.Shader.source("ecgl.displayShadow.vertex"), fragment: graphicGL.Shader.source("ecgl.displayShadow.fragment") }), depthMask: false }), // Render first renderOrder: -100, culling: false, castShadow: false, $ignorePicking: true, renderNormal: true }); }, _initMaptalksLayer: function(mapbox3DModel, api) { var zr = api.getZr(); this._zrLayer = new Maptalks3DLayer("maptalks3D", zr, mapbox3DModel.get("center"), mapbox3DModel.get("zoom")); zr.painter.insertLayer(-1e3, this._zrLayer); this._lightRoot = new graphicGL.Node(); this._sceneHelper = new SceneHelper(this._lightRoot); this._sceneHelper.initLight(this._lightRoot); var maptalks2 = this._zrLayer.getMaptalks(); var dispatchInteractAction = this._dispatchInteractAction.bind(this, api, maptalks2); ["zoomend", "zooming", "zoomstart", "dragrotating", "pitch", "pitchend", "movestart", "moving", "moveend", "resize", "touchstart", "touchmove", "touchend", "animating"].forEach(function(eName) { maptalks2.on(eName, dispatchInteractAction); }); }, render: function(maptalks3DModel, ecModel, api) { if (!this._zrLayer) { this._initMaptalksLayer(maptalks3DModel, api); } var mtks = this._zrLayer.getMaptalks(); var urlTemplate = maptalks3DModel.get("urlTemplate"); var baseLayer = mtks.getBaseLayer(); if (urlTemplate !== this._oldUrlTemplate) { if (!baseLayer) { baseLayer = new maptalks.TileLayer("maptalks-echarts-gl-baselayer", { urlTemplate, // used sequentially to help with browser parallel requests per domain limitation subdomains: ["a", "b", "c"], attribution: maptalks3DModel.get("attribution") }); mtks.setBaseLayer(baseLayer); } else { baseLayer.setOptions({ urlTemplate, attribution: maptalks3DModel.get("attribution") }); } } this._oldUrlTemplate = urlTemplate; mtks.setCenter(maptalks3DModel.get("center")); mtks.setZoom(maptalks3DModel.get("zoom"), { animation: false }); mtks.setPitch(maptalks3DModel.get("pitch")); mtks.setBearing(maptalks3DModel.get("bearing")); maptalks3DModel.setMaptalks(mtks); var coordSys = maptalks3DModel.coordinateSystem; coordSys.viewGL.scene.add(this._lightRoot); coordSys.viewGL.add(this._groundMesh); this._updateGroundMesh(); this._sceneHelper.setScene(coordSys.viewGL.scene); this._sceneHelper.updateLight(maptalks3DModel); coordSys.viewGL.setPostEffect(maptalks3DModel.getModel("postEffect"), api); coordSys.viewGL.setTemporalSuperSampling(maptalks3DModel.getModel("temporalSuperSampling")); this._maptalks3DModel = maptalks3DModel; }, afterRender: function(maptalks3DModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; this._sceneHelper.updateAmbientCubemap(renderer, maptalks3DModel, api); this._sceneHelper.updateSkybox(renderer, maptalks3DModel, api); maptalks3DModel.coordinateSystem.viewGL.scene.traverse(function(mesh2) { if (mesh2.material) { mesh2.material.define("fragment", "NORMAL_UP_AXIS", 2); mesh2.material.define("fragment", "NORMAL_FRONT_AXIS", 1); } }); }, updateCamera: function(maptalks3DModel, ecModel, api, payload) { maptalks3DModel.coordinateSystem.setCameraOption(payload); this._updateGroundMesh(); api.getZr().refresh(); }, _dispatchInteractAction: function(api, maptalks2, maptalks3DModel) { api.dispatchAction({ type: "maptalks3DChangeCamera", pitch: maptalks2.getPitch(), zoom: getMapboxZoom(maptalks2.getResolution()) + 1, center: maptalks2.getCenter().toArray(), bearing: maptalks2.getBearing(), maptalks3DId: this._maptalks3DModel && this._maptalks3DModel.id }); }, _updateGroundMesh: function() { if (this._maptalks3DModel) { var coordSys = this._maptalks3DModel.coordinateSystem; var pt = coordSys.dataToPoint(coordSys.center); this._groundMesh.position.set(pt[0], pt[1], -1e-3); var plane = new graphicGL.Plane(new graphicGL.Vector3(0, 0, 1), 0); var ray1 = coordSys.viewGL.camera.castRay(new graphicGL.Vector2(-1, -1)); var ray2 = coordSys.viewGL.camera.castRay(new graphicGL.Vector2(1, 1)); var pos0 = ray1.intersectPlane(plane); var pos1 = ray2.intersectPlane(plane); var scale = pos0.dist(pos1) / coordSys.viewGL.rootNode.scale.x; this._groundMesh.scale.set(scale, scale, 1); } }, dispose: function(ecModel, api) { if (this._zrLayer) { this._zrLayer.dispose(); } api.getZr().painter.delLayer(-1e3); } }); const MAX_RES = 2 * 6378137 * Math.PI / (256 * Math.pow(2, 20)); function getMapboxZoom(res) { return 19 - Math.log(res / MAX_RES) / Math.LN2; } function Maptalks3D() { MapServiceCoordSys3D.apply(this, arguments); this.maxPitch = 85; this.zoomOffset = 1; } Maptalks3D.prototype = new MapServiceCoordSys3D(); Maptalks3D.prototype.constructor = Maptalks3D; Maptalks3D.prototype.type = "maptalks3D"; var maptalks3DCreator = createMapService3DCreator("maptalks3D", Maptalks3D, function(maptalks3DList) { maptalks3DList.forEach(function(maptalks3D) { maptalks3D.setCameraOption(maptalks3D.model.getMaptalksCameraOption()); }); }); function install$b(registers) { registers.registerComponentModel(Maptalks3DModel); registers.registerComponentView(Maptalks3DView); registers.registerCoordinateSystem("maptalks3D", maptalks3DCreator); registers.registerAction({ type: "maptalks3DChangeCamera", event: "maptalks3dcamerachanged", update: "maptalks3D:updateCamera" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "maptalks3D", query: payload }, function(componentModel) { componentModel.setMaptalksCameraOption(payload); }); }); } use(install$b); var vec3$8 = glmatrix.vec3; var isDimensionStacked$1 = dataStack.isDimensionStacked; function ifCrossZero(extent) { var min = extent[0]; var max = extent[1]; return !(min > 0 && max > 0 || min < 0 && max < 0); } function cartesian3DLayout(seriesModel, coordSys) { var data = seriesModel.getData(); var barSize = seriesModel.get("barSize"); if (barSize == null) { var size = coordSys.size; var barWidth; var barDepth; var xAxis = coordSys.getAxis("x"); var yAxis = coordSys.getAxis("y"); if (xAxis.type === "category") { barWidth = xAxis.getBandWidth() * 0.7; } else { barWidth = Math.round(size[0] / Math.sqrt(data.count())) * 0.6; } if (yAxis.type === "category") { barDepth = yAxis.getBandWidth() * 0.7; } else { barDepth = Math.round(size[1] / Math.sqrt(data.count())) * 0.6; } barSize = [barWidth, barDepth]; } else if (!isArray(barSize)) { barSize = [barSize, barSize]; } var zAxisExtent = coordSys.getAxis("z").scale.getExtent(); var ifZAxisCrossZero = ifCrossZero(zAxisExtent); var dims = ["x", "y", "z"].map(function(coordDimName) { return seriesModel.coordDimToDataDim(coordDimName)[0]; }); var isStacked = isDimensionStacked$1(data, dims[2]); var valueDim = isStacked ? data.getCalculationInfo("stackResultDimension") : dims[2]; data.each(dims, function(x, y, z, idx) { var stackedValue = data.get(valueDim, idx); var baseValue = isStacked ? stackedValue - z : ifZAxisCrossZero ? 0 : zAxisExtent[0]; var start = coordSys.dataToPoint([x, y, baseValue]); var end = coordSys.dataToPoint([x, y, stackedValue]); var height = vec3$8.dist(start, end); var dir = [0, end[1] < start[1] ? -1 : 1, 0]; if (Math.abs(height) === 0) { height = 0.1; } var size2 = [barSize[0], height, barSize[1]]; data.setItemLayout(idx, [start, dir, size2]); }); data.setLayout("orient", [1, 0, 0]); } function evaluateBarSparseness(data, dimX, dimY) { var xExtent = data.getDataExtent(dimX); var yExtent = data.getDataExtent(dimY); var xSpan = xExtent[1] - xExtent[0] || xExtent[0]; var ySpan = yExtent[1] - yExtent[0] || yExtent[0]; var dimSize = 50; var tmp = new Uint8Array(dimSize * dimSize); for (var i = 0; i < data.count(); i++) { var x = data.get(dimX, i); var y = data.get(dimY, i); var xIdx = Math.floor((x - xExtent[0]) / xSpan * (dimSize - 1)); var yIdx = Math.floor((y - yExtent[0]) / ySpan * (dimSize - 1)); var idx = yIdx * dimSize + xIdx; tmp[idx] = tmp[idx] || 1; } var filledCount = 0; for (var i = 0; i < tmp.length; i++) { if (tmp[i]) { filledCount++; } } return filledCount / tmp.length; } var vec3$7 = glmatrix.vec3; var isDimensionStacked = dataStack.isDimensionStacked; function globeLayout(seriesModel, coordSys) { var data = seriesModel.getData(); var barMinHeight = seriesModel.get("minHeight") || 0; var barSize = seriesModel.get("barSize"); var dims = ["lng", "lat", "alt"].map(function(coordDimName) { return seriesModel.coordDimToDataDim(coordDimName)[0]; }); if (barSize == null) { var perimeter = coordSys.radius * Math.PI; var fillRatio = evaluateBarSparseness(data, dims[0], dims[1]); barSize = [perimeter / Math.sqrt(data.count() / fillRatio), perimeter / Math.sqrt(data.count() / fillRatio)]; } else if (!isArray(barSize)) { barSize = [barSize, barSize]; } var valueDim = getValueDimension(data, dims); data.each(dims, function(lng, lat, val, idx) { var stackedValue = data.get(valueDim.dimension, idx); var baseValue = valueDim.isStacked ? stackedValue - val : coordSys.altitudeAxis.scale.getExtent()[0]; var height = Math.max(coordSys.altitudeAxis.dataToCoord(val), barMinHeight); var start = coordSys.dataToPoint([lng, lat, baseValue]); var end = coordSys.dataToPoint([lng, lat, stackedValue]); var dir = vec3$7.sub([], end, start); vec3$7.normalize(dir, dir); var size = [barSize[0], height, barSize[1]]; data.setItemLayout(idx, [start, dir, size]); }); data.setLayout("orient", Vector3.UP.array); } function geo3DLayout(seriesModel, coordSys) { var data = seriesModel.getData(); var barSize = seriesModel.get("barSize"); var barMinHeight = seriesModel.get("minHeight") || 0; var dims = ["lng", "lat", "alt"].map(function(coordDimName) { return seriesModel.coordDimToDataDim(coordDimName)[0]; }); if (barSize == null) { var size = Math.min(coordSys.size[0], coordSys.size[2]); var fillRatio = evaluateBarSparseness(data, dims[0], dims[1]); barSize = [size / Math.sqrt(data.count() / fillRatio), size / Math.sqrt(data.count() / fillRatio)]; } else if (!isArray(barSize)) { barSize = [barSize, barSize]; } var dir = [0, 1, 0]; var valueDim = getValueDimension(data, dims); data.each(dims, function(lng, lat, val, idx) { var stackedValue = data.get(valueDim.dimension, idx); var baseValue = valueDim.isStacked ? stackedValue - val : coordSys.altitudeAxis.scale.getExtent()[0]; var height = Math.max(coordSys.altitudeAxis.dataToCoord(val), barMinHeight); var start = coordSys.dataToPoint([lng, lat, baseValue]); var size2 = [barSize[0], height, barSize[1]]; data.setItemLayout(idx, [start, dir, size2]); }); data.setLayout("orient", [1, 0, 0]); } function mapService3DLayout(seriesModel, coordSys) { var data = seriesModel.getData(); var dimLng = seriesModel.coordDimToDataDim("lng")[0]; var dimLat = seriesModel.coordDimToDataDim("lat")[0]; var dimAlt = seriesModel.coordDimToDataDim("alt")[0]; var barSize = seriesModel.get("barSize"); var barMinHeight = seriesModel.get("minHeight") || 0; if (barSize == null) { var xExtent = data.getDataExtent(dimLng); var yExtent = data.getDataExtent(dimLat); var corner0 = coordSys.dataToPoint([xExtent[0], yExtent[0]]); var corner1 = coordSys.dataToPoint([xExtent[1], yExtent[1]]); var size = Math.min(Math.abs(corner0[0] - corner1[0]), Math.abs(corner0[1] - corner1[1])) || 1; var fillRatio = evaluateBarSparseness(data, dimLng, dimLat); barSize = [size / Math.sqrt(data.count() / fillRatio), size / Math.sqrt(data.count() / fillRatio)]; } else { if (!isArray(barSize)) { barSize = [barSize, barSize]; } barSize[0] /= coordSys.getScale() / 16; barSize[1] /= coordSys.getScale() / 16; } var dir = [0, 0, 1]; var dims = [dimLng, dimLat, dimAlt]; var valueDim = getValueDimension(data, dims); data.each(dims, function(lng, lat, val, idx) { var stackedValue = data.get(valueDim.dimension, idx); var baseValue = valueDim.isStacked ? stackedValue - val : 0; var start = coordSys.dataToPoint([lng, lat, baseValue]); var end = coordSys.dataToPoint([lng, lat, stackedValue]); var height = Math.max(end[2] - start[2], barMinHeight); var size2 = [barSize[0], height, barSize[1]]; data.setItemLayout(idx, [start, dir, size2]); }); data.setLayout("orient", [1, 0, 0]); } function getValueDimension(data, dataDims) { var isStacked = isDimensionStacked(data, dataDims[2]); return { dimension: isStacked ? data.getCalculationInfo("stackResultDimension") : dataDims[2], isStacked }; } function registerBarLayout(registers) { registers.registerLayout(function(ecModel, api) { ecModel.eachSeriesByType("bar3D", function(seriesModel) { var coordSys = seriesModel.coordinateSystem; var coordSysType = coordSys && coordSys.type; if (coordSysType === "globe") { globeLayout(seriesModel, coordSys); } else if (coordSysType === "cartesian3D") { cartesian3DLayout(seriesModel, coordSys); } else if (coordSysType === "geo3D") { geo3DLayout(seriesModel, coordSys); } else if (coordSysType === "mapbox3D" || coordSysType === "maptalks3D") { mapService3DLayout(seriesModel, coordSys); } else ; }); }); } var formatUtil = {}; formatUtil.getFormattedLabel = function(seriesModel, dataIndex, status, dataType, dimIndex) { status = status || "normal"; var data = seriesModel.getData(dataType); var itemModel = data.getItemModel(dataIndex); var params = seriesModel.getDataParams(dataIndex, dataType); if (dimIndex != null && params.value instanceof Array) { params.value = params.value[dimIndex]; } var formatter = itemModel.get(status === "normal" ? ["label", "formatter"] : ["emphasis", "label", "formatter"]); if (formatter == null) { formatter = itemModel.get(["label", "formatter"]); } var text; if (typeof formatter === "function") { params.status = status; text = formatter(params); } else if (typeof formatter === "string") { text = formatTpl(formatter, params); } return text; }; formatUtil.normalizeToArray = function(value) { return value instanceof Array ? value : value == null ? [] : [value]; }; function otherDimToDataDim(data, otherDim2) { var dataDim = []; each(data.dimensions, function(dimName) { var dimItem = data.getDimensionInfo(dimName); var otherDims = dimItem.otherDims; var dimIndex = otherDims[otherDim2]; if (dimIndex != null && dimIndex !== false) { dataDim[dimIndex] = dimItem.name; } }); return dataDim; } function formatTooltip(seriesModel, dataIndex, multipleSeries) { function formatArrayValue(value2) { var result = []; var tooltipDims = otherDimToDataDim(data, "tooltip"); tooltipDims.length ? each(tooltipDims, function(dimIdx) { setEachItem(data.get(dimIdx, dataIndex), dimIdx); }) : each(value2, setEachItem); function setEachItem(val, dimIdx) { var dimInfo = data.getDimensionInfo(dimIdx); if (!dimInfo || dimInfo.otherDims.tooltip === false) { return; } var dimType = dimInfo.type; var valStr = "- " + (dimInfo.tooltipName || dimInfo.name) + ": " + (dimType === "ordinal" ? val + "" : dimType === "time" ? formatTime("yyyy/MM/dd hh:mm:ss", val) : addCommas(val)); valStr && result.push(encodeHTML(valStr)); } return "
" + result.join("
"); } var data = seriesModel.getData(); var value = seriesModel.getRawValue(dataIndex); var formattedValue = isArray(value) ? formatArrayValue(value) : encodeHTML(addCommas(value)); var name = data.getName(dataIndex); var color = getItemVisualColor(data, dataIndex); if (isObject(color) && color.colorStops) { color = (color.colorStops[0] || {}).color; } color = color || "transparent"; var colorEl = getTooltipMarker(color); var seriesName = seriesModel.name; if (seriesName === "\0-") { seriesName = ""; } seriesName = seriesName ? encodeHTML(seriesName) + "
" : ""; return true ? seriesName + colorEl + (name ? encodeHTML(name) + ": " + formattedValue : formattedValue) : colorEl + seriesName + formattedValue; } function createList(seriesModel, dims, source) { source = source || seriesModel.getSource(); var coordSysDimensions = dims || getCoordinateSystemDimensions(seriesModel.get("coordinateSystem")) || ["x", "y", "z"]; var dimensions = createDimensions(source, { dimensionsDefine: source.dimensionsDefine || seriesModel.get("dimensions"), encodeDefine: source.encodeDefine || seriesModel.get("encode"), coordDimensions: coordSysDimensions.map(function(dim) { var axis3DModel = seriesModel.getReferringComponents(dim + "Axis3D").models[0]; return { type: axis3DModel && axis3DModel.get("type") === "category" ? "ordinal" : "float", name: dim // Find stackable dimension. Which will represent value. // stackable: dim === 'z' }; }) }); if (seriesModel.get("coordinateSystem") === "cartesian3D") { dimensions.forEach(function(dimInfo) { if (coordSysDimensions.indexOf(dimInfo.coordDim) >= 0) { var axis3DModel = seriesModel.getReferringComponents(dimInfo.coordDim + "Axis3D").models[0]; if (axis3DModel && axis3DModel.get("type") === "category") { dimInfo.ordinalMeta = axis3DModel.getOrdinalMeta(); } } }); } var stackCalculationInfo = dataStack.enableDataStack( // Only support 'z' and `byIndex` now. seriesModel, dimensions, { byIndex: true, stackedCoordDimension: "z" } ); var data = new SeriesData(dimensions, seriesModel); data.setCalculationInfo(stackCalculationInfo); data.initData(source); return data; } var Bar3DSeries = SeriesModel.extend({ type: "series.bar3D", dependencies: ["globe"], visualStyleAccessPathvisu: "itemStyle", getInitialData: function(option, ecModel) { return createList(this); }, getFormattedLabel: function(dataIndex, status, dataType, dimIndex) { var text = formatUtil.getFormattedLabel(this, dataIndex, status, dataType, dimIndex); if (text == null) { text = this.getData().get("z", dataIndex); } return text; }, formatTooltip: function(dataIndex) { return formatTooltip(this, dataIndex); }, defaultOption: { coordinateSystem: "cartesian3D", globeIndex: 0, grid3DIndex: 0, zlevel: -10, // bevelSize, 0 has no bevel bevelSize: 0, // higher is smoother bevelSmoothness: 2, // Bar width and depth // barSize: [1, 1], // On grid plane when coordinateSystem is cartesian3D onGridPlane: "xy", // Shading of globe shading: "color", minHeight: 0, itemStyle: { opacity: 1 }, label: { show: false, distance: 2, textStyle: { fontSize: 14, color: "#000", backgroundColor: "rgba(255,255,255,0.7)", padding: 3, borderRadius: 3 } }, emphasis: { label: { show: true } }, animationDurationUpdate: 500 } }); merge(Bar3DSeries.prototype, componentShadingMixin); var vec3$6 = glmatrix.vec3; var mat3 = glmatrix.mat3; var BarsGeometry = Geometry.extend( function() { return { attributes: { position: new Geometry.Attribute("position", "float", 3, "POSITION"), normal: new Geometry.Attribute("normal", "float", 3, "NORMAL"), color: new Geometry.Attribute("color", "float", 4, "COLOR"), prevPosition: new Geometry.Attribute("prevPosition", "float", 3), prevNormal: new Geometry.Attribute("prevNormal", "float", 3) }, dynamic: true, enableNormal: false, bevelSize: 1, bevelSegments: 0, // Map from vertexIndex to dataIndex. _dataIndices: null, _vertexOffset: 0, _triangleOffset: 0 }; }, /** @lends module:echarts-gl/chart/bars/BarsGeometry.prototype */ { resetOffset: function() { this._vertexOffset = 0; this._triangleOffset = 0; }, setBarCount: function(barCount) { var enableNormal = this.enableNormal; var vertexCount = this.getBarVertexCount() * barCount; var triangleCount = this.getBarTriangleCount() * barCount; if (this.vertexCount !== vertexCount) { this.attributes.position.init(vertexCount); if (enableNormal) { this.attributes.normal.init(vertexCount); } else { this.attributes.normal.value = null; } this.attributes.color.init(vertexCount); } if (this.triangleCount !== triangleCount) { this.indices = vertexCount > 65535 ? new Uint32Array(triangleCount * 3) : new Uint16Array(triangleCount * 3); this._dataIndices = new Uint32Array(vertexCount); } }, getBarVertexCount: function() { var bevelSegments = this.bevelSize > 0 ? this.bevelSegments : 0; return bevelSegments > 0 ? this._getBevelBarVertexCount(bevelSegments) : this.enableNormal ? 24 : 8; }, getBarTriangleCount: function() { var bevelSegments = this.bevelSize > 0 ? this.bevelSegments : 0; return bevelSegments > 0 ? this._getBevelBarTriangleCount(bevelSegments) : 12; }, _getBevelBarVertexCount: function(bevelSegments) { return (bevelSegments + 1) * 4 * (bevelSegments + 1) * 2; }, _getBevelBarTriangleCount: function(bevelSegments) { var widthSegments = bevelSegments * 4 + 3; var heightSegments = bevelSegments * 2 + 1; return (widthSegments + 1) * heightSegments * 2 + 4; }, setColor: function(idx, color) { var vertexCount = this.getBarVertexCount(); var start = vertexCount * idx; var end = vertexCount * (idx + 1); for (var i = start; i < end; i++) { this.attributes.color.set(i, color); } this.dirtyAttribute("color"); }, /** * Get dataIndex of vertex. * @param {number} vertexIndex */ getDataIndexOfVertex: function(vertexIndex) { return this._dataIndices ? this._dataIndices[vertexIndex] : null; }, /** * Add a bar * @param {Array.} start * @param {Array.} end * @param {Array.} orient right direction * @param {Array.} size size on x and z * @param {Array.} color */ addBar: function() { var v3Create = vec3$6.create; var v3ScaleAndAdd = vec3$6.scaleAndAdd; var end = v3Create(); var px = v3Create(); var py = v3Create(); var pz = v3Create(); var nx = v3Create(); var ny = v3Create(); var nz = v3Create(); var pts = []; var normals = []; for (var i = 0; i < 8; i++) { pts[i] = v3Create(); } var cubeFaces4 = [ // PX [0, 1, 5, 4], // NX [2, 3, 7, 6], // PY [4, 5, 6, 7], // NY [3, 2, 1, 0], // PZ [0, 4, 7, 3], // NZ [1, 2, 6, 5] ]; var face4To3 = [0, 1, 2, 0, 2, 3]; var cubeFaces3 = []; for (var i = 0; i < cubeFaces4.length; i++) { var face4 = cubeFaces4[i]; for (var j = 0; j < 2; j++) { var face = []; for (var k = 0; k < 3; k++) { face.push(face4[face4To3[j * 3 + k]]); } cubeFaces3.push(face); } } return function(start, dir, leftDir, size, color, dataIndex) { var startVertex = this._vertexOffset; if (this.bevelSize > 0 && this.bevelSegments > 0) { this._addBevelBar(start, dir, leftDir, size, this.bevelSize, this.bevelSegments, color); } else { vec3$6.copy(py, dir); vec3$6.normalize(py, py); vec3$6.cross(pz, leftDir, py); vec3$6.normalize(pz, pz); vec3$6.cross(px, py, pz); vec3$6.normalize(pz, pz); vec3$6.negate(nx, px); vec3$6.negate(ny, py); vec3$6.negate(nz, pz); v3ScaleAndAdd(pts[0], start, px, size[0] / 2); v3ScaleAndAdd(pts[0], pts[0], pz, size[2] / 2); v3ScaleAndAdd(pts[1], start, px, size[0] / 2); v3ScaleAndAdd(pts[1], pts[1], nz, size[2] / 2); v3ScaleAndAdd(pts[2], start, nx, size[0] / 2); v3ScaleAndAdd(pts[2], pts[2], nz, size[2] / 2); v3ScaleAndAdd(pts[3], start, nx, size[0] / 2); v3ScaleAndAdd(pts[3], pts[3], pz, size[2] / 2); v3ScaleAndAdd(end, start, py, size[1]); v3ScaleAndAdd(pts[4], end, px, size[0] / 2); v3ScaleAndAdd(pts[4], pts[4], pz, size[2] / 2); v3ScaleAndAdd(pts[5], end, px, size[0] / 2); v3ScaleAndAdd(pts[5], pts[5], nz, size[2] / 2); v3ScaleAndAdd(pts[6], end, nx, size[0] / 2); v3ScaleAndAdd(pts[6], pts[6], nz, size[2] / 2); v3ScaleAndAdd(pts[7], end, nx, size[0] / 2); v3ScaleAndAdd(pts[7], pts[7], pz, size[2] / 2); var attributes = this.attributes; if (this.enableNormal) { normals[0] = px; normals[1] = nx; normals[2] = py; normals[3] = ny; normals[4] = pz; normals[5] = nz; var vertexOffset = this._vertexOffset; for (var i2 = 0; i2 < cubeFaces4.length; i2++) { var idx3 = this._triangleOffset * 3; for (var k2 = 0; k2 < 6; k2++) { this.indices[idx3++] = vertexOffset + face4To3[k2]; } vertexOffset += 4; this._triangleOffset += 2; } for (var i2 = 0; i2 < cubeFaces4.length; i2++) { var normal2 = normals[i2]; for (var k2 = 0; k2 < 4; k2++) { var idx = cubeFaces4[i2][k2]; attributes.position.set(this._vertexOffset, pts[idx]); attributes.normal.set(this._vertexOffset, normal2); attributes.color.set(this._vertexOffset++, color); } } } else { for (var i2 = 0; i2 < cubeFaces3.length; i2++) { var idx3 = this._triangleOffset * 3; for (var k2 = 0; k2 < 3; k2++) { this.indices[idx3 + k2] = cubeFaces3[i2][k2] + this._vertexOffset; } this._triangleOffset++; } for (var i2 = 0; i2 < pts.length; i2++) { attributes.position.set(this._vertexOffset, pts[i2]); attributes.color.set(this._vertexOffset++, color); } } } var endVerex = this._vertexOffset; for (var i2 = startVertex; i2 < endVerex; i2++) { this._dataIndices[i2] = dataIndex; } }; }(), /** * Add a bar with bevel * @param {Array.} start * @param {Array.} end * @param {Array.} orient right direction * @param {Array.} size size on x and z * @param {number} bevelSize * @param {number} bevelSegments * @param {Array.} color */ _addBevelBar: function() { var px = vec3$6.create(); var py = vec3$6.create(); var pz = vec3$6.create(); var rotateMat = mat3.create(); var bevelStartSize = []; var xOffsets = [1, -1, -1, 1]; var zOffsets = [1, 1, -1, -1]; var yOffsets = [2, 0]; return function(start, dir, leftDir, size, bevelSize, bevelSegments, color) { vec3$6.copy(py, dir); vec3$6.normalize(py, py); vec3$6.cross(pz, leftDir, py); vec3$6.normalize(pz, pz); vec3$6.cross(px, py, pz); vec3$6.normalize(pz, pz); rotateMat[0] = px[0]; rotateMat[1] = px[1]; rotateMat[2] = px[2]; rotateMat[3] = py[0]; rotateMat[4] = py[1]; rotateMat[5] = py[2]; rotateMat[6] = pz[0]; rotateMat[7] = pz[1]; rotateMat[8] = pz[2]; bevelSize = Math.min(size[0], size[2]) / 2 * bevelSize; for (var i = 0; i < 3; i++) { bevelStartSize[i] = Math.max(size[i] - bevelSize * 2, 0); } var rx = (size[0] - bevelStartSize[0]) / 2; var ry = (size[1] - bevelStartSize[1]) / 2; var rz = (size[2] - bevelStartSize[2]) / 2; var pos = []; var normal2 = []; var vertexOffset = this._vertexOffset; var endIndices = []; for (var i = 0; i < 2; i++) { endIndices[i] = endIndices[i] = []; for (var m = 0; m <= bevelSegments; m++) { for (var j = 0; j < 4; j++) { if (m === 0 && i === 0 || i === 1 && m === bevelSegments) { endIndices[i].push(vertexOffset); } for (var n = 0; n <= bevelSegments; n++) { var phi = n / bevelSegments * Math.PI / 2 + Math.PI / 2 * j; var theta = m / bevelSegments * Math.PI / 2 + Math.PI / 2 * i; normal2[0] = rx * Math.cos(phi) * Math.sin(theta); normal2[1] = ry * Math.cos(theta); normal2[2] = rz * Math.sin(phi) * Math.sin(theta); pos[0] = normal2[0] + xOffsets[j] * bevelStartSize[0] / 2; pos[1] = normal2[1] + ry + yOffsets[i] * bevelStartSize[1] / 2; pos[2] = normal2[2] + zOffsets[j] * bevelStartSize[2] / 2; if (!(Math.abs(rx - ry) < 1e-6 && Math.abs(ry - rz) < 1e-6)) { normal2[0] /= rx * rx; normal2[1] /= ry * ry; normal2[2] /= rz * rz; } vec3$6.normalize(normal2, normal2); vec3$6.transformMat3(pos, pos, rotateMat); vec3$6.transformMat3(normal2, normal2, rotateMat); vec3$6.add(pos, pos, start); this.attributes.position.set(vertexOffset, pos); if (this.enableNormal) { this.attributes.normal.set(vertexOffset, normal2); } this.attributes.color.set(vertexOffset, color); vertexOffset++; } } } } var widthSegments = bevelSegments * 4 + 3; var heightSegments = bevelSegments * 2 + 1; var len = widthSegments + 1; for (var j = 0; j < heightSegments; j++) { for (var i = 0; i <= widthSegments; i++) { var i2 = j * len + i + this._vertexOffset; var i1 = j * len + (i + 1) % len + this._vertexOffset; var i4 = (j + 1) * len + (i + 1) % len + this._vertexOffset; var i3 = (j + 1) * len + i + this._vertexOffset; this.setTriangleIndices(this._triangleOffset++, [i4, i2, i1]); this.setTriangleIndices(this._triangleOffset++, [i4, i3, i2]); } } this.setTriangleIndices(this._triangleOffset++, [endIndices[0][0], endIndices[0][2], endIndices[0][1]]); this.setTriangleIndices(this._triangleOffset++, [endIndices[0][0], endIndices[0][3], endIndices[0][2]]); this.setTriangleIndices(this._triangleOffset++, [endIndices[1][0], endIndices[1][1], endIndices[1][2]]); this.setTriangleIndices(this._triangleOffset++, [endIndices[1][0], endIndices[1][2], endIndices[1][3]]); this._vertexOffset = vertexOffset; }; }() } ); defaults(BarsGeometry.prototype, dynamicConvertMixin); defaults(BarsGeometry.prototype, trianglesSortMixin); var vec3$5 = glmatrix.vec3; const Bar3DView = ChartView.extend({ type: "bar3D", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this._api = api; this._labelsBuilder = new LabelsBuilder(256, 256, api); var self2 = this; this._labelsBuilder.getLabelPosition = function(dataIndex, position, distance) { if (self2._data) { var layout = self2._data.getItemLayout(dataIndex); var start = layout[0]; var dir = layout[1]; var height = layout[2][1]; return vec3$5.scaleAndAdd([], start, dir, distance + height); } else { return [0, 0]; } }; this._labelsBuilder.getMesh().renderOrder = 100; }, render: function(seriesModel, ecModel, api) { var tmp = this._prevBarMesh; this._prevBarMesh = this._barMesh; this._barMesh = tmp; if (!this._barMesh) { this._barMesh = new graphicGL.Mesh({ geometry: new BarsGeometry(), shadowDepthMaterial: new graphicGL.Material({ shader: new graphicGL.Shader(graphicGL.Shader.source("ecgl.sm.depth.vertex"), graphicGL.Shader.source("ecgl.sm.depth.fragment")) }), // Only cartesian3D enable culling // FIXME Performance culling: seriesModel.coordinateSystem.type === "cartesian3D", // Render after axes renderOrder: 10, // Render normal in normal pass renderNormal: true }); } this.groupGL.remove(this._prevBarMesh); this.groupGL.add(this._barMesh); this.groupGL.add(this._labelsBuilder.getMesh()); var coordSys = seriesModel.coordinateSystem; this._doRender(seriesModel, api); if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); var methodName = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; this._barMesh.material[methodName]("fragment", "SRGB_DECODE"); } this._data = seriesModel.getData(); this._labelsBuilder.updateData(this._data); this._labelsBuilder.updateLabels(); this._updateAnimation(seriesModel); }, _updateAnimation: function(seriesModel) { graphicGL.updateVertexAnimation([["prevPosition", "position"], ["prevNormal", "normal"]], this._prevBarMesh, this._barMesh, seriesModel); }, _doRender: function(seriesModel, api) { var data = seriesModel.getData(); var shading = seriesModel.get("shading"); var enableNormal = shading !== "color"; var self2 = this; var barMesh = this._barMesh; var shadingPrefix = "ecgl." + shading; if (!barMesh.material || barMesh.material.shader.name !== shadingPrefix) { barMesh.material = graphicGL.createMaterial(shadingPrefix, ["VERTEX_COLOR"]); } graphicGL.setMaterialFromModel(shading, barMesh.material, seriesModel, api); barMesh.geometry.enableNormal = enableNormal; barMesh.geometry.resetOffset(); var bevelSize = seriesModel.get("bevelSize"); var bevelSegments = seriesModel.get("bevelSmoothness"); barMesh.geometry.bevelSegments = bevelSegments; barMesh.geometry.bevelSize = bevelSize; var colorArr = []; var vertexColors = new Float32Array(data.count() * 4); var colorOffset = 0; var barCount = 0; var hasTransparent = false; data.each(function(idx) { if (!data.hasValue(idx)) { return; } var color = getItemVisualColor(data, idx); var opacity = getItemVisualOpacity(data, idx); if (opacity == null) { opacity = 1; } graphicGL.parseColor(color, colorArr); colorArr[3] *= opacity; vertexColors[colorOffset++] = colorArr[0]; vertexColors[colorOffset++] = colorArr[1]; vertexColors[colorOffset++] = colorArr[2]; vertexColors[colorOffset++] = colorArr[3]; if (colorArr[3] > 0) { barCount++; if (colorArr[3] < 0.99) { hasTransparent = true; } } }); barMesh.geometry.setBarCount(barCount); var orient = data.getLayout("orient"); var barIndexOfData = this._barIndexOfData = new Int32Array(data.count()); var barCount = 0; data.each(function(idx) { if (!data.hasValue(idx)) { barIndexOfData[idx] = -1; return; } var layout = data.getItemLayout(idx); var start = layout[0]; var dir = layout[1]; var size = layout[2]; var idx4 = idx * 4; colorArr[0] = vertexColors[idx4++]; colorArr[1] = vertexColors[idx4++]; colorArr[2] = vertexColors[idx4++]; colorArr[3] = vertexColors[idx4++]; if (colorArr[3] > 0) { self2._barMesh.geometry.addBar(start, dir, orient, size, colorArr, idx); barIndexOfData[idx] = barCount++; } }); barMesh.geometry.dirty(); barMesh.geometry.updateBoundingBox(); var material = barMesh.material; material.transparent = hasTransparent; material.depthMask = !hasTransparent; barMesh.geometry.sortTriangles = hasTransparent; this._initHandler(seriesModel, api); }, _initHandler: function(seriesModel, api) { var data = seriesModel.getData(); var barMesh = this._barMesh; var isCartesian3D = seriesModel.coordinateSystem.type === "cartesian3D"; barMesh.seriesIndex = seriesModel.seriesIndex; var lastDataIndex = -1; barMesh.off("mousemove"); barMesh.off("mouseout"); barMesh.on("mousemove", function(e2) { var dataIndex = barMesh.geometry.getDataIndexOfVertex(e2.triangle[0]); if (dataIndex !== lastDataIndex) { this._downplay(lastDataIndex); this._highlight(dataIndex); this._labelsBuilder.updateLabels([dataIndex]); if (isCartesian3D) { api.dispatchAction({ type: "grid3DShowAxisPointer", value: [data.get("x", dataIndex), data.get("y", dataIndex), data.get("z", dataIndex, true)] }); } } lastDataIndex = dataIndex; barMesh.dataIndex = dataIndex; }, this); barMesh.on("mouseout", function(e2) { this._downplay(lastDataIndex); this._labelsBuilder.updateLabels(); lastDataIndex = -1; barMesh.dataIndex = -1; if (isCartesian3D) { api.dispatchAction({ type: "grid3DHideAxisPointer" }); } }, this); }, _highlight: function(dataIndex) { var data = this._data; if (!data) { return; } var barIndex = this._barIndexOfData[dataIndex]; if (barIndex < 0) { return; } var itemModel = data.getItemModel(dataIndex); var emphasisItemStyleModel = itemModel.getModel("emphasis.itemStyle"); var emphasisColor = emphasisItemStyleModel.get("color"); var emphasisOpacity = emphasisItemStyleModel.get("opacity"); if (emphasisColor == null) { var color = getItemVisualColor(data, dataIndex); emphasisColor = lift(color, -0.4); } if (emphasisOpacity == null) { emphasisOpacity = getItemVisualOpacity(data, dataIndex); } var colorArr = graphicGL.parseColor(emphasisColor); colorArr[3] *= emphasisOpacity; this._barMesh.geometry.setColor(barIndex, colorArr); this._api.getZr().refresh(); }, _downplay: function(dataIndex) { var data = this._data; if (!data) { return; } var barIndex = this._barIndexOfData[dataIndex]; if (barIndex < 0) { return; } var color = getItemVisualColor(data, dataIndex); var opacity = getItemVisualOpacity(data, dataIndex); var colorArr = graphicGL.parseColor(color); colorArr[3] *= opacity; this._barMesh.geometry.setColor(barIndex, colorArr); this._api.getZr().refresh(); }, highlight: function(seriesModel, ecModel, api, payload) { this._toggleStatus("highlight", seriesModel, ecModel, api, payload); }, downplay: function(seriesModel, ecModel, api, payload) { this._toggleStatus("downplay", seriesModel, ecModel, api, payload); }, _toggleStatus: function(status, seriesModel, ecModel, api, payload) { var data = seriesModel.getData(); var dataIndex = retrieve.queryDataIndex(data, payload); var self2 = this; if (dataIndex != null) { each(formatUtil.normalizeToArray(dataIndex), function(dataIdx) { status === "highlight" ? this._highlight(dataIdx) : this._downplay(dataIdx); }, this); } else { data.each(function(dataIdx) { status === "highlight" ? self2._highlight(dataIdx) : self2._downplay(dataIdx); }); } }, remove: function() { this.groupGL.removeAll(); }, dispose: function() { this._labelsBuilder.dispose(); this.groupGL.removeAll(); } }); function install$a(registers) { registers.registerChartView(Bar3DView); registers.registerSeriesModel(Bar3DSeries); registerBarLayout(registers); registers.registerProcessor(function(ecModel, api) { ecModel.eachSeriesByType("bar3d", function(seriesModel) { var data = seriesModel.getData(); data.filterSelf(function(idx) { return data.hasValue(idx); }); }); }); } use(install$a); var Line3DSeries = SeriesModel.extend({ type: "series.line3D", dependencies: ["grid3D"], visualStyleAccessPath: "lineStyle", visualDrawType: "stroke", getInitialData: function(option, ecModel) { return createList(this); }, formatTooltip: function(dataIndex) { return formatTooltip(this, dataIndex); }, defaultOption: { coordinateSystem: "cartesian3D", zlevel: -10, // Cartesian coordinate system grid3DIndex: 0, lineStyle: { width: 2 }, animationDurationUpdate: 500 } }); function containStroke(x0, y0, x1, y1, lineWidth, x, y) { if (lineWidth === 0) { return false; } var _l = lineWidth; var _a = 0; var _b = x0; if (y > y0 + _l && y > y1 + _l || y < y0 - _l && y < y1 - _l || x > x0 + _l && x > x1 + _l || x < x0 - _l && x < x1 - _l) { return false; } if (x0 !== x1) { _a = (y0 - y1) / (x0 - x1); _b = (x0 * y1 - x1 * y0) / (x0 - x1); } else { return Math.abs(x - x0) <= _l / 2; } var tmp = _a * x - y + _b; var _s = tmp * tmp / (_a * _a + 1); return _s <= _l / 2 * _l / 2; } var vec3$4 = glmatrix.vec3; graphicGL.Shader.import(lines3DGLSL); const Line3DView = ChartView.extend({ type: "line3D", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this._api = api; }, render: function(seriesModel, ecModel, api) { var tmp = this._prevLine3DMesh; this._prevLine3DMesh = this._line3DMesh; this._line3DMesh = tmp; if (!this._line3DMesh) { this._line3DMesh = new graphicGL.Mesh({ geometry: new LinesGeometry$2({ useNativeLine: false, sortTriangles: true }), material: new graphicGL.Material({ shader: graphicGL.createShader("ecgl.meshLines3D") }), // Render after axes renderOrder: 10 }); this._line3DMesh.geometry.pick = this._pick.bind(this); } this.groupGL.remove(this._prevLine3DMesh); this.groupGL.add(this._line3DMesh); var coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); var methodName = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; this._line3DMesh.material[methodName]("fragment", "SRGB_DECODE"); } this._doRender(seriesModel, api); this._data = seriesModel.getData(); this._camera = coordSys.viewGL.camera; this.updateCamera(); this._updateAnimation(seriesModel); }, updateCamera: function() { this._updateNDCPosition(); }, _doRender: function(seriesModel, api) { var data = seriesModel.getData(); var lineMesh = this._line3DMesh; lineMesh.geometry.resetOffset(); var points = data.getLayout("points"); var colorArr = []; var vertexColors = new Float32Array(points.length / 3 * 4); var colorOffset = 0; var hasTransparent = false; data.each(function(idx) { var color = getItemVisualColor(data, idx); var opacity = getItemVisualOpacity(data, idx); if (opacity == null) { opacity = 1; } graphicGL.parseColor(color, colorArr); colorArr[3] *= opacity; vertexColors[colorOffset++] = colorArr[0]; vertexColors[colorOffset++] = colorArr[1]; vertexColors[colorOffset++] = colorArr[2]; vertexColors[colorOffset++] = colorArr[3]; if (colorArr[3] < 0.99) { hasTransparent = true; } }); lineMesh.geometry.setVertexCount(lineMesh.geometry.getPolylineVertexCount(points)); lineMesh.geometry.setTriangleCount(lineMesh.geometry.getPolylineTriangleCount(points)); lineMesh.geometry.addPolyline(points, vertexColors, retrieve.firstNotNull(seriesModel.get("lineStyle.width"), 1)); lineMesh.geometry.dirty(); lineMesh.geometry.updateBoundingBox(); var material = lineMesh.material; material.transparent = hasTransparent; material.depthMask = !hasTransparent; var debugWireframeModel = seriesModel.getModel("debug.wireframe"); if (debugWireframeModel.get("show")) { lineMesh.geometry.createAttribute("barycentric", "float", 3); lineMesh.geometry.generateBarycentric(); lineMesh.material.set("both", "WIREFRAME_TRIANGLE"); lineMesh.material.set("wireframeLineColor", graphicGL.parseColor(debugWireframeModel.get("lineStyle.color") || "rgba(0,0,0,0.5)")); lineMesh.material.set("wireframeLineWidth", retrieve.firstNotNull(debugWireframeModel.get("lineStyle.width"), 1)); } else { lineMesh.material.set("both", "WIREFRAME_TRIANGLE"); } this._points = points; this._initHandler(seriesModel, api); }, _updateAnimation: function(seriesModel) { graphicGL.updateVertexAnimation([["prevPosition", "position"], ["prevPositionPrev", "positionPrev"], ["prevPositionNext", "positionNext"]], this._prevLine3DMesh, this._line3DMesh, seriesModel); }, _initHandler: function(seriesModel, api) { var data = seriesModel.getData(); var coordSys = seriesModel.coordinateSystem; var lineMesh = this._line3DMesh; var lastDataIndex = -1; lineMesh.seriesIndex = seriesModel.seriesIndex; lineMesh.off("mousemove"); lineMesh.off("mouseout"); lineMesh.on("mousemove", function(e2) { var value = coordSys.pointToData(e2.point.array); var dataIndex = data.indicesOfNearest("x", value[0])[0]; if (dataIndex !== lastDataIndex) { api.dispatchAction({ type: "grid3DShowAxisPointer", value: [data.get("x", dataIndex), data.get("y", dataIndex), data.get("z", dataIndex)] }); lineMesh.dataIndex = dataIndex; } lastDataIndex = dataIndex; }, this); lineMesh.on("mouseout", function(e2) { lastDataIndex = -1; lineMesh.dataIndex = -1; api.dispatchAction({ type: "grid3DHideAxisPointer" }); }, this); }, // _highlight: function (dataIndex) { // var data = this._data; // if (!data) { // return; // } // }, // _downplay: function (dataIndex) { // var data = this._data; // if (!data) { // return; // } // }, _updateNDCPosition: function() { var worldViewProjection = new Matrix4(); var camera2 = this._camera; Matrix4.multiply(worldViewProjection, camera2.projectionMatrix, camera2.viewMatrix); var positionNDC = this._positionNDC; var points = this._points; var nPoints = points.length / 3; if (!positionNDC || positionNDC.length / 2 !== nPoints) { positionNDC = this._positionNDC = new Float32Array(nPoints * 2); } var pos = []; for (var i = 0; i < nPoints; i++) { var i3 = i * 3; var i2 = i * 2; pos[0] = points[i3]; pos[1] = points[i3 + 1]; pos[2] = points[i3 + 2]; pos[3] = 1; vec3$4.transformMat4(pos, pos, worldViewProjection.array); positionNDC[i2] = pos[0] / pos[3]; positionNDC[i2 + 1] = pos[1] / pos[3]; } }, _pick: function(x, y, renderer, camera2, renderable, out) { var positionNDC = this._positionNDC; var seriesModel = this._data.hostModel; var lineWidth = seriesModel.get("lineStyle.width"); var dataIndex = -1; var width = renderer.viewport.width; var height = renderer.viewport.height; var halfWidth = width * 0.5; var halfHeight = height * 0.5; x = (x + 1) * halfWidth; y = (y + 1) * halfHeight; for (var i = 1; i < positionNDC.length / 2; i++) { var x0 = (positionNDC[(i - 1) * 2] + 1) * halfWidth; var y0 = (positionNDC[(i - 1) * 2 + 1] + 1) * halfHeight; var x1 = (positionNDC[i * 2] + 1) * halfWidth; var y1 = (positionNDC[i * 2 + 1] + 1) * halfHeight; if (containStroke(x0, y0, x1, y1, lineWidth, x, y)) { var dist0 = (x0 - x) * (x0 - x) + (y0 - y) * (y0 - y); var dist1 = (x1 - x) * (x1 - x) + (y1 - y) * (y1 - y); dataIndex = dist0 < dist1 ? i - 1 : i; } } if (dataIndex >= 0) { var i3 = dataIndex * 3; var point = new Vector3(this._points[i3], this._points[i3 + 1], this._points[i3 + 2]); out.push({ dataIndex, point, pointWorld: point.clone(), target: this._line3DMesh, distance: this._camera.getWorldPosition().dist(point) }); } }, remove: function() { this.groupGL.removeAll(); }, dispose: function() { this.groupGL.removeAll(); } }); function install$9(registers) { registers.registerChartView(Line3DView); registers.registerSeriesModel(Line3DSeries); registers.registerLayout(function(ecModel, api) { ecModel.eachSeriesByType("line3D", function(seriesModel) { var data = seriesModel.getData(); var coordSys = seriesModel.coordinateSystem; if (coordSys) { if (coordSys.type !== "cartesian3D") { return; } var points = new Float32Array(data.count() * 3); var item = []; var out = []; var coordDims = coordSys.dimensions; var dims = coordDims.map(function(coordDim) { return seriesModel.coordDimToDataDim(coordDim)[0]; }); if (coordSys) { data.each(dims, function(x, y, z, idx) { item[0] = x; item[1] = y; item[2] = z; coordSys.dataToPoint(item, out); points[idx * 3] = out[0]; points[idx * 3 + 1] = out[1]; points[idx * 3 + 2] = out[2]; }); } data.setLayout("points", points); } }); }); } use(install$9); const Scatter3DSeries = SeriesModel.extend({ type: "series.scatter3D", dependencies: ["globe", "grid3D", "geo3D"], visualStyleAccessPath: "itemStyle", hasSymbolVisual: true, getInitialData: function(option, ecModel) { return createList(this); }, getFormattedLabel: function(dataIndex, status, dataType, dimIndex) { var text = formatUtil.getFormattedLabel(this, dataIndex, status, dataType, dimIndex); if (text == null) { var data = this.getData(); var lastDim = data.dimensions[data.dimensions.length - 1]; text = data.get(lastDim, dataIndex); } return text; }, formatTooltip: function(dataIndex) { return formatTooltip(this, dataIndex); }, defaultOption: { coordinateSystem: "cartesian3D", zlevel: -10, progressive: 1e5, progressiveThreshold: 1e5, // Cartesian coordinate system grid3DIndex: 0, globeIndex: 0, symbol: "circle", symbolSize: 10, // Support source-over, lighter blendMode: "source-over", label: { show: false, position: "right", // Screen space distance distance: 5, textStyle: { fontSize: 14, color: "#000", backgroundColor: "rgba(255,255,255,0.7)", padding: 3, borderRadius: 3 } }, itemStyle: { opacity: 0.8 }, emphasis: { label: { show: true } }, animationDurationUpdate: 500 } }); function makeSprite(size, canvas, draw) { var canvas = canvas || document.createElement("canvas"); canvas.width = size; canvas.height = size; var ctx = canvas.getContext("2d"); draw && draw(ctx); return canvas; } function makePath(symbol, symbolSize, style, marginBias) { if (!isArray(symbolSize)) { symbolSize = [symbolSize, symbolSize]; } var margin = spriteUtil.getMarginByStyle(style, marginBias); var width = symbolSize[0] + margin.left + margin.right; var height = symbolSize[1] + margin.top + margin.bottom; var path = createSymbol(symbol, 0, 0, symbolSize[0], symbolSize[1]); var size = Math.max(width, height); path.x = margin.left; path.y = margin.top; if (width > height) { path.y += (size - height) / 2; } else { path.x += (size - width) / 2; } var rect = path.getBoundingRect(); path.x -= rect.x; path.y -= rect.y; path.setStyle(style); path.update(); path.__size = size; return path; } function generateSDF(ctx, sourceImageData, range) { var sourceWidth = sourceImageData.width; var sourceHeight = sourceImageData.height; var width = ctx.canvas.width; var height = ctx.canvas.height; var scaleX = sourceWidth / width; var scaleY = sourceHeight / height; function sign2(r) { return r < 128 ? 1 : -1; } function searchMinDistance(x2, y2) { var minDistSqr = Infinity; x2 = Math.floor(x2 * scaleX); y2 = Math.floor(y2 * scaleY); var i2 = y2 * sourceWidth + x2; var r = sourceImageData.data[i2 * 4]; var a = sign2(r); for (var y22 = Math.max(y2 - range, 0); y22 < Math.min(y2 + range, sourceHeight); y22++) { for (var x22 = Math.max(x2 - range, 0); x22 < Math.min(x2 + range, sourceWidth); x22++) { var i2 = y22 * sourceWidth + x22; var r2 = sourceImageData.data[i2 * 4]; var b = sign2(r2); var dx = x22 - x2; var dy = y22 - y2; if (a !== b) { var distSqr = dx * dx + dy * dy; if (distSqr < minDistSqr) { minDistSqr = distSqr; } } } } return a * Math.sqrt(minDistSqr); } var sdfImageData = ctx.createImageData(width, height); for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var dist = searchMinDistance(x, y); var normalized = dist / range * 0.5 + 0.5; var i = (y * width + x) * 4; sdfImageData.data[i++] = (1 - normalized) * 255; sdfImageData.data[i++] = (1 - normalized) * 255; sdfImageData.data[i++] = (1 - normalized) * 255; sdfImageData.data[i++] = 255; } } return sdfImageData; } var spriteUtil = { getMarginByStyle: function(style) { var minMargin = style.minMargin || 0; var lineWidth = 0; if (style.stroke && style.stroke !== "none") { lineWidth = style.lineWidth == null ? 1 : style.lineWidth; } var shadowBlurSize = style.shadowBlur || 0; var shadowOffsetX = style.shadowOffsetX || 0; var shadowOffsetY = style.shadowOffsetY || 0; var margin = {}; margin.left = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize, minMargin); margin.right = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize, minMargin); margin.top = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize, minMargin); margin.bottom = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize, minMargin); return margin; }, // TODO Not consider shadowOffsetX, shadowOffsetY. /** * @param {string} symbol * @param {number | Array.} symbolSize * @param {Object} style */ createSymbolSprite: function(symbol, symbolSize, style, canvas) { var path = makePath(symbol, symbolSize, style); var margin = spriteUtil.getMarginByStyle(style); return { image: makeSprite(path.__size, canvas, function(ctx) { brushSingle(ctx, path); }), margin }; }, createSDFFromCanvas: function(canvas, size, range, outCanvas) { return makeSprite(size, outCanvas, function(outCtx) { var ctx = canvas.getContext("2d"); var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); outCtx.putImageData(generateSDF(outCtx, imgData, range), 0, 0); }); }, createSimpleSprite: function(size, canvas) { return makeSprite(size, canvas, function(ctx) { var halfSize = size / 2; ctx.beginPath(); ctx.arc(halfSize, halfSize, 60, 0, Math.PI * 2, false); ctx.closePath(); var gradient = ctx.createRadialGradient(halfSize, halfSize, 0, halfSize, halfSize, halfSize); gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); gradient.addColorStop(0.5, "rgba(255, 255, 255, 0.5)"); gradient.addColorStop(1, "rgba(255, 255, 255, 0)"); ctx.fillStyle = gradient; ctx.fill(); }); } }; var vec3$3 = glmatrix.vec3; const verticesSortMixin = { needsSortVertices: function() { return this.sortVertices; }, needsSortVerticesProgressively: function() { return this.needsSortVertices() && this.vertexCount >= 2e4; }, doSortVertices: function(cameraPos, frame) { var indices = this.indices; var p = vec3$3.create(); if (!indices) { indices = this.indices = this.vertexCount > 65535 ? new Uint32Array(this.vertexCount) : new Uint16Array(this.vertexCount); for (var i = 0; i < indices.length; i++) { indices[i] = i; } } if (frame === 0) { var posAttr = this.attributes.position; var cameraPos = cameraPos.array; var noneCount = 0; if (!this._zList || this._zList.length !== this.vertexCount) { this._zList = new Float32Array(this.vertexCount); } var firstZ; for (var i = 0; i < this.vertexCount; i++) { posAttr.get(i, p); var z = vec3$3.sqrDist(p, cameraPos); if (isNaN(z)) { z = 1e7; noneCount++; } if (i === 0) { firstZ = z; z = 0; } else { z = z - firstZ; } this._zList[i] = z; } this._noneCount = noneCount; } if (this.vertexCount < 2e4) { if (frame === 0) { this._simpleSort(this._noneCount / this.vertexCount > 0.05); } } else { for (var i = 0; i < 3; i++) { this._progressiveQuickSort(frame * 3 + i); } } this.dirtyIndices(); }, _simpleSort: function(useNativeQuickSort) { var zList = this._zList; var indices = this.indices; function compare(a, b) { return zList[b] - zList[a]; } if (useNativeQuickSort) { Array.prototype.sort.call(indices, compare); } else { ProgressiveQuickSort.sort(indices, compare, 0, indices.length - 1); } }, _progressiveQuickSort: function(frame) { var zList = this._zList; var indices = this.indices; this._quickSort = this._quickSort || new ProgressiveQuickSort(); this._quickSort.step(indices, function(a, b) { return zList[b] - zList[a]; }, frame); } }; const sdfSpriteGLSL = "@export ecgl.sdfSprite.vertex\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform float elapsedTime : 0;\n\nattribute vec3 position : POSITION;\n\n#ifdef VERTEX_SIZE\nattribute float size;\n#else\nuniform float u_Size;\n#endif\n\n#ifdef VERTEX_COLOR\nattribute vec4 a_FillColor: COLOR;\nvarying vec4 v_Color;\n#endif\n\n#ifdef VERTEX_ANIMATION\nattribute vec3 prevPosition;\nattribute float prevSize;\nuniform float percent : 1.0;\n#endif\n\n\n#ifdef POSITIONTEXTURE_ENABLED\nuniform sampler2D positionTexture;\n#endif\n\nvarying float v_Size;\n\nvoid main()\n{\n\n#ifdef POSITIONTEXTURE_ENABLED\n gl_Position = worldViewProjection * vec4(texture2D(positionTexture, position.xy).xy, -10.0, 1.0);\n#else\n\n #ifdef VERTEX_ANIMATION\n vec3 pos = mix(prevPosition, position, percent);\n #else\n vec3 pos = position;\n #endif\n gl_Position = worldViewProjection * vec4(pos, 1.0);\n#endif\n\n#ifdef VERTEX_SIZE\n#ifdef VERTEX_ANIMATION\n v_Size = mix(prevSize, size, percent);\n#else\n v_Size = size;\n#endif\n#else\n v_Size = u_Size;\n#endif\n\n#ifdef VERTEX_COLOR\n v_Color = a_FillColor;\n #endif\n\n gl_PointSize = v_Size;\n}\n\n@end\n\n@export ecgl.sdfSprite.fragment\n\nuniform vec4 color: [1, 1, 1, 1];\nuniform vec4 strokeColor: [1, 1, 1, 1];\nuniform float smoothing: 0.07;\n\nuniform float lineWidth: 0.0;\n\n#ifdef VERTEX_COLOR\nvarying vec4 v_Color;\n#endif\n\nvarying float v_Size;\n\nuniform sampler2D sprite;\n\n@import clay.util.srgb\n\nvoid main()\n{\n gl_FragColor = color;\n\n vec4 _strokeColor = strokeColor;\n\n#ifdef VERTEX_COLOR\n gl_FragColor *= v_Color;\n #endif\n\n#ifdef SPRITE_ENABLED\n float d = texture2D(sprite, gl_PointCoord).r;\n gl_FragColor.a *= smoothstep(0.5 - smoothing, 0.5 + smoothing, d);\n\n if (lineWidth > 0.0) {\n float sLineWidth = lineWidth / 2.0;\n\n float outlineMaxValue0 = 0.5 + sLineWidth;\n float outlineMaxValue1 = 0.5 + sLineWidth + smoothing;\n float outlineMinValue0 = 0.5 - sLineWidth - smoothing;\n float outlineMinValue1 = 0.5 - sLineWidth;\n\n if (d <= outlineMaxValue1 && d >= outlineMinValue0) {\n float a = _strokeColor.a;\n if (d <= outlineMinValue1) {\n a = a * smoothstep(outlineMinValue0, outlineMinValue1, d);\n }\n else {\n a = a * smoothstep(outlineMaxValue1, outlineMaxValue0, d);\n }\n gl_FragColor.rgb = mix(gl_FragColor.rgb * gl_FragColor.a, _strokeColor.rgb, a);\n gl_FragColor.a = gl_FragColor.a * (1.0 - a) + a;\n }\n }\n#endif\n\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(gl_FragColor);\n#endif\n}\n@end"; var vec4 = glmatrix.vec4; graphicGL.Shader.import(sdfSpriteGLSL); var PointsMesh = graphicGL.Mesh.extend(function() { var geometry = new graphicGL.Geometry({ dynamic: true, attributes: { color: new graphicGL.Geometry.Attribute("color", "float", 4, "COLOR"), position: new graphicGL.Geometry.Attribute("position", "float", 3, "POSITION"), size: new graphicGL.Geometry.Attribute("size", "float", 1), prevPosition: new graphicGL.Geometry.Attribute("prevPosition", "float", 3), prevSize: new graphicGL.Geometry.Attribute("prevSize", "float", 1) } }); Object.assign(geometry, verticesSortMixin); var material = new graphicGL.Material({ shader: graphicGL.createShader("ecgl.sdfSprite"), transparent: true, depthMask: false }); material.enableTexture("sprite"); material.define("both", "VERTEX_COLOR"); material.define("both", "VERTEX_SIZE"); var sdfTexture = new graphicGL.Texture2D({ image: document.createElement("canvas"), flipY: false }); material.set("sprite", sdfTexture); geometry.pick = this._pick.bind(this); return { geometry, material, mode: graphicGL.Mesh.POINTS, sizeScale: 1 }; }, { _pick: function(x, y, renderer, camera2, renderable, out) { var positionNDC = this._positionNDC; if (!positionNDC) { return; } var viewport = renderer.viewport; var ndcScaleX = 2 / viewport.width; var ndcScaleY = 2 / viewport.height; for (var i = this.geometry.vertexCount - 1; i >= 0; i--) { var idx; if (!this.geometry.indices) { idx = i; } else { idx = this.geometry.indices[i]; } var cx = positionNDC[idx * 2]; var cy = positionNDC[idx * 2 + 1]; var size = this.geometry.attributes.size.get(idx) / this.sizeScale; var halfSize = size / 2; if (x > cx - halfSize * ndcScaleX && x < cx + halfSize * ndcScaleX && y > cy - halfSize * ndcScaleY && y < cy + halfSize * ndcScaleY) { var point = new graphicGL.Vector3(); var pointWorld = new graphicGL.Vector3(); this.geometry.attributes.position.get(idx, point.array); graphicGL.Vector3.transformMat4(pointWorld, point, this.worldTransform); out.push({ vertexIndex: idx, point, pointWorld, target: this, distance: pointWorld.distance(camera2.getWorldPosition()) }); } } }, updateNDCPosition: function(worldViewProjection, is2D, api) { var positionNDC = this._positionNDC; var geometry = this.geometry; if (!positionNDC || positionNDC.length / 2 !== geometry.vertexCount) { positionNDC = this._positionNDC = new Float32Array(geometry.vertexCount * 2); } var pos = vec4.create(); for (var i = 0; i < geometry.vertexCount; i++) { geometry.attributes.position.get(i, pos); pos[3] = 1; vec4.transformMat4(pos, pos, worldViewProjection.array); vec4.scale(pos, pos, 1 / pos[3]); positionNDC[i * 2] = pos[0]; positionNDC[i * 2 + 1] = pos[1]; } } }); var SDF_RANGE = 20; var Z_2D = -10; function isSymbolSizeSame(a, b) { return a && b && a[0] === b[0] && a[1] === b[1]; } function PointsBuilder(is2D, api) { this.rootNode = new graphicGL.Node(); this.is2D = is2D; this._labelsBuilder = new LabelsBuilder(256, 256, api); this._labelsBuilder.getMesh().renderOrder = 100; this.rootNode.add(this._labelsBuilder.getMesh()); this._api = api; this._spriteImageCanvas = document.createElement("canvas"); this._startDataIndex = 0; this._endDataIndex = 0; this._sizeScale = 1; } PointsBuilder.prototype = { constructor: PointsBuilder, /** * If highlight on over */ highlightOnMouseover: true, update: function(seriesModel, ecModel, api, start, end) { var tmp = this._prevMesh; this._prevMesh = this._mesh; this._mesh = tmp; var data = seriesModel.getData(); if (start == null) { start = 0; } if (end == null) { end = data.count(); } this._startDataIndex = start; this._endDataIndex = end - 1; if (!this._mesh) { var material = this._prevMesh && this._prevMesh.material; this._mesh = new PointsMesh({ // Render after axes renderOrder: 10, // FIXME frustumCulling: false }); if (material) { this._mesh.material = material; } } var material = this._mesh.material; var geometry = this._mesh.geometry; var attributes = geometry.attributes; this.rootNode.remove(this._prevMesh); this.rootNode.add(this._mesh); this._setPositionTextureToMesh(this._mesh, this._positionTexture); var symbolInfo = this._getSymbolInfo(seriesModel, start, end); var dpr = api.getDevicePixelRatio(); var itemStyle = seriesModel.getModel("itemStyle").getItemStyle(); var largeMode = seriesModel.get("large"); var pointSizeScale = 1; if (symbolInfo.maxSize > 2) { pointSizeScale = this._updateSymbolSprite(seriesModel, itemStyle, symbolInfo, dpr); material.enableTexture("sprite"); } else { material.disableTexture("sprite"); } attributes.position.init(end - start); var rgbaArr = []; if (largeMode) { material.undefine("VERTEX_SIZE"); material.undefine("VERTEX_COLOR"); var color = getVisualColor(data); var opacity = getVisualOpacity(data); graphicGL.parseColor(color, rgbaArr); rgbaArr[3] *= opacity; material.set({ color: rgbaArr, "u_Size": symbolInfo.maxSize * this._sizeScale }); } else { material.set({ color: [1, 1, 1, 1] }); material.define("VERTEX_SIZE"); material.define("VERTEX_COLOR"); attributes.size.init(end - start); attributes.color.init(end - start); this._originalOpacity = new Float32Array(end - start); } var points = data.getLayout("points"); var positionArr = attributes.position.value; for (var i = 0; i < end - start; i++) { var i3 = i * 3; var i2 = i * 2; if (this.is2D) { positionArr[i3] = points[i2]; positionArr[i3 + 1] = points[i2 + 1]; positionArr[i3 + 2] = Z_2D; } else { positionArr[i3] = points[i3]; positionArr[i3 + 1] = points[i3 + 1]; positionArr[i3 + 2] = points[i3 + 2]; } if (!largeMode) { var color = getItemVisualColor(data, i); var opacity = getItemVisualOpacity(data, i); graphicGL.parseColor(color, rgbaArr); rgbaArr[3] *= opacity; attributes.color.set(i, rgbaArr); if (rgbaArr[3] < 0.99) ; var symbolSize = data.getItemVisual(i, "symbolSize"); symbolSize = symbolSize instanceof Array ? Math.max(symbolSize[0], symbolSize[1]) : symbolSize; if (isNaN(symbolSize)) { symbolSize = 0; } attributes.size.value[i] = symbolSize * pointSizeScale * this._sizeScale; this._originalOpacity[i] = rgbaArr[3]; } } this._mesh.sizeScale = pointSizeScale; geometry.updateBoundingBox(); geometry.dirty(); this._updateMaterial(seriesModel, itemStyle); var coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.viewGL) { var methodName = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; material[methodName]("fragment", "SRGB_DECODE"); } if (!largeMode) { this._updateLabelBuilder(seriesModel, start, end); } this._updateHandler(seriesModel, ecModel, api); this._updateAnimation(seriesModel); this._api = api; }, getPointsMesh: function() { return this._mesh; }, updateLabels: function(highlightDataIndices) { this._labelsBuilder.updateLabels(highlightDataIndices); }, hideLabels: function() { this.rootNode.remove(this._labelsBuilder.getMesh()); }, showLabels: function() { this.rootNode.add(this._labelsBuilder.getMesh()); }, dispose: function() { this._labelsBuilder.dispose(); }, _updateSymbolSprite: function(seriesModel, itemStyle, symbolInfo, dpr) { symbolInfo.maxSize = Math.min(symbolInfo.maxSize * 2, 200); var symbolSize = []; if (symbolInfo.aspect > 1) { symbolSize[0] = symbolInfo.maxSize; symbolSize[1] = symbolInfo.maxSize / symbolInfo.aspect; } else { symbolSize[1] = symbolInfo.maxSize; symbolSize[0] = symbolInfo.maxSize * symbolInfo.aspect; } symbolSize[0] = symbolSize[0] || 1; symbolSize[1] = symbolSize[1] || 1; if (this._symbolType !== symbolInfo.type || !isSymbolSizeSame(this._symbolSize, symbolSize) || this._lineWidth !== itemStyle.lineWidth) { spriteUtil.createSymbolSprite(symbolInfo.type, symbolSize, { fill: "#fff", lineWidth: itemStyle.lineWidth, stroke: "transparent", shadowColor: "transparent", minMargin: Math.min(symbolSize[0] / 2, 10) }, this._spriteImageCanvas); spriteUtil.createSDFFromCanvas(this._spriteImageCanvas, Math.min(this._spriteImageCanvas.width, 32), SDF_RANGE, this._mesh.material.get("sprite").image); this._symbolType = symbolInfo.type; this._symbolSize = symbolSize; this._lineWidth = itemStyle.lineWidth; } return this._spriteImageCanvas.width / symbolInfo.maxSize * dpr; }, _updateMaterial: function(seriesModel, itemStyle) { var blendFunc = seriesModel.get("blendMode") === "lighter" ? graphicGL.additiveBlend : null; var material = this._mesh.material; material.blend = blendFunc; material.set("lineWidth", itemStyle.lineWidth / SDF_RANGE); var strokeColor = graphicGL.parseColor(itemStyle.stroke); material.set("strokeColor", strokeColor); material.transparent = true; material.depthMask = false; material.depthTest = !this.is2D; material.sortVertices = !this.is2D; }, _updateLabelBuilder: function(seriesModel, start, end) { var data = seriesModel.getData(); var geometry = this._mesh.geometry; var positionArr = geometry.attributes.position.value; var start = this._startDataIndex; var pointSizeScale = this._mesh.sizeScale; this._labelsBuilder.updateData(data, start, end); this._labelsBuilder.getLabelPosition = function(dataIndex, positionDesc, distance) { var idx3 = (dataIndex - start) * 3; return [positionArr[idx3], positionArr[idx3 + 1], positionArr[idx3 + 2]]; }; this._labelsBuilder.getLabelDistance = function(dataIndex, positionDesc, distance) { var size = geometry.attributes.size.get(dataIndex - start) / pointSizeScale; return size / 2 + distance; }; this._labelsBuilder.updateLabels(); }, _updateAnimation: function(seriesModel) { graphicGL.updateVertexAnimation([["prevPosition", "position"], ["prevSize", "size"]], this._prevMesh, this._mesh, seriesModel); }, _updateHandler: function(seriesModel, ecModel, api) { var data = seriesModel.getData(); var pointsMesh = this._mesh; var self2 = this; var lastDataIndex = -1; var isCartesian3D = seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === "cartesian3D"; var grid3DModel; if (isCartesian3D) { grid3DModel = seriesModel.coordinateSystem.model; } pointsMesh.seriesIndex = seriesModel.seriesIndex; pointsMesh.off("mousemove"); pointsMesh.off("mouseout"); pointsMesh.on("mousemove", function(e2) { var dataIndex = e2.vertexIndex + self2._startDataIndex; if (dataIndex !== lastDataIndex) { if (this.highlightOnMouseover) { this.downplay(data, lastDataIndex); this.highlight(data, dataIndex); this._labelsBuilder.updateLabels([dataIndex]); } if (isCartesian3D) { api.dispatchAction({ type: "grid3DShowAxisPointer", value: [data.get(seriesModel.coordDimToDataDim("x")[0], dataIndex), data.get(seriesModel.coordDimToDataDim("y")[0], dataIndex), data.get(seriesModel.coordDimToDataDim("z")[0], dataIndex)], grid3DIndex: grid3DModel.componentIndex }); } } pointsMesh.dataIndex = dataIndex; lastDataIndex = dataIndex; }, this); pointsMesh.on("mouseout", function(e2) { var dataIndex = e2.vertexIndex + self2._startDataIndex; if (this.highlightOnMouseover) { this.downplay(data, dataIndex); this._labelsBuilder.updateLabels(); } lastDataIndex = -1; pointsMesh.dataIndex = -1; if (isCartesian3D) { api.dispatchAction({ type: "grid3DHideAxisPointer", grid3DIndex: grid3DModel.componentIndex }); } }, this); }, updateLayout: function(seriesModel, ecModel, api) { var data = seriesModel.getData(); if (!this._mesh) { return; } var positionArr = this._mesh.geometry.attributes.position.value; var points = data.getLayout("points"); if (this.is2D) { for (var i = 0; i < points.length / 2; i++) { var i3 = i * 3; var i2 = i * 2; positionArr[i3] = points[i2]; positionArr[i3 + 1] = points[i2 + 1]; positionArr[i3 + 2] = Z_2D; } } else { for (var i = 0; i < points.length; i++) { positionArr[i] = points[i]; } } this._mesh.geometry.dirty(); api.getZr().refresh(); }, updateView: function(camera2) { if (!this._mesh) { return; } var worldViewProjection = new Matrix4(); Matrix4.mul(worldViewProjection, camera2.viewMatrix, this._mesh.worldTransform); Matrix4.mul(worldViewProjection, camera2.projectionMatrix, worldViewProjection); this._mesh.updateNDCPosition(worldViewProjection, this.is2D, this._api); }, highlight: function(data, dataIndex) { if (dataIndex > this._endDataIndex || dataIndex < this._startDataIndex) { return; } var itemModel = data.getItemModel(dataIndex); var emphasisItemStyleModel = itemModel.getModel("emphasis.itemStyle"); var emphasisColor = emphasisItemStyleModel.get("color"); var emphasisOpacity = emphasisItemStyleModel.get("opacity"); if (emphasisColor == null) { var color = getItemVisualColor(data, dataIndex); emphasisColor = lift(color, -0.4); } if (emphasisOpacity == null) { emphasisOpacity = getItemVisualOpacity(data, dataIndex); } var colorArr = graphicGL.parseColor(emphasisColor); colorArr[3] *= emphasisOpacity; this._mesh.geometry.attributes.color.set(dataIndex - this._startDataIndex, colorArr); this._mesh.geometry.dirtyAttribute("color"); this._api.getZr().refresh(); }, downplay: function(data, dataIndex) { if (dataIndex > this._endDataIndex || dataIndex < this._startDataIndex) { return; } var color = getItemVisualColor(data, dataIndex); var opacity = getItemVisualOpacity(data, dataIndex); var colorArr = graphicGL.parseColor(color); colorArr[3] *= opacity; this._mesh.geometry.attributes.color.set(dataIndex - this._startDataIndex, colorArr); this._mesh.geometry.dirtyAttribute("color"); this._api.getZr().refresh(); }, fadeOutAll: function(fadeOutPercent) { if (this._originalOpacity) { var geo = this._mesh.geometry; for (var i = 0; i < geo.vertexCount; i++) { var fadeOutOpacity = this._originalOpacity[i] * fadeOutPercent; geo.attributes.color.value[i * 4 + 3] = fadeOutOpacity; } geo.dirtyAttribute("color"); this._api.getZr().refresh(); } }, fadeInAll: function() { this.fadeOutAll(1); }, setPositionTexture: function(texture) { if (this._mesh) { this._setPositionTextureToMesh(this._mesh, texture); } this._positionTexture = texture; }, removePositionTexture: function() { this._positionTexture = null; if (this._mesh) { this._setPositionTextureToMesh(this._mesh, null); } }, setSizeScale: function(sizeScale) { if (sizeScale !== this._sizeScale) { if (this._mesh) { var originalSize = this._mesh.material.get("u_Size"); this._mesh.material.set("u_Size", originalSize / this._sizeScale * sizeScale); var attributes = this._mesh.geometry.attributes; if (attributes.size.value) { for (var i = 0; i < attributes.size.value.length; i++) { attributes.size.value[i] = attributes.size.value[i] / this._sizeScale * sizeScale; } } } this._sizeScale = sizeScale; } }, _setPositionTextureToMesh: function(mesh2, texture) { if (texture) { mesh2.material.set("positionTexture", texture); } mesh2.material[texture ? "enableTexture" : "disableTexture"]("positionTexture"); }, _getSymbolInfo: function(seriesModel, start, end) { if (seriesModel.get("large")) { var symbolSize = retrieve.firstNotNull(seriesModel.get("symbolSize"), 1); var maxSymbolSize; var symbolAspect; if (symbolSize instanceof Array) { maxSymbolSize = Math.max(symbolSize[0], symbolSize[1]); symbolAspect = symbolSize[0] / symbolSize[1]; } else { maxSymbolSize = symbolSize; symbolAspect = 1; } return { maxSize: symbolSize, type: seriesModel.get("symbol"), aspect: symbolAspect }; } var data = seriesModel.getData(); var symbolAspect; var symbolType = data.getItemVisual(0, "symbol") || "circle"; var maxSymbolSize = 0; for (var idx = start; idx < end; idx++) { var symbolSize = data.getItemVisual(idx, "symbolSize"); var currentSymbolType = data.getItemVisual(idx, "symbol"); var currentSymbolAspect; if (!(symbolSize instanceof Array)) { if (isNaN(symbolSize)) { continue; } currentSymbolAspect = 1; maxSymbolSize = Math.max(symbolSize, maxSymbolSize); } else { currentSymbolAspect = symbolSize[0] / symbolSize[1]; maxSymbolSize = Math.max(Math.max(symbolSize[0], symbolSize[1]), maxSymbolSize); } symbolType = currentSymbolType; symbolAspect = currentSymbolAspect; } return { maxSize: maxSymbolSize, type: symbolType, aspect: symbolAspect }; } }; const Scatter3DView = ChartView.extend({ type: "scatter3D", hasSymbolVisual: true, __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this._pointsBuilderList = []; this._currentStep = 0; }, render: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); if (!seriesModel.getData().count()) { return; } var coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); this._camera = coordSys.viewGL.camera; var pointsBuilder = this._pointsBuilderList[0]; if (!pointsBuilder) { pointsBuilder = this._pointsBuilderList[0] = new PointsBuilder(false, api); } this._pointsBuilderList.length = 1; this.groupGL.add(pointsBuilder.rootNode); pointsBuilder.update(seriesModel, ecModel, api); pointsBuilder.updateView(coordSys.viewGL.camera); } }, incrementalPrepareRender: function(seriesModel, ecModel, api) { var coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); this._camera = coordSys.viewGL.camera; } this.groupGL.removeAll(); this._currentStep = 0; }, incrementalRender: function(params, seriesModel, ecModel, api) { if (params.end <= params.start) { return; } var pointsBuilder = this._pointsBuilderList[this._currentStep]; if (!pointsBuilder) { pointsBuilder = new PointsBuilder(false, api); this._pointsBuilderList[this._currentStep] = pointsBuilder; } this.groupGL.add(pointsBuilder.rootNode); pointsBuilder.update(seriesModel, ecModel, api, params.start, params.end); pointsBuilder.updateView(seriesModel.coordinateSystem.viewGL.camera); this._currentStep++; }, updateCamera: function() { this._pointsBuilderList.forEach(function(pointsBuilder) { pointsBuilder.updateView(this._camera); }, this); }, highlight: function(seriesModel, ecModel, api, payload) { this._toggleStatus("highlight", seriesModel, ecModel, api, payload); }, downplay: function(seriesModel, ecModel, api, payload) { this._toggleStatus("downplay", seriesModel, ecModel, api, payload); }, _toggleStatus: function(status, seriesModel, ecModel, api, payload) { var data = seriesModel.getData(); var dataIndex = retrieve.queryDataIndex(data, payload); var isHighlight = status === "highlight"; if (dataIndex != null) { each(formatUtil.normalizeToArray(dataIndex), function(dataIdx) { for (var i = 0; i < this._pointsBuilderList.length; i++) { var pointsBuilder = this._pointsBuilderList[i]; isHighlight ? pointsBuilder.highlight(data, dataIdx) : pointsBuilder.downplay(data, dataIdx); } }, this); } else { data.each(function(dataIdx) { for (var i = 0; i < this._pointsBuilderList.length; i++) { var pointsBuilder = this._pointsBuilderList[i]; isHighlight ? pointsBuilder.highlight(data, dataIdx) : pointsBuilder.downplay(data, dataIdx); } }); } }, dispose: function() { this._pointsBuilderList.forEach(function(pointsBuilder) { pointsBuilder.dispose(); }); this.groupGL.removeAll(); }, remove: function() { this.groupGL.removeAll(); } }); function install$8(registers) { registers.registerChartView(Scatter3DView); registers.registerSeriesModel(Scatter3DSeries); registers.registerLayout({ seriesType: "scatter3D", reset: function(seriesModel) { var coordSys = seriesModel.coordinateSystem; if (coordSys) { var coordDims = coordSys.dimensions; if (coordDims.length < 3) { return; } var dims = coordDims.map(function(coordDim) { return seriesModel.coordDimToDataDim(coordDim)[0]; }); var item = []; var out = []; return { progress: function(params, data) { var points = new Float32Array((params.end - params.start) * 3); for (var idx = params.start; idx < params.end; idx++) { var idx3 = (idx - params.start) * 3; item[0] = data.get(dims[0], idx); item[1] = data.get(dims[1], idx); item[2] = data.get(dims[2], idx); coordSys.dataToPoint(item, out); points[idx3] = out[0]; points[idx3 + 1] = out[1]; points[idx3 + 2] = out[2]; } data.setLayout("points", points); } }; } } }); } use(install$8); var vec3$2 = glmatrix.vec3; var vec2$2 = glmatrix.vec2; var normalize$1 = vec3$2.normalize; var cross = vec3$2.cross; var sub = vec3$2.sub; var add = vec3$2.add; var create$1 = vec3$2.create; var normal = create$1(); var tangent = create$1(); var bitangent = create$1(); var halfVector = create$1(); var coord0 = []; var coord1 = []; function getCubicPointsOnGlobe(coords, coordSys) { vec2$2.copy(coord0, coords[0]); vec2$2.copy(coord1, coords[1]); var pts = []; var p02 = pts[0] = create$1(); var p12 = pts[1] = create$1(); var p22 = pts[2] = create$1(); var p3 = pts[3] = create$1(); coordSys.dataToPoint(coord0, p02); coordSys.dataToPoint(coord1, p3); normalize$1(normal, p02); sub(tangent, p3, p02); normalize$1(tangent, tangent); cross(bitangent, tangent, normal); normalize$1(bitangent, bitangent); cross(tangent, normal, bitangent); add(p12, normal, tangent); normalize$1(p12, p12); normalize$1(normal, p3); sub(tangent, p02, p3); normalize$1(tangent, tangent); cross(bitangent, tangent, normal); normalize$1(bitangent, bitangent); cross(tangent, normal, bitangent); add(p22, normal, tangent); normalize$1(p22, p22); add(halfVector, p02, p3); normalize$1(halfVector, halfVector); var projDist = vec3$2.dot(p02, halfVector); var cosTheta = vec3$2.dot(halfVector, p12); var len = (Math.max(vec3$2.len(p02), vec3$2.len(p3)) - projDist) / cosTheta * 2; vec3$2.scaleAndAdd(p12, p02, p12, len); vec3$2.scaleAndAdd(p22, p3, p22, len); return pts; } function getCubicPointsOnPlane(coords, coordSys, up) { var pts = []; var p02 = pts[0] = vec3$2.create(); var p12 = pts[1] = vec3$2.create(); var p22 = pts[2] = vec3$2.create(); var p3 = pts[3] = vec3$2.create(); coordSys.dataToPoint(coords[0], p02); coordSys.dataToPoint(coords[1], p3); var len = vec3$2.dist(p02, p3); vec3$2.lerp(p12, p02, p3, 0.3); vec3$2.lerp(p22, p02, p3, 0.3); vec3$2.scaleAndAdd(p12, p12, up, Math.min(len * 0.1, 10)); vec3$2.scaleAndAdd(p22, p22, up, Math.min(len * 0.1, 10)); return pts; } function getPolylinePoints(coords, coordSys) { var pts = new Float32Array(coords.length * 3); var off = 0; var pt = []; for (var i = 0; i < coords.length; i++) { coordSys.dataToPoint(coords[i], pt); pts[off++] = pt[0]; pts[off++] = pt[1]; pts[off++] = pt[2]; } return pts; } function prepareCoords(data) { var coordsList = []; data.each(function(idx) { var itemModel = data.getItemModel(idx); var coords = itemModel.option instanceof Array ? itemModel.option : itemModel.getShallow("coords", true); coordsList.push(coords); }); return { coordsList }; } function layoutGlobe(seriesModel, coordSys) { var data = seriesModel.getData(); var isPolyline = seriesModel.get("polyline"); data.setLayout("lineType", isPolyline ? "polyline" : "cubicBezier"); var res = prepareCoords(data); data.each(function(idx) { var coords = res.coordsList[idx]; var getPointsMethod = isPolyline ? getPolylinePoints : getCubicPointsOnGlobe; data.setItemLayout(idx, getPointsMethod(coords, coordSys)); }); } function layoutOnPlane(seriesModel, coordSys, normal2) { var data = seriesModel.getData(); var isPolyline = seriesModel.get("polyline"); var res = prepareCoords(data); data.setLayout("lineType", isPolyline ? "polyline" : "cubicBezier"); data.each(function(idx) { var coords = res.coordsList[idx]; var pts = isPolyline ? getPolylinePoints(coords, coordSys) : getCubicPointsOnPlane(coords, coordSys, normal2); data.setItemLayout(idx, pts); }); } function lines3DLayout(ecModel, api) { ecModel.eachSeriesByType("lines3D", function(seriesModel) { var coordSys = seriesModel.coordinateSystem; if (coordSys.type === "globe") { layoutGlobe(seriesModel, coordSys); } else if (coordSys.type === "geo3D") { layoutOnPlane(seriesModel, coordSys, [0, 1, 0]); } else if (coordSys.type === "mapbox3D" || coordSys.type === "maptalks3D") { layoutOnPlane(seriesModel, coordSys, [0, 0, 1]); } }); } const Lines3DSeries = SeriesModel.extend({ type: "series.lines3D", dependencies: ["globe"], visualStyleAccessPath: "lineStyle", visualDrawType: "stroke", getInitialData: function(option, ecModel) { var lineData = new SeriesData(["value"], this); lineData.hasItemOption = false; lineData.initData(option.data, [], function(dataItem, dimName, dataIndex, dimIndex) { if (dataItem instanceof Array) { return NaN; } else { lineData.hasItemOption = true; var value = dataItem.value; if (value != null) { return value instanceof Array ? value[dimIndex] : value; } } }); return lineData; }, defaultOption: { coordinateSystem: "globe", globeIndex: 0, geo3DIndex: 0, zlevel: -10, polyline: false, effect: { show: false, period: 4, // Trail width trailWidth: 4, trailLength: 0.2, spotIntensity: 6 }, silent: true, // Support source-over, lighter blendMode: "source-over", lineStyle: { width: 1, opacity: 0.5 // color } } }); const trail2GLSL = "@export ecgl.trail2.vertex\nattribute vec3 position: POSITION;\nattribute vec3 positionPrev;\nattribute vec3 positionNext;\nattribute float offset;\nattribute float dist;\nattribute float distAll;\nattribute float start;\n\nattribute vec4 a_Color : COLOR;\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform vec4 viewport : VIEWPORT;\nuniform float near : NEAR;\n\nuniform float speed : 0;\nuniform float trailLength: 0.3;\nuniform float time;\nuniform float period: 1000;\n\nuniform float spotSize: 1;\n\nvarying vec4 v_Color;\nvarying float v_Percent;\nvarying float v_SpotPercent;\n\n@import ecgl.common.wireframe.vertexHeader\n\n@import ecgl.lines3D.clipNear\n\nvoid main()\n{\n @import ecgl.lines3D.expandLine\n\n gl_Position = currProj;\n\n v_Color = a_Color;\n\n @import ecgl.common.wireframe.vertexMain\n\n#ifdef CONSTANT_SPEED\n float t = mod((speed * time + start) / distAll, 1. + trailLength) - trailLength;\n#else\n float t = mod((time + start) / period, 1. + trailLength) - trailLength;\n#endif\n\n float trailLen = distAll * trailLength;\n\n v_Percent = (dist - t * distAll) / trailLen;\n\n v_SpotPercent = spotSize / distAll;\n\n }\n@end\n\n\n@export ecgl.trail2.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\nuniform float spotIntensity: 5;\n\nvarying vec4 v_Color;\nvarying float v_Percent;\nvarying float v_SpotPercent;\n\n@import ecgl.common.wireframe.fragmentHeader\n\n@import clay.util.srgb\n\nvoid main()\n{\n if (v_Percent > 1.0 || v_Percent < 0.0) {\n discard;\n }\n\n float fade = v_Percent;\n\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(color * v_Color);\n#else\n gl_FragColor = color * v_Color;\n#endif\n\n @import ecgl.common.wireframe.fragmentMain\n\n if (v_Percent > (1.0 - v_SpotPercent)) {\n gl_FragColor.rgb *= spotIntensity;\n }\n\n gl_FragColor.a *= fade;\n}\n\n@end"; var vec3$1 = glmatrix.vec3; function sign(a) { return a > 0 ? 1 : -1; } graphicGL.Shader.import(trail2GLSL); const TrailMesh2 = graphicGL.Mesh.extend(function() { var material = new graphicGL.Material({ shader: new graphicGL.Shader(graphicGL.Shader.source("ecgl.trail2.vertex"), graphicGL.Shader.source("ecgl.trail2.fragment")), transparent: true, depthMask: false }); var geometry = new LinesGeometry$2({ dynamic: true }); geometry.createAttribute("dist", "float", 1); geometry.createAttribute("distAll", "float", 1); geometry.createAttribute("start", "float", 1); return { geometry, material, culling: false, $ignorePicking: true }; }, { updateData: function(data, api, lines3DGeometry) { var seriesModel = data.hostModel; var geometry = this.geometry; var effectModel = seriesModel.getModel("effect"); var size = effectModel.get("trailWidth") * api.getDevicePixelRatio(); var trailLength = effectModel.get("trailLength"); var speed = seriesModel.get("effect.constantSpeed"); var period = seriesModel.get("effect.period") * 1e3; var useConstantSpeed = speed != null; useConstantSpeed ? this.material.set("speed", speed / 1e3) : this.material.set("period", period); this.material[useConstantSpeed ? "define" : "undefine"]("vertex", "CONSTANT_SPEED"); var isPolyline = seriesModel.get("polyline"); geometry.trailLength = trailLength; this.material.set("trailLength", trailLength); geometry.resetOffset(); ["position", "positionPrev", "positionNext"].forEach(function(attrName) { geometry.attributes[attrName].value = lines3DGeometry.attributes[attrName].value; }); var extraAttrs = ["dist", "distAll", "start", "offset", "color"]; extraAttrs.forEach(function(attrName) { geometry.attributes[attrName].init(geometry.vertexCount); }); geometry.indices = lines3DGeometry.indices; var colorArr = []; var effectColor = effectModel.get("trailColor"); var effectOpacity = effectModel.get("trailOpacity"); var hasEffectColor = effectColor != null; var hasEffectOpacity = effectOpacity != null; this.updateWorldTransform(); var xScale = this.worldTransform.x.len(); var yScale = this.worldTransform.y.len(); var zScale = this.worldTransform.z.len(); var vertexOffset = 0; var maxDistance = 0; data.each(function(idx) { var pts = data.getItemLayout(idx); var opacity = hasEffectOpacity ? effectOpacity : getItemVisualOpacity(data, idx); var color = getItemVisualColor(data, idx); if (opacity == null) { opacity = 1; } colorArr = graphicGL.parseColor(hasEffectColor ? effectColor : color, colorArr); colorArr[3] *= opacity; var vertexCount = isPolyline ? lines3DGeometry.getPolylineVertexCount(pts) : lines3DGeometry.getCubicCurveVertexCount(pts[0], pts[1], pts[2], pts[3]); var dist = 0; var pos = []; var posPrev = []; for (var i = vertexOffset; i < vertexOffset + vertexCount; i++) { geometry.attributes.position.get(i, pos); pos[0] *= xScale; pos[1] *= yScale; pos[2] *= zScale; if (i > vertexOffset) { dist += vec3$1.dist(pos, posPrev); } geometry.attributes.dist.set(i, dist); vec3$1.copy(posPrev, pos); } maxDistance = Math.max(maxDistance, dist); var randomStart = Math.random() * (useConstantSpeed ? dist : period); for (var i = vertexOffset; i < vertexOffset + vertexCount; i++) { geometry.attributes.distAll.set(i, dist); geometry.attributes.start.set(i, randomStart); geometry.attributes.offset.set(i, sign(lines3DGeometry.attributes.offset.get(i)) * size / 2); geometry.attributes.color.set(i, colorArr); } vertexOffset += vertexCount; }); this.material.set("spotSize", maxDistance * 0.1 * trailLength); this.material.set("spotIntensity", effectModel.get("spotIntensity")); geometry.dirty(); }, setAnimationTime: function(time) { this.material.set("time", time); } }); graphicGL.Shader.import(lines3DGLSL); function getCoordSysSize(coordSys) { if (coordSys.radius != null) { return coordSys.radius; } if (coordSys.size != null) { return Math.max(coordSys.size[0], coordSys.size[1], coordSys.size[2]); } else { return 100; } } const Lines3DView = ChartView.extend({ type: "lines3D", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this._meshLinesMaterial = new graphicGL.Material({ shader: graphicGL.createShader("ecgl.meshLines3D"), transparent: true, depthMask: false }); this._linesMesh = new graphicGL.Mesh({ geometry: new LinesGeometry$2(), material: this._meshLinesMaterial, $ignorePicking: true }); this._trailMesh = new TrailMesh2(); }, render: function(seriesModel, ecModel, api) { this.groupGL.add(this._linesMesh); var coordSys = seriesModel.coordinateSystem; var data = seriesModel.getData(); if (coordSys && coordSys.viewGL) { var viewGL = coordSys.viewGL; viewGL.add(this.groupGL); this._updateLines(seriesModel, ecModel, api); var methodName = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; this._linesMesh.material[methodName]("fragment", "SRGB_DECODE"); this._trailMesh.material[methodName]("fragment", "SRGB_DECODE"); } var trailMesh = this._trailMesh; trailMesh.stopAnimation(); if (seriesModel.get("effect.show")) { this.groupGL.add(trailMesh); trailMesh.updateData(data, api, this._linesMesh.geometry); trailMesh.__time = trailMesh.__time || 0; var time = 3600 * 1e3; this._curveEffectsAnimator = trailMesh.animate("", { loop: true }).when(time, { __time: time }).during(function() { trailMesh.setAnimationTime(trailMesh.__time); }).start(); } else { this.groupGL.remove(trailMesh); this._curveEffectsAnimator = null; } this._linesMesh.material.blend = this._trailMesh.material.blend = seriesModel.get("blendMode") === "lighter" ? graphicGL.additiveBlend : null; }, pauseEffect: function() { if (this._curveEffectsAnimator) { this._curveEffectsAnimator.pause(); } }, resumeEffect: function() { if (this._curveEffectsAnimator) { this._curveEffectsAnimator.resume(); } }, toggleEffect: function() { var animator = this._curveEffectsAnimator; if (animator) { animator.isPaused() ? animator.resume() : animator.pause(); } }, _updateLines: function(seriesModel, ecModel, api) { var data = seriesModel.getData(); var coordSys = seriesModel.coordinateSystem; var geometry = this._linesMesh.geometry; var isPolyline = seriesModel.get("polyline"); geometry.expandLine = true; var size = getCoordSysSize(coordSys); geometry.segmentScale = size / 20; var lineWidthQueryPath = "lineStyle.width".split("."); var dpr = api.getDevicePixelRatio(); data.each(function(idx) { var itemModel = data.getItemModel(idx); var lineWidth = itemModel.get(lineWidthQueryPath); if (lineWidth == null) { lineWidth = 1; } data.setItemVisual(idx, "lineWidth", lineWidth); }); geometry.useNativeLine = false; var nVertex = 0; var nTriangle = 0; data.each(function(idx) { var pts = data.getItemLayout(idx); if (isPolyline) { nVertex += geometry.getPolylineVertexCount(pts); nTriangle += geometry.getPolylineTriangleCount(pts); } else { nVertex += geometry.getCubicCurveVertexCount(pts[0], pts[1], pts[2], pts[3]); nTriangle += geometry.getCubicCurveTriangleCount(pts[0], pts[1], pts[2], pts[3]); } }); geometry.setVertexCount(nVertex); geometry.setTriangleCount(nTriangle); geometry.resetOffset(); var colorArr = []; data.each(function(idx) { var pts = data.getItemLayout(idx); var color = getItemVisualColor(data, idx); var opacity = getItemVisualOpacity(data, idx); var lineWidth = data.getItemVisual(idx, "lineWidth") * dpr; if (opacity == null) { opacity = 1; } colorArr = graphicGL.parseColor(color, colorArr); colorArr[3] *= opacity; if (isPolyline) { geometry.addPolyline(pts, colorArr, lineWidth); } else { geometry.addCubicCurve(pts[0], pts[1], pts[2], pts[3], colorArr, lineWidth); } }); geometry.dirty(); }, remove: function() { this.groupGL.removeAll(); }, dispose: function() { this.groupGL.removeAll(); } }); function install$7(registers) { registers.registerChartView(Lines3DView); registers.registerSeriesModel(Lines3DSeries); registers.registerLayout(lines3DLayout); registers.registerAction({ type: "lines3DPauseEffect", event: "lines3deffectpaused", update: "series.lines3D:pauseEffect" }, function() { }); registers.registerAction({ type: "lines3DResumeEffect", event: "lines3deffectresumed", update: "series.lines3D:resumeEffect" }, function() { }); registers.registerAction({ type: "lines3DToggleEffect", event: "lines3deffectchanged", update: "series.lines3D:toggleEffect" }, function() { }); } use(install$7); function transformPolygon$1(coordSys, poly) { var ret2 = []; for (var i = 0; i < poly.length; i++) { ret2.push(coordSys.dataToPoint(poly[i])); } return ret2; } var Polygons3DSeries = SeriesModel.extend({ type: "series.polygons3D", getRegionModel: function(idx) { return this.getData().getItemModel(idx); }, getRegionPolygonCoords: function(idx) { var coordSys = this.coordinateSystem; var itemModel = this.getData().getItemModel(idx); var coords = itemModel.option instanceof Array ? itemModel.option : itemModel.getShallow("coords"); if (!itemModel.get("multiPolygon")) { coords = [coords]; } var out = []; for (var i = 0; i < coords.length; i++) { var interiors = []; for (var k = 1; k < coords[i].length; k++) { interiors.push(transformPolygon$1(coordSys, coords[i][k])); } out.push({ exterior: transformPolygon$1(coordSys, coords[i][0]), interiors }); } return out; }, getInitialData: function(option) { var polygonsData = new SeriesData(["value"], this); polygonsData.hasItemOption = false; polygonsData.initData(option.data, [], function(dataItem, dimName, dataIndex, dimIndex) { if (dataItem instanceof Array) { return NaN; } else { polygonsData.hasItemOption = true; var value = dataItem.value; if (value != null) { return value instanceof Array ? value[dimIndex] : value; } } }); return polygonsData; }, defaultOption: { show: true, data: null, multiPolygon: false, progressiveThreshold: 1e3, progressive: 1e3, zlevel: -10, label: { show: false, // Distance in 3d space. distance: 2, textStyle: { fontSize: 20, color: "#000", backgroundColor: "rgba(255,255,255,0.7)", padding: 3, borderRadius: 4 } }, itemStyle: { color: "#fff", borderWidth: 0, borderColor: "#333" }, emphasis: { itemStyle: { color: "#639fc0" }, label: { show: true } } } }); merge(Polygons3DSeries.prototype, componentShadingMixin); const Polygons3DView = ChartView.extend({ type: "polygons3D", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this._geo3DBuilderList = []; this._currentStep = 0; }, render: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); var coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); } var geo3DBuilder = this._geo3DBuilderList[0]; if (!geo3DBuilder) { geo3DBuilder = new Geo3DBuilder(api); geo3DBuilder.extrudeY = coordSys.type !== "mapbox3D" && coordSys.type !== "maptalks3D"; this._geo3DBuilderList[0] = geo3DBuilder; } this._updateShaderDefines(coordSys, geo3DBuilder); geo3DBuilder.update(seriesModel, ecModel, api); this._geo3DBuilderList.length = 1; this.groupGL.add(geo3DBuilder.rootNode); }, incrementalPrepareRender: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); var coordSys = seriesModel.coordinateSystem; if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); } this._currentStep = 0; }, incrementalRender: function(params, seriesModel, ecModel, api) { var geo3DBuilder = this._geo3DBuilderList[this._currentStep]; var coordSys = seriesModel.coordinateSystem; if (!geo3DBuilder) { geo3DBuilder = new Geo3DBuilder(api); geo3DBuilder.extrudeY = coordSys.type !== "mapbox3D" && coordSys.type !== "maptalks3D"; this._geo3DBuilderList[this._currentStep] = geo3DBuilder; } geo3DBuilder.update(seriesModel, ecModel, api, params.start, params.end); this.groupGL.add(geo3DBuilder.rootNode); this._updateShaderDefines(coordSys, geo3DBuilder); this._currentStep++; }, _updateShaderDefines: function(coordSys, geo3DBuilder) { var methodName = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; geo3DBuilder.rootNode.traverse(function(mesh2) { if (mesh2.material) { mesh2.material[methodName]("fragment", "SRGB_DECODE"); if (coordSys.type === "mapbox3D" || coordSys.type === "maptalks3D") { mesh2.material.define("fragment", "NORMAL_UP_AXIS", 2); mesh2.material.define("fragment", "NORMAL_FRONT_AXIS", 1); } } }); }, remove: function() { this.groupGL.removeAll(); }, dispose: function() { this.groupGL.removeAll(); this._geo3DBuilderList.forEach(function(geo3DBuilder) { geo3DBuilder.dispose(); }); } }); function install$6(registers) { registers.registerChartView(Polygons3DView); registers.registerSeriesModel(Polygons3DSeries); } use(install$6); var SurfaceSeries = SeriesModel.extend({ type: "series.surface", dependencies: ["globe", "grid3D", "geo3D"], visualStyleAccessPath: "itemStyle", formatTooltip: function(dataIndex) { return formatTooltip(this, dataIndex); }, getInitialData: function(option, ecModel) { var data = option.data; function validateDimension(dimOpts) { return !(isNaN(dimOpts.min) || isNaN(dimOpts.max) || isNaN(dimOpts.step)); } function getPrecision(dimOpts) { var getPrecision2 = getPrecisionSafe; return Math.max(getPrecision2(dimOpts.min), getPrecision2(dimOpts.max), getPrecision2(dimOpts.step)) + 1; } if (!data) { if (!option.parametric) { var equation = option.equation || {}; var xOpts = equation.x || {}; var yOpts = equation.y || {}; ["x", "y"].forEach(function(dim) { if (!validateDimension(equation[dim])) { return; } }); if (typeof equation.z !== "function") { return; } var xCount = Math.floor((xOpts.max + xOpts.step - xOpts.min) / xOpts.step); var yCount = Math.floor((yOpts.max + yOpts.step - yOpts.min) / yOpts.step); data = new Float32Array(xCount * yCount * 3); var xPrecision = getPrecision(xOpts); var yPrecision = getPrecision(yOpts); var off = 0; for (var j = 0; j < yCount; j++) { for (var i = 0; i < xCount; i++) { var x = i * xOpts.step + xOpts.min; var y = j * yOpts.step + yOpts.min; var x2 = round(Math.min(x, xOpts.max), xPrecision); var y2 = round(Math.min(y, yOpts.max), yPrecision); var z = equation.z(x2, y2); data[off++] = x2; data[off++] = y2; data[off++] = z; } } } else { var parametricEquation = option.parametricEquation || {}; var uOpts = parametricEquation.u || {}; var vOpts = parametricEquation.v || {}; ["u", "v"].forEach(function(dim) { if (!validateDimension(parametricEquation[dim])) { return; } }); ["x", "y", "z"].forEach(function(dim) { if (typeof parametricEquation[dim] !== "function") { return; } }); var uCount = Math.floor((uOpts.max + uOpts.step - uOpts.min) / uOpts.step); var vCount = Math.floor((vOpts.max + vOpts.step - vOpts.min) / vOpts.step); data = new Float32Array(uCount * vCount * 5); var uPrecision = getPrecision(uOpts); var vPrecision = getPrecision(vOpts); var off = 0; for (var j = 0; j < vCount; j++) { for (var i = 0; i < uCount; i++) { var u = i * uOpts.step + uOpts.min; var v = j * vOpts.step + vOpts.min; var u2 = round(Math.min(u, uOpts.max), uPrecision); var v2 = round(Math.min(v, vOpts.max), vPrecision); var x = parametricEquation.x(u2, v2); var y = parametricEquation.y(u2, v2); var z = parametricEquation.z(u2, v2); data[off++] = x; data[off++] = y; data[off++] = z; data[off++] = u2; data[off++] = v2; } } } } var dims = ["x", "y", "z"]; if (option.parametric) { dims.push("u", "v"); } var list = createList(this, dims, data); return list; }, defaultOption: { coordinateSystem: "cartesian3D", zlevel: -10, // Cartesian coordinate system grid3DIndex: 0, // Surface needs lambert shading to show the difference shading: "lambert", // If parametric surface parametric: false, wireframe: { show: true, lineStyle: { color: "rgba(0,0,0,0.5)", width: 1 } }, /** * Generate surface data from z = f(x, y) equation */ equation: { // [min, max, step] x: { min: -1, max: 1, step: 0.1 }, y: { min: -1, max: 1, step: 0.1 }, z: null }, parametricEquation: { // [min, max, step] u: { min: -1, max: 1, step: 0.1 }, v: { min: -1, max: 1, step: 0.1 }, // [x, y, z] = f(x, y) x: null, y: null, z: null }, // Shape of give data // It is an array to specify rows and columns. // For example [30, 30] dataShape: null, itemStyle: { // Color }, animationDurationUpdate: 500 } }); merge(SurfaceSeries.prototype, componentShadingMixin); var vec3 = glmatrix.vec3; function isPointsNaN(pt) { return isNaN(pt[0]) || isNaN(pt[1]) || isNaN(pt[2]); } const SurfaceView = ChartView.extend({ type: "surface", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); }, render: function(seriesModel, ecModel, api) { var tmp = this._prevSurfaceMesh; this._prevSurfaceMesh = this._surfaceMesh; this._surfaceMesh = tmp; if (!this._surfaceMesh) { this._surfaceMesh = this._createSurfaceMesh(); } this.groupGL.remove(this._prevSurfaceMesh); this.groupGL.add(this._surfaceMesh); var coordSys = seriesModel.coordinateSystem; var shading = seriesModel.get("shading"); var data = seriesModel.getData(); var shadingPrefix = "ecgl." + shading; if (!this._surfaceMesh.material || this._surfaceMesh.material.shader.name !== shadingPrefix) { this._surfaceMesh.material = graphicGL.createMaterial(shadingPrefix, ["VERTEX_COLOR", "DOUBLE_SIDED"]); } graphicGL.setMaterialFromModel(shading, this._surfaceMesh.material, seriesModel, api); if (coordSys && coordSys.viewGL) { coordSys.viewGL.add(this.groupGL); var methodName = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; this._surfaceMesh.material[methodName]("fragment", "SRGB_DECODE"); } var isParametric = seriesModel.get("parametric"); var dataShape = seriesModel.get("dataShape"); if (!dataShape) { dataShape = this._getDataShape(data, isParametric); } var wireframeModel = seriesModel.getModel("wireframe"); var wireframeLineWidth = wireframeModel.get("lineStyle.width"); var showWireframe = wireframeModel.get("show") && wireframeLineWidth > 0; this._updateSurfaceMesh(this._surfaceMesh, seriesModel, dataShape, showWireframe); var material = this._surfaceMesh.material; if (showWireframe) { material.define("WIREFRAME_QUAD"); material.set("wireframeLineWidth", wireframeLineWidth); material.set("wireframeLineColor", graphicGL.parseColor(wireframeModel.get("lineStyle.color"))); } else { material.undefine("WIREFRAME_QUAD"); } this._initHandler(seriesModel, api); this._updateAnimation(seriesModel); }, _updateAnimation: function(seriesModel) { graphicGL.updateVertexAnimation([["prevPosition", "position"], ["prevNormal", "normal"]], this._prevSurfaceMesh, this._surfaceMesh, seriesModel); }, _createSurfaceMesh: function() { var mesh2 = new graphicGL.Mesh({ geometry: new graphicGL.Geometry({ dynamic: true, sortTriangles: true }), shadowDepthMaterial: new graphicGL.Material({ shader: new graphicGL.Shader(graphicGL.Shader.source("ecgl.sm.depth.vertex"), graphicGL.Shader.source("ecgl.sm.depth.fragment")) }), culling: false, // Render after axes renderOrder: 10, // Render normal in normal pass renderNormal: true }); mesh2.geometry.createAttribute("barycentric", "float", 4); mesh2.geometry.createAttribute("prevPosition", "float", 3); mesh2.geometry.createAttribute("prevNormal", "float", 3); Object.assign(mesh2.geometry, trianglesSortMixin); return mesh2; }, _initHandler: function(seriesModel, api) { var data = seriesModel.getData(); var surfaceMesh = this._surfaceMesh; var coordSys = seriesModel.coordinateSystem; function getNearestPointIdx(triangle, point) { var nearestDist = Infinity; var nearestIdx = -1; var pos = []; for (var i = 0; i < triangle.length; i++) { surfaceMesh.geometry.attributes.position.get(triangle[i], pos); var dist = vec3.dist(point.array, pos); if (dist < nearestDist) { nearestDist = dist; nearestIdx = triangle[i]; } } return nearestIdx; } surfaceMesh.seriesIndex = seriesModel.seriesIndex; var lastDataIndex = -1; surfaceMesh.off("mousemove"); surfaceMesh.off("mouseout"); surfaceMesh.on("mousemove", function(e2) { var idx = getNearestPointIdx(e2.triangle, e2.point); if (idx >= 0) { var point = []; surfaceMesh.geometry.attributes.position.get(idx, point); var value = coordSys.pointToData(point); var minDist = Infinity; var dataIndex = -1; var item = []; for (var i = 0; i < data.count(); i++) { item[0] = data.get("x", i); item[1] = data.get("y", i); item[2] = data.get("z", i); var dist = vec3.squaredDistance(item, value); if (dist < minDist) { dataIndex = i; minDist = dist; } } if (dataIndex !== lastDataIndex) { api.dispatchAction({ type: "grid3DShowAxisPointer", value }); } lastDataIndex = dataIndex; surfaceMesh.dataIndex = dataIndex; } else { surfaceMesh.dataIndex = -1; } }, this); surfaceMesh.on("mouseout", function(e2) { lastDataIndex = -1; surfaceMesh.dataIndex = -1; api.dispatchAction({ type: "grid3DHideAxisPointer" }); }, this); }, _updateSurfaceMesh: function(surfaceMesh, seriesModel, dataShape, showWireframe) { var geometry = surfaceMesh.geometry; var data = seriesModel.getData(); var pointsArr = data.getLayout("points"); var invalidDataCount = 0; data.each(function(idx2) { if (!data.hasValue(idx2)) { invalidDataCount++; } }); var needsSplitQuad = invalidDataCount || showWireframe; var positionAttr = geometry.attributes.position; var normalAttr = geometry.attributes.normal; var texcoordAttr = geometry.attributes.texcoord0; var barycentricAttr = geometry.attributes.barycentric; var colorAttr = geometry.attributes.color; var row = dataShape[0]; var column = dataShape[1]; var shading = seriesModel.get("shading"); var needsNormal = shading !== "color"; if (needsSplitQuad) { var vertexCount = (row - 1) * (column - 1) * 4; positionAttr.init(vertexCount); if (showWireframe) { barycentricAttr.init(vertexCount); } } else { positionAttr.value = new Float32Array(pointsArr); } colorAttr.init(geometry.vertexCount); texcoordAttr.init(geometry.vertexCount); var quadToTriangle = [0, 3, 1, 1, 3, 2]; var quadBarycentric = [[1, 1, 0, 0], [0, 1, 0, 1], [1, 0, 0, 1], [1, 0, 1, 0]]; var indices = geometry.indices = new (geometry.vertexCount > 65535 ? Uint32Array : Uint16Array)((row - 1) * (column - 1) * 6); var getQuadIndices = function(i2, j2, out) { out[1] = i2 * column + j2; out[0] = i2 * column + j2 + 1; out[3] = (i2 + 1) * column + j2 + 1; out[2] = (i2 + 1) * column + j2; }; var isTransparent = false; if (needsSplitQuad) { var quadIndices = []; var pos = []; var faceOffset = 0; if (needsNormal) { normalAttr.init(geometry.vertexCount); } else { normalAttr.value = null; } var pts = [[], [], []]; var v21 = [], v32 = []; var normal2 = vec3.create(); var getFromArray = function(arr, idx2, out) { var idx32 = idx2 * 3; out[0] = arr[idx32]; out[1] = arr[idx32 + 1]; out[2] = arr[idx32 + 2]; return out; }; var vertexNormals = new Float32Array(pointsArr.length); var vertexColors = new Float32Array(pointsArr.length / 3 * 4); for (var i = 0; i < data.count(); i++) { if (data.hasValue(i)) { var rgbaArr = graphicGL.parseColor(getItemVisualColor(data, i)); var opacity = getItemVisualOpacity(data, i); opacity != null && (rgbaArr[3] *= opacity); if (rgbaArr[3] < 0.99) { isTransparent = true; } for (var k = 0; k < 4; k++) { vertexColors[i * 4 + k] = rgbaArr[k]; } } } var farPoints = [1e7, 1e7, 1e7]; for (var i = 0; i < row - 1; i++) { for (var j = 0; j < column - 1; j++) { var dataIndex = i * (column - 1) + j; var vertexOffset = dataIndex * 4; getQuadIndices(i, j, quadIndices); var invisibleQuad = false; for (var k = 0; k < 4; k++) { getFromArray(pointsArr, quadIndices[k], pos); if (isPointsNaN(pos)) { invisibleQuad = true; } } for (var k = 0; k < 4; k++) { if (invisibleQuad) { positionAttr.set(vertexOffset + k, farPoints); } else { getFromArray(pointsArr, quadIndices[k], pos); positionAttr.set(vertexOffset + k, pos); } if (showWireframe) { barycentricAttr.set(vertexOffset + k, quadBarycentric[k]); } } for (var k = 0; k < 6; k++) { indices[faceOffset++] = quadToTriangle[k] + vertexOffset; } if (needsNormal && !invisibleQuad) { for (var k = 0; k < 2; k++) { var k3 = k * 3; for (var m = 0; m < 3; m++) { var idx = quadIndices[quadToTriangle[k3] + m]; getFromArray(pointsArr, idx, pts[m]); } vec3.sub(v21, pts[0], pts[1]); vec3.sub(v32, pts[1], pts[2]); vec3.cross(normal2, v21, v32); for (var m = 0; m < 3; m++) { var idx3 = quadIndices[quadToTriangle[k3] + m] * 3; vertexNormals[idx3] = vertexNormals[idx3] + normal2[0]; vertexNormals[idx3 + 1] = vertexNormals[idx3 + 1] + normal2[1]; vertexNormals[idx3 + 2] = vertexNormals[idx3 + 2] + normal2[2]; } } } } } if (needsNormal) { for (var i = 0; i < vertexNormals.length / 3; i++) { getFromArray(vertexNormals, i, normal2); vec3.normalize(normal2, normal2); vertexNormals[i * 3] = normal2[0]; vertexNormals[i * 3 + 1] = normal2[1]; vertexNormals[i * 3 + 2] = normal2[2]; } } var rgbaArr = []; var uvArr = []; for (var i = 0; i < row - 1; i++) { for (var j = 0; j < column - 1; j++) { var dataIndex = i * (column - 1) + j; var vertexOffset = dataIndex * 4; getQuadIndices(i, j, quadIndices); for (var k = 0; k < 4; k++) { for (var m = 0; m < 4; m++) { rgbaArr[m] = vertexColors[quadIndices[k] * 4 + m]; } colorAttr.set(vertexOffset + k, rgbaArr); if (needsNormal) { getFromArray(vertexNormals, quadIndices[k], normal2); normalAttr.set(vertexOffset + k, normal2); } var idx = quadIndices[k]; uvArr[0] = idx % column / (column - 1); uvArr[1] = Math.floor(idx / column) / (row - 1); texcoordAttr.set(vertexOffset + k, uvArr); } dataIndex++; } } } else { var uvArr = []; for (var i = 0; i < data.count(); i++) { uvArr[0] = i % column / (column - 1); uvArr[1] = Math.floor(i / column) / (row - 1); var rgbaArr = graphicGL.parseColor(getItemVisualColor(data, i)); var opacity = getItemVisualOpacity(data, i); opacity != null && (rgbaArr[3] *= opacity); if (rgbaArr[3] < 0.99) { isTransparent = true; } colorAttr.set(i, rgbaArr); texcoordAttr.set(i, uvArr); } var quadIndices = []; var cursor = 0; for (var i = 0; i < row - 1; i++) { for (var j = 0; j < column - 1; j++) { getQuadIndices(i, j, quadIndices); for (var k = 0; k < 6; k++) { indices[cursor++] = quadIndices[quadToTriangle[k]]; } } } if (needsNormal) { geometry.generateVertexNormals(); } else { normalAttr.value = null; } } if (surfaceMesh.material.get("normalMap")) { geometry.generateTangents(); } geometry.updateBoundingBox(); geometry.dirty(); surfaceMesh.material.transparent = isTransparent; surfaceMesh.material.depthMask = !isTransparent; }, _getDataShape: function(data, isParametric) { var prevX = -Infinity; var rowCount = 0; var columnCount = 0; var mayInvalid = false; var rowDim = isParametric ? "u" : "x"; var dataCount = data.count(); for (var i = 0; i < dataCount; i++) { var x = data.get(rowDim, i); if (x < prevX) { columnCount = 0; rowCount++; } prevX = x; columnCount++; } if (!rowCount || columnCount === 1) { mayInvalid = true; } if (!mayInvalid) { return [rowCount + 1, columnCount]; } var rows = Math.floor(Math.sqrt(dataCount)); while (rows > 0) { if (Math.floor(dataCount / rows) === dataCount / rows) { return [rows, dataCount / rows]; } rows--; } rows = Math.floor(Math.sqrt(dataCount)); return [rows, rows]; }, dispose: function() { this.groupGL.removeAll(); }, remove: function() { this.groupGL.removeAll(); } }); function install$5(registers) { registers.registerChartView(SurfaceView); registers.registerSeriesModel(SurfaceSeries); registers.registerLayout(function(ecModel, api) { ecModel.eachSeriesByType("surface", function(surfaceModel) { var cartesian = surfaceModel.coordinateSystem; if (!cartesian || cartesian.type !== "cartesian3D") ; var data = surfaceModel.getData(); var points = new Float32Array(3 * data.count()); var nanPoint = [NaN, NaN, NaN]; if (cartesian && cartesian.type === "cartesian3D") { var coordDims = cartesian.dimensions; var dims = coordDims.map(function(coordDim) { return surfaceModel.coordDimToDataDim(coordDim)[0]; }); data.each(dims, function(x, y, z, idx) { var pt; if (!data.hasValue(idx)) { pt = nanPoint; } else { pt = cartesian.dataToPoint([x, y, z]); } points[idx * 3] = pt[0]; points[idx * 3 + 1] = pt[1]; points[idx * 3 + 2] = pt[2]; }); } data.setLayout("points", points); }); }); } use(install$5); function transformPolygon(mapbox3DCoordSys, poly) { var newPoly = []; for (var k = 0; k < poly.length; k++) { newPoly.push(mapbox3DCoordSys.dataToPoint(poly[k])); } return newPoly; } var Map3DSeries = SeriesModel.extend({ type: "series.map3D", layoutMode: "box", coordinateSystem: null, visualStyleAccessPath: "itemStyle", optionUpdated: function(newOpt) { var coordSysType = this.get("coordinateSystem"); if (coordSysType == null || coordSysType === "geo3D") { return; } if (this.get("groundPlane.show")) { this.option.groundPlane.show = false; } this._geo = null; }, getInitialData: function(option) { option.data = this.getFilledRegions(option.data, option.map); var dimensions = createDimensions(option.data, { coordDimensions: ["value"] }); var list = new SeriesData(dimensions, this); list.initData(option.data); var regionModelMap = {}; list.each(function(idx) { var name = list.getName(idx); var itemModel = list.getItemModel(idx); regionModelMap[name] = itemModel; }); this._regionModelMap = regionModelMap; return list; }, formatTooltip: function(dataIndex) { return formatTooltip(this, dataIndex); }, getRegionModel: function(idx) { var name = this.getData().getName(idx); return this._regionModelMap[name] || new Model(null, this); }, getRegionPolygonCoords: function(idx) { var coordSys = this.coordinateSystem; var name = this.getData().getName(idx); if (coordSys.transform) { var region = coordSys.getRegion(name); return region ? region.geometries : []; } else { if (!this._geo) { this._geo = geo3DCreator.createGeo3D(this); } var region = this._geo.getRegion(name); var ret2 = []; for (var k = 0; k < region.geometries.length; k++) { var geo = region.geometries[k]; var interiors = []; var exterior = transformPolygon(coordSys, geo.exterior); if (interiors && interiors.length) { for (var m = 0; m < geo.interiors.length; m++) { interiors.push(transformPolygon(coordSys, interiors[m])); } } ret2.push({ interiors, exterior }); } return ret2; } }, /** * Format label * @param {string} name Region name * @param {string} [status='normal'] 'normal' or 'emphasis' * @return {string} */ getFormattedLabel: function(dataIndex, status) { var text = formatUtil.getFormattedLabel(this, dataIndex, status); if (text == null) { text = this.getData().getName(dataIndex); } return text; }, defaultOption: { // Support geo3D, mapbox, maptalks3D coordinateSystem: "geo3D", // itemStyle: {}, // height, // label: {} data: null } }); merge(Map3DSeries.prototype, geo3DModelMixin); merge(Map3DSeries.prototype, componentViewControlMixin); merge(Map3DSeries.prototype, componentPostEffectMixin); merge(Map3DSeries.prototype, componentLightMixin); merge(Map3DSeries.prototype, componentShadingMixin); const Map3DView = ChartView.extend({ type: "map3D", __ecgl__: true, init: function(ecModel, api) { this._geo3DBuilder = new Geo3DBuilder(api); this.groupGL = new graphicGL.Node(); }, render: function(map3DModel, ecModel, api) { var coordSys = map3DModel.coordinateSystem; if (!coordSys || !coordSys.viewGL) { return; } this.groupGL.add(this._geo3DBuilder.rootNode); coordSys.viewGL.add(this.groupGL); if (coordSys.type === "geo3D") { if (!this._sceneHelper) { this._sceneHelper = new SceneHelper(); this._sceneHelper.initLight(this.groupGL); } this._sceneHelper.setScene(coordSys.viewGL.scene); this._sceneHelper.updateLight(map3DModel); coordSys.viewGL.setPostEffect(map3DModel.getModel("postEffect"), api); coordSys.viewGL.setTemporalSuperSampling(map3DModel.getModel("temporalSuperSampling")); var control = this._control; if (!control) { control = this._control = new OrbitControl({ zr: api.getZr() }); this._control.init(); } var viewControlModel = map3DModel.getModel("viewControl"); control.setViewGL(coordSys.viewGL); control.setFromViewControlModel(viewControlModel, 0); control.off("update"); control.on("update", function() { api.dispatchAction({ type: "map3DChangeCamera", alpha: control.getAlpha(), beta: control.getBeta(), distance: control.getDistance(), from: this.uid, map3DId: map3DModel.id }); }); this._geo3DBuilder.extrudeY = true; } else { if (this._control) { this._control.dispose(); this._control = null; } if (this._sceneHelper) { this._sceneHelper.dispose(); this._sceneHelper = null; } map3DModel.getData().getLayout("geo3D"); this._geo3DBuilder.extrudeY = false; } this._geo3DBuilder.update(map3DModel, ecModel, api, 0, map3DModel.getData().count()); var srgbDefineMethod = coordSys.viewGL.isLinearSpace() ? "define" : "undefine"; this._geo3DBuilder.rootNode.traverse(function(mesh2) { if (mesh2.material) { mesh2.material[srgbDefineMethod]("fragment", "SRGB_DECODE"); } }); }, afterRender: function(map3DModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; var coordSys = map3DModel.coordinateSystem; if (coordSys && coordSys.type === "geo3D") { this._sceneHelper.updateAmbientCubemap(renderer, map3DModel, api); this._sceneHelper.updateSkybox(renderer, map3DModel, api); } }, dispose: function() { this.groupGL.removeAll(); this._control.dispose(); this._geo3DBuilder.dispose(); } }); function install$4(registers) { install$e(registers); registers.registerChartView(Map3DView); registers.registerSeriesModel(Map3DSeries); registers.registerAction({ type: "map3DChangeCamera", event: "map3dcamerachanged", update: "series:updateCamera" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "series", subType: "map3D", query: payload }, function(componentModel) { componentModel.setView(payload); }); }); } use(install$4); const ScatterGLSeries = SeriesModel.extend({ type: "series.scatterGL", dependencies: ["grid", "polar", "geo", "singleAxis"], visualStyleAccessPath: "itemStyle", hasSymbolVisual: true, getInitialData: function() { return createList$1(this); }, defaultOption: { coordinateSystem: "cartesian2d", zlevel: 10, progressive: 1e5, progressiveThreshold: 1e5, // Cartesian coordinate system // xAxisIndex: 0, // yAxisIndex: 0, // Polar coordinate system // polarIndex: 0, // Geo coordinate system // geoIndex: 0, large: false, symbol: "circle", symbolSize: 10, // symbolSize scale when zooming. zoomScale: 0, // Support source-over, lighter blendMode: "source-over", itemStyle: { opacity: 0.8 }, postEffect: { enable: false, colorCorrection: { exposure: 0, brightness: 0, contrast: 1, saturation: 1, enable: true } } } }); function create() { return [1, 0, 0, 1, 0, 0]; } function invert(out, a) { var aa = a[0]; var ac = a[2]; var atx = a[4]; var ab = a[1]; var ad = a[3]; var aty = a[5]; var det = aa * ad - ab * ac; if (!det) { return null; } det = 1 / det; out[0] = ad * det; out[1] = -ab * det; out[2] = -ac * det; out[3] = aa * det; out[4] = (ac * aty - ad * atx) * det; out[5] = (ab * atx - aa * aty) * det; return out; } function GLViewHelper(viewGL) { this.viewGL = viewGL; } GLViewHelper.prototype.reset = function(seriesModel, api) { this._updateCamera(api.getWidth(), api.getHeight(), api.getDevicePixelRatio()); this._viewTransform = create(); this.updateTransform(seriesModel, api); }; GLViewHelper.prototype.updateTransform = function(seriesModel, api) { var coordinateSystem = seriesModel.coordinateSystem; if (coordinateSystem.getRoamTransform) { invert(this._viewTransform, coordinateSystem.getRoamTransform()); this._setCameraTransform(this._viewTransform); api.getZr().refresh(); } }; GLViewHelper.prototype.dataToPoint = function(coordSys, data, pt) { pt = coordSys.dataToPoint(data, null, pt); var viewTransform = this._viewTransform; if (viewTransform) { applyTransform(pt, pt, viewTransform); } }; GLViewHelper.prototype.removeTransformInPoint = function(pt) { if (this._viewTransform) { applyTransform(pt, pt, this._viewTransform); } return pt; }; GLViewHelper.prototype.getZoom = function() { if (this._viewTransform) { var m = this._viewTransform; return 1 / Math.max(Math.sqrt(m[0] * m[0] + m[1] * m[1]), Math.sqrt(m[2] * m[2] + m[3] * m[3])); } return 1; }; GLViewHelper.prototype._setCameraTransform = function(m) { var camera2 = this.viewGL.camera; camera2.position.set(m[4], m[5], 0); camera2.scale.set(Math.sqrt(m[0] * m[0] + m[1] * m[1]), Math.sqrt(m[2] * m[2] + m[3] * m[3]), 1); }; GLViewHelper.prototype._updateCamera = function(width, height, dpr) { this.viewGL.setViewport(0, 0, width, height, dpr); var camera2 = this.viewGL.camera; camera2.left = camera2.top = 0; camera2.bottom = height; camera2.right = width; camera2.near = 0; camera2.far = 100; }; const ScatterGLView = ChartView.extend({ type: "scatterGL", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this.viewGL = new ViewGL("orthographic"); this.viewGL.add(this.groupGL); this._pointsBuilderList = []; this._currentStep = 0; this._sizeScale = 1; this._glViewHelper = new GLViewHelper(this.viewGL); }, render: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); this._glViewHelper.reset(seriesModel, api); if (!seriesModel.getData().count()) { return; } var pointsBuilder = this._pointsBuilderList[0]; if (!pointsBuilder) { pointsBuilder = this._pointsBuilderList[0] = new PointsBuilder(true, api); } this._pointsBuilderList.length = 1; this.groupGL.add(pointsBuilder.rootNode); this._removeTransformInPoints(seriesModel.getData().getLayout("points")); pointsBuilder.update(seriesModel, ecModel, api); this.viewGL.setPostEffect(seriesModel.getModel("postEffect"), api); }, incrementalPrepareRender: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); this._glViewHelper.reset(seriesModel, api); this._currentStep = 0; this.viewGL.setPostEffect(seriesModel.getModel("postEffect"), api); }, incrementalRender: function(params, seriesModel, ecModel, api) { if (params.end <= params.start) { return; } var pointsBuilder = this._pointsBuilderList[this._currentStep]; if (!pointsBuilder) { pointsBuilder = new PointsBuilder(true, api); this._pointsBuilderList[this._currentStep] = pointsBuilder; } this.groupGL.add(pointsBuilder.rootNode); this._removeTransformInPoints(seriesModel.getData().getLayout("points")); pointsBuilder.setSizeScale(this._sizeScale); pointsBuilder.update(seriesModel, ecModel, api, params.start, params.end); api.getZr().refresh(); this._currentStep++; }, updateTransform: function(seriesModel, ecModel, api) { if (seriesModel.coordinateSystem.getRoamTransform) { this._glViewHelper.updateTransform(seriesModel, api); var zoom = this._glViewHelper.getZoom(); var sizeScale = Math.max((seriesModel.get("zoomScale") || 0) * (zoom - 1) + 1, 0); this._sizeScale = sizeScale; this._pointsBuilderList.forEach(function(pointsBuilder) { pointsBuilder.setSizeScale(sizeScale); }); } }, _removeTransformInPoints: function(points) { if (!points) { return; } var pt = []; for (var i = 0; i < points.length; i += 2) { pt[0] = points[i]; pt[1] = points[i + 1]; this._glViewHelper.removeTransformInPoint(pt); points[i] = pt[0]; points[i + 1] = pt[1]; } }, dispose: function() { this.groupGL.removeAll(); this._pointsBuilderList.forEach(function(pointsBuilder) { pointsBuilder.dispose(); }); }, remove: function() { this.groupGL.removeAll(); } }); function install$3(registers) { registers.registerChartView(ScatterGLView); registers.registerSeriesModel(ScatterGLSeries); registers.registerLayout({ seriesType: "scatterGL", reset: function(seriesModel) { var coordSys = seriesModel.coordinateSystem; var data = seriesModel.getData(); var progress; if (coordSys) { var dims = coordSys.dimensions.map(function(dim) { return data.mapDimension(dim); }).slice(0, 2); var pt = []; if (dims.length === 1) { progress = function(params) { var points = new Float32Array((params.end - params.start) * 2); for (var idx = params.start; idx < params.end; idx++) { var offset = (idx - params.start) * 2; var x = data.get(dims[0], idx); var pt2 = coordSys.dataToPoint(x); points[offset] = pt2[0]; points[offset + 1] = pt2[1]; } data.setLayout("points", points); }; } else if (dims.length === 2) { progress = function(params) { var points = new Float32Array((params.end - params.start) * 2); for (var idx = params.start; idx < params.end; idx++) { var offset = (idx - params.start) * 2; var x = data.get(dims[0], idx); var y = data.get(dims[1], idx); pt[0] = x; pt[1] = y; pt = coordSys.dataToPoint(pt); points[offset] = pt[0]; points[offset + 1] = pt[1]; } data.setLayout("points", points); }; } } return { progress }; } }); } use(install$3); function createGraphFromNodeEdge(nodes, edges, hostModel, directed, beforeLink) { var graph = new Graph$1(directed); for (var i = 0; i < nodes.length; i++) { graph.addNode(retrieve.firstNotNull( // Id, name, dataIndex nodes[i].id, nodes[i].name, i ), i); } var linkNameList = []; var validEdges = []; var linkCount = 0; for (var i = 0; i < edges.length; i++) { var link = edges[i]; var source = link.source; var target = link.target; if (graph.addEdge(source, target, linkCount)) { validEdges.push(link); linkNameList.push(retrieve.firstNotNull(link.id, source + " > " + target)); linkCount++; } } var nodeData; var dimensionNames = createDimensions(nodes, { coordDimensions: ["value"] }); nodeData = new SeriesData(dimensionNames, hostModel); nodeData.initData(nodes); var edgeData = new SeriesData(["value"], hostModel); edgeData.initData(validEdges, linkNameList); beforeLink && beforeLink(nodeData, edgeData); linkSeriesData({ mainData: nodeData, struct: graph, structAttr: "graph", datas: { node: nodeData, edge: edgeData }, datasAttr: { node: "data", edge: "edgeData" } }); graph.update(); return graph; } var GraphSeries = SeriesModel.extend({ type: "series.graphGL", visualStyleAccessPath: "itemStyle", hasSymbolVisual: true, init: function(option) { GraphSeries.superApply(this, "init", arguments); this.legendDataProvider = function() { return this._categoriesData; }; this._updateCategoriesData(); }, mergeOption: function(option) { GraphSeries.superApply(this, "mergeOption", arguments); this._updateCategoriesData(); }, getFormattedLabel: function(dataIndex, status, dataType, dimIndex) { var text = formatUtil.getFormattedLabel(this, dataIndex, status, dataType, dimIndex); if (text == null) { var data = this.getData(); var lastDim = data.dimensions[data.dimensions.length - 1]; text = data.get(lastDim, dataIndex); } return text; }, getInitialData: function(option, ecModel) { var edges = option.edges || option.links || []; var nodes = option.data || option.nodes || []; var self2 = this; if (nodes && edges) { return createGraphFromNodeEdge(nodes, edges, this, true, beforeLink).data; } function beforeLink(nodeData, edgeData) { nodeData.wrapMethod("getItemModel", function(model) { const categoriesModels = self2._categoriesModels; const categoryIdx = model.getShallow("category"); const categoryModel = categoriesModels[categoryIdx]; if (categoryModel) { categoryModel.parentModel = model.parentModel; model.parentModel = categoryModel; } return model; }); const oldGetModel = ecModel.getModel([]).getModel; function newGetModel(path, parentModel) { const model = oldGetModel.call(this, path, parentModel); model.resolveParentPath = resolveParentPath; return model; } edgeData.wrapMethod("getItemModel", function(model) { model.resolveParentPath = resolveParentPath; model.getModel = newGetModel; return model; }); function resolveParentPath(pathArr) { if (pathArr && (pathArr[0] === "label" || pathArr[1] === "label")) { const newPathArr = pathArr.slice(); if (pathArr[0] === "label") { newPathArr[0] = "edgeLabel"; } else if (pathArr[1] === "label") { newPathArr[1] = "edgeLabel"; } return newPathArr; } return pathArr; } } }, /** * @return {module:echarts/data/Graph} */ getGraph: function() { return this.getData().graph; }, /** * @return {module:echarts/data/List} */ getEdgeData: function() { return this.getGraph().edgeData; }, /** * @return {module:echarts/data/List} */ getCategoriesData: function() { return this._categoriesData; }, /** * @override */ formatTooltip: function(dataIndex, multipleSeries, dataType) { if (dataType === "edge") { var nodeData = this.getData(); var params = this.getDataParams(dataIndex, dataType); var edge = nodeData.graph.getEdgeByIndex(dataIndex); var sourceName = nodeData.getName(edge.node1.dataIndex); var targetName = nodeData.getName(edge.node2.dataIndex); var html = []; sourceName != null && html.push(sourceName); targetName != null && html.push(targetName); html = encodeHTML(html.join(" > ")); if (params.value) { html += " : " + encodeHTML(params.value); } return html; } else { return GraphSeries.superApply(this, "formatTooltip", arguments); } }, _updateCategoriesData: function() { var categories = (this.option.categories || []).map(function(category) { return category.value != null ? category : Object.assign({ value: 0 }, category); }); var categoriesData = new SeriesData(["value"], this); categoriesData.initData(categories); this._categoriesData = categoriesData; this._categoriesModels = categoriesData.mapArray(function(idx) { return categoriesData.getItemModel(idx, true); }); }, setView: function(payload) { if (payload.zoom != null) { this.option.zoom = payload.zoom; } if (payload.offset != null) { this.option.offset = payload.offset; } }, setNodePosition: function(points) { for (var i = 0; i < points.length / 2; i++) { var x = points[i * 2]; var y = points[i * 2 + 1]; var opt = this.getData().getRawDataItem(i); opt.x = x; opt.y = y; } }, isAnimationEnabled: function() { return GraphSeries.superCall(this, "isAnimationEnabled") && !(this.get("layout") === "force" && this.get("force.layoutAnimation")); }, defaultOption: { zlevel: 10, z: 2, legendHoverLink: true, // Only support forceAtlas2 layout: "forceAtlas2", // Configuration of force directed layout forceAtlas2: { initLayout: null, GPU: true, steps: 1, // barnesHutOptimize // Maxp layout steps. maxSteps: 1e3, repulsionByDegree: true, linLogMode: false, strongGravityMode: false, gravity: 1, // scaling: 1.0, edgeWeightInfluence: 1, // Edge weight range. edgeWeight: [1, 4], // Node weight range. nodeWeight: [1, 4], // jitterTolerence: 0.1, preventOverlap: false, gravityCenter: null }, focusNodeAdjacency: true, focusNodeAdjacencyOn: "mouseover", left: "center", top: "center", // right: null, // bottom: null, // width: '80%', // height: '80%', symbol: "circle", symbolSize: 5, roam: false, // Default on center of graph center: null, zoom: 1, // categories: [], // data: [] // Or // nodes: [] // // links: [] // Or // edges: [] label: { show: false, formatter: "{b}", position: "right", distance: 5, textStyle: { fontSize: 14 } }, itemStyle: {}, lineStyle: { color: "#aaa", width: 1, opacity: 0.5 }, emphasis: { label: { show: true } }, animation: false } }); var vec2$1 = glmatrix.vec2; var sampleLinePoints = [[0, 0], [1, 1]]; var LinesGeometry$1 = Geometry.extend( function() { return { segmentScale: 4, dynamic: true, /** * Need to use mesh to expand lines if lineWidth > MAX_LINE_WIDTH */ useNativeLine: true, attributes: { position: new Geometry.Attribute("position", "float", 2, "POSITION"), normal: new Geometry.Attribute("normal", "float", 2), offset: new Geometry.Attribute("offset", "float", 1), color: new Geometry.Attribute("color", "float", 4, "COLOR") } }; }, /** @lends module: echarts-gl/util/geometry/LinesGeometry.prototype */ { /** * Reset offset */ resetOffset: function() { this._vertexOffset = 0; this._faceOffset = 0; this._itemVertexOffsets = []; }, /** * @param {number} nVertex */ setVertexCount: function(nVertex) { var attributes = this.attributes; if (this.vertexCount !== nVertex) { attributes.position.init(nVertex); attributes.color.init(nVertex); if (!this.useNativeLine) { attributes.offset.init(nVertex); attributes.normal.init(nVertex); } if (nVertex > 65535) { if (this.indices instanceof Uint16Array) { this.indices = new Uint32Array(this.indices); } } else { if (this.indices instanceof Uint32Array) { this.indices = new Uint16Array(this.indices); } } } }, /** * @param {number} nTriangle */ setTriangleCount: function(nTriangle) { if (this.triangleCount !== nTriangle) { if (nTriangle === 0) { this.indices = null; } else { this.indices = this.vertexCount > 65535 ? new Uint32Array(nTriangle * 3) : new Uint16Array(nTriangle * 3); } } }, _getCubicCurveApproxStep: function(p02, p12, p22, p3) { var len = vec2$1.dist(p02, p12) + vec2$1.dist(p22, p12) + vec2$1.dist(p3, p22); var step = 1 / (len + 1) * this.segmentScale; return step; }, /** * Get vertex count of cubic curve * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @param {Array.} p3 * @return number */ getCubicCurveVertexCount: function(p02, p12, p22, p3) { var step = this._getCubicCurveApproxStep(p02, p12, p22, p3); var segCount = Math.ceil(1 / step); if (!this.useNativeLine) { return segCount * 2 + 2; } else { return segCount * 2; } }, /** * Get face count of cubic curve * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @param {Array.} p3 * @return number */ getCubicCurveTriangleCount: function(p02, p12, p22, p3) { var step = this._getCubicCurveApproxStep(p02, p12, p22, p3); var segCount = Math.ceil(1 / step); if (!this.useNativeLine) { return segCount * 2; } else { return 0; } }, /** * Get vertex count of line * @return {number} */ getLineVertexCount: function() { return this.getPolylineVertexCount(sampleLinePoints); }, /** * Get face count of line * @return {number} */ getLineTriangleCount: function() { return this.getPolylineTriangleCount(sampleLinePoints); }, /** * Get how many vertices will polyline take. * @type {number|Array} points Can be a 1d/2d list of points, or a number of points amount. * @return {number} */ getPolylineVertexCount: function(points) { var pointsLen; if (typeof points === "number") { pointsLen = points; } else { var is2DArray = typeof points[0] !== "number"; pointsLen = is2DArray ? points.length : points.length / 2; } return !this.useNativeLine ? (pointsLen - 1) * 2 + 2 : (pointsLen - 1) * 2; }, /** * Get how many triangles will polyline take. * @type {number|Array} points Can be a 1d/2d list of points, or a number of points amount. * @return {number} */ getPolylineTriangleCount: function(points) { var pointsLen; if (typeof points === "number") { pointsLen = points; } else { var is2DArray = typeof points[0] !== "number"; pointsLen = is2DArray ? points.length : points.length / 2; } return !this.useNativeLine ? (pointsLen - 1) * 2 : 0; }, /** * Add a cubic curve * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @param {Array.} p3 * @param {Array.} color * @param {number} [lineWidth=1] */ addCubicCurve: function(p02, p12, p22, p3, color, lineWidth) { if (lineWidth == null) { lineWidth = 1; } var x0 = p02[0], y0 = p02[1]; var x1 = p12[0], y1 = p12[1]; var x2 = p22[0], y2 = p22[1]; var x3 = p3[0], y3 = p3[1]; var step = this._getCubicCurveApproxStep(p02, p12, p22, p3); var step2 = step * step; var step3 = step2 * step; var pre1 = 3 * step; var pre2 = 3 * step2; var pre4 = 6 * step2; var pre5 = 6 * step3; var tmp1x = x0 - x1 * 2 + x2; var tmp1y = y0 - y1 * 2 + y2; var tmp2x = (x1 - x2) * 3 - x0 + x3; var tmp2y = (y1 - y2) * 3 - y0 + y3; var fx = x0; var fy = y0; var dfx = (x1 - x0) * pre1 + tmp1x * pre2 + tmp2x * step3; var dfy = (y1 - y0) * pre1 + tmp1y * pre2 + tmp2y * step3; var ddfx = tmp1x * pre4 + tmp2x * pre5; var ddfy = tmp1y * pre4 + tmp2y * pre5; var dddfx = tmp2x * pre5; var dddfy = tmp2y * pre5; var t = 0; var k = 0; var segCount = Math.ceil(1 / step); var points = new Float32Array((segCount + 1) * 3); var points = []; var offset = 0; for (var k = 0; k < segCount + 1; k++) { points[offset++] = fx; points[offset++] = fy; fx += dfx; fy += dfy; dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; t += step; if (t > 1) { fx = dfx > 0 ? Math.min(fx, x3) : Math.max(fx, x3); fy = dfy > 0 ? Math.min(fy, y3) : Math.max(fy, y3); } } this.addPolyline(points, color, lineWidth); }, /** * Add a straight line * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} color * @param {number} [lineWidth=1] */ addLine: function(p02, p12, color, lineWidth) { this.addPolyline([p02, p12], color, lineWidth); }, /** * Add a straight line * @param {Array. | Array.} points * @param {Array. | Array.} color * @param {number} [lineWidth=1] * @param {number} [arrayOffset=0] * @param {number} [pointsCount] Default to be amount of points in the first argument */ addPolyline: function() { var dirA = vec2$1.create(); var dirB = vec2$1.create(); var normal2 = vec2$1.create(); var tangent2 = vec2$1.create(); var point = [], nextPoint = [], prevPoint = []; return function(points, color, lineWidth, arrayOffset, pointsCount) { if (!points.length) { return; } var is2DArray = typeof points[0] !== "number"; if (pointsCount == null) { pointsCount = is2DArray ? points.length : points.length / 2; } if (pointsCount < 2) { return; } if (arrayOffset == null) { arrayOffset = 0; } if (lineWidth == null) { lineWidth = 1; } this._itemVertexOffsets.push(this._vertexOffset); var notSharingColor = is2DArray ? typeof color[0] !== "number" : color.length / 4 === pointsCount; var positionAttr = this.attributes.position; var colorAttr = this.attributes.color; var offsetAttr = this.attributes.offset; var normalAttr = this.attributes.normal; var indices = this.indices; var vertexOffset = this._vertexOffset; var pointColor; for (var k = 0; k < pointsCount; k++) { if (is2DArray) { point = points[k + arrayOffset]; if (notSharingColor) { pointColor = color[k + arrayOffset]; } else { pointColor = color; } } else { var k2 = k * 2 + arrayOffset; point = point || []; point[0] = points[k2]; point[1] = points[k2 + 1]; if (notSharingColor) { var k4 = k * 4 + arrayOffset; pointColor = pointColor || []; pointColor[0] = color[k4]; pointColor[1] = color[k4 + 1]; pointColor[2] = color[k4 + 2]; pointColor[3] = color[k4 + 3]; } else { pointColor = color; } } if (!this.useNativeLine) { var offset; if (k < pointsCount - 1) { if (is2DArray) { vec2$1.copy(nextPoint, points[k + 1]); } else { var k2 = (k + 1) * 2 + arrayOffset; nextPoint = nextPoint || []; nextPoint[0] = points[k2]; nextPoint[1] = points[k2 + 1]; } if (k > 0) { vec2$1.sub(dirA, point, prevPoint); vec2$1.sub(dirB, nextPoint, point); vec2$1.normalize(dirA, dirA); vec2$1.normalize(dirB, dirB); vec2$1.add(tangent2, dirA, dirB); vec2$1.normalize(tangent2, tangent2); var miter = lineWidth / 2 * Math.min(1 / vec2$1.dot(dirA, tangent2), 2); normal2[0] = -tangent2[1]; normal2[1] = tangent2[0]; offset = miter; } else { vec2$1.sub(dirA, nextPoint, point); vec2$1.normalize(dirA, dirA); normal2[0] = -dirA[1]; normal2[1] = dirA[0]; offset = lineWidth / 2; } } else { vec2$1.sub(dirA, point, prevPoint); vec2$1.normalize(dirA, dirA); normal2[0] = -dirA[1]; normal2[1] = dirA[0]; offset = lineWidth / 2; } normalAttr.set(vertexOffset, normal2); normalAttr.set(vertexOffset + 1, normal2); offsetAttr.set(vertexOffset, offset); offsetAttr.set(vertexOffset + 1, -offset); vec2$1.copy(prevPoint, point); positionAttr.set(vertexOffset, point); positionAttr.set(vertexOffset + 1, point); colorAttr.set(vertexOffset, pointColor); colorAttr.set(vertexOffset + 1, pointColor); vertexOffset += 2; } else { if (k > 1) { positionAttr.copy(vertexOffset, vertexOffset - 1); colorAttr.copy(vertexOffset, vertexOffset - 1); vertexOffset++; } } if (!this.useNativeLine) { if (k > 0) { var idx3 = this._faceOffset * 3; var indices = this.indices; indices[idx3] = vertexOffset - 4; indices[idx3 + 1] = vertexOffset - 3; indices[idx3 + 2] = vertexOffset - 2; indices[idx3 + 3] = vertexOffset - 3; indices[idx3 + 4] = vertexOffset - 1; indices[idx3 + 5] = vertexOffset - 2; this._faceOffset += 2; } } else { colorAttr.set(vertexOffset, pointColor); positionAttr.set(vertexOffset, point); vertexOffset++; } } this._vertexOffset = vertexOffset; }; }(), /** * Set color of single line. */ setItemColor: function(idx, color) { var startOffset = this._itemVertexOffsets[idx]; var endOffset = idx < this._itemVertexOffsets.length - 1 ? this._itemVertexOffsets[idx + 1] : this._vertexOffset; for (var i = startOffset; i < endOffset; i++) { this.attributes.color.set(i, color); } this.dirty("color"); } } ); defaults(LinesGeometry$1.prototype, dynamicConvertMixin); const forceAtlas2Code = "@export ecgl.forceAtlas2.updateNodeRepulsion\n\n#define NODE_COUNT 0\n\nuniform sampler2D positionTex;\n\nuniform vec2 textureSize;\nuniform float gravity;\nuniform float scaling;\nuniform vec2 gravityCenter;\n\nuniform bool strongGravityMode;\nuniform bool preventOverlap;\n\nvarying vec2 v_Texcoord;\n\nvoid main() {\n\n vec4 n0 = texture2D(positionTex, v_Texcoord);\n\n vec2 force = vec2(0.0);\n for (int i = 0; i < NODE_COUNT; i++) {\n vec2 uv = vec2(\n mod(float(i), textureSize.x) / (textureSize.x - 1.0),\n floor(float(i) / textureSize.x) / (textureSize.y - 1.0)\n );\n vec4 n1 = texture2D(positionTex, uv);\n\n vec2 dir = n0.xy - n1.xy;\n float d2 = dot(dir, dir);\n\n if (d2 > 0.0) {\n float factor = 0.0;\n if (preventOverlap) {\n float d = sqrt(d2);\n d = d - n0.w - n1.w;\n if (d > 0.0) {\n factor = scaling * n0.z * n1.z / (d * d);\n }\n else if (d < 0.0) {\n factor = scaling * 100.0 * n0.z * n1.z;\n }\n }\n else {\n factor = scaling * n0.z * n1.z / d2;\n }\n force += dir * factor;\n }\n }\n\n vec2 dir = gravityCenter - n0.xy;\n float d = 1.0;\n if (!strongGravityMode) {\n d = length(dir);\n }\n\n force += dir * n0.z * gravity / (d + 1.0);\n\n gl_FragColor = vec4(force, 0.0, 1.0);\n}\n@end\n\n@export ecgl.forceAtlas2.updateEdgeAttraction.vertex\n\nattribute vec2 node1;\nattribute vec2 node2;\nattribute float weight;\n\nuniform sampler2D positionTex;\nuniform float edgeWeightInfluence;\nuniform bool preventOverlap;\nuniform bool linLogMode;\n\nuniform vec2 windowSize: WINDOW_SIZE;\n\nvarying vec2 v_Force;\n\nvoid main() {\n\n vec4 n0 = texture2D(positionTex, node1);\n vec4 n1 = texture2D(positionTex, node2);\n\n vec2 dir = n1.xy - n0.xy;\n float d = length(dir);\n float w;\n if (edgeWeightInfluence == 0.0) {\n w = 1.0;\n }\n else if (edgeWeightInfluence == 1.0) {\n w = weight;\n }\n else {\n w = pow(weight, edgeWeightInfluence);\n }\n vec2 offset = vec2(1.0 / windowSize.x, 1.0 / windowSize.y);\n vec2 scale = vec2((windowSize.x - 1.0) / windowSize.x, (windowSize.y - 1.0) / windowSize.y);\n vec2 pos = node1 * scale * 2.0 - 1.0;\n gl_Position = vec4(pos + offset, 0.0, 1.0);\n gl_PointSize = 1.0;\n\n float factor;\n if (preventOverlap) {\n d = d - n1.w - n0.w;\n }\n if (d <= 0.0) {\n v_Force = vec2(0.0);\n return;\n }\n\n if (linLogMode) {\n factor = w * log(d) / d;\n }\n else {\n factor = w;\n }\n v_Force = dir * factor;\n}\n@end\n\n@export ecgl.forceAtlas2.updateEdgeAttraction.fragment\n\nvarying vec2 v_Force;\n\nvoid main() {\n gl_FragColor = vec4(v_Force, 0.0, 0.0);\n}\n@end\n\n@export ecgl.forceAtlas2.calcWeightedSum.vertex\n\nattribute vec2 node;\n\nvarying vec2 v_NodeUv;\n\nvoid main() {\n\n v_NodeUv = node;\n gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n gl_PointSize = 1.0;\n}\n@end\n\n@export ecgl.forceAtlas2.calcWeightedSum.fragment\n\nvarying vec2 v_NodeUv;\n\nuniform sampler2D positionTex;\nuniform sampler2D forceTex;\nuniform sampler2D forcePrevTex;\n\nvoid main() {\n vec2 force = texture2D(forceTex, v_NodeUv).rg;\n vec2 forcePrev = texture2D(forcePrevTex, v_NodeUv).rg;\n\n float mass = texture2D(positionTex, v_NodeUv).z;\n float swing = length(force - forcePrev) * mass;\n float traction = length(force + forcePrev) * 0.5 * mass;\n\n gl_FragColor = vec4(swing, traction, 0.0, 0.0);\n}\n@end\n\n@export ecgl.forceAtlas2.calcGlobalSpeed\n\nuniform sampler2D globalSpeedPrevTex;\nuniform sampler2D weightedSumTex;\nuniform float jitterTolerence;\n\nvoid main() {\n vec2 weightedSum = texture2D(weightedSumTex, vec2(0.5)).xy;\n float prevGlobalSpeed = texture2D(globalSpeedPrevTex, vec2(0.5)).x;\n float globalSpeed = jitterTolerence * jitterTolerence\n * weightedSum.y / weightedSum.x;\n if (prevGlobalSpeed > 0.0) {\n globalSpeed = min(globalSpeed / prevGlobalSpeed, 1.5) * prevGlobalSpeed;\n }\n gl_FragColor = vec4(globalSpeed, 0.0, 0.0, 1.0);\n}\n@end\n\n@export ecgl.forceAtlas2.updatePosition\n\nuniform sampler2D forceTex;\nuniform sampler2D forcePrevTex;\nuniform sampler2D positionTex;\nuniform sampler2D globalSpeedTex;\n\nvarying vec2 v_Texcoord;\n\nvoid main() {\n vec2 force = texture2D(forceTex, v_Texcoord).xy;\n vec2 forcePrev = texture2D(forcePrevTex, v_Texcoord).xy;\n vec4 node = texture2D(positionTex, v_Texcoord);\n\n float globalSpeed = texture2D(globalSpeedTex, vec2(0.5)).r;\n float swing = length(force - forcePrev);\n float speed = 0.1 * globalSpeed / (0.1 + globalSpeed * sqrt(swing));\n\n float df = length(force);\n if (df > 0.0) {\n speed = min(df * speed, 10.0) / df;\n\n gl_FragColor = vec4(node.xy + speed * force, node.zw);\n }\n else {\n gl_FragColor = node;\n }\n}\n@end\n\n@export ecgl.forceAtlas2.edges.vertex\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\nattribute vec2 node;\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n\nuniform sampler2D positionTex;\n\nvoid main()\n{\n gl_Position = worldViewProjection * vec4(\n texture2D(positionTex, node).xy, -10.0, 1.0\n );\n v_Color = a_Color;\n}\n@end\n\n@export ecgl.forceAtlas2.edges.fragment\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\nvarying vec4 v_Color;\nvoid main() {\n gl_FragColor = color * v_Color;\n}\n@end"; graphicGL.Shader.import(forceAtlas2Code); var defaultConfigs$1 = { repulsionByDegree: true, linLogMode: false, strongGravityMode: false, gravity: 1, scaling: 1, edgeWeightInfluence: 1, jitterTolerence: 0.1, preventOverlap: false, dissuadeHubs: false, gravityCenter: null }; function ForceAtlas2GPU(options) { var textureOpt = { type: graphicGL.Texture.FLOAT, minFilter: graphicGL.Texture.NEAREST, magFilter: graphicGL.Texture.NEAREST }; this._positionSourceTex = new graphicGL.Texture2D(textureOpt); this._positionSourceTex.flipY = false; this._positionTex = new graphicGL.Texture2D(textureOpt); this._positionPrevTex = new graphicGL.Texture2D(textureOpt); this._forceTex = new graphicGL.Texture2D(textureOpt); this._forcePrevTex = new graphicGL.Texture2D(textureOpt); this._weightedSumTex = new graphicGL.Texture2D(textureOpt); this._weightedSumTex.width = this._weightedSumTex.height = 1; this._globalSpeedTex = new graphicGL.Texture2D(textureOpt); this._globalSpeedPrevTex = new graphicGL.Texture2D(textureOpt); this._globalSpeedTex.width = this._globalSpeedTex.height = 1; this._globalSpeedPrevTex.width = this._globalSpeedPrevTex.height = 1; this._nodeRepulsionPass = new Pass({ fragment: graphicGL.Shader.source("ecgl.forceAtlas2.updateNodeRepulsion") }); this._positionPass = new Pass({ fragment: graphicGL.Shader.source("ecgl.forceAtlas2.updatePosition") }); this._globalSpeedPass = new Pass({ fragment: graphicGL.Shader.source("ecgl.forceAtlas2.calcGlobalSpeed") }); this._copyPass = new Pass({ fragment: graphicGL.Shader.source("clay.compositor.output") }); var additiveBlend = function(gl) { gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.ONE, gl.ONE); }; this._edgeForceMesh = new graphicGL.Mesh({ geometry: new graphicGL.Geometry({ attributes: { node1: new graphicGL.Geometry.Attribute("node1", "float", 2), node2: new graphicGL.Geometry.Attribute("node2", "float", 2), weight: new graphicGL.Geometry.Attribute("weight", "float", 1) }, dynamic: true, mainAttribute: "node1" }), material: new graphicGL.Material({ transparent: true, shader: graphicGL.createShader("ecgl.forceAtlas2.updateEdgeAttraction"), blend: additiveBlend, depthMask: false, depthText: false }), mode: graphicGL.Mesh.POINTS }); this._weightedSumMesh = new graphicGL.Mesh({ geometry: new graphicGL.Geometry({ attributes: { node: new graphicGL.Geometry.Attribute("node", "float", 2) }, dynamic: true, mainAttribute: "node" }), material: new graphicGL.Material({ transparent: true, shader: graphicGL.createShader("ecgl.forceAtlas2.calcWeightedSum"), blend: additiveBlend, depthMask: false, depthText: false }), mode: graphicGL.Mesh.POINTS }); this._framebuffer = new FrameBuffer({ depthBuffer: false }); this._dummyCamera = new graphicGL.OrthographicCamera({ left: -1, right: 1, top: 1, bottom: -1, near: 0, far: 100 }); this._globalSpeed = 0; } ForceAtlas2GPU.prototype.updateOption = function(options) { for (var name in defaultConfigs$1) { this[name] = defaultConfigs$1[name]; } var nNodes = this._nodes.length; if (nNodes > 5e4) { this.jitterTolerence = 10; } else if (nNodes > 5e3) { this.jitterTolerence = 1; } else { this.jitterTolerence = 0.1; } if (nNodes > 100) { this.scaling = 2; } else { this.scaling = 10; } if (options) { for (var name in defaultConfigs$1) { if (options[name] != null) { this[name] = options[name]; } } } if (this.repulsionByDegree) { var positionBuffer = this._positionSourceTex.pixels; for (var i = 0; i < this._nodes.length; i++) { positionBuffer[i * 4 + 2] = (this._nodes[i].degree || 0) + 1; } } }; ForceAtlas2GPU.prototype._updateGravityCenter = function(options) { var nodes = this._nodes; var edges = this._edges; if (!this.gravityCenter) { var min = [Infinity, Infinity]; var max = [-Infinity, -Infinity]; for (var i = 0; i < nodes.length; i++) { min[0] = Math.min(nodes[i].x, min[0]); min[1] = Math.min(nodes[i].y, min[1]); max[0] = Math.max(nodes[i].x, max[0]); max[1] = Math.max(nodes[i].y, max[1]); } this._gravityCenter = [(min[0] + max[0]) * 0.5, (min[1] + max[1]) * 0.5]; } else { this._gravityCenter = this.gravityCenter; } for (var i = 0; i < edges.length; i++) { var node1 = edges[i].node1; var node2 = edges[i].node2; nodes[node1].degree = (nodes[node1].degree || 0) + 1; nodes[node2].degree = (nodes[node2].degree || 0) + 1; } }; ForceAtlas2GPU.prototype.initData = function(nodes, edges) { this._nodes = nodes; this._edges = edges; this._updateGravityCenter(); var textureWidth = Math.ceil(Math.sqrt(nodes.length)); var textureHeight = textureWidth; var positionBuffer = new Float32Array(textureWidth * textureHeight * 4); this._resize(textureWidth, textureHeight); var offset = 0; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; positionBuffer[offset++] = node.x || 0; positionBuffer[offset++] = node.y || 0; positionBuffer[offset++] = node.mass || 1; positionBuffer[offset++] = node.size || 1; } this._positionSourceTex.pixels = positionBuffer; var edgeGeometry = this._edgeForceMesh.geometry; var edgeLen = edges.length; edgeGeometry.attributes.node1.init(edgeLen * 2); edgeGeometry.attributes.node2.init(edgeLen * 2); edgeGeometry.attributes.weight.init(edgeLen * 2); var uv = []; for (var i = 0; i < edges.length; i++) { var attributes = edgeGeometry.attributes; var weight = edges[i].weight; if (weight == null) { weight = 1; } attributes.node1.set(i, this.getNodeUV(edges[i].node1, uv)); attributes.node2.set(i, this.getNodeUV(edges[i].node2, uv)); attributes.weight.set(i, weight); attributes.node1.set(i + edgeLen, this.getNodeUV(edges[i].node2, uv)); attributes.node2.set(i + edgeLen, this.getNodeUV(edges[i].node1, uv)); attributes.weight.set(i + edgeLen, weight); } var weigtedSumGeo = this._weightedSumMesh.geometry; weigtedSumGeo.attributes.node.init(nodes.length); for (var i = 0; i < nodes.length; i++) { weigtedSumGeo.attributes.node.set(i, this.getNodeUV(i, uv)); } edgeGeometry.dirty(); weigtedSumGeo.dirty(); this._nodeRepulsionPass.material.define("fragment", "NODE_COUNT", nodes.length); this._nodeRepulsionPass.material.setUniform("textureSize", [textureWidth, textureHeight]); this._inited = false; this._frame = 0; }; ForceAtlas2GPU.prototype.getNodes = function() { return this._nodes; }; ForceAtlas2GPU.prototype.getEdges = function() { return this._edges; }; ForceAtlas2GPU.prototype.step = function(renderer) { if (!this._inited) { this._initFromSource(renderer); this._inited = true; } this._frame++; this._framebuffer.attach(this._forceTex); this._framebuffer.bind(renderer); var nodeRepulsionPass = this._nodeRepulsionPass; nodeRepulsionPass.setUniform("strongGravityMode", this.strongGravityMode); nodeRepulsionPass.setUniform("gravity", this.gravity); nodeRepulsionPass.setUniform("gravityCenter", this._gravityCenter); nodeRepulsionPass.setUniform("scaling", this.scaling); nodeRepulsionPass.setUniform("preventOverlap", this.preventOverlap); nodeRepulsionPass.setUniform("positionTex", this._positionPrevTex); nodeRepulsionPass.render(renderer); var edgeForceMesh = this._edgeForceMesh; edgeForceMesh.material.set("linLogMode", this.linLogMode); edgeForceMesh.material.set("edgeWeightInfluence", this.edgeWeightInfluence); edgeForceMesh.material.set("preventOverlap", this.preventOverlap); edgeForceMesh.material.set("positionTex", this._positionPrevTex); renderer.gl.enable(renderer.gl.BLEND); renderer.renderPass([edgeForceMesh], this._dummyCamera); this._framebuffer.attach(this._weightedSumTex); renderer.gl.clearColor(0, 0, 0, 0); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); renderer.gl.enable(renderer.gl.BLEND); var weightedSumMesh = this._weightedSumMesh; weightedSumMesh.material.set("positionTex", this._positionPrevTex); weightedSumMesh.material.set("forceTex", this._forceTex); weightedSumMesh.material.set("forcePrevTex", this._forcePrevTex); renderer.renderPass([weightedSumMesh], this._dummyCamera); this._framebuffer.attach(this._globalSpeedTex); var globalSpeedPass = this._globalSpeedPass; globalSpeedPass.setUniform("globalSpeedPrevTex", this._globalSpeedPrevTex); globalSpeedPass.setUniform("weightedSumTex", this._weightedSumTex); globalSpeedPass.setUniform("jitterTolerence", this.jitterTolerence); renderer.gl.disable(renderer.gl.BLEND); globalSpeedPass.render(renderer); var positionPass = this._positionPass; this._framebuffer.attach(this._positionTex); positionPass.setUniform("globalSpeedTex", this._globalSpeedTex); positionPass.setUniform("positionTex", this._positionPrevTex); positionPass.setUniform("forceTex", this._forceTex); positionPass.setUniform("forcePrevTex", this._forcePrevTex); positionPass.render(renderer); this._framebuffer.unbind(renderer); this._swapTexture(); }; ForceAtlas2GPU.prototype.update = function(renderer, steps, cb) { if (steps == null) { steps = 1; } steps = Math.max(steps, 1); for (var i = 0; i < steps; i++) { this.step(renderer); } cb && cb(); }; ForceAtlas2GPU.prototype.getNodePositionTexture = function() { return this._inited ? this._positionPrevTex : this._positionSourceTex; }; ForceAtlas2GPU.prototype.getNodeUV = function(nodeIndex, uv) { uv = uv || []; var textureWidth = this._positionTex.width; var textureHeight = this._positionTex.height; uv[0] = nodeIndex % textureWidth / (textureWidth - 1); uv[1] = Math.floor(nodeIndex / textureWidth) / (textureHeight - 1) || 0; return uv; }; ForceAtlas2GPU.prototype.getNodePosition = function(renderer, out) { var positionArr = this._positionArr; var width = this._positionTex.width; var height = this._positionTex.height; var size = width * height; if (!positionArr || positionArr.length !== size * 4) { positionArr = this._positionArr = new Float32Array(size * 4); } this._framebuffer.bind(renderer); this._framebuffer.attach(this._positionPrevTex); renderer.gl.readPixels(0, 0, width, height, renderer.gl.RGBA, renderer.gl.FLOAT, positionArr); this._framebuffer.unbind(renderer); if (!out) { out = new Float32Array(this._nodes.length * 2); } for (var i = 0; i < this._nodes.length; i++) { out[i * 2] = positionArr[i * 4]; out[i * 2 + 1] = positionArr[i * 4 + 1]; } return out; }; ForceAtlas2GPU.prototype.getTextureData = function(renderer, textureName) { var tex = this["_" + textureName + "Tex"]; var width = tex.width; var height = tex.height; this._framebuffer.bind(renderer); this._framebuffer.attach(tex); var arr = new Float32Array(width * height * 4); renderer.gl.readPixels(0, 0, width, height, renderer.gl.RGBA, renderer.gl.FLOAT, arr); this._framebuffer.unbind(renderer); return arr; }; ForceAtlas2GPU.prototype.getTextureSize = function() { return { width: this._positionTex.width, height: this._positionTex.height }; }; ForceAtlas2GPU.prototype.isFinished = function(maxSteps) { return this._frame > maxSteps; }; ForceAtlas2GPU.prototype._swapTexture = function() { var tmp = this._positionPrevTex; this._positionPrevTex = this._positionTex; this._positionTex = tmp; var tmp = this._forcePrevTex; this._forcePrevTex = this._forceTex; this._forceTex = tmp; var tmp = this._globalSpeedPrevTex; this._globalSpeedPrevTex = this._globalSpeedTex; this._globalSpeedTex = tmp; }; ForceAtlas2GPU.prototype._initFromSource = function(renderer) { this._framebuffer.attach(this._positionPrevTex); this._framebuffer.bind(renderer); this._copyPass.setUniform("texture", this._positionSourceTex); this._copyPass.render(renderer); renderer.gl.clearColor(0, 0, 0, 0); this._framebuffer.attach(this._forcePrevTex); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); this._framebuffer.attach(this._globalSpeedPrevTex); renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT); this._framebuffer.unbind(renderer); }; ForceAtlas2GPU.prototype._resize = function(width, height) { ["_positionSourceTex", "_positionTex", "_positionPrevTex", "_forceTex", "_forcePrevTex"].forEach(function(texName) { this[texName].width = width; this[texName].height = height; this[texName].dirty(); }, this); }; ForceAtlas2GPU.prototype.dispose = function(renderer) { this._framebuffer.dispose(renderer); this._copyPass.dispose(renderer); this._nodeRepulsionPass.dispose(renderer); this._positionPass.dispose(renderer); this._globalSpeedPass.dispose(renderer); this._edgeForceMesh.geometry.dispose(renderer); this._weightedSumMesh.geometry.dispose(renderer); this._positionSourceTex.dispose(renderer); this._positionTex.dispose(renderer); this._positionPrevTex.dispose(renderer); this._forceTex.dispose(renderer); this._forcePrevTex.dispose(renderer); this._weightedSumTex.dispose(renderer); this._globalSpeedTex.dispose(renderer); this._globalSpeedPrevTex.dispose(renderer); }; function forceAtlas2Worker() { var vec22 = { create: function() { return new Float32Array(2); }, dist: function(a, b) { var x = b[0] - a[0]; var y = b[1] - a[1]; return Math.sqrt(x * x + y * y); }, len: function(a) { var x = a[0]; var y = a[1]; return Math.sqrt(x * x + y * y); }, scaleAndAdd: function(out, a, b, scale) { out[0] = a[0] + b[0] * scale; out[1] = a[1] + b[1] * scale; return out; }, scale: function(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; return out; }, add: function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; }, sub: function(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; return out; }, normalize: function(out, a) { var x = a[0]; var y = a[1]; var len = x * x + y * y; if (len > 0) { len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; } return out; }, negate: function(out, a) { out[0] = -a[0]; out[1] = -a[1]; return out; }, copy: function(out, a) { out[0] = a[0]; out[1] = a[1]; return out; }, set: function(out, x, y) { out[0] = x; out[1] = y; return out; } }; function Region() { this.subRegions = []; this.nSubRegions = 0; this.node = null; this.mass = 0; this.centerOfMass = null; this.bbox = new Float32Array(4); this.size = 0; } var regionProto = Region.prototype; regionProto.beforeUpdate = function() { for (var i = 0; i < this.nSubRegions; i++) { this.subRegions[i].beforeUpdate(); } this.mass = 0; if (this.centerOfMass) { this.centerOfMass[0] = 0; this.centerOfMass[1] = 0; } this.nSubRegions = 0; this.node = null; }; regionProto.afterUpdate = function() { this.subRegions.length = this.nSubRegions; for (var i = 0; i < this.nSubRegions; i++) { this.subRegions[i].afterUpdate(); } }; regionProto.addNode = function(node) { if (this.nSubRegions === 0) { if (this.node == null) { this.node = node; return; } else { this._addNodeToSubRegion(this.node); this.node = null; } } this._addNodeToSubRegion(node); this._updateCenterOfMass(node); }; regionProto.findSubRegion = function(x, y) { for (var i = 0; i < this.nSubRegions; i++) { var region = this.subRegions[i]; if (region.contain(x, y)) { return region; } } }; regionProto.contain = function(x, y) { return this.bbox[0] <= x && this.bbox[2] >= x && this.bbox[1] <= y && this.bbox[3] >= y; }; regionProto.setBBox = function(minX, minY, maxX, maxY) { this.bbox[0] = minX; this.bbox[1] = minY; this.bbox[2] = maxX; this.bbox[3] = maxY; this.size = (maxX - minX + maxY - minY) / 2; }; regionProto._newSubRegion = function() { var subRegion = this.subRegions[this.nSubRegions]; if (!subRegion) { subRegion = new Region(); this.subRegions[this.nSubRegions] = subRegion; } this.nSubRegions++; return subRegion; }; regionProto._addNodeToSubRegion = function(node) { var subRegion = this.findSubRegion(node.position[0], node.position[1]); var bbox = this.bbox; if (!subRegion) { var cx = (bbox[0] + bbox[2]) / 2; var cy = (bbox[1] + bbox[3]) / 2; var w = (bbox[2] - bbox[0]) / 2; var h = (bbox[3] - bbox[1]) / 2; var xi = node.position[0] >= cx ? 1 : 0; var yi = node.position[1] >= cy ? 1 : 0; var subRegion = this._newSubRegion(); subRegion.setBBox( // Min xi * w + bbox[0], yi * h + bbox[1], // Max (xi + 1) * w + bbox[0], (yi + 1) * h + bbox[1] ); } subRegion.addNode(node); }; regionProto._updateCenterOfMass = function(node) { if (this.centerOfMass == null) { this.centerOfMass = new Float32Array(2); } var x = this.centerOfMass[0] * this.mass; var y = this.centerOfMass[1] * this.mass; x += node.position[0] * node.mass; y += node.position[1] * node.mass; this.mass += node.mass; this.centerOfMass[0] = x / this.mass; this.centerOfMass[1] = y / this.mass; }; function GraphNode() { this.position = new Float32Array(2); this.force = vec22.create(); this.forcePrev = vec22.create(); this.mass = 1; this.inDegree = 0; this.outDegree = 0; } function GraphEdge(source, target) { this.source = source; this.target = target; this.weight = 1; } function ForceAtlas22() { this.autoSettings = true; this.barnesHutOptimize = true; this.barnesHutTheta = 1.5; this.repulsionByDegree = true; this.linLogMode = false; this.strongGravityMode = false; this.gravity = 1; this.scaling = 1; this.edgeWeightInfluence = 1; this.jitterTolerence = 0.1; this.preventOverlap = false; this.dissuadeHubs = false; this.rootRegion = new Region(); this.rootRegion.centerOfMass = vec22.create(); this.nodes = []; this.edges = []; this.bbox = new Float32Array(4); this.gravityCenter = null; this._massArr = null; this._swingingArr = null; this._sizeArr = null; this._globalSpeed = 0; } var forceAtlas2Proto = ForceAtlas22.prototype; forceAtlas2Proto.initNodes = function(positionArr, massArr, sizeArr) { var nNodes = massArr.length; this.nodes.length = 0; var haveSize = typeof sizeArr != "undefined"; for (var i = 0; i < nNodes; i++) { var node = new GraphNode(); node.position[0] = positionArr[i * 2]; node.position[1] = positionArr[i * 2 + 1]; node.mass = massArr[i]; if (haveSize) { node.size = sizeArr[i]; } this.nodes.push(node); } this._massArr = massArr; this._swingingArr = new Float32Array(nNodes); if (haveSize) { this._sizeArr = sizeArr; } }; forceAtlas2Proto.initEdges = function(edgeArr, edgeWeightArr) { var nEdges = edgeArr.length / 2; this.edges.length = 0; for (var i = 0; i < nEdges; i++) { var sIdx = edgeArr[i * 2]; var tIdx = edgeArr[i * 2 + 1]; var sNode = this.nodes[sIdx]; var tNode = this.nodes[tIdx]; if (!sNode || !tNode) { console.error("Node not exists, try initNodes before initEdges"); return; } sNode.outDegree++; tNode.inDegree++; var edge = new GraphEdge(sNode, tNode); if (edgeWeightArr) { edge.weight = edgeWeightArr[i]; } this.edges.push(edge); } }; forceAtlas2Proto.updateSettings = function() { if (this.repulsionByDegree) { for (var i = 0; i < this.nodes.length; i++) { var node = this.nodes[i]; node.mass = node.inDegree + node.outDegree + 1; } } else { for (var i = 0; i < this.nodes.length; i++) { var node = this.nodes[i]; node.mass = this._massArr[i]; } } }; forceAtlas2Proto.update = function() { var nNodes = this.nodes.length; this.updateSettings(); this.updateBBox(); if (this.barnesHutOptimize) { this.rootRegion.setBBox(this.bbox[0], this.bbox[1], this.bbox[2], this.bbox[3]); this.rootRegion.beforeUpdate(); for (var i = 0; i < nNodes; i++) { this.rootRegion.addNode(this.nodes[i]); } this.rootRegion.afterUpdate(); } for (var i = 0; i < nNodes; i++) { var node = this.nodes[i]; vec22.copy(node.forcePrev, node.force); vec22.set(node.force, 0, 0); } for (var i = 0; i < nNodes; i++) { var na = this.nodes[i]; if (this.barnesHutOptimize) { this.applyRegionToNodeRepulsion(this.rootRegion, na); } else { for (var j = i + 1; j < nNodes; j++) { var nb = this.nodes[j]; this.applyNodeToNodeRepulsion(na, nb, false); } } if (this.gravity > 0) { if (this.strongGravityMode) { this.applyNodeStrongGravity(na); } else { this.applyNodeGravity(na); } } } for (var i = 0; i < this.edges.length; i++) { this.applyEdgeAttraction(this.edges[i]); } var swingWeightedSum = 0; var tractionWeightedSum = 0; var tmp = vec22.create(); for (var i = 0; i < nNodes; i++) { var node = this.nodes[i]; var swing = vec22.dist(node.force, node.forcePrev); swingWeightedSum += swing * node.mass; vec22.add(tmp, node.force, node.forcePrev); var traction = vec22.len(tmp) * 0.5; tractionWeightedSum += traction * node.mass; this._swingingArr[i] = swing; } var globalSpeed = this.jitterTolerence * this.jitterTolerence * tractionWeightedSum / swingWeightedSum; if (this._globalSpeed > 0) { globalSpeed = Math.min(globalSpeed / this._globalSpeed, 1.5) * this._globalSpeed; } this._globalSpeed = globalSpeed; for (var i = 0; i < nNodes; i++) { var node = this.nodes[i]; var swing = this._swingingArr[i]; var speed = 0.1 * globalSpeed / (1 + globalSpeed * Math.sqrt(swing)); var df = vec22.len(node.force); if (df > 0) { speed = Math.min(df * speed, 10) / df; vec22.scaleAndAdd(node.position, node.position, node.force, speed); } } }; forceAtlas2Proto.applyRegionToNodeRepulsion = function() { var v = vec22.create(); return function applyRegionToNodeRepulsion(region, node) { if (region.node) { this.applyNodeToNodeRepulsion(region.node, node, true); } else { vec22.sub(v, node.position, region.centerOfMass); var d2 = v[0] * v[0] + v[1] * v[1]; if (d2 > this.barnesHutTheta * region.size * region.size) { var factor = this.scaling * node.mass * region.mass / d2; vec22.scaleAndAdd(node.force, node.force, v, factor); } else { for (var i = 0; i < region.nSubRegions; i++) { this.applyRegionToNodeRepulsion(region.subRegions[i], node); } } } }; }(); forceAtlas2Proto.applyNodeToNodeRepulsion = function() { var v = vec22.create(); return function applyNodeToNodeRepulsion(na, nb, oneWay) { if (na == nb) { return; } vec22.sub(v, na.position, nb.position); var d2 = v[0] * v[0] + v[1] * v[1]; if (d2 === 0) { return; } var factor; if (this.preventOverlap) { var d = Math.sqrt(d2); d = d - na.size - nb.size; if (d > 0) { factor = this.scaling * na.mass * nb.mass / (d * d); } else if (d < 0) { factor = this.scaling * 100 * na.mass * nb.mass; } else { return; } } else { factor = this.scaling * na.mass * nb.mass / d2; } vec22.scaleAndAdd(na.force, na.force, v, factor); vec22.scaleAndAdd(nb.force, nb.force, v, -factor); }; }(); forceAtlas2Proto.applyEdgeAttraction = function() { var v = vec22.create(); return function applyEdgeAttraction(edge) { var na = edge.source; var nb = edge.target; vec22.sub(v, na.position, nb.position); var d = vec22.len(v); var w; if (this.edgeWeightInfluence === 0) { w = 1; } else if (this.edgeWeightInfluence === 1) { w = edge.weight; } else { w = Math.pow(edge.weight, this.edgeWeightInfluence); } var factor; if (this.preventOverlap) { d = d - na.size - nb.size; if (d <= 0) { return; } } if (this.linLogMode) { factor = -w * Math.log(d + 1) / (d + 1); } else { factor = -w; } vec22.scaleAndAdd(na.force, na.force, v, factor); vec22.scaleAndAdd(nb.force, nb.force, v, -factor); }; }(); forceAtlas2Proto.applyNodeGravity = function() { var v = vec22.create(); return function(node) { vec22.sub(v, this.gravityCenter, node.position); var d = vec22.len(v); vec22.scaleAndAdd(node.force, node.force, v, this.gravity * node.mass / (d + 1)); }; }(); forceAtlas2Proto.applyNodeStrongGravity = function() { var v = vec22.create(); return function(node) { vec22.sub(v, this.gravityCenter, node.position); vec22.scaleAndAdd(node.force, node.force, v, this.gravity * node.mass); }; }(); forceAtlas2Proto.updateBBox = function() { var minX = Infinity; var minY = Infinity; var maxX = -Infinity; var maxY = -Infinity; for (var i = 0; i < this.nodes.length; i++) { var pos = this.nodes[i].position; minX = Math.min(minX, pos[0]); minY = Math.min(minY, pos[1]); maxX = Math.max(maxX, pos[0]); maxY = Math.max(maxY, pos[1]); } this.bbox[0] = minX; this.bbox[1] = minY; this.bbox[2] = maxX; this.bbox[3] = maxY; }; forceAtlas2Proto.getGlobalSpeed = function() { return this._globalSpeed; }; var forceAtlas2 = null; self.onmessage = function(e2) { switch (e2.data.cmd) { case "init": forceAtlas2 = new ForceAtlas22(); forceAtlas2.initNodes(e2.data.nodesPosition, e2.data.nodesMass, e2.data.nodesSize); forceAtlas2.initEdges(e2.data.edges, e2.data.edgesWeight); break; case "updateConfig": if (forceAtlas2) { for (var name in e2.data.config) { forceAtlas2[name] = e2.data.config[name]; } } break; case "update": var steps = e2.data.steps; if (forceAtlas2) { for (var i = 0; i < steps; i++) { forceAtlas2.update(); } var nNodes = forceAtlas2.nodes.length; var positionArr = new Float32Array(nNodes * 2); for (var i = 0; i < nNodes; i++) { var node = forceAtlas2.nodes[i]; positionArr[i * 2] = node.position[0]; positionArr[i * 2 + 1] = node.position[1]; } self.postMessage({ buffer: positionArr.buffer, globalSpeed: forceAtlas2.getGlobalSpeed() }, [positionArr.buffer]); } else { var emptyArr = new Float32Array(); self.postMessage({ buffer: emptyArr.buffer, globalSpeed: forceAtlas2.getGlobalSpeed() }, [emptyArr.buffer]); } break; } }; } var workerUrl = forceAtlas2Worker.toString(); workerUrl = workerUrl.slice(workerUrl.indexOf("{") + 1, workerUrl.lastIndexOf("}")); var defaultConfigs = { barnesHutOptimize: true, barnesHutTheta: 1.5, repulsionByDegree: true, linLogMode: false, strongGravityMode: false, gravity: 1, scaling: 1, edgeWeightInfluence: 1, jitterTolerence: 0.1, preventOverlap: false, dissuadeHubs: false, gravityCenter: null }; var ForceAtlas2 = function(options) { for (var name in defaultConfigs) { this[name] = defaultConfigs[name]; } if (options) { for (var name in options) { this[name] = options[name]; } } this._nodes = []; this._edges = []; this._disposed = false; this._positionTex = new Texture2D({ type: Texture.FLOAT, flipY: false, minFilter: Texture.NEAREST, magFilter: Texture.NEAREST }); }; ForceAtlas2.prototype.initData = function(nodes, edges) { var bb = new Blob([workerUrl]); var blobURL = window.URL.createObjectURL(bb); this._worker = new Worker(blobURL); this._worker.onmessage = this._$onupdate.bind(this); this._nodes = nodes; this._edges = edges; this._frame = 0; var nNodes = nodes.length; var nEdges = edges.length; var positionArr = new Float32Array(nNodes * 2); var massArr = new Float32Array(nNodes); var sizeArr = new Float32Array(nNodes); var edgeArr = new Float32Array(nEdges * 2); var edgeWeightArr = new Float32Array(nEdges); for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; positionArr[i * 2] = node.x; positionArr[i * 2 + 1] = node.y; massArr[i] = node.mass == null ? 1 : node.mass; sizeArr[i] = node.size == null ? 1 : node.size; } for (var i = 0; i < edges.length; i++) { var edge = edges[i]; var source = edge.node1; var target = edge.node2; edgeArr[i * 2] = source; edgeArr[i * 2 + 1] = target; edgeWeightArr[i] = edge.weight == null ? 1 : edge.weight; } var textureWidth = Math.ceil(Math.sqrt(nodes.length)); var textureHeight = textureWidth; var pixels = new Float32Array(textureWidth * textureHeight * 4); var positionTex = this._positionTex; positionTex.width = textureWidth; positionTex.height = textureHeight; positionTex.pixels = pixels; this._worker.postMessage({ cmd: "init", nodesPosition: positionArr, nodesMass: massArr, nodesSize: sizeArr, edges: edgeArr, edgesWeight: edgeWeightArr }); this._globalSpeed = Infinity; }; ForceAtlas2.prototype.updateOption = function(options) { var config = {}; for (var name in defaultConfigs) { config[name] = defaultConfigs[name]; } var nodes = this._nodes; var edges = this._edges; var nNodes = nodes.length; if (nNodes > 5e4) { config.jitterTolerence = 10; } else if (nNodes > 5e3) { config.jitterTolerence = 1; } else { config.jitterTolerence = 0.1; } if (nNodes > 100) { config.scaling = 2; } else { config.scaling = 10; } if (nNodes > 1e3) { config.barnesHutOptimize = true; } else { config.barnesHutOptimize = false; } if (options) { for (var name in defaultConfigs) { if (options[name] != null) { config[name] = options[name]; } } } if (!config.gravityCenter) { var min = [Infinity, Infinity]; var max = [-Infinity, -Infinity]; for (var i = 0; i < nodes.length; i++) { min[0] = Math.min(nodes[i].x, min[0]); min[1] = Math.min(nodes[i].y, min[1]); max[0] = Math.max(nodes[i].x, max[0]); max[1] = Math.max(nodes[i].y, max[1]); } config.gravityCenter = [(min[0] + max[0]) * 0.5, (min[1] + max[1]) * 0.5]; } for (var i = 0; i < edges.length; i++) { var node1 = edges[i].node1; var node2 = edges[i].node2; nodes[node1].degree = (nodes[node1].degree || 0) + 1; nodes[node2].degree = (nodes[node2].degree || 0) + 1; } if (this._worker) { this._worker.postMessage({ cmd: "updateConfig", config }); } }; ForceAtlas2.prototype.update = function(renderer, steps, cb) { if (steps == null) { steps = 1; } steps = Math.max(steps, 1); this._frame += steps; this._onupdate = cb; if (this._worker) { this._worker.postMessage({ cmd: "update", steps: Math.round(steps) }); } }; ForceAtlas2.prototype._$onupdate = function(e2) { if (this._disposed) { return; } var positionArr = new Float32Array(e2.data.buffer); this._globalSpeed = e2.data.globalSpeed; this._positionArr = positionArr; this._updateTexture(positionArr); this._onupdate && this._onupdate(); }; ForceAtlas2.prototype.getNodePositionTexture = function() { return this._positionTex; }; ForceAtlas2.prototype.getNodeUV = function(nodeIndex, uv) { uv = uv || []; var textureWidth = this._positionTex.width; var textureHeight = this._positionTex.height; uv[0] = nodeIndex % textureWidth / (textureWidth - 1); uv[1] = Math.floor(nodeIndex / textureWidth) / (textureHeight - 1); return uv; }; ForceAtlas2.prototype.getNodes = function() { return this._nodes; }; ForceAtlas2.prototype.getEdges = function() { return this._edges; }; ForceAtlas2.prototype.isFinished = function(maxSteps) { return this._frame > maxSteps; }; ForceAtlas2.prototype.getNodePosition = function(renderer, out) { if (!out) { out = new Float32Array(this._nodes.length * 2); } if (this._positionArr) { for (var i = 0; i < this._positionArr.length; i++) { out[i] = this._positionArr[i]; } } return out; }; ForceAtlas2.prototype._updateTexture = function(positionArr) { var pixels = this._positionTex.pixels; var offset = 0; for (var i = 0; i < positionArr.length; ) { pixels[offset++] = positionArr[i++]; pixels[offset++] = positionArr[i++]; pixels[offset++] = 1; pixels[offset++] = 1; } this._positionTex.dirty(); }; ForceAtlas2.prototype.dispose = function(renderer) { this._disposed = true; this._worker = null; }; var Roam2DControl = Base.extend(function() { return { /** * @type {module:zrender~ZRender} */ zr: null, /** * @type {module:echarts-gl/core/ViewGL} */ viewGL: null, minZoom: 0.2, maxZoom: 5, _needsUpdate: false, _dx: 0, _dy: 0, _zoom: 1 }; }, function() { this._mouseDownHandler = this._mouseDownHandler.bind(this); this._mouseWheelHandler = this._mouseWheelHandler.bind(this); this._mouseMoveHandler = this._mouseMoveHandler.bind(this); this._mouseUpHandler = this._mouseUpHandler.bind(this); this._update = this._update.bind(this); }, { init: function() { var zr = this.zr; zr.on("mousedown", this._mouseDownHandler); zr.on("mousewheel", this._mouseWheelHandler); zr.on("globalout", this._mouseUpHandler); zr.animation.on("frame", this._update); }, setTarget: function(target) { this._target = target; }, setZoom: function(zoom) { this._zoom = Math.max(Math.min(zoom, this.maxZoom), this.minZoom); this._needsUpdate = true; }, setOffset: function(offset) { this._dx = offset[0]; this._dy = offset[1]; this._needsUpdate = true; }, getZoom: function() { return this._zoom; }, getOffset: function() { return [this._dx, this._dy]; }, _update: function() { if (!this._target) { return; } if (!this._needsUpdate) { return; } var target = this._target; var scale = this._zoom; target.position.x = this._dx; target.position.y = this._dy; target.scale.set(scale, scale, scale); this.zr.refresh(); this._needsUpdate = false; this.trigger("update"); }, _mouseDownHandler: function(e2) { if (e2.target) { return; } var x = e2.offsetX; var y = e2.offsetY; if (this.viewGL && !this.viewGL.containPoint(x, y)) { return; } this.zr.on("mousemove", this._mouseMoveHandler); this.zr.on("mouseup", this._mouseUpHandler); var pos = this._convertPos(x, y); this._x = pos.x; this._y = pos.y; }, // Convert pos from screen space to viewspace. _convertPos: function(x, y) { var camera2 = this.viewGL.camera; var viewport = this.viewGL.viewport; return { x: (x - viewport.x) / viewport.width * (camera2.right - camera2.left) + camera2.left, y: (y - viewport.y) / viewport.height * (camera2.bottom - camera2.top) + camera2.top }; }, _mouseMoveHandler: function(e2) { var pos = this._convertPos(e2.offsetX, e2.offsetY); this._dx += pos.x - this._x; this._dy += pos.y - this._y; this._x = pos.x; this._y = pos.y; this._needsUpdate = true; }, _mouseUpHandler: function(e2) { this.zr.off("mousemove", this._mouseMoveHandler); this.zr.off("mouseup", this._mouseUpHandler); }, _mouseWheelHandler: function(e2) { e2 = e2.event; var delta = e2.wheelDelta || -e2.detail; if (delta === 0) { return; } var x = e2.offsetX; var y = e2.offsetY; if (this.viewGL && !this.viewGL.containPoint(x, y)) { return; } var zoomScale = delta > 0 ? 1.1 : 0.9; var newZoom = Math.max(Math.min(this._zoom * zoomScale, this.maxZoom), this.minZoom); zoomScale = newZoom / this._zoom; var pos = this._convertPos(x, y); var fixX = (pos.x - this._dx) * (zoomScale - 1); var fixY = (pos.y - this._dy) * (zoomScale - 1); this._dx -= fixX; this._dy -= fixY; this._zoom = newZoom; this._needsUpdate = true; }, dispose: function() { var zr = this.zr; zr.off("mousedown", this._mouseDownHandler); zr.off("mousemove", this._mouseMoveHandler); zr.off("mouseup", this._mouseUpHandler); zr.off("mousewheel", this._mouseWheelHandler); zr.off("globalout", this._mouseUpHandler); zr.animation.off("frame", this._update); } }); const lines2DGLSL = "@export ecgl.lines2D.vertex\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\nattribute vec2 position: POSITION;\nattribute vec4 a_Color : COLOR;\nvarying vec4 v_Color;\n\n#ifdef POSITIONTEXTURE_ENABLED\nuniform sampler2D positionTexture;\n#endif\n\nvoid main()\n{\n gl_Position = worldViewProjection * vec4(position, -10.0, 1.0);\n\n v_Color = a_Color;\n}\n\n@end\n\n@export ecgl.lines2D.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nvarying vec4 v_Color;\n\nvoid main()\n{\n gl_FragColor = color * v_Color;\n}\n@end\n\n\n@export ecgl.meshLines2D.vertex\n\nattribute vec2 position: POSITION;\nattribute vec2 normal;\nattribute float offset;\nattribute vec4 a_Color : COLOR;\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform vec4 viewport : VIEWPORT;\n\nvarying vec4 v_Color;\nvarying float v_Miter;\n\nvoid main()\n{\n vec4 p2 = worldViewProjection * vec4(position + normal, -10.0, 1.0);\n gl_Position = worldViewProjection * vec4(position, -10.0, 1.0);\n\n p2.xy /= p2.w;\n gl_Position.xy /= gl_Position.w;\n\n vec2 N = normalize(p2.xy - gl_Position.xy);\n gl_Position.xy += N * offset / viewport.zw * 2.0;\n\n gl_Position.xy *= gl_Position.w;\n\n v_Color = a_Color;\n}\n@end\n\n\n@export ecgl.meshLines2D.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nvarying vec4 v_Color;\nvarying float v_Miter;\n\nvoid main()\n{\n gl_FragColor = color * v_Color;\n}\n\n@end"; var vec2 = glmatrix.vec2; graphicGL.Shader.import(lines2DGLSL); var globalLayoutId = 1; const GraphGLView = ChartView.extend({ type: "graphGL", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this.viewGL = new ViewGL("orthographic"); this.viewGL.camera.left = this.viewGL.camera.right = 0; this.viewGL.add(this.groupGL); this._pointsBuilder = new PointsBuilder(true, api); this._forceEdgesMesh = new graphicGL.Mesh({ material: new graphicGL.Material({ shader: graphicGL.createShader("ecgl.forceAtlas2.edges"), transparent: true, depthMask: false, depthTest: false }), $ignorePicking: true, geometry: new graphicGL.Geometry({ attributes: { node: new graphicGL.Geometry.Attribute("node", "float", 2), color: new graphicGL.Geometry.Attribute("color", "float", 4, "COLOR") }, dynamic: true, mainAttribute: "node" }), renderOrder: -1, mode: graphicGL.Mesh.LINES }); this._edgesMesh = new graphicGL.Mesh({ material: new graphicGL.Material({ shader: graphicGL.createShader("ecgl.meshLines2D"), transparent: true, depthMask: false, depthTest: false }), $ignorePicking: true, geometry: new LinesGeometry$1({ useNativeLine: false, dynamic: true }), renderOrder: -1, culling: false }); this._layoutId = 0; this._control = new Roam2DControl({ zr: api.getZr(), viewGL: this.viewGL }); this._control.setTarget(this.groupGL); this._control.init(); this._clickHandler = this._clickHandler.bind(this); }, render: function(seriesModel, ecModel, api) { this.groupGL.add(this._pointsBuilder.rootNode); this._model = seriesModel; this._api = api; this._initLayout(seriesModel, ecModel, api); this._pointsBuilder.update(seriesModel, ecModel, api); if (!(this._forceLayoutInstance instanceof ForceAtlas2GPU)) { this.groupGL.remove(this._forceEdgesMesh); } this._updateCamera(seriesModel, api); this._control.off("update"); this._control.on("update", function() { api.dispatchAction({ type: "graphGLRoam", seriesId: seriesModel.id, zoom: this._control.getZoom(), offset: this._control.getOffset() }); this._pointsBuilder.updateView(this.viewGL.camera); }, this); this._control.setZoom(retrieve.firstNotNull(seriesModel.get("zoom"), 1)); this._control.setOffset(seriesModel.get("offset") || [0, 0]); var mesh2 = this._pointsBuilder.getPointsMesh(); mesh2.off("mousemove", this._mousemoveHandler); mesh2.off("mouseout", this._mouseOutHandler, this); api.getZr().off("click", this._clickHandler); this._pointsBuilder.highlightOnMouseover = true; if (seriesModel.get("focusNodeAdjacency")) { var focusNodeAdjacencyOn = seriesModel.get("focusNodeAdjacencyOn"); if (focusNodeAdjacencyOn === "click") { api.getZr().on("click", this._clickHandler); } else if (focusNodeAdjacencyOn === "mouseover") { mesh2.on("mousemove", this._mousemoveHandler, this); mesh2.on("mouseout", this._mouseOutHandler, this); this._pointsBuilder.highlightOnMouseover = false; } else ; } this._lastMouseOverDataIndex = -1; }, _clickHandler: function(e2) { if (this._layouting) { return; } var dataIndex = this._pointsBuilder.getPointsMesh().dataIndex; if (dataIndex >= 0) { this._api.dispatchAction({ type: "graphGLFocusNodeAdjacency", seriesId: this._model.id, dataIndex }); } else { this._api.dispatchAction({ type: "graphGLUnfocusNodeAdjacency", seriesId: this._model.id }); } }, _mousemoveHandler: function(e2) { if (this._layouting) { return; } var dataIndex = this._pointsBuilder.getPointsMesh().dataIndex; if (dataIndex >= 0) { if (dataIndex !== this._lastMouseOverDataIndex) { this._api.dispatchAction({ type: "graphGLFocusNodeAdjacency", seriesId: this._model.id, dataIndex }); } } else { this._mouseOutHandler(e2); } this._lastMouseOverDataIndex = dataIndex; }, _mouseOutHandler: function(e2) { if (this._layouting) { return; } this._api.dispatchAction({ type: "graphGLUnfocusNodeAdjacency", seriesId: this._model.id }); this._lastMouseOverDataIndex = -1; }, _updateForceEdgesGeometry: function(edges, seriesModel) { var geometry = this._forceEdgesMesh.geometry; var edgeData = seriesModel.getEdgeData(); var offset = 0; var layoutInstance = this._forceLayoutInstance; var vertexCount = edgeData.count() * 2; geometry.attributes.node.init(vertexCount); geometry.attributes.color.init(vertexCount); edgeData.each(function(idx) { var edge = edges[idx]; geometry.attributes.node.set(offset, layoutInstance.getNodeUV(edge.node1)); geometry.attributes.node.set(offset + 1, layoutInstance.getNodeUV(edge.node2)); var color = getItemVisualColor(edgeData, edge.dataIndex); var colorArr = graphicGL.parseColor(color); colorArr[3] *= retrieve.firstNotNull(getItemVisualOpacity(edgeData, edge.dataIndex), 1); geometry.attributes.color.set(offset, colorArr); geometry.attributes.color.set(offset + 1, colorArr); offset += 2; }); geometry.dirty(); }, _updateMeshLinesGeometry: function() { var edgeData = this._model.getEdgeData(); var geometry = this._edgesMesh.geometry; var edgeData = this._model.getEdgeData(); var points = this._model.getData().getLayout("points"); geometry.resetOffset(); geometry.setVertexCount(edgeData.count() * geometry.getLineVertexCount()); geometry.setTriangleCount(edgeData.count() * geometry.getLineTriangleCount()); var p02 = []; var p12 = []; var lineWidthQuery = ["lineStyle", "width"]; this._originalEdgeColors = new Float32Array(edgeData.count() * 4); this._edgeIndicesMap = new Float32Array(edgeData.count()); edgeData.each(function(idx) { var edge = edgeData.graph.getEdgeByIndex(idx); var idx1 = edge.node1.dataIndex * 2; var idx2 = edge.node2.dataIndex * 2; p02[0] = points[idx1]; p02[1] = points[idx1 + 1]; p12[0] = points[idx2]; p12[1] = points[idx2 + 1]; var color = getItemVisualColor(edgeData, edge.dataIndex); var colorArr = graphicGL.parseColor(color); colorArr[3] *= retrieve.firstNotNull(getItemVisualOpacity(edgeData, edge.dataIndex), 1); var itemModel = edgeData.getItemModel(edge.dataIndex); var lineWidth = retrieve.firstNotNull(itemModel.get(lineWidthQuery), 1) * this._api.getDevicePixelRatio(); geometry.addLine(p02, p12, colorArr, lineWidth); for (var k = 0; k < 4; k++) { this._originalEdgeColors[edge.dataIndex * 4 + k] = colorArr[k]; } this._edgeIndicesMap[edge.dataIndex] = idx; }, this); geometry.dirty(); }, _updateForceNodesGeometry: function(nodeData) { var pointsMesh = this._pointsBuilder.getPointsMesh(); var pos = []; for (var i = 0; i < nodeData.count(); i++) { this._forceLayoutInstance.getNodeUV(i, pos); pointsMesh.geometry.attributes.position.set(i, pos); } pointsMesh.geometry.dirty("position"); }, _initLayout: function(seriesModel, ecModel, api) { var layout = seriesModel.get("layout"); var graph = seriesModel.getGraph(); var boxLayoutOption = seriesModel.getBoxLayoutParams(); var viewport = getLayoutRect(boxLayoutOption, { width: api.getWidth(), height: api.getHeight() }); if (layout === "force") { layout = "forceAtlas2"; } this.stopLayout(seriesModel, ecModel, api, { beforeLayout: true }); var nodeData = seriesModel.getData(); var edgeData = seriesModel.getData(); if (layout === "forceAtlas2") { var layoutModel = seriesModel.getModel("forceAtlas2"); var layoutInstance = this._forceLayoutInstance; var nodes = []; var edges = []; var nodeDataExtent = nodeData.getDataExtent("value"); var edgeDataExtent = edgeData.getDataExtent("value"); var edgeWeightRange = retrieve.firstNotNull(layoutModel.get("edgeWeight"), 1); var nodeWeightRange = retrieve.firstNotNull(layoutModel.get("nodeWeight"), 1); if (typeof edgeWeightRange === "number") { edgeWeightRange = [edgeWeightRange, edgeWeightRange]; } if (typeof nodeWeightRange === "number") { nodeWeightRange = [nodeWeightRange, nodeWeightRange]; } var offset = 0; var nodesIndicesMap = {}; var layoutPoints = new Float32Array(nodeData.count() * 2); graph.eachNode(function(node) { var dataIndex = node.dataIndex; var value = nodeData.get("value", dataIndex); var x; var y; if (nodeData.hasItemOption) { var itemModel = nodeData.getItemModel(dataIndex); x = itemModel.get("x"); y = itemModel.get("y"); } if (x == null) { x = viewport.x + Math.random() * viewport.width; y = viewport.y + Math.random() * viewport.height; } layoutPoints[offset * 2] = x; layoutPoints[offset * 2 + 1] = y; nodesIndicesMap[node.id] = offset++; var mass = linearMap(value, nodeDataExtent, nodeWeightRange); if (isNaN(mass)) { if (!isNaN(nodeWeightRange[0])) { mass = nodeWeightRange[0]; } else { mass = 1; } } nodes.push({ x, y, mass, size: nodeData.getItemVisual(dataIndex, "symbolSize") }); }); nodeData.setLayout("points", layoutPoints); graph.eachEdge(function(edge) { var dataIndex = edge.dataIndex; var value = nodeData.get("value", dataIndex); var weight = linearMap(value, edgeDataExtent, edgeWeightRange); if (isNaN(weight)) { if (!isNaN(edgeWeightRange[0])) { weight = edgeWeightRange[0]; } else { weight = 1; } } edges.push({ node1: nodesIndicesMap[edge.node1.id], node2: nodesIndicesMap[edge.node2.id], weight, dataIndex }); }); if (!layoutInstance) { var isGPU = layoutModel.get("GPU"); if (this._forceLayoutInstance) { if (isGPU && !(this._forceLayoutInstance instanceof ForceAtlas2GPU) || !isGPU && !(this._forceLayoutInstance instanceof ForceAtlas2)) { this._forceLayoutInstanceToDispose = this._forceLayoutInstance; } } layoutInstance = this._forceLayoutInstance = isGPU ? new ForceAtlas2GPU() : new ForceAtlas2(); } layoutInstance.initData(nodes, edges); layoutInstance.updateOption(layoutModel.option); this._updateForceEdgesGeometry(layoutInstance.getEdges(), seriesModel); this._updatePositionTexture(); api.dispatchAction({ type: "graphGLStartLayout", from: this.uid }); } else { var layoutPoints = new Float32Array(nodeData.count() * 2); var offset = 0; graph.eachNode(function(node) { var dataIndex = node.dataIndex; var x; var y; if (nodeData.hasItemOption) { var itemModel = nodeData.getItemModel(dataIndex); x = itemModel.get("x"); y = itemModel.get("y"); } layoutPoints[offset++] = x; layoutPoints[offset++] = y; }); nodeData.setLayout("points", layoutPoints); this._updateAfterLayout(seriesModel, ecModel, api); } }, _updatePositionTexture: function() { var positionTex = this._forceLayoutInstance.getNodePositionTexture(); this._pointsBuilder.setPositionTexture(positionTex); this._forceEdgesMesh.material.set("positionTex", positionTex); }, startLayout: function(seriesModel, ecModel, api, payload) { if (payload && payload.from != null && payload.from !== this.uid) { return; } var viewGL = this.viewGL; var api = this._api; var layoutInstance = this._forceLayoutInstance; var data = this._model.getData(); var layoutModel = this._model.getModel("forceAtlas2"); if (!layoutInstance) { return; } this.groupGL.remove(this._edgesMesh); this.groupGL.add(this._forceEdgesMesh); if (!this._forceLayoutInstance) { return; } this._updateForceNodesGeometry(seriesModel.getData()); this._pointsBuilder.hideLabels(); var self2 = this; var layoutId = this._layoutId = globalLayoutId++; var maxSteps = layoutModel.getShallow("maxSteps"); var steps = layoutModel.getShallow("steps"); var stepsCount = 0; var syncStepCount = Math.max(steps * 2, 20); var doLayout = function(layoutId2) { if (layoutId2 !== self2._layoutId) { return; } if (layoutInstance.isFinished(maxSteps)) { api.dispatchAction({ type: "graphGLStopLayout", from: self2.uid }); api.dispatchAction({ type: "graphGLFinishLayout", points: data.getLayout("points"), from: self2.uid }); return; } layoutInstance.update(viewGL.layer.renderer, steps, function() { self2._updatePositionTexture(); stepsCount += steps; if (stepsCount >= syncStepCount) { self2._syncNodePosition(seriesModel); stepsCount = 0; } api.getZr().refresh(); requestAnimationFrame(function() { doLayout(layoutId2); }); }); }; requestAnimationFrame(function() { if (self2._forceLayoutInstanceToDispose) { self2._forceLayoutInstanceToDispose.dispose(viewGL.layer.renderer); self2._forceLayoutInstanceToDispose = null; } doLayout(layoutId); }); this._layouting = true; }, stopLayout: function(seriesModel, ecModel, api, payload) { if (payload && payload.from != null && payload.from !== this.uid) { return; } this._layoutId = 0; this.groupGL.remove(this._forceEdgesMesh); this.groupGL.add(this._edgesMesh); if (!this._forceLayoutInstance) { return; } if (!this.viewGL.layer) { return; } if (!(payload && payload.beforeLayout)) { this._syncNodePosition(seriesModel); this._updateAfterLayout(seriesModel, ecModel, api); } this._api.getZr().refresh(); this._layouting = false; }, _syncNodePosition: function(seriesModel) { var points = this._forceLayoutInstance.getNodePosition(this.viewGL.layer.renderer); seriesModel.getData().setLayout("points", points); seriesModel.setNodePosition(points); }, _updateAfterLayout: function(seriesModel, ecModel, api) { this._updateMeshLinesGeometry(); this._pointsBuilder.removePositionTexture(); this._pointsBuilder.updateLayout(seriesModel, ecModel, api); this._pointsBuilder.updateView(this.viewGL.camera); this._pointsBuilder.updateLabels(); this._pointsBuilder.showLabels(); }, focusNodeAdjacency: function(seriesModel, ecModel, api, payload) { var data = this._model.getData(); this._downplayAll(); var dataIndex = payload.dataIndex; var graph = data.graph; var focusNodes = []; var node = graph.getNodeByIndex(dataIndex); focusNodes.push(node); node.edges.forEach(function(edge) { if (edge.dataIndex < 0) { return; } edge.node1 !== node && focusNodes.push(edge.node1); edge.node2 !== node && focusNodes.push(edge.node2); }, this); this._pointsBuilder.fadeOutAll(0.05); this._fadeOutEdgesAll(0.05); focusNodes.forEach(function(node2) { this._pointsBuilder.highlight(data, node2.dataIndex); }, this); this._pointsBuilder.updateLabels(focusNodes.map(function(node2) { return node2.dataIndex; })); var focusEdges = []; node.edges.forEach(function(edge) { if (edge.dataIndex >= 0) { this._highlightEdge(edge.dataIndex); focusEdges.push(edge); } }, this); this._focusNodes = focusNodes; this._focusEdges = focusEdges; }, unfocusNodeAdjacency: function(seriesModel, ecModel, api, payload) { this._downplayAll(); this._pointsBuilder.fadeInAll(); this._fadeInEdgesAll(); this._pointsBuilder.updateLabels(); }, _highlightEdge: function(dataIndex) { var itemModel = this._model.getEdgeData().getItemModel(dataIndex); var emphasisColor = graphicGL.parseColor(itemModel.get("emphasis.lineStyle.color") || itemModel.get("lineStyle.color")); var emphasisOpacity = retrieve.firstNotNull(itemModel.get("emphasis.lineStyle.opacity"), itemModel.get("lineStyle.opacity"), 1); emphasisColor[3] *= emphasisOpacity; this._edgesMesh.geometry.setItemColor(this._edgeIndicesMap[dataIndex], emphasisColor); }, _downplayAll: function() { if (this._focusNodes) { this._focusNodes.forEach(function(node) { this._pointsBuilder.downplay(this._model.getData(), node.dataIndex); }, this); } if (this._focusEdges) { this._focusEdges.forEach(function(edge) { this._downplayEdge(edge.dataIndex); }, this); } }, _downplayEdge: function(dataIndex) { var color = this._getColor(dataIndex, []); this._edgesMesh.geometry.setItemColor(this._edgeIndicesMap[dataIndex], color); }, _setEdgeFade: /* @__PURE__ */ function() { var color = []; return function(dataIndex, percent) { this._getColor(dataIndex, color); color[3] *= percent; this._edgesMesh.geometry.setItemColor(this._edgeIndicesMap[dataIndex], color); }; }(), _getColor: function(dataIndex, out) { for (var i = 0; i < 4; i++) { out[i] = this._originalEdgeColors[dataIndex * 4 + i]; } return out; }, _fadeOutEdgesAll: function(percent) { var graph = this._model.getData().graph; graph.eachEdge(function(edge) { this._setEdgeFade(edge.dataIndex, percent); }, this); }, _fadeInEdgesAll: function() { this._fadeOutEdgesAll(1); }, _updateCamera: function(seriesModel, api) { this.viewGL.setViewport(0, 0, api.getWidth(), api.getHeight(), api.getDevicePixelRatio()); var camera2 = this.viewGL.camera; var nodeData = seriesModel.getData(); var points = nodeData.getLayout("points"); var min = vec2.create(Infinity, Infinity); var max = vec2.create(-Infinity, -Infinity); var pt = []; for (var i = 0; i < points.length; ) { pt[0] = points[i++]; pt[1] = points[i++]; vec2.min(min, min, pt); vec2.max(max, max, pt); } var cy = (max[1] + min[1]) / 2; var cx = (max[0] + min[0]) / 2; if (cx > camera2.left && cx < camera2.right && cy < camera2.bottom && cy > camera2.top) { return; } var width = Math.max(max[0] - min[0], 10); var height = width / api.getWidth() * api.getHeight(); width *= 1.4; height *= 1.4; min[0] -= width * 0.2; camera2.left = min[0]; camera2.top = cy - height / 2; camera2.bottom = cy + height / 2; camera2.right = width + min[0]; camera2.near = 0; camera2.far = 100; }, dispose: function() { var renderer = this.viewGL.layer.renderer; if (this._forceLayoutInstance) { this._forceLayoutInstance.dispose(renderer); } this.groupGL.removeAll(); this._layoutId = -1; this._pointsBuilder.dispose(); }, remove: function() { this.groupGL.removeAll(); this._control.dispose(); } }); function normalize(a) { if (!(a instanceof Array)) { a = [a, a]; } return a; } function install$2(registers) { registers.registerChartView(GraphGLView); registers.registerSeriesModel(GraphSeries); registers.registerVisual(function(ecModel) { const paletteScope = {}; ecModel.eachSeriesByType("graphGL", function(seriesModel) { var categoriesData = seriesModel.getCategoriesData(); var data = seriesModel.getData(); var categoryNameIdxMap = {}; categoriesData.each(function(idx) { var name = categoriesData.getName(idx); categoryNameIdxMap["ec-" + name] = idx; var itemModel = categoriesData.getItemModel(idx); var style = itemModel.getModel("itemStyle").getItemStyle(); if (!style.fill) { style.fill = seriesModel.getColorFromPalette(name, paletteScope); } categoriesData.setItemVisual(idx, "style", style); var symbolVisualList = ["symbol", "symbolSize", "symbolKeepAspect"]; for (let i = 0; i < symbolVisualList.length; i++) { var symbolVisual = itemModel.getShallow(symbolVisualList[i], true); if (symbolVisual != null) { categoriesData.setItemVisual(idx, symbolVisualList[i], symbolVisual); } } }); if (categoriesData.count()) { data.each(function(idx) { var model = data.getItemModel(idx); let categoryIdx = model.getShallow("category"); if (categoryIdx != null) { if (typeof categoryIdx === "string") { categoryIdx = categoryNameIdxMap["ec-" + categoryIdx]; } var categoryStyle = categoriesData.getItemVisual(categoryIdx, "style"); var style = data.ensureUniqueItemVisual(idx, "style"); extend$2(style, categoryStyle); var visualList = ["symbol", "symbolSize", "symbolKeepAspect"]; for (let i = 0; i < visualList.length; i++) { data.setItemVisual(idx, visualList[i], categoriesData.getItemVisual(categoryIdx, visualList[i])); } } }); } }); }); registers.registerVisual(function(ecModel) { ecModel.eachSeriesByType("graphGL", function(seriesModel) { var graph = seriesModel.getGraph(); var edgeData = seriesModel.getEdgeData(); var symbolType = normalize(seriesModel.get("edgeSymbol")); var symbolSize = normalize(seriesModel.get("edgeSymbolSize")); edgeData.setVisual("drawType", "stroke"); edgeData.setVisual("fromSymbol", symbolType && symbolType[0]); edgeData.setVisual("toSymbol", symbolType && symbolType[1]); edgeData.setVisual("fromSymbolSize", symbolSize && symbolSize[0]); edgeData.setVisual("toSymbolSize", symbolSize && symbolSize[1]); edgeData.setVisual("style", seriesModel.getModel("lineStyle").getLineStyle()); edgeData.each(function(idx) { var itemModel = edgeData.getItemModel(idx); var edge = graph.getEdgeByIndex(idx); var symbolType2 = normalize(itemModel.getShallow("symbol", true)); var symbolSize2 = normalize(itemModel.getShallow("symbolSize", true)); var style = itemModel.getModel("lineStyle").getLineStyle(); var existsStyle = edgeData.ensureUniqueItemVisual(idx, "style"); extend$2(existsStyle, style); switch (existsStyle.stroke) { case "source": { var nodeStyle = edge.node1.getVisual("style"); existsStyle.stroke = nodeStyle && nodeStyle.fill; break; } case "target": { var nodeStyle = edge.node2.getVisual("style"); existsStyle.stroke = nodeStyle && nodeStyle.fill; break; } } symbolType2[0] && edge.setVisual("fromSymbol", symbolType2[0]); symbolType2[1] && edge.setVisual("toSymbol", symbolType2[1]); symbolSize2[0] && edge.setVisual("fromSymbolSize", symbolSize2[0]); symbolSize2[1] && edge.setVisual("toSymbolSize", symbolSize2[1]); }); }); }); registers.registerAction({ type: "graphGLRoam", event: "graphglroam", update: "series.graphGL:roam" }, function(payload, ecModel) { ecModel.eachComponent({ mainType: "series", query: payload }, function(componentModel) { componentModel.setView(payload); }); }); function noop2() { } registers.registerAction({ type: "graphGLStartLayout", event: "graphgllayoutstarted", update: "series.graphGL:startLayout" }, noop2); registers.registerAction({ type: "graphGLStopLayout", event: "graphgllayoutstopped", update: "series.graphGL:stopLayout" }, noop2); registers.registerAction({ type: "graphGLFocusNodeAdjacency", event: "graphGLFocusNodeAdjacency", update: "series.graphGL:focusNodeAdjacency" }, noop2); registers.registerAction({ type: "graphGLUnfocusNodeAdjacency", event: "graphGLUnfocusNodeAdjacency", update: "series.graphGL:unfocusNodeAdjacency" }, noop2); } use(install$2); const FlowGLSeries = SeriesModel.extend({ type: "series.flowGL", dependencies: ["geo", "grid", "bmap"], visualStyleAccessPath: "itemStyle", getInitialData: function(option, ecModel) { var coordType = this.get("coordinateSystem"); var coordSysDimensions = coordType === "geo" ? ["lng", "lat"] : getCoordinateSystemDimensions(coordType) || ["x", "y"]; coordSysDimensions.push("vx", "vy"); var dimensions = createDimensions(this.getSource(), { coordDimensions: coordSysDimensions, encodeDefine: this.get("encode"), dimensionsDefine: this.get("dimensions") }); var data = new SeriesData(dimensions, this); data.initData(this.getSource()); return data; }, defaultOption: { coordinateSystem: "cartesian2d", zlevel: 10, supersampling: 1, // 128x128 particles particleType: "point", particleDensity: 128, particleSize: 1, particleSpeed: 1, particleTrail: 2, colorTexture: null, gridWidth: "auto", gridHeight: "auto", itemStyle: { color: "#fff", opacity: 0.8 } } }); var LinesGeometry = Geometry.extend( function() { return { dynamic: true, attributes: { position: new Geometry.Attribute("position", "float", 3, "POSITION") } }; }, /** @lends module: echarts-gl/util/geometry/LinesGeometry.prototype */ { /** * Reset offset */ resetOffset: function() { this._vertexOffset = 0; this._faceOffset = 0; }, /** * @param {number} nVertex */ setLineCount: function(nLine) { var attributes = this.attributes; var nVertex = 4 * nLine; var nTriangle = 2 * nLine; if (this.vertexCount !== nVertex) { attributes.position.init(nVertex); } if (this.triangleCount !== nTriangle) { if (nTriangle === 0) { this.indices = null; } else { this.indices = this.vertexCount > 65535 ? new Uint32Array(nTriangle * 3) : new Uint16Array(nTriangle * 3); } } }, addLine: function(p) { var vertexOffset = this._vertexOffset; this.attributes.position.set(vertexOffset, [p[0], p[1], 1]); this.attributes.position.set(vertexOffset + 1, [p[0], p[1], -1]); this.attributes.position.set(vertexOffset + 2, [p[0], p[1], 2]); this.attributes.position.set(vertexOffset + 3, [p[0], p[1], -2]); this.setTriangleIndices(this._faceOffset++, [vertexOffset, vertexOffset + 1, vertexOffset + 2]); this.setTriangleIndices(this._faceOffset++, [vertexOffset + 1, vertexOffset + 2, vertexOffset + 3]); this._vertexOffset += 4; } } ); const vectorFieldParticleGLSL = "@export ecgl.vfParticle.particle.fragment\n\nuniform sampler2D particleTexture;\nuniform sampler2D spawnTexture;\nuniform sampler2D velocityTexture;\n\nuniform float deltaTime;\nuniform float elapsedTime;\n\nuniform float speedScaling : 1.0;\n\nuniform vec2 textureSize;\nuniform vec4 region : [0, 0, 1, 1];\nuniform float firstFrameTime;\n\nvarying vec2 v_Texcoord;\n\n\nvoid main()\n{\n vec4 p = texture2D(particleTexture, v_Texcoord);\n bool spawn = false;\n if (p.w <= 0.0) {\n p = texture2D(spawnTexture, fract(v_Texcoord + elapsedTime / 10.0));\n p.w -= firstFrameTime;\n spawn = true;\n }\n vec2 v = texture2D(velocityTexture, fract(p.xy * region.zw + region.xy)).xy;\n v = (v - 0.5) * 2.0;\n p.z = length(v);\n p.xy += v * deltaTime / 10.0 * speedScaling;\n p.w -= deltaTime;\n\n if (spawn || p.xy != fract(p.xy)) {\n p.z = 0.0;\n }\n p.xy = fract(p.xy);\n\n gl_FragColor = p;\n}\n@end\n\n@export ecgl.vfParticle.renderPoints.vertex\n\n#define PI 3.1415926\n\nattribute vec2 texcoord : TEXCOORD_0;\n\nuniform sampler2D particleTexture;\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\nuniform float size : 1.0;\n\nvarying float v_Mag;\nvarying vec2 v_Uv;\n\nvoid main()\n{\n vec4 p = texture2D(particleTexture, texcoord);\n\n if (p.w > 0.0 && p.z > 1e-5) {\n gl_Position = worldViewProjection * vec4(p.xy * 2.0 - 1.0, 0.0, 1.0);\n }\n else {\n gl_Position = vec4(100000.0, 100000.0, 100000.0, 1.0);\n }\n\n v_Mag = p.z;\n v_Uv = p.xy;\n\n gl_PointSize = size;\n}\n\n@end\n\n@export ecgl.vfParticle.renderPoints.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\nuniform sampler2D gradientTexture;\nuniform sampler2D colorTexture;\nuniform sampler2D spriteTexture;\n\nvarying float v_Mag;\nvarying vec2 v_Uv;\n\nvoid main()\n{\n gl_FragColor = color;\n#ifdef SPRITETEXTURE_ENABLED\n gl_FragColor *= texture2D(spriteTexture, gl_PointCoord);\n if (color.a == 0.0) {\n discard;\n }\n#endif\n#ifdef GRADIENTTEXTURE_ENABLED\n gl_FragColor *= texture2D(gradientTexture, vec2(v_Mag, 0.5));\n#endif\n#ifdef COLORTEXTURE_ENABLED\n gl_FragColor *= texture2D(colorTexture, v_Uv);\n#endif\n}\n\n@end\n\n@export ecgl.vfParticle.renderLines.vertex\n\n#define PI 3.1415926\n\nattribute vec3 position : POSITION;\n\nuniform sampler2D particleTexture;\nuniform sampler2D prevParticleTexture;\n\nuniform float size : 1.0;\nuniform vec4 vp: VIEWPORT;\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\n\nvarying float v_Mag;\nvarying vec2 v_Uv;\n\n@import clay.util.rand\n\nvoid main()\n{\n vec4 p = texture2D(particleTexture, position.xy);\n vec4 p2 = texture2D(prevParticleTexture, position.xy);\n\n p.xy = p.xy * 2.0 - 1.0;\n p2.xy = p2.xy * 2.0 - 1.0;\n\n if (p.w > 0.0 && p.z > 1e-5) {\n vec2 dir = normalize(p.xy - p2.xy);\n vec2 norm = vec2(dir.y / vp.z, -dir.x / vp.w) * sign(position.z) * size;\n if (abs(position.z) == 2.0) {\n gl_Position = vec4(p.xy + norm, 0.0, 1.0);\n v_Uv = p.xy;\n v_Mag = p.z;\n }\n else {\n gl_Position = vec4(p2.xy + norm, 0.0, 1.0);\n v_Mag = p2.z;\n v_Uv = p2.xy;\n }\n gl_Position = worldViewProjection * gl_Position;\n }\n else {\n gl_Position = vec4(100000.0, 100000.0, 100000.0, 1.0);\n }\n}\n\n@end\n\n@export ecgl.vfParticle.renderLines.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\nuniform sampler2D gradientTexture;\nuniform sampler2D colorTexture;\n\nvarying float v_Mag;\nvarying vec2 v_Uv;\n\nvoid main()\n{\n gl_FragColor = color;\n #ifdef GRADIENTTEXTURE_ENABLED\n gl_FragColor *= texture2D(gradientTexture, vec2(v_Mag, 0.5));\n#endif\n#ifdef COLORTEXTURE_ENABLED\n gl_FragColor *= texture2D(colorTexture, v_Uv);\n#endif\n}\n\n@end\n"; Shader["import"](vectorFieldParticleGLSL); function createSpriteCanvas(size) { var canvas = document.createElement("canvas"); canvas.width = canvas.height = size; var ctx = canvas.getContext("2d"); ctx.fillStyle = "#fff"; ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2); ctx.fill(); return canvas; } var VectorFieldParticleSurface = function() { this.motionBlurFactor = 0.99; this.vectorFieldTexture = new Texture2D({ type: Texture.FLOAT, // minFilter: Texture.NEAREST, // magFilter: Texture.NEAREST, flipY: false }); this.particleLife = [5, 20]; this._particleType = "point"; this._particleSize = 1; this.particleColor = [1, 1, 1, 1]; this.particleSpeedScaling = 1; this._thisFrameTexture = null; this._particlePass = null; this._spawnTexture = null; this._particleTexture0 = null; this._particleTexture1 = null; this._particlePointsMesh = null; this._surfaceFrameBuffer = null; this._elapsedTime = 0; this._scene = null; this._camera = null; this._lastFrameTexture = null; this._supersampling = 1; this._downsampleTextures = []; this._width = 512; this._height = 512; this.init(); }; VectorFieldParticleSurface.prototype = { constructor: VectorFieldParticleSurface, init: function() { var parameters = { type: Texture.FLOAT, minFilter: Texture.NEAREST, magFilter: Texture.NEAREST, useMipmap: false }; this._spawnTexture = new Texture2D(parameters); this._particleTexture0 = new Texture2D(parameters); this._particleTexture1 = new Texture2D(parameters); this._frameBuffer = new FrameBuffer({ depthBuffer: false }); this._particlePass = new Pass({ fragment: Shader.source("ecgl.vfParticle.particle.fragment") }); this._particlePass.setUniform("velocityTexture", this.vectorFieldTexture); this._particlePass.setUniform("spawnTexture", this._spawnTexture); this._downsamplePass = new Pass({ fragment: Shader.source("clay.compositor.downsample") }); var particlePointsMesh = new Mesh({ // Render after last frame full quad renderOrder: 10, material: new Material({ shader: new Shader(Shader.source("ecgl.vfParticle.renderPoints.vertex"), Shader.source("ecgl.vfParticle.renderPoints.fragment")) }), mode: Mesh.POINTS, geometry: new Geometry({ dynamic: true, mainAttribute: "texcoord0" }) }); var particleLinesMesh = new Mesh({ // Render after last frame full quad renderOrder: 10, material: new Material({ shader: new Shader(Shader.source("ecgl.vfParticle.renderLines.vertex"), Shader.source("ecgl.vfParticle.renderLines.fragment")) }), geometry: new LinesGeometry(), culling: false }); var lastFrameFullQuad = new Mesh({ material: new Material({ shader: new Shader(Shader.source("ecgl.color.vertex"), Shader.source("ecgl.color.fragment")) // DO NOT BLEND Blend will multiply alpha // transparent: true }), geometry: new Plane() }); lastFrameFullQuad.material.enableTexture("diffuseMap"); this._particlePointsMesh = particlePointsMesh; this._particleLinesMesh = particleLinesMesh; this._lastFrameFullQuadMesh = lastFrameFullQuad; this._camera = new Orthographic(); this._thisFrameTexture = new Texture2D(); this._lastFrameTexture = new Texture2D(); }, setParticleDensity: function(width, height) { var nVertex = width * height; var spawnTextureData = new Float32Array(nVertex * 4); var off = 0; var lifeRange = this.particleLife; for (var i = 0; i < width; i++) { for (var j = 0; j < height; j++, off++) { spawnTextureData[off * 4] = Math.random(); spawnTextureData[off * 4 + 1] = Math.random(); spawnTextureData[off * 4 + 2] = Math.random(); var life = (lifeRange[1] - lifeRange[0]) * Math.random() + lifeRange[0]; spawnTextureData[off * 4 + 3] = life; } } if (this._particleType === "line") { this._setLineGeometry(width, height); } else { this._setPointsGeometry(width, height); } this._spawnTexture.width = width; this._spawnTexture.height = height; this._spawnTexture.pixels = spawnTextureData; this._particleTexture0.width = this._particleTexture1.width = width; this._particleTexture0.height = this._particleTexture1.height = height; this._particlePass.setUniform("textureSize", [width, height]); }, _setPointsGeometry: function(width, height) { var nVertex = width * height; var geometry = this._particlePointsMesh.geometry; var attributes = geometry.attributes; attributes.texcoord0.init(nVertex); var off = 0; for (var i = 0; i < width; i++) { for (var j = 0; j < height; j++, off++) { attributes.texcoord0.value[off * 2] = i / width; attributes.texcoord0.value[off * 2 + 1] = j / height; } } geometry.dirty(); }, _setLineGeometry: function(width, height) { var nLine = width * height; var geometry = this._getParticleMesh().geometry; geometry.setLineCount(nLine); geometry.resetOffset(); for (var i = 0; i < width; i++) { for (var j = 0; j < height; j++) { geometry.addLine([i / width, j / height]); } } geometry.dirty(); }, _getParticleMesh: function() { return this._particleType === "line" ? this._particleLinesMesh : this._particlePointsMesh; }, update: function(renderer, api, deltaTime, firstFrame) { var particleMesh = this._getParticleMesh(); var frameBuffer = this._frameBuffer; var particlePass = this._particlePass; if (firstFrame) { this._updateDownsampleTextures(renderer, api); } particleMesh.material.set("size", this._particleSize * this._supersampling); particleMesh.material.set("color", this.particleColor); particlePass.setUniform("speedScaling", this.particleSpeedScaling); frameBuffer.attach(this._particleTexture1); particlePass.setUniform("firstFrameTime", firstFrame ? (this.particleLife[1] + this.particleLife[0]) / 2 : 0); particlePass.setUniform("particleTexture", this._particleTexture0); particlePass.setUniform("deltaTime", deltaTime); particlePass.setUniform("elapsedTime", this._elapsedTime); particlePass.render(renderer, frameBuffer); particleMesh.material.set("particleTexture", this._particleTexture1); particleMesh.material.set("prevParticleTexture", this._particleTexture0); frameBuffer.attach(this._thisFrameTexture); frameBuffer.bind(renderer); renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT); var lastFrameFullQuad = this._lastFrameFullQuadMesh; lastFrameFullQuad.material.set("diffuseMap", this._lastFrameTexture); lastFrameFullQuad.material.set("color", [1, 1, 1, this.motionBlurFactor]); this._camera.update(true); renderer.renderPass([lastFrameFullQuad, particleMesh], this._camera); frameBuffer.unbind(renderer); this._downsample(renderer); this._swapTexture(); this._elapsedTime += deltaTime; }, _downsample: function(renderer) { var downsampleTextures = this._downsampleTextures; if (downsampleTextures.length === 0) { return; } var current = 0; var sourceTexture = this._thisFrameTexture; var targetTexture = downsampleTextures[current]; while (targetTexture) { this._frameBuffer.attach(targetTexture); this._downsamplePass.setUniform("texture", sourceTexture); this._downsamplePass.setUniform("textureSize", [sourceTexture.width, sourceTexture.height]); this._downsamplePass.render(renderer, this._frameBuffer); sourceTexture = targetTexture; targetTexture = downsampleTextures[++current]; } }, getSurfaceTexture: function() { var downsampleTextures = this._downsampleTextures; return downsampleTextures.length > 0 ? downsampleTextures[downsampleTextures.length - 1] : this._lastFrameTexture; }, setRegion: function(region) { this._particlePass.setUniform("region", region); }, resize: function(width, height) { this._lastFrameTexture.width = width * this._supersampling; this._lastFrameTexture.height = height * this._supersampling; this._thisFrameTexture.width = width * this._supersampling; this._thisFrameTexture.height = height * this._supersampling; this._width = width; this._height = height; }, setParticleSize: function(size) { var particleMesh = this._getParticleMesh(); if (size <= 2) { particleMesh.material.disableTexture("spriteTexture"); particleMesh.material.transparent = false; return; } if (!this._spriteTexture) { this._spriteTexture = new Texture2D(); } if (!this._spriteTexture.image || this._spriteTexture.image.width !== size) { this._spriteTexture.image = createSpriteCanvas(size); this._spriteTexture.dirty(); } particleMesh.material.transparent = true; particleMesh.material.enableTexture("spriteTexture"); particleMesh.material.set("spriteTexture", this._spriteTexture); this._particleSize = size; }, setGradientTexture: function(gradientTexture) { var material = this._getParticleMesh().material; material[gradientTexture ? "enableTexture" : "disableTexture"]("gradientTexture"); material.setUniform("gradientTexture", gradientTexture); }, setColorTextureImage: function(colorTextureImg, api) { var material = this._getParticleMesh().material; material.setTextureImage("colorTexture", colorTextureImg, api, { flipY: true }); }, setParticleType: function(type) { this._particleType = type; }, clearFrame: function(renderer) { var frameBuffer = this._frameBuffer; frameBuffer.attach(this._lastFrameTexture); frameBuffer.bind(renderer); renderer.gl.clear(renderer.gl.DEPTH_BUFFER_BIT | renderer.gl.COLOR_BUFFER_BIT); frameBuffer.unbind(renderer); }, setSupersampling: function(supersampling) { this._supersampling = supersampling; this.resize(this._width, this._height); }, _updateDownsampleTextures: function(renderer, api) { var downsampleTextures = this._downsampleTextures; var upScale = Math.max(Math.floor(Math.log(this._supersampling / api.getDevicePixelRatio()) / Math.log(2)), 0); var scale = 2; var width = this._width * this._supersampling; var height = this._height * this._supersampling; for (var i = 0; i < upScale; i++) { downsampleTextures[i] = downsampleTextures[i] || new Texture2D(); downsampleTextures[i].width = width / scale; downsampleTextures[i].height = height / scale; scale *= 2; } for (; i < downsampleTextures.length; i++) { downsampleTextures[i].dispose(renderer); } downsampleTextures.length = upScale; }, _swapTexture: function() { var tmp = this._particleTexture0; this._particleTexture0 = this._particleTexture1; this._particleTexture1 = tmp; var tmp = this._thisFrameTexture; this._thisFrameTexture = this._lastFrameTexture; this._lastFrameTexture = tmp; }, dispose: function(renderer) { renderer.disposeFrameBuffer(this._frameBuffer); renderer.disposeTexture(this.vectorFieldTexture); renderer.disposeTexture(this._spawnTexture); renderer.disposeTexture(this._particleTexture0); renderer.disposeTexture(this._particleTexture1); renderer.disposeTexture(this._thisFrameTexture); renderer.disposeTexture(this._lastFrameTexture); renderer.disposeGeometry(this._particleLinesMesh.geometry); renderer.disposeGeometry(this._particlePointsMesh.geometry); renderer.disposeGeometry(this._lastFrameFullQuadMesh.geometry); if (this._spriteTexture) { renderer.disposeTexture(this._spriteTexture); } this._particlePass.dispose(renderer); this._downsamplePass.dispose(renderer); this._downsampleTextures.forEach(function(texture) { texture.dispose(renderer); }); } }; const FlowGLView = ChartView.extend({ type: "flowGL", __ecgl__: true, init: function(ecModel, api) { this.viewGL = new ViewGL("orthographic"); this.groupGL = new graphicGL.Node(); this.viewGL.add(this.groupGL); this._particleSurface = new VectorFieldParticleSurface(); var planeMesh = new graphicGL.Mesh({ geometry: new graphicGL.PlaneGeometry(), material: new graphicGL.Material({ shader: new graphicGL.Shader({ vertex: graphicGL.Shader.source("ecgl.color.vertex"), fragment: graphicGL.Shader.source("ecgl.color.fragment") }), // Must enable blending and multiply alpha. // Or premultipliedAlpha will let the alpha useless. transparent: true }) }); planeMesh.material.enableTexture("diffuseMap"); this.groupGL.add(planeMesh); this._planeMesh = planeMesh; }, render: function(seriesModel, ecModel, api) { var particleSurface = this._particleSurface; particleSurface.setParticleType(seriesModel.get("particleType")); particleSurface.setSupersampling(seriesModel.get("supersampling")); this._updateData(seriesModel, api); this._updateCamera(api.getWidth(), api.getHeight(), api.getDevicePixelRatio()); var particleDensity = retrieve.firstNotNull(seriesModel.get("particleDensity"), 128); particleSurface.setParticleDensity(particleDensity, particleDensity); var planeMesh = this._planeMesh; var time = +/* @__PURE__ */ new Date(); var self2 = this; var firstFrame = true; planeMesh.__percent = 0; planeMesh.stopAnimation(); planeMesh.animate("", { loop: true }).when(1e5, { __percent: 1 }).during(function() { var timeNow = +/* @__PURE__ */ new Date(); var dTime = Math.min(timeNow - time, 20); time = time + dTime; if (self2._renderer) { particleSurface.update(self2._renderer, api, dTime / 1e3, firstFrame); planeMesh.material.set("diffuseMap", particleSurface.getSurfaceTexture()); } firstFrame = false; }).start(); var itemStyleModel = seriesModel.getModel("itemStyle"); var color = graphicGL.parseColor(itemStyleModel.get("color")); color[3] *= retrieve.firstNotNull(itemStyleModel.get("opacity"), 1); planeMesh.material.set("color", color); particleSurface.setColorTextureImage(seriesModel.get("colorTexture"), api); particleSurface.setParticleSize(seriesModel.get("particleSize")); particleSurface.particleSpeedScaling = seriesModel.get("particleSpeed"); particleSurface.motionBlurFactor = 1 - Math.pow(0.1, seriesModel.get("particleTrail")); }, updateTransform: function(seriesModel, ecModel, api) { this._updateData(seriesModel, api); }, afterRender: function(globeModel, ecModel, api, layerGL) { var renderer = layerGL.renderer; this._renderer = renderer; }, _updateData: function(seriesModel, api) { var coordSys = seriesModel.coordinateSystem; var dims = coordSys.dimensions.map(function(coordDim) { return seriesModel.coordDimToDataDim(coordDim)[0]; }); var data = seriesModel.getData(); var xExtent = data.getDataExtent(dims[0]); var yExtent = data.getDataExtent(dims[1]); var gridWidth = seriesModel.get("gridWidth"); var gridHeight = seriesModel.get("gridHeight"); if (gridWidth == null || gridWidth === "auto") { var aspect = (xExtent[1] - xExtent[0]) / (yExtent[1] - yExtent[0]); gridWidth = Math.round(Math.sqrt(aspect * data.count())); } if (gridHeight == null || gridHeight === "auto") { gridHeight = Math.ceil(data.count() / gridWidth); } var vectorFieldTexture = this._particleSurface.vectorFieldTexture; var pixels = vectorFieldTexture.pixels; if (!pixels || pixels.length !== gridHeight * gridWidth * 4) { pixels = vectorFieldTexture.pixels = new Float32Array(gridWidth * gridHeight * 4); } else { for (var i = 0; i < pixels.length; i++) { pixels[i] = 0; } } var maxMag = 0; var minMag = Infinity; var points = new Float32Array(data.count() * 2); var offset = 0; var bbox = [[Infinity, Infinity], [-Infinity, -Infinity]]; data.each([dims[0], dims[1], "vx", "vy"], function(x, y, vx, vy) { var pt = coordSys.dataToPoint([x, y]); points[offset++] = pt[0]; points[offset++] = pt[1]; bbox[0][0] = Math.min(pt[0], bbox[0][0]); bbox[0][1] = Math.min(pt[1], bbox[0][1]); bbox[1][0] = Math.max(pt[0], bbox[1][0]); bbox[1][1] = Math.max(pt[1], bbox[1][1]); var mag = Math.sqrt(vx * vx + vy * vy); maxMag = Math.max(maxMag, mag); minMag = Math.min(minMag, mag); }); data.each(["vx", "vy"], function(vx, vy, i2) { var xPix = Math.round((points[i2 * 2] - bbox[0][0]) / (bbox[1][0] - bbox[0][0]) * (gridWidth - 1)); var yPix = gridHeight - 1 - Math.round((points[i2 * 2 + 1] - bbox[0][1]) / (bbox[1][1] - bbox[0][1]) * (gridHeight - 1)); var idx = (yPix * gridWidth + xPix) * 4; pixels[idx] = vx / maxMag * 0.5 + 0.5; pixels[idx + 1] = vy / maxMag * 0.5 + 0.5; pixels[idx + 3] = 1; }); vectorFieldTexture.width = gridWidth; vectorFieldTexture.height = gridHeight; if (seriesModel.get("coordinateSystem") === "bmap") { this._fillEmptyPixels(vectorFieldTexture); } vectorFieldTexture.dirty(); this._updatePlanePosition(bbox[0], bbox[1], seriesModel, api); this._updateGradientTexture(data.getVisual("visualMeta"), [minMag, maxMag]); }, // PENDING Use grid mesh ? or delaunay triangulation? _fillEmptyPixels: function(texture) { var pixels = texture.pixels; var width = texture.width; var height = texture.height; function fetchPixel(x2, y2, rg) { x2 = Math.max(Math.min(x2, width - 1), 0); y2 = Math.max(Math.min(y2, height - 1), 0); var idx2 = (y2 * (width - 1) + x2) * 4; if (pixels[idx2 + 3] === 0) { return false; } rg[0] = pixels[idx2]; rg[1] = pixels[idx2 + 1]; return true; } function addPixel(a, b, out) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; } var center = [], left = [], right = [], top = [], bottom = []; var weight = 0; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var idx = (y * (width - 1) + x) * 4; if (pixels[idx + 3] === 0) { weight = center[0] = center[1] = 0; if (fetchPixel(x - 1, y, left)) { weight++; addPixel(left, center, center); } if (fetchPixel(x + 1, y, right)) { weight++; addPixel(right, center, center); } if (fetchPixel(x, y - 1, top)) { weight++; addPixel(top, center, center); } if (fetchPixel(x, y + 1, bottom)) { weight++; addPixel(bottom, center, center); } center[0] /= weight; center[1] /= weight; pixels[idx] = center[0]; pixels[idx + 1] = center[1]; } pixels[idx + 3] = 1; } } }, _updateGradientTexture: function(visualMeta, magExtent) { if (!visualMeta || !visualMeta.length) { this._particleSurface.setGradientTexture(null); return; } this._gradientTexture = this._gradientTexture || new graphicGL.Texture2D({ image: document.createElement("canvas") }); var gradientTexture = this._gradientTexture; var canvas = gradientTexture.image; canvas.width = 200; canvas.height = 1; var ctx = canvas.getContext("2d"); var gradient = ctx.createLinearGradient(0, 0.5, canvas.width, 0.5); visualMeta[0].stops.forEach(function(stop) { var offset; if (magExtent[1] === magExtent[0]) { offset = 0; } else { offset = stop.value / magExtent[1]; offset = Math.min(Math.max(offset, 0), 1); } gradient.addColorStop(offset, stop.color); }); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); gradientTexture.dirty(); this._particleSurface.setGradientTexture(this._gradientTexture); }, _updatePlanePosition: function(leftTop, rightBottom, seriesModel, api) { var limitedResult = this._limitInViewportAndFullFill(leftTop, rightBottom, seriesModel, api); leftTop = limitedResult.leftTop; rightBottom = limitedResult.rightBottom; this._particleSurface.setRegion(limitedResult.region); this._planeMesh.position.set((leftTop[0] + rightBottom[0]) / 2, api.getHeight() - (leftTop[1] + rightBottom[1]) / 2, 0); var width = rightBottom[0] - leftTop[0]; var height = rightBottom[1] - leftTop[1]; this._planeMesh.scale.set(width / 2, height / 2, 1); this._particleSurface.resize(Math.max(Math.min(width, 2048), 1), Math.max(Math.min(height, 2048), 1)); if (this._renderer) { this._particleSurface.clearFrame(this._renderer); } }, _limitInViewportAndFullFill: function(leftTop, rightBottom, seriesModel, api) { var newLeftTop = [Math.max(leftTop[0], 0), Math.max(leftTop[1], 0)]; var newRightBottom = [Math.min(rightBottom[0], api.getWidth()), Math.min(rightBottom[1], api.getHeight())]; if (seriesModel.get("coordinateSystem") === "bmap") { var lngRange = seriesModel.getData().getDataExtent(seriesModel.coordDimToDataDim("lng")[0]); var isContinuous = Math.floor(lngRange[1] - lngRange[0]) >= 359; if (isContinuous) { if (newLeftTop[0] > 0) { newLeftTop[0] = 0; } if (newRightBottom[0] < api.getWidth()) { newRightBottom[0] = api.getWidth(); } } } var width = rightBottom[0] - leftTop[0]; var height = rightBottom[1] - leftTop[1]; var newWidth = newRightBottom[0] - newLeftTop[0]; var newHeight = newRightBottom[1] - newLeftTop[1]; var region = [(newLeftTop[0] - leftTop[0]) / width, 1 - newHeight / height - (newLeftTop[1] - leftTop[1]) / height, newWidth / width, newHeight / height]; return { leftTop: newLeftTop, rightBottom: newRightBottom, region }; }, _updateCamera: function(width, height, dpr) { this.viewGL.setViewport(0, 0, width, height, dpr); var camera2 = this.viewGL.camera; camera2.left = camera2.bottom = 0; camera2.top = height; camera2.right = width; camera2.near = 0; camera2.far = 100; camera2.position.z = 10; }, remove: function() { this._planeMesh.stopAnimation(); this.groupGL.removeAll(); }, dispose: function() { if (this._renderer) { this._particleSurface.dispose(this._renderer); } this.groupGL.removeAll(); } }); function install$1(registers) { registers.registerChartView(FlowGLView); registers.registerSeriesModel(FlowGLSeries); } use(install$1); var LinesGLSeries = SeriesModel.extend({ type: "series.linesGL", dependencies: ["grid", "geo"], visualStyleAccessPath: "lineStyle", visualDrawType: "stroke", streamEnabled: true, init: function(option) { var result = this._processFlatCoordsArray(option.data); this._flatCoords = result.flatCoords; this._flatCoordsOffset = result.flatCoordsOffset; if (result.flatCoords) { option.data = new Float32Array(result.count); } LinesGLSeries.superApply(this, "init", arguments); }, mergeOption: function(option) { var result = this._processFlatCoordsArray(option.data); this._flatCoords = result.flatCoords; this._flatCoordsOffset = result.flatCoordsOffset; if (result.flatCoords) { option.data = new Float32Array(result.count); } LinesGLSeries.superApply(this, "mergeOption", arguments); }, appendData: function(params) { var result = this._processFlatCoordsArray(params.data); if (result.flatCoords) { if (!this._flatCoords) { this._flatCoords = result.flatCoords; this._flatCoordsOffset = result.flatCoordsOffset; } else { this._flatCoords = concatArray(this._flatCoords, result.flatCoords); this._flatCoordsOffset = concatArray(this._flatCoordsOffset, result.flatCoordsOffset); } params.data = new Float32Array(result.count); } this.getRawData().appendData(params.data); }, _getCoordsFromItemModel: function(idx) { var itemModel = this.getData().getItemModel(idx); var coords = itemModel.option instanceof Array ? itemModel.option : itemModel.getShallow("coords"); return coords; }, getLineCoordsCount: function(idx) { if (this._flatCoordsOffset) { return this._flatCoordsOffset[idx * 2 + 1]; } else { return this._getCoordsFromItemModel(idx).length; } }, getLineCoords: function(idx, out) { if (this._flatCoordsOffset) { var offset = this._flatCoordsOffset[idx * 2]; var len = this._flatCoordsOffset[idx * 2 + 1]; for (var i = 0; i < len; i++) { out[i] = out[i] || []; out[i][0] = this._flatCoords[offset + i * 2]; out[i][1] = this._flatCoords[offset + i * 2 + 1]; } return len; } else { var coords = this._getCoordsFromItemModel(idx); for (var i = 0; i < coords.length; i++) { out[i] = out[i] || []; out[i][0] = coords[i][0]; out[i][1] = coords[i][1]; } return coords.length; } }, _processFlatCoordsArray: function(data) { var startOffset = 0; if (this._flatCoords) { startOffset = this._flatCoords.length; } if (typeof data[0] === "number") { var len = data.length; var coordsOffsetAndLenStorage = new Uint32Array(len); var coordsStorage = new Float64Array(len); var coordsCursor = 0; var offsetCursor = 0; var dataCount = 0; for (var i = 0; i < len; ) { dataCount++; var count = data[i++]; coordsOffsetAndLenStorage[offsetCursor++] = coordsCursor + startOffset; coordsOffsetAndLenStorage[offsetCursor++] = count; for (var k = 0; k < count; k++) { var x = data[i++]; var y = data[i++]; coordsStorage[coordsCursor++] = x; coordsStorage[coordsCursor++] = y; } } return { flatCoordsOffset: new Uint32Array(coordsOffsetAndLenStorage.buffer, 0, offsetCursor), flatCoords: coordsStorage, count: dataCount }; } return { flatCoordsOffset: null, flatCoords: null, count: data.length }; }, getInitialData: function(option, ecModel) { var lineData = new SeriesData(["value"], this); lineData.hasItemOption = false; lineData.initData(option.data, [], function(dataItem, dimName, dataIndex, dimIndex) { if (dataItem instanceof Array) { return NaN; } else { lineData.hasItemOption = true; var value = dataItem.value; if (value != null) { return value instanceof Array ? value[dimIndex] : value; } } }); return lineData; }, defaultOption: { coordinateSystem: "geo", zlevel: 10, progressive: 1e4, progressiveThreshold: 5e4, // Cartesian coordinate system // xAxisIndex: 0, // yAxisIndex: 0, // Geo coordinate system // geoIndex: 0, // Support source-over, lighter blendMode: "source-over", lineStyle: { opacity: 0.8 }, postEffect: { enable: false, colorCorrection: { exposure: 0, brightness: 0, contrast: 1, saturation: 1, enable: true } } } }); const LinesGLView = ChartView.extend({ type: "linesGL", __ecgl__: true, init: function(ecModel, api) { this.groupGL = new graphicGL.Node(); this.viewGL = new ViewGL("orthographic"); this.viewGL.add(this.groupGL); this._glViewHelper = new GLViewHelper(this.viewGL); this._nativeLinesShader = graphicGL.createShader("ecgl.lines3D"); this._meshLinesShader = graphicGL.createShader("ecgl.meshLines3D"); this._linesMeshes = []; this._currentStep = 0; }, render: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); this._glViewHelper.reset(seriesModel, api); var linesMesh = this._linesMeshes[0]; if (!linesMesh) { linesMesh = this._linesMeshes[0] = this._createLinesMesh(seriesModel); } this._linesMeshes.length = 1; this.groupGL.add(linesMesh); this._updateLinesMesh(seriesModel, linesMesh, 0, seriesModel.getData().count()); this.viewGL.setPostEffect(seriesModel.getModel("postEffect"), api); }, incrementalPrepareRender: function(seriesModel, ecModel, api) { this.groupGL.removeAll(); this._glViewHelper.reset(seriesModel, api); this._currentStep = 0; this.viewGL.setPostEffect(seriesModel.getModel("postEffect"), api); }, incrementalRender: function(params, seriesModel, ecModel, api) { var linesMesh = this._linesMeshes[this._currentStep]; if (!linesMesh) { linesMesh = this._createLinesMesh(seriesModel); this._linesMeshes[this._currentStep] = linesMesh; } this._updateLinesMesh(seriesModel, linesMesh, params.start, params.end); this.groupGL.add(linesMesh); api.getZr().refresh(); this._currentStep++; }, updateTransform: function(seriesModel, ecModel, api) { if (seriesModel.coordinateSystem.getRoamTransform) { this._glViewHelper.updateTransform(seriesModel, api); } }, _createLinesMesh: function(seriesModel) { var linesMesh = new graphicGL.Mesh({ $ignorePicking: true, material: new graphicGL.Material({ shader: graphicGL.createShader("ecgl.lines3D"), transparent: true, depthMask: false, depthTest: false }), geometry: new LinesGeometry$1({ segmentScale: 10, useNativeLine: true, dynamic: false }), mode: graphicGL.Mesh.LINES, culling: false }); return linesMesh; }, _updateLinesMesh: function(seriesModel, linesMesh, start, end) { var data = seriesModel.getData(); linesMesh.material.blend = seriesModel.get("blendMode") === "lighter" ? graphicGL.additiveBlend : null; var curveness = seriesModel.get("lineStyle.curveness") || 0; var isPolyline = seriesModel.get("polyline"); var geometry = linesMesh.geometry; var coordSys = seriesModel.coordinateSystem; var lineWidth = retrieve.firstNotNull(seriesModel.get("lineStyle.width"), 1); if (lineWidth > 1) { if (linesMesh.material.shader !== this._meshLinesShader) { linesMesh.material.attachShader(this._meshLinesShader); } linesMesh.mode = graphicGL.Mesh.TRIANGLES; } else { if (linesMesh.material.shader !== this._nativeLinesShader) { linesMesh.material.attachShader(this._nativeLinesShader); } linesMesh.mode = graphicGL.Mesh.LINES; } start = start || 0; end = end || data.count(); geometry.resetOffset(); var vertexCount = 0; var triangleCount = 0; var p02 = []; var p12 = []; var p22 = []; var p3 = []; var lineCoords = []; var t = 0.3; var t2 = 0.7; function updateBezierControlPoints() { p12[0] = p02[0] * t2 + p3[0] * t - (p02[1] - p3[1]) * curveness; p12[1] = p02[1] * t2 + p3[1] * t - (p3[0] - p02[0]) * curveness; p22[0] = p02[0] * t + p3[0] * t2 - (p02[1] - p3[1]) * curveness; p22[1] = p02[1] * t + p3[1] * t2 - (p3[0] - p02[0]) * curveness; } if (isPolyline || curveness !== 0) { for (var idx = start; idx < end; idx++) { if (isPolyline) { var count = seriesModel.getLineCoordsCount(idx); vertexCount += geometry.getPolylineVertexCount(count); triangleCount += geometry.getPolylineTriangleCount(count); } else { seriesModel.getLineCoords(idx, lineCoords); this._glViewHelper.dataToPoint(coordSys, lineCoords[0], p02); this._glViewHelper.dataToPoint(coordSys, lineCoords[1], p3); updateBezierControlPoints(); vertexCount += geometry.getCubicCurveVertexCount(p02, p12, p22, p3); triangleCount += geometry.getCubicCurveTriangleCount(p02, p12, p22, p3); } } } else { var lineCount = end - start; vertexCount += lineCount * geometry.getLineVertexCount(); triangleCount += lineCount * geometry.getLineVertexCount(); } geometry.setVertexCount(vertexCount); geometry.setTriangleCount(triangleCount); var dataIndex = start; var colorArr = []; for (var idx = start; idx < end; idx++) { graphicGL.parseColor(getItemVisualColor(data, dataIndex), colorArr); var opacity = retrieve.firstNotNull(getItemVisualOpacity(data, dataIndex), 1); colorArr[3] *= opacity; var count = seriesModel.getLineCoords(idx, lineCoords); for (var k = 0; k < count; k++) { this._glViewHelper.dataToPoint(coordSys, lineCoords[k], lineCoords[k]); } if (isPolyline) { geometry.addPolyline(lineCoords, colorArr, lineWidth, 0, count); } else if (curveness !== 0) { p02 = lineCoords[0]; p3 = lineCoords[1]; updateBezierControlPoints(); geometry.addCubicCurve(p02, p12, p22, p3, colorArr, lineWidth); } else { geometry.addPolyline(lineCoords, colorArr, lineWidth, 0, 2); } dataIndex++; } }, dispose: function() { this.groupGL.removeAll(); }, remove: function() { this.groupGL.removeAll(); } }); function install(registers) { registers.registerChartView(LinesGLView); registers.registerSeriesModel(LinesGLSeries); } use(install); //# sourceMappingURL=index-DUQY-caJ.js.map