import * as echarts from 'echarts/lib/echarts'; import graphicGL from '../../util/graphicGL'; import retrieve from '../../util/retrieve'; import Lines3DGeometry from '../../util/geometry/Lines3D'; import Matrix4 from 'claygl/src/math/Matrix4'; import Vector3 from 'claygl/src/math/Vector3'; import * as lineContain from 'zrender/lib/contain/line'; import glmatrix from 'claygl/src/dep/glmatrix'; import { getItemVisualColor, getItemVisualOpacity } from '../../util/visual'; import lines3DGLSL from '../../util/shader/lines3D.glsl.js'; var vec3 = glmatrix.vec3; graphicGL.Shader.import(lines3DGLSL); export default echarts.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 Lines3DGeometry({ 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); // TODO 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 (e) { var value = coordSys.pointToData(e.point.array); var dataIndex = data.indicesOfNearest('x', value[0])[0]; if (dataIndex !== lastDataIndex) { // this._downplay(lastDataIndex); // this._highlight(dataIndex); 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 (e) { // this._downplay(lastDataIndex); 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 camera = this._camera; Matrix4.multiply(worldViewProjection, camera.projectionMatrix, camera.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.transformMat4(pos, pos, worldViewProjection.array); positionNDC[i2] = pos[0] / pos[3]; positionNDC[i2 + 1] = pos[1] / pos[3]; } }, _pick: function (x, y, renderer, camera, 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 (lineContain.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); // Nearest point. 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: dataIndex, point: point, pointWorld: point.clone(), target: this._line3DMesh, distance: this._camera.getWorldPosition().dist(point) }); } }, remove: function () { this.groupGL.removeAll(); }, dispose: function () { this.groupGL.removeAll(); } });