/** * Lines geometry * Use screen space projected lines lineWidth > MAX_LINE_WIDTH * https://mattdesl.svbtle.com/drawing-lines-is-hard * @module echarts-gl/util/geometry/LinesGeometry * @author Yi Shen(http://github.com/pissang) */ import Geometry from 'claygl/src/Geometry'; import * as echarts from 'echarts/lib/echarts'; import dynamicConvertMixin from './dynamicConvertMixin'; import glmatrix from 'claygl/src/dep/glmatrix'; var vec2 = glmatrix.vec2; // var CURVE_RECURSION_LIMIT = 8; // var CURVE_COLLINEAR_EPSILON = 40; var sampleLinePoints = [[0, 0], [1, 1]]; /** * @constructor * @alias module:echarts-gl/util/geometry/LinesGeometry * @extends clay.Geometry */ var LinesGeometry = 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 > 0xffff) { 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 > 0xffff ? new Uint32Array(nTriangle * 3) : new Uint16Array(nTriangle * 3); } } }, _getCubicCurveApproxStep: function (p0, p1, p2, p3) { var len = vec2.dist(p0, p1) + vec2.dist(p2, p1) + vec2.dist(p3, p2); 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 (p0, p1, p2, p3) { var step = this._getCubicCurveApproxStep(p0, p1, p2, 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 (p0, p1, p2, p3) { var step = this._getCubicCurveApproxStep(p0, p1, p2, 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 (p0, p1, p2, p3, color, lineWidth) { if (lineWidth == null) { lineWidth = 1; } // incremental interpolation // http://antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION var x0 = p0[0], y0 = p0[1]; var x1 = p1[0], y1 = p1[1]; var x2 = p2[0], y2 = p2[1]; var x3 = p3[0], y3 = p3[1]; var step = this._getCubicCurveApproxStep(p0, p1, p2, p3); var step2 = step * step; var step3 = step2 * step; var pre1 = 3.0 * step; var pre2 = 3.0 * step2; var pre4 = 6.0 * step2; var pre5 = 6.0 * step3; var tmp1x = x0 - x1 * 2.0 + x2; var tmp1y = y0 - y1 * 2.0 + y2; var tmp2x = (x1 - x2) * 3.0 - x0 + x3; var tmp2y = (y1 - y2) * 3.0 - 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 (p0, p1, color, lineWidth) { this.addPolyline([p0, p1], 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.create(); var dirB = vec2.create(); var normal = vec2.create(); var tangent = vec2.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.copy(nextPoint, points[k + 1]); } else { var k2 = (k + 1) * 2 + arrayOffset; nextPoint = nextPoint || []; nextPoint[0] = points[k2]; nextPoint[1] = points[k2 + 1]; } // TODO In case dir is (0, 0) // TODO miterLimit if (k > 0) { vec2.sub(dirA, point, prevPoint); vec2.sub(dirB, nextPoint, point); vec2.normalize(dirA, dirA); vec2.normalize(dirB, dirB); vec2.add(tangent, dirA, dirB); vec2.normalize(tangent, tangent); var miter = lineWidth / 2 * Math.min(1 / vec2.dot(dirA, tangent), 2); normal[0] = -tangent[1]; normal[1] = tangent[0]; offset = miter; } else { vec2.sub(dirA, nextPoint, point); vec2.normalize(dirA, dirA); normal[0] = -dirA[1]; normal[1] = dirA[0]; offset = lineWidth / 2; } } else { vec2.sub(dirA, point, prevPoint); vec2.normalize(dirA, dirA); normal[0] = -dirA[1]; normal[1] = dirA[0]; offset = lineWidth / 2; } normalAttr.set(vertexOffset, normal); normalAttr.set(vertexOffset + 1, normal); offsetAttr.set(vertexOffset, offset); offsetAttr.set(vertexOffset + 1, -offset); vec2.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; // 0-----2 // 1-----3 // 0->1->2, 1->3->2 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'); } }); echarts.util.defaults(LinesGeometry.prototype, dynamicConvertMixin); export default LinesGeometry;