diff --git a/demos/03_choropleths_polygon.html b/demos/03_choropleths_polygon.html index 0700fcf2b9..a9c6ba4da4 100644 --- a/demos/03_choropleths_polygon.html +++ b/demos/03_choropleths_polygon.html @@ -81,7 +81,6 @@ scene.on('loaded', () => { } }) .shape('fill') - .pattern('id1') .active(true) .style({ opacity: 1 diff --git a/demos/grid.html b/demos/grid.html index ba02a4fa0e..d64144ef14 100644 --- a/demos/grid.html +++ b/demos/grid.html @@ -53,16 +53,19 @@ scene.on('loaded', () => { }, { type: 'grid', - size: 15000, + size: 160000, field:'v', method:'sum' } ] }) - .shape('grid') + .shape('squareColumn') + .size('count',(value)=>{ + return value * 1000; + }) .active({fill:'red'}) .style({ - coverage: 0.8 + coverage: 0.6 }) .color('count', ["#002466","#105CB3","#2894E0","#CFF6FF","#FFF5B8","#FFAB5C","#F27049","#730D1C"]) .render(); diff --git a/src/core/engine/renderer.js b/src/core/engine/renderer.js index d1c689441f..aedae63622 100644 --- a/src/core/engine/renderer.js +++ b/src/core/engine/renderer.js @@ -8,7 +8,7 @@ export default class Renderer { } initRender() { this.renderer = new THREE.WebGLRenderer({ - antialias: false, + antialias: true, alpha: true, autoClear: false }); diff --git a/src/geom/buffer/heatmap/grid_3d.js b/src/geom/buffer/heatmap/grid_3d.js new file mode 100644 index 0000000000..01b1e0bc51 --- /dev/null +++ b/src/geom/buffer/heatmap/grid_3d.js @@ -0,0 +1,89 @@ +import BufferBase from '../buffer'; +export default class Grid3D extends BufferBase { + _buildFeatures() { + const layerData = this.get('layerData'); + this._offset = 0; + const shapeType = this.get('shapeType'); + layerData.forEach(feature => { + this._calculateTop(feature); + if (shapeType === 'squareColumn') { + this._calculateWall(feature); + } + delete feature.bufferInfo; + }); + } + _initAttributes() { + super._initAttributes(); + this.attributes.miters = new Float32Array(this.verticesCount * 3); + this.attributes.normals = new Float32Array(this.verticesCount * 3); + } + _calculateFeatures() { + const layerData = this.get('layerData'); + const shapeType = this.get('shapeType'); + if (shapeType === 'squareColumn') { + this.verticesCount = layerData.length * 20; + } else { + this.verticesCount = layerData.length * 4; + } + this.indexCount = this.verticesCount * 1.5; + } + _calculateTop(feature) { + const [ x, y ] = feature.coordinates; + let { size } = feature; + + feature.bufferInfo = { + verticesOffset: this._offset + }; + const shapeType = this.get('shapeType'); + if (shapeType !== 'squareColumn') { + size = 0; + } + this._encodeArray(feature, 4); + this.attributes.positions.set([ x, y, size, x, y, size, x, y, size, x, y, size ], this._offset * 3); + this.attributes.miters.set([ -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1 ], this._offset * 3); + this.attributes.normals.set([ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 ], this._offset * 3); // top normal + const indexArray = [ 0, 2, 1, 2, 3, 1 ].map(v => { return v + this._offset; }); + this.indexArray.set(indexArray, this._offset * 1.5); + this._offset += 4; + } + _calculateWall(feature) { + const { size } = feature; + const [ x, y ] = feature.coordinates; + const vertices = [ 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1 ]; + feature.bufferInfo = { + verticesOffset: this._offset + }; + this._encodeArray(feature, 20); + // front left, back right + this.attributes.normals.set([ + 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, // bottom + -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // left + 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // top + 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0 // right + ], this._offset * 3); // top normal + + for (let i = 0; i < 4; i++) { + this.attributes.positions.set([ x, y, 1, x, y, 1, x, y, 1, x, y, 1 ], this._offset * 3); + const prePoint = vertices.slice(i * 3, i * 3 + 3); + const nextPoint = vertices.slice(i * 3 + 3, i * 3 + 6); + this._calculateExtrudeFace(prePoint, nextPoint, this._offset, this._offset * 1.5, size); + this._offset += 4; + } + } + _calculateExtrudeFace(prePoint, nextPoint, positionOffset, indexOffset, size) { + this.attributes.miters.set([ + prePoint[0], prePoint[1], size, + nextPoint[0], nextPoint[1], size, + prePoint[0], prePoint[1], 0, + nextPoint[0], nextPoint[1], 0 + ], + positionOffset * 3); + const indexArray = [ 0, 1, 2, 1, 3, 2 ].map(v => { return v + positionOffset; }); + if (this.get('uv')) { + // temp 点亮城市demo + this.attributes.uv.set([ 0.1, 0, 0, 0, 0.1, size / 2000, 0, size / 2000 ], positionOffset * 2); + } + this.indexArray.set(indexArray, indexOffset); + } + +} diff --git a/src/geom/buffer/heatmap/hexagon.js b/src/geom/buffer/heatmap/hexagon.js index eb95ef5d5b..68cb3555ff 100644 --- a/src/geom/buffer/heatmap/hexagon.js +++ b/src/geom/buffer/heatmap/hexagon.js @@ -1,4 +1,4 @@ -import { fill } from '../../shape/polygon'; +import { fill, extrude } from '../../shape/polygon'; export default function hexagonBuffer(layerData) { const attribute = { vertices: [], diff --git a/src/geom/buffer/heatmap/hexagon_3d.js b/src/geom/buffer/heatmap/hexagon_3d.js new file mode 100644 index 0000000000..ccbf30cbc0 --- /dev/null +++ b/src/geom/buffer/heatmap/hexagon_3d.js @@ -0,0 +1,33 @@ +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 { + _buildFeatures() { + const layerData = this.get('layerData'); + this._offset = 0; + + layerData.forEach(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 layerData = this.get('layerData'); + this.verticesCount = hexgonFill.positions.length / 3 * layerData.length; + this.indexCount = hexgonFill.indexArray * layerData.length; + this.featureBuffer = hexgonFill; + } + _calculatefill(feature) { + // this. + } + _getPoints(num) { + return polygonPath(num); + } +} diff --git a/src/geom/buffer/index.js b/src/geom/buffer/index.js index bb38e3e071..55c3ff3090 100644 --- a/src/geom/buffer/index.js +++ b/src/geom/buffer/index.js @@ -9,6 +9,10 @@ import ExtrudeBuffer from './polygon/extrude_buffer'; import MeshLineBuffer from './line/meshline'; import ArcLineBuffer from './line/arcline'; +// heatmap + +import Grid3D from './heatmap/grid_3d'; + import { registerBuffer, getBuffer } from './factory'; registerBuffer('polygon', 'fill', FillBuffer); @@ -20,4 +24,9 @@ registerBuffer('line', 'line', MeshLineBuffer); registerBuffer('line', 'arc', ArcLineBuffer); registerBuffer('line', 'greatCircle', ArcLineBuffer); +// heatmap + +registerBuffer('heatmap', 'square', Grid3D); +registerBuffer('heatmap', 'squareColumn', Grid3D); + export { getBuffer }; diff --git a/src/geom/extrude copy.js b/src/geom/extrude copy.js new file mode 100644 index 0000000000..85fc86cad9 --- /dev/null +++ b/src/geom/extrude copy.js @@ -0,0 +1,107 @@ +import earcut from 'earcut'; + +/** + * 计算是否拉伸 + * @param {Array} points 点坐标数组 + * @param {boolean} extrude 是否拉伸 + * @return {object} 顶点坐标顶点索引 + */ +export default function extrudePolygon(points, extrude) { + // height += Math.random() * 100; // 解决 depth + const p1 = points[0][0]; + const p2 = points[0][points[0].length - 1]; + const faceUv = []; + if (p1[0] === p2[0] && p1[1] === p2[1]) { + points[0] = points[0].slice(0, points[0].length - 1); + } + const n = points[0].length; + const flattengeo = earcut.flatten(points); + const positions = []; + let cells = []; + const { dimensions } = flattengeo; + const triangles = earcut(flattengeo.vertices, flattengeo.holes, flattengeo.dimensions); + cells = triangles; + + const pointCount = flattengeo.vertices.length / dimensions; + const { vertices } = flattengeo; + extrude ? full() : flat(); + + + function flat() { + for (let i = 0; i < pointCount; i++) { + positions.push([ vertices[ i * dimensions ], vertices[i * dimensions + 1 ], 0 ]); + } + } + function full() { + // 顶部纹理 + triangles.forEach(() => { + faceUv.push(-1, -1); + }); + // 顶部坐标 + + for (let i = 0; i < pointCount; i++) { + positions.push([ vertices[ i * dimensions ], vertices[i * dimensions + 1 ], 1 ]); + } + for (let i = 0; i < pointCount; i++) { + positions.push([ vertices[ i * dimensions ], vertices[i * dimensions + 1 ], 0 ]); + } + for (let i = 0; i < n; i++) { + if (i === (n - 1)) { + cells.push(i, n, i + n); + faceUv.push(1, 0, 0, 1, 1, 1); + cells.push(i, 0, n); + faceUv.push(1, 0, 0, 0, 0, 1); + } else { + cells.push(i + n, i, i + n + 1); + faceUv.push(1, 1, 1, 0, 0, 1); + cells.push(i, i + 1, i + n + 1); + faceUv.push(1, 0, 0, 0, 0, 1); + } + } + } + points = []; + return { + positions, + faceUv, + positionsIndex: cells + }; +} + +export function extrudePolygonLine(points, extrude) { + // height += Math.random() * 100; // 解决 depth + const p1 = points[0][0]; + const p2 = points[0][points[0].length - 1]; + if (p1[0] === p2[0] && p1[1] === p2[1]) { + points[0] = points[0].slice(0, points[0].length - 1); + } + + const n = points[0].length; + const flattengeo = earcut.flatten(points); + const positions = []; + let cells = []; + const triangles = earcut(flattengeo.vertices, flattengeo.holes, flattengeo.dimensions); + cells = triangles.map(e => e); + extrude === 0 ? flat() : full(); + + function flat() { + points[0].forEach(p => { positions.push([ p[0], p[1], 0 ]); }); // top + } + function full() { + points[0].forEach(p => { positions.push([ p[0], p[1], 1 ]); }); // top + points[0].forEach(p => { positions.push([ p[0], p[1], 0 ]); }); // bottom + for (let i = 0; i < n; i++) { + if (i === (n - 1)) { + cells.push(i + n, n, i); + cells.push(0, i, n); + } else { + cells.push(i + n, i + n + 1, i); + cells.push(i + 1, i, i + n + 1); + } + } + } + points = []; + return { + positions, + positionsIndex: cells + }; +} diff --git a/src/geom/extrude.js b/src/geom/extrude.js index 85fc86cad9..452c82f0ee 100644 --- a/src/geom/extrude.js +++ b/src/geom/extrude.js @@ -105,3 +105,38 @@ export function extrudePolygonLine(points, extrude) { positionsIndex: cells }; } + +export function fillPolygon(points) { + const flattengeo = earcut.flatten(points); + const triangles = earcut(flattengeo.vertices, flattengeo.holes, flattengeo.dimensions); + return { + positions: flattengeo.vertices, + indexArray: triangles + }; +} + +export function extrude_Polygon(points) { + const p1 = points[0][0]; + const p2 = points[0][points[0].length - 1]; + if (p1[0] === p2[0] && p1[1] === p2[1]) { + points[0] = points[0].slice(0, points[0].length - 1); + } + const n = points[0].length; + const flattengeo = earcut.flatten(points); + const positions = []; + const indexArray = []; + 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; + 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; })); + } + return { + positions: flattengeo.vertices, + indexArray: triangles + }; +} diff --git a/src/geom/material/grid.js b/src/geom/material/grid.js index 44f3985be6..938b4edb32 100644 --- a/src/geom/material/grid.js +++ b/src/geom/material/grid.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 GridMaterial extends Material { getDefaultParameters() { return { @@ -19,9 +20,9 @@ export default class GridMaterial extends Material { } constructor(_uniforms, _defines, parameters) { super(parameters); - const { uniforms, defines } = this.getDefaultParameters(); - const { vs, fs } = getModule('grid'); - this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms)); + const { defines } = this.getDefaultParameters(); + const { vs, fs, uniforms } = getModule('grid'); + this.uniforms = wrapUniforms(merge(uniforms, _uniforms)); this.type = 'GridMaterial'; this.defines = Object.assign(defines, _defines); this.vertexShader = vs; diff --git a/src/geom/shader/grid_vert.glsl b/src/geom/shader/grid_vert.glsl index 81ae913d6a..35fba3dc04 100644 --- a/src/geom/shader/grid_vert.glsl +++ b/src/geom/shader/grid_vert.glsl @@ -1,5 +1,5 @@ precision highp float; -attribute vec2 miter; +attribute vec3 miter; attribute vec4 a_color; uniform float u_xOffset; uniform float u_yOffset; @@ -9,15 +9,27 @@ uniform float u_activeId; uniform vec4 u_activeColor; varying vec4 v_color; +#pragma include "lighting" + void main() { mat4 matModelViewProjection = projectionMatrix * modelViewMatrix; v_color = a_color; v_color.a *= u_opacity; - if(pickingId == u_activeId) { + + if(pickingId == u_activeId) { v_color = u_activeColor; - } + } float x = position.x + miter.x * u_xOffset * u_coverage; float y = position.y + miter.y * u_yOffset * u_coverage; - gl_Position = matModelViewProjection * vec4(x, y, position.z, 1.0); + float z = position.z + miter.z; + + #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/layer/render/heatmap/gird.js b/src/layer/render/heatmap/gird.js index 04b31b40bf..865b995ffe 100644 --- a/src/layer/render/heatmap/gird.js +++ b/src/layer/render/heatmap/gird.js @@ -1,24 +1,37 @@ import * as THREE from '../../../core/three'; -import gridBuffer from '../../../geom/buffer/heatmap/grid'; import GridMaterial from '../../../geom/material/grid'; -export default function DrawGrid(layerdata, layer, source) { - const { opacity, coverage } = layer.get('styleOptions'); +import { getBuffer } from '../../../geom/buffer/'; +import { generateLightingUniforms } from '../../../util/shaderModule'; +export default function DrawGrid(layerData, layer, source) { + const { opacity, coverage, lights } = layer.get('styleOptions'); const activeOption = layer.get('activedOptions'); const { xOffset, yOffset } = source.data; - const attributes = new gridBuffer(layerdata); + + // const attributes = new gridBuffer(layerdata); + const geometryBuffer = getBuffer(layer.type, layer.shapeType); + const buffer = new geometryBuffer({ + layerData, + shapeType: layer.shapeType + }); + const { attributes, indexArray } = buffer; const geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); - geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miter, 2)); + 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)); + const material = new GridMaterial({ u_opacity: opacity, u_xOffset: xOffset, u_yOffset: yOffset, u_coverage: coverage, - u_activeColor: activeOption.fill + u_activeColor: activeOption.fill, + ...generateLightingUniforms(lights) }, { - SHAPE: false + SHAPE: false, + LIGHTING: layer.shapeType !== 'square' }); const gridMesh = new THREE.Mesh(geometry, material); return gridMesh; diff --git a/src/layer/render/index.js b/src/layer/render/index.js index 79158b56d9..53297cb3a8 100644 --- a/src/layer/render/index.js +++ b/src/layer/render/index.js @@ -38,7 +38,8 @@ import DrawGrid from './heatmap/gird'; import DrawHeatmap from './heatmap/heatmap'; import DrawHexagon from './heatmap/hexagon'; -registerRender('heatmap', 'grid', DrawGrid); +registerRender('heatmap', 'square', DrawGrid); +registerRender('heatmap', 'squareColumn', DrawGrid); registerRender('heatmap', 'heatmap', DrawHeatmap); registerRender('heatmap', 'hexagon', DrawHexagon); diff --git a/src/layer/render/polygon/drawLine.js b/src/layer/render/polygon/drawLine.js index 7763282689..908ec65e0d 100644 --- a/src/layer/render/polygon/drawLine.js +++ b/src/layer/render/polygon/drawLine.js @@ -1,5 +1,5 @@ import * as THREE from '../../../core/three'; -import PolygonBuffer from '../../../geom/buffer/polygon'; +import { getBuffer } from '../../../geom/buffer/'; import { LineMaterial } from '../../../geom/material/lineMaterial'; export default function DrawPolygonLine(layerData, layer, buffer) { const style = layer.get('styleOptions'); @@ -9,13 +9,14 @@ export default function DrawPolygonLine(layerData, layer, buffer) { activeColor: activeOption.fill }; const { opacity } = config; - let { attributes, indexArray } = buffer; - if (!attributes) { - attributes = new PolygonBuffer({ - shape: layer.shape, + if (!buffer) { + const geometryBuffer = getBuffer(layer.type, layer.shape); + buffer = new geometryBuffer({ layerData - }).attributes; + }); + } + const { attributes, indexArray } = buffer; const geometry = new THREE.BufferGeometry(); if (indexArray) { geometry.setIndex(new THREE.Uint32BufferAttribute(indexArray, 1));