From cd7fffc161c4ebb4df7bdf8e547892e1cd1ee38c Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Wed, 31 Jul 2019 11:52:52 +0800 Subject: [PATCH] refactor(heatmap) buffer --- demos/hexgon.html | 11 ++-- src/geom/buffer/heatmap/hexagon.js | 88 +++++++++++++++++-------- src/geom/buffer/heatmap/hexagon_3d.js | 43 +++++++----- src/geom/buffer/index.js | 12 +++- src/geom/extrude.js | 39 +++++++++-- src/geom/material/hexagon.js | 9 +-- src/geom/shader/hexagon_frag.glsl | 4 +- src/geom/shader/hexagon_vert.glsl | 23 +++++-- src/geom/shape/path.js | 2 +- src/layer/heatmap_layer.js | 4 +- src/layer/render/heatmap/hexagon.js | 38 +++++++---- src/layer/render/index.js | 4 +- src/layer/render/point/draw_3d_shape.js | 41 ++++++++++++ src/source/transform/grid.js | 1 + 14 files changed, 234 insertions(+), 85 deletions(-) create mode 100644 src/layer/render/point/draw_3d_shape.js diff --git a/demos/hexgon.html b/demos/hexgon.html index 3cc2da6a73..bbfd0379d6 100644 --- a/demos/hexgon.html +++ b/demos/hexgon.html @@ -25,7 +25,7 @@ const scene = new L7.Scene({ mapStyle: 'dark', // 样式URL center: [120.132624,30.281774], pitch: 0, - zoom: 10 + zoom: 7 }); scene.on('loaded', () => { $.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => { @@ -53,16 +53,19 @@ scene.on('loaded', () => { }, { type: 'hexagon', - size: 6000, + size: 10000, field:'v', method:'sum' } ] }) .active(true) - .shape('hexagon') + .size('count',(value)=>{ + return value * 1000; + }) + .shape('square') .style({ - coverage: 0.9, + coverage: 0.8, angle: 0, }) .color('count', ["#002466","#105CB3","#2894E0","#CFF6FF","#FFF5B8","#FFAB5C","#F27049","#730D1C"]) diff --git a/src/geom/buffer/heatmap/hexagon.js b/src/geom/buffer/heatmap/hexagon.js index 68cb3555ff..2c30133ec1 100644 --- a/src/geom/buffer/heatmap/hexagon.js +++ b/src/geom/buffer/heatmap/hexagon.js @@ -1,31 +1,61 @@ -import { fill, extrude } from '../../shape/polygon'; -export default function hexagonBuffer(layerData) { - const attribute = { - vertices: [], - miter: [], - colors: [], - pickingIds: [] - }; - const a = Math.cos(Math.PI / 6); - const points = [ - [ 0, -1, 0 ], - [ -a, -0.5, 0 ], - [ -a, 0.5, 0 ], - [ 0, 1, 0 ], - [ a, 0.5, 0 ], - [ a, -0.5, 0 ], - [ 0, -1, 0 ] - ]; - // const hexgonPoints = polygonPath(6); - const hexgonFill = fill([ points ]); - const { positionsIndex, positions } = hexgonFill; - layerData.forEach(element => { - positionsIndex.forEach(pointIndex => { - attribute.vertices.push(...element.coordinates); - attribute.miter.push(positions[pointIndex][0], positions[pointIndex][1]); - attribute.pickingIds.push(element.id); - attribute.colors.push(...element.color); +import BufferBase from '../buffer'; +import Global from '../../../global'; +import { fillPolygon, extrude_Polygon } from '../../extrude'; +import { polygonPath } from '../../shape/path'; + +export default class Shape_3D extends BufferBase { + _buildFeatures() { + const layerData = this.get('layerData'); + this._offset = 0; + + layerData.forEach(feature => { + this._calculateFill(feature); }); - }); - return attribute; + } + _initAttributes() { + super._initAttributes(); + this.attributes.miters = new Float32Array(this.verticesCount * 3); + this.attributes.normals = new Float32Array(this.verticesCount * 3); + } + _calculateFeatures() { + this._calcultateGeometry(); + const layerData = this.get('layerData'); + const { positions, indexArray } = this.instanceGeometry; + const numFeature = layerData.length; + this.verticesCount = positions.length * numFeature / 3; + this.indexCount = indexArray.length * numFeature; + } + _calcultateGeometry() { + const shape = this.get('shapeType'); + const hexgonFill = this.getShapeFunction(shape)([ this._getPoints(6) ]); + this.instanceGeometry = hexgonFill; + } + _calculateFill(feature) { + + feature.bufferInfo = { verticesOffset: this._offset }; + const { coordinates } = feature; + const numPoint = this.instanceGeometry.positions.length / 3; + this._encodeArray(feature, numPoint); + this.attributes.miters.set(this.instanceGeometry.positions, this._offset * 3); + const indexArray = this.instanceGeometry.indexArray.map(v => { return v + this._offset; }); + this.indexArray.set(indexArray, this._offset); + if (this.instanceGeometry.normals) { + this.attributes.normals.set(this.instanceGeometry.normals, this._offset * 3); + } + const position = []; + for (let i = 0; i < numPoint; i++) { + position.push(...coordinates); + } + this.attributes.positions.set(position, this._offset * 3); + this._offset += numPoint; + } + _getPoints(num) { + return polygonPath(num, 1); + } + getShapeFunction(shape) { + const { pointShape } = Global; + if (pointShape['3d'].indexOf(shape) !== -1) return extrude_Polygon; + return fillPolygon; + + } } diff --git a/src/geom/buffer/heatmap/hexagon_3d.js b/src/geom/buffer/heatmap/hexagon_3d.js index ccbf30cbc0..aec1e3936e 100644 --- a/src/geom/buffer/heatmap/hexagon_3d.js +++ b/src/geom/buffer/heatmap/hexagon_3d.js @@ -1,33 +1,40 @@ +/** + * instantcebufferGeometry的组装方式 + */ import BufferBase from '../buffer'; -import { fill, extrude } from '../../shape/polygon'; import { fillPolygon, extrude_Polygon } from '../../extrude'; -import { polygonPath } from '../../shape/path'; -export default class Grid3D extends BufferBase { +import Global from '../../../global'; +import * as shapePath from '../../shape/path'; + +export default class Hexagon3D extends BufferBase { _buildFeatures() { const layerData = this.get('layerData'); this._offset = 0; layerData.forEach(feature => { - + this._calculateFill(feature); }); } - _initAttributes() { - super._initAttributes(); - this.attributes.miters = new Float32Array(this.verticesCount * 3); - this.attributes.normals = new Float32Array(this.verticesCount * 3); - } _calculateFeatures() { - const hexgonPoints = polygonPath(6); - const hexgonFill = fillPolygon([ hexgonPoints ]); + const shape = this.get('shapeType'); + const hexgonFill = this.getShape(shape); const layerData = this.get('layerData'); - this.verticesCount = hexgonFill.positions.length / 3 * layerData.length; - this.indexCount = hexgonFill.indexArray * layerData.length; - this.featureBuffer = hexgonFill; + this.verticesCount = layerData.length; + this.indexCount = 0; + this.instanceGeometry = hexgonFill; } - _calculatefill(feature) { - // this. + _calculateFill(feature) { + + feature.bufferInfo = { verticesOffset: this._offset }; + const { coordinates } = feature; + this._encodeArray(feature, 1); + this.attributes.positions.set(coordinates, this._offset * 3); + this._offset++; } - _getPoints(num) { - return polygonPath(num); + getShape(shape) { + const { pointShape } = Global; + if (pointShape['3d'].indexOf(shape) !== -1) return extrude_Polygon([ shapePath[shape]() ]); + if (pointShape['2d'].indexOf(shape) !== -1) return fillPolygon([ shapePath[shape]() ]); + return fillPolygon([ shapePath[shape]() ]); } } diff --git a/src/geom/buffer/index.js b/src/geom/buffer/index.js index 55c3ff3090..fc4e009e51 100644 --- a/src/geom/buffer/index.js +++ b/src/geom/buffer/index.js @@ -12,6 +12,10 @@ import ArcLineBuffer from './line/arcline'; // heatmap import Grid3D from './heatmap/grid_3d'; +import Hexagon3D from './heatmap/hexagon_3d'; + +// 3D Shape +import Shape_3D from './heatmap/hexagon'; import { registerBuffer, getBuffer } from './factory'; @@ -26,7 +30,11 @@ registerBuffer('line', 'greatCircle', ArcLineBuffer); // heatmap -registerBuffer('heatmap', 'square', Grid3D); -registerBuffer('heatmap', 'squareColumn', Grid3D); +// registerBuffer('heatmap', 'square', Grid3D); +// registerBuffer('heatmap', 'squareColumn', Grid3D); +registerBuffer('heatmap', 'shape', Hexagon3D); +// 3D Shape + +registerBuffer('shape', 'extrude', Shape_3D); export { getBuffer }; diff --git a/src/geom/extrude.js b/src/geom/extrude.js index 452c82f0ee..09698185dc 100644 --- a/src/geom/extrude.js +++ b/src/geom/extrude.js @@ -1,5 +1,5 @@ import earcut from 'earcut'; - +import * as THREE from '../core/three'; /** * 计算是否拉伸 * @param {Array} points 点坐标数组 @@ -125,18 +125,45 @@ export function extrude_Polygon(points) { const flattengeo = earcut.flatten(points); const positions = []; const indexArray = []; + const normals = []; + // 设置顶部z值 + for (let j = 0; j < flattengeo.vertices.length / 3; j++) { + flattengeo.vertices[j * 3 + 2] = 1; + normals.push(0, 0, 1); + } positions.push(...flattengeo.vertices); const triangles = earcut(flattengeo.vertices, flattengeo.holes, flattengeo.dimensions); indexArray.push(...triangles); for (let i = 0; i < n; i++) { const prePoint = flattengeo.vertices.slice(i * 3, i * 3 + 3); - const nextPoint = flattengeo.vertices.slice(i * 3 + 3, i * 3 + 6); - const indexOffset = positions.length; + let nextPoint = flattengeo.vertices.slice(i * 3 + 3, i * 3 + 6); + nextPoint.length === 0 && (nextPoint = flattengeo.vertices.slice(0, 3)); + const indexOffset = positions.length / 3; positions.push(prePoint[0], prePoint[1], 1, nextPoint[0], nextPoint[1], 1, prePoint[0], prePoint[1], 0, nextPoint[0], nextPoint[1], 0); - indexArray.push([ 1, 2, 0, 3, 2, 1 ].map(v => { return v + indexOffset; })); + const normal = computeNormal([ nextPoint[0], nextPoint[1], 1 ], [ prePoint[0], prePoint[1], 0 ], [ prePoint[0], prePoint[1], 1 ]); + normals.push(...normal, ...normal, ...normal, ...normal); + indexArray.push(...[ 1, 2, 0, 3, 2, 1 ].map(v => { return v + indexOffset; })); } return { - positions: flattengeo.vertices, - indexArray: triangles + positions, + indexArray, + normals }; } + +function computeNormal(v1, v2, v3) { + const pA = new THREE.Vector3(); + const pB = new THREE.Vector3(); + const pC = new THREE.Vector3(); + const cb = new THREE.Vector3(); + const ab = new THREE.Vector3(); + pA.set(...v1); + pB.set(...v2); + pC.set(...v3); + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); + cb.normalize(); + const { x, y, z } = cb; + return [ x, y, z ]; +}; diff --git a/src/geom/material/hexagon.js b/src/geom/material/hexagon.js index ffb1739d1f..fc2bc30071 100644 --- a/src/geom/material/hexagon.js +++ b/src/geom/material/hexagon.js @@ -1,5 +1,6 @@ import Material from './material'; -import { getModule } from '../../util/shaderModule'; +import { getModule, wrapUniforms } from '../../util/shaderModule'; +import merge from '@antv/util/lib/deep-mix'; export default class hexagonMaterial extends Material { getDefaultParameters() { return { @@ -19,9 +20,9 @@ export default class hexagonMaterial extends Material { } constructor(_uniforms, _defines, parameters) { super(parameters); - const { uniforms, defines } = this.getDefaultParameters(); - const { vs, fs } = getModule('hexagon'); - this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms)); + const { defines } = this.getDefaultParameters(); + const { vs, fs, uniforms } = getModule('hexagon'); + this.uniforms = wrapUniforms(merge(uniforms, _uniforms)); this.type = 'hexagonMaterial'; this.defines = Object.assign(defines, _defines); this.vertexShader = vs; diff --git a/src/geom/shader/hexagon_frag.glsl b/src/geom/shader/hexagon_frag.glsl index 77c7d120d0..6fd41421b4 100644 --- a/src/geom/shader/hexagon_frag.glsl +++ b/src/geom/shader/hexagon_frag.glsl @@ -1,7 +1,5 @@ precision highp float; - uniform float u_opacity; varying vec4 v_color; void main() { - vec4 color = v_color; - gl_FragColor = color; + gl_FragColor = v_color; } \ No newline at end of file diff --git a/src/geom/shader/hexagon_vert.glsl b/src/geom/shader/hexagon_vert.glsl index 1bd95bbbdf..3940da986e 100644 --- a/src/geom/shader/hexagon_vert.glsl +++ b/src/geom/shader/hexagon_vert.glsl @@ -1,26 +1,41 @@ precision highp float; -attribute vec2 miter; +// attribute vec2 miter; +attribute vec3 miter; +attribute vec3 a_shape; +attribute float a_size; attribute vec4 a_color; uniform float u_radius; uniform float u_coverage; uniform float u_opacity; uniform float u_angle; + uniform float u_activeId; uniform vec4 u_activeColor; varying vec4 v_color; +#pragma include "lighting" + void main() { mat4 matModelViewProjection = projectionMatrix * modelViewMatrix; mat2 rotationMatrix = mat2(cos(u_angle), sin(u_angle), -sin(u_angle), cos(u_angle)); v_color = a_color; - v_color.a *= u_opacity; + v_color.a *= u_opacity; if(pickingId == u_activeId) { v_color = u_activeColor; } - vec2 offset =vec2(rotationMatrix * miter * u_radius * u_coverage ); + vec2 offset =vec2(rotationMatrix * miter.xy * u_radius * u_coverage ); + // vec2 offset =vec2(rotationMatrix * a_shape.xy * u_radius * u_coverage ); float x = position.x + offset.x; float y = position.y + offset.y; - gl_Position = matModelViewProjection * vec4(x, y, position.z, 1.0); + // float z = a_shape.z * a_size; + float z = miter.z * a_size; + + #ifdef LIGHTING + vec3 viewDir = normalize(cameraPosition - vec3(x, y, z)); + v_color.rgb *= calc_lighting(vec3(x, y, z), normal, viewDir); + #endif + + gl_Position = matModelViewProjection * vec4(x, y, z, 1.0); worldId = id_toPickColor(pickingId); } \ No newline at end of file diff --git a/src/geom/shape/path.js b/src/geom/shape/path.js index 82bd51d68e..4eaf321e18 100644 --- a/src/geom/shape/path.js +++ b/src/geom/shape/path.js @@ -33,7 +33,7 @@ export function polygonPath(pointCount, start = 0) { const step = Math.PI * 2 / pointCount; const line = []; for (let i = 0; i < pointCount; i++) { - line.push(step * i - start * Math.PI / 12); + line.push(step * i + start * Math.PI / 12); } const path = line.map(t => { const x = Math.sin(t + Math.PI / 4), diff --git a/src/layer/heatmap_layer.js b/src/layer/heatmap_layer.js index b75c270c1f..5468e542b8 100644 --- a/src/layer/heatmap_layer.js +++ b/src/layer/heatmap_layer.js @@ -8,6 +8,8 @@ export default class HeatMapLayer extends Layer { } draw() { this.type = 'heatmap'; - this.add(getRender('heatmap', this.shapeType || 'heatmap')(this.layerData, this, this.layerSource)); + if (!this.shapeType) this.shapeType = 'heatmap'; + const renderType = this.shapeType === 'heatmap' ? 'heatmap' : 'shape'; + this.add(getRender('heatmap', renderType)(this.layerData, this, this.layerSource)); } } diff --git a/src/layer/render/heatmap/hexagon.js b/src/layer/render/heatmap/hexagon.js index 8c2c42ee14..3fa4c03285 100644 --- a/src/layer/render/heatmap/hexagon.js +++ b/src/layer/render/heatmap/hexagon.js @@ -1,27 +1,41 @@ import * as THREE from '../../../core/three'; -import hexagonBuffer from '../../../geom/buffer/heatmap/hexagon'; import GridMaterial from '../../../geom/material/hexagon'; -export default function DrawHexagon(layerdata, layer, source) { +import { getBuffer } from '../../../geom/buffer/'; +import { generateLightingUniforms } from '../../../util/shaderModule'; +export default function DrawHexagon(layerData, layer, source) { const style = layer.get('styleOptions'); const { fill } = layer.get('activedOptions'); const { radius } = source.data; - const attributes = new hexagonBuffer(layerdata); - const { opacity, angle, coverage } = style; - const geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); - geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miter, 2)); - geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); - geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); + const { opacity, angle = 0, coverage, lights } = style; + const geometryBuffer = getBuffer(layer.type, 'shape'); + const buffer = new geometryBuffer({ + layerData, + shapeType: layer.shapeType + }); + const { attributes, instanceGeometry } = buffer; + const instancedGeometry = new THREE.InstancedBufferGeometry(); + instancedGeometry.setIndex(instanceGeometry.indexArray); + instancedGeometry.addAttribute('miter', new THREE.Float32BufferAttribute(instanceGeometry.positions, 3)); + if (instanceGeometry.normals) { + instancedGeometry.addAttribute('normal', new THREE.Float32BufferAttribute(instanceGeometry.normals, 3)); + } + instancedGeometry.addAttribute('position', new THREE.InstancedBufferAttribute(new Float32Array(attributes.positions), 3)); + instancedGeometry.addAttribute('a_color', new THREE.InstancedBufferAttribute(new Float32Array(attributes.colors), 4)); + instancedGeometry.addAttribute('pickingId', new THREE.InstancedBufferAttribute(new Float32Array(attributes.pickingIds), 1)); + instancedGeometry.addAttribute('a_size', new THREE.InstancedBufferAttribute(new Float32Array(attributes.sizes), 1)); + const material = new GridMaterial({ u_opacity: opacity, u_radius: radius, u_angle: angle / 180 * Math.PI, u_coverage: coverage, - u_activeColor: fill + u_activeColor: fill, + ...generateLightingUniforms(lights) }, { - SHAPE: false + SHAPE: false, + LIGHTING: !!instanceGeometry.normals }); - const hexgonMesh = new THREE.Mesh(geometry, material); + const hexgonMesh = new THREE.Mesh(instancedGeometry, material); return hexgonMesh; } diff --git a/src/layer/render/index.js b/src/layer/render/index.js index 53297cb3a8..4272da7ab9 100644 --- a/src/layer/render/index.js +++ b/src/layer/render/index.js @@ -3,6 +3,7 @@ import { registerRender, getRender } from './factory'; import DrawFill from './polygon/drawFill'; import DrawLine from './polygon/drawLine'; import DrawAnimate from './polygon/drawAnimate'; +import Draw3DShape from './point/draw_3d_shape'; registerRender('polygon', 'fill', DrawFill); registerRender('polygon', 'extrude', DrawFill); @@ -31,6 +32,7 @@ registerRender('point', 'normal', DrawPointNormal); registerRender('point', 'stroke', DrawPointStroke); registerRender('point', 'text', DrawPointText); registerRender('point', 'circle', DrawPointCircle); +registerRender('point', 'shape', Draw3DShape); // heatmap @@ -41,7 +43,7 @@ import DrawHexagon from './heatmap/hexagon'; registerRender('heatmap', 'square', DrawGrid); registerRender('heatmap', 'squareColumn', DrawGrid); registerRender('heatmap', 'heatmap', DrawHeatmap); -registerRender('heatmap', 'hexagon', DrawHexagon); +registerRender('heatmap', 'shape', DrawHexagon); // image diff --git a/src/layer/render/point/draw_3d_shape.js b/src/layer/render/point/draw_3d_shape.js new file mode 100644 index 0000000000..422e6e520d --- /dev/null +++ b/src/layer/render/point/draw_3d_shape.js @@ -0,0 +1,41 @@ +import * as THREE from '../../../core/three'; +import GridMaterial from '../../../geom/material/hexagon'; +import { getBuffer } from '../../../geom/buffer/'; +import { generateLightingUniforms } from '../../../util/shaderModule'; +export default function Draw3DShape(layerData, layer, source) { + const style = layer.get('styleOptions'); + const { fill } = layer.get('activedOptions'); + const { radius } = source.data; + // const attributes = new hexagonBuffer(layerdata); + const { opacity, angle, coverage, lights } = style; + const geometryBuffer = getBuffer('shape', 'extrude'); + const buffer = new geometryBuffer({ + layerData, + shapeType: layer.shapeType + }); + const { attributes, indexArray } = buffer; + const geometry = new THREE.BufferGeometry(); + geometry.setIndex(new THREE.Uint32BufferAttribute(indexArray, 1)); + geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.positions, 3)); + geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miters, 3)); + geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); + geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); + geometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normals, 3)); + geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.sizes, 1)); + + + const material = new GridMaterial({ + u_opacity: opacity, + u_radius: radius, + u_angle: angle / 180 * Math.PI, + u_coverage: coverage, + u_activeColor: fill, + ...generateLightingUniforms(lights) + }, { + SHAPE: false, + LIGHTING: true + }); + const hexgonMesh = new THREE.Mesh(geometry, material); + return hexgonMesh; +} + diff --git a/src/source/transform/grid.js b/src/source/transform/grid.js index 4098f15e15..9b1067566a 100644 --- a/src/source/transform/grid.js +++ b/src/source/transform/grid.js @@ -13,6 +13,7 @@ export function aggregatorToGrid(data, option) { return { yOffset: gridOffset.xOffset / 360 * (256 << 20) / 2, xOffset: gridOffset.xOffset / 360 * (256 << 20) / 2, + radius: gridOffset.xOffset / 360 * (256 << 20) / 2, dataArray: layerData }; }