cione-comp-lib
Version:
`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`
560 lines (559 loc) • 22.7 kB
JavaScript
import "../../../../echarts/lib/echarts.mjs";
import graphicGL from "../../util/graphicGL.mjs";
import earcut from "../../util/earcut.mjs";
import LinesGeometry from "../../util/geometry/Lines3D.mjs";
import retrieve from "../../util/retrieve.mjs";
import glmatrix from "../../../../claygl/src/glmatrix/index.mjs";
import trianglesSortMixin from "../../util/geometry/trianglesSortMixin.mjs";
import LabelsBuilder from "./LabelsBuilder.mjs";
import lines3DGLSL from "../../util/shader/lines3D.glsl.mjs";
import { getItemVisualColor, getItemVisualOpacity } from "../../util/visual.mjs";
import { lift } from "../../../../zrender/lib/tool/color.mjs";
var vec3 = 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,
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 self = 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 = self._triangulationResults[dataIndex - self._startIndex];
var center = self.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 self = this;
function createPolygonMesh() {
var mesh = new graphicGL.Mesh({
name: "Polygon",
material: new graphicGL.Material({
shader: self._shadersMap.lambert
}),
geometry: new graphicGL.Geometry({
sortTriangles: true,
dynamic: true
}),
culling: false,
ignorePicking: true,
renderNormal: true
});
Object.assign(mesh.geometry, trianglesSortMixin);
return mesh;
}
var polygonMesh = createPolygonMesh();
var linesMesh = new graphicGL.Mesh({
material: new graphicGL.Material({
shader: this._linesShader
}),
castShadow: false,
ignorePicking: true,
$ignorePicking: true,
geometry: new LinesGeometry({
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 mesh = this._polygonMesh;
mesh.geometry.generateBarycentric();
mesh.material.define("both", "WIREFRAME_TRIANGLE");
mesh.material.set("wireframeLineColor", color);
mesh.material.set("wireframeLineWidth", width);
}
},
_onmousemove: function(e) {
var dataIndex = this._dataIndexOfVertex[e.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(e) {
if (e.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.set(pos, points[j++], 0, points[j++]);
if (coordSys && coordSys.transform) {
vec3.transformMat4(pos, pos, coordSys.transform);
}
vec3.min(min, min, pos);
vec3.max(max, max, pos);
points3[off3++] = pos[0];
points3[off3++] = pos[1];
points3[off3++] = pos[2];
}
vec3.min(minAll, minAll, min);
vec3.max(maxAll, maxAll, max);
polygons.push({
points: points3,
indices: triangles,
min,
max
});
}
this._triangulationResults.push(polygons);
}
this._geoBoundingBox = [minAll, maxAll];
},
_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.mul([], this._geoBoundingBox[0], scale);
var max = vec3.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.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 normal = [];
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.sub(a, quadPos[1], quadPos[0]);
vec3.sub(b, quadPos[3], quadPos[0]);
vec3.cross(normal, a, b);
vec3.normalize(normal, normal);
for (var k = 0; k < 4; k++) {
normalAttr.set(vertexOffset + k, normal);
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.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();
}
};
export { Geo3DBuilder as default };